1. 问题现象与背景分析
最近在调试STM32的DMA串口发送功能时,遇到了一个相当棘手的问题:当单片机在DMA传输完成后立即复位时,串口数据经常出现发送不完整的情况。具体表现为:
- 前几个字节正常发送
- 尾部1-2个字节丢失
- 偶尔会出现整个数据包都未发出的情况
这个问题在需要快速重启的应用场景中尤为突出,比如:
- 固件升级后的自动重启
- 看门狗复位恢复
- 异常处理后的系统复位
经过多次测试复现,发现该问题与DMA传输的硬件特性密切相关。STM32的DMA控制器是独立于CPU的外设,当主芯片复位时,DMA传输可能尚未真正完成,即使软件层面已经收到了传输完成中断。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DMA传输机制深度解析
2.1 STM32 DMA工作原理
DMA(Direct Memory Access)是STM32中用于高效数据传输的重要外设,其核心优势在于不占用CPU资源。以USART发送为例:
- 配置DMA源地址(内存缓冲区)
- 配置DMA目标地址(USART数据寄存器)
- 设置传输数据量
- 启动DMA传输
关键时间节点:
- 软件触发DMA启动(T0)
- DMA开始搬运第一个字节(T1)
- 最后一个字节写入USART(Tn)
- USART实际发送完成(Tn+x)
2.2 复位对DMA的影响
当单片机执行复位操作时:
- 所有外设寄存器恢复默认值
- DMA传输被强制中止
- 时钟系统重新初始化
问题症结在于:USART的发送移位寄存器需要一定时间才能将接收到的数据全部发出,这个时间与波特率直接相关。例如:
- 115200bps:每个字节约87μs
- 发送20字节需要约1.74ms
如果在这段时间内发生复位,未移出的数据就会丢失。
3. 解决方案与实现细节
3.1 硬件复位延迟方案
最可靠的解决方案是在DMA传输完成后增加适当的延迟再复位:
c复制void SystemResetWithDMAWait(void)
{
// 等待DMA传输完成标志
while(!DMA_GetFlagStatus(DMA_FLAG_TC));
// 计算基于波特率的延迟
uint32_t byte_time_us = 1000000 / CurrentBaudRate;
uint32_t needed_delay = byte_time_us * LastTransferSize;
// 增加20%余量
needed_delay += needed_delay / 5;
// 毫秒级延迟
Delay_ms(needed_delay / 1000 + 1);
// 执行复位
NVIC_SystemReset();
}
注意:实际延迟时间应该比理论计算值多20-30%,以应对硬件响应时间的波动。
3.2 软件复位替代方案
如果无法修改硬件复位时序,可以考虑使用软件复位替代:
c复制void SoftResetAfterDMA(void)
{
__disable_irq(); // 关闭所有中断
// 等待DMA完成
while(DMA_GetCmdStatus() == ENABLE);
// 手动完成USART发送
while(USART_GetFlagStatus(USART_FLAG_TC) == RESET);
// 执行软件复位
NVIC_SystemReset();
}
3.3 看门狗复位优化
对于看门狗触发的复位,建议在初始化时配置:
c复制void IWDG_Configuration(void)
{
// 40kHz LSI时钟,预分频64
IWDG_SetPrescaler(IWDG_Prescaler_64);
// 设置重载值为最保守值(约1.6s)
IWDG_SetReload(0xFFF);
// 启动看
