【STM32】关于DMA发送后立刻复位单片机导致无法正确发送的问题

一、问题背景与现象

项目中基于 STM32F407 实现 Ymodem 协议固件升级功能,在处理结束帧应答时遇到一个诡异问题:

协议流程:STM32 收到 Ymodem 结束帧(SOH+0x00+0xFF)后,发送 0x6(ACK 应答),写入升级标志位到 Flash,最后执行系统复位,启动新固件。

核心代码(简化版):

/* 收到结束帧 /
else if(pData[0] == YMODEM_SOH && pData[1] == 0x00 && pData[2] == 0xff)
{
/
发送正确应答(0x6),非阻塞发送 */

IAP_Send((uint8_t *)&YMODEM_ACK, 1);

/* 写入升级成功标志位到Flash */

uint8_t data_type = IAP_UPDATE_ON;

IAP_FLASH_WriteByte(IAP_UPDATE_FLAG_ADDR, &data_type, 1);

/* 复位设备 */

SYSTEM_RESET;

}

异常现象:上位机始终收不到 0x6,反而收到乱码 0xFE;但在IAP_Send和SYSTEM_RESET之间加入printf(如打印 "即将重启"),上位机就能正常收到 0x6 的 ACK 应答。

二、问题根源分析

这个问题的核心是混淆了 "非阻塞发送的指令发起" 和 "硬件实际发送完成",结合 STM32 的外设工作机制,拆解原因如下:

  1. 非阻塞发送(IAP_Send)的本质

    项目中IAP_Send是基于串口 DMA / 中断的非阻塞发送,执行该函数时,STM32 仅做 3 件事:

    把 0x6(ACK)写入发送缓冲区;

    配置 DMA 通道 / 开启串口发送中断,让硬件后台执行 "从缓冲区→串口寄存器→TX 引脚" 的传输;

    函数立即返回,CPU 继续执行后续的 "写 Flash" 和 "复位" 操作。

    整个IAP_Send的 CPU 耗时仅微秒级,但串口发送 1 个字节的硬件耗时是固定的(由波特率决定):

    以 115200 波特率为例,单个字节(8 数据位 + 1 停止位 + 无校验)的发送耗时 = 10bit / 115200bps = 86.8μs,这是硬件层面无法缩短的时间。

  2. 系统复位(SYSTEM_RESET)的 "终止特性"

    SYSTEM_RESET是 STM32 的全局强制复位,执行后会立即:

    禁用所有外设(包括串口、DMA 控制器),清空串口发送寄存器、DMA 传输计数器;

    中断 AHB/APB 总线操作,导致硬件后台的发送流程直接中断;

    TX 引脚电平因复位出现乱跳,上位机收到的 0xFE 就是复位导致的乱码,而非有效 ACK。

  3. printf "修复" 问题的真相

    printf本身是阻塞式串口发送(即使底层是 DMA,printf的格式解析 + 字符发送也会耗时),以 115200 波特率打印 10 个字符为例,耗时约 868μs,这个耗时恰好覆盖了 0x6 的硬件发送时间(86.8μs),让 0x6 在复位前通过 TX 引脚完整发送,属于 "治标不治本" 的临时解决方案。

    三、解决思路:等待硬件发送完成后再复位

    核心原则:非阻塞发送后,必须等待 "硬件实际发送完成" 的标志,再执行复位,不能依赖 printf 等耗时操作。

    STM32 串口的 "发送完成" 有一个关键硬件标志 ------TC 位(Transmission Complete),其含义是:

    串口发送寄存器(TDR)中的数据已全部通过移位寄存器发送到TX引脚,且移位寄存器为空,这是判断数据是否真正发出去的最终标志。

    无论IAP_Send是 DMA 发送还是中断发送,只要等待 TC 标志置 1,就能 100% 确保 0x6 已发送完成,再执行复位就不会丢失数据。

四、最终改进代码(适配 HAL 库 / 标准库)

以下提供两种常用库的完整改进代码,核心是在IAP_Send和SYSTEM_RESET之间添加 "等待 TC 标志" 的逻辑,并加入超时保护防止死等。

方案 1:HAL 库(STM32CubeMX 生成代码)

假设串口句柄为huart1(根据实际项目修改):

/* 收到结束帧 /
else if(pData[0] == YMODEM_SOH && pData[1] == 0x00 && pData[2] == 0xff)
{
/
1. 非阻塞发送ACK(0x6) */

IAP_Send((uint8_t *)&YMODEM_ACK, 1);

/* 2. 核心改进:等待串口硬件发送完成(TC标志),带超时保护 */

uint32_t tx_timeout = HAL_GetTick() + 10; // 10ms超时(足够覆盖1字节发送)

while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET)

{

if(HAL_GetTick() > tx_timeout) break; // 超时退出,避免死等

}

__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC); // 清除TC标志,防止后续失效

/* 3. 写入升级成功标志位到Flash */

uint8_t data_type = IAP_UPDATE_ON;

if(IAP_FLASH_WriteByte(IAP_UPDATE_FLAG_ADDR, &data_type, 1) == 0)

{

// 可选:记录写Flash失败日志

}

/* 4. IAP状态复位(可选) */

iap_state.status = IAP_STATUS_IDLE;

/* 5. 安全复位:此时ACK已100%发送完成 */

SYSTEM_RESET;

}

方案 2:标准库(STM32F4xx_StdPeriph_Driver)

假设使用 USART1(根据实际项目修改):

/* 收到结束帧 /
else if(pData[0] == YMODEM_SOH && pData[1] == 0x00 && pData[2] == 0xff)
{
/
1. 非阻塞发送ACK(0x6) */

IAP_Send((uint8_t *)&YMODEM_ACK, 1);

/* 2. 核心改进:等待串口TC标志,带超时保护 */

uint32_t timeout = 0xFFFF; // 超时计数值(足够覆盖1字节发送)

while((USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) && (timeout-- > 0));

USART_ClearFlag(USART1, USART_FLAG_TC); // 清除TC标志

/* 3. 写入升级成功标志位到Flash */

uint8_t data_type = IAP_UPDATE_ON;

if(IAP_FLASH_WriteByte(IAP_UPDATE_FLAG_ADDR, &data_type, 1) == 0)

{

// 可选:记录写Flash失败日志

}

/* 4. IAP状态复位(可选) */

iap_state.status = IAP_STATUS_IDLE;

/* 5. 安全复位 */

SYSTEM_RESET;

}

五、关键细节避坑

必须清除 TC 标志:TC 标志不会自动清零,需手动通过__HAL_UART_CLEAR_FLAG或USART_ClearFlag清除,否则下次等待时会误判为 "已完成";

超时保护不可少:避免因串口硬件异常(如引脚短路)导致程序卡在 while 循环,无法复位;

无需额外等待 DMA 完成:若IAP_Send是 DMA 发送,DMA 完成标志(TCIF)仅表示 "数据写入串口寄存器",而 TC 标志表示 "数据发送到 TX 引脚",直接等 TC 标志更简洁;

写 Flash 操作不影响发送:写 1 字节 Flash 耗时约几十微秒,在等待 TC 标志之后执行,完全不影响 ACK 发送。

六、总结

这个问题是 STM32 非阻塞发送 + 复位的典型坑,核心教训是:

非阻塞发送的 "函数返回"≠"数据发送完成",硬件后台传输需要时间,复位、断电等强制终止操作前,必须通过硬件标志确认传输完成,不能依赖 printf 等临时耗时操作。

改进后的代码通过等待串口 TC 标志,实现了 "精准等待 + 通用适配"(适配所有波特率),比 printf 更可靠、更高效,已在实际项目中验证通过,上位机可稳定接收 0x6 的 ACK 应答。

相关推荐
fie888913 小时前
基于51单片机的航模遥控器6通道接收机程序
单片机·嵌入式硬件·51单片机
bu_shuo13 小时前
嵌入式硬件工程师VS单板硬件工程师
嵌入式硬件·电子工程师·单板硬件
llilian_1613 小时前
选择北斗导航卫星信号模拟器注意事项总结 北斗导航卫星模拟器 北斗导航信号模拟器
功能测试·单片机·嵌入式硬件·测试工具·51单片机·硬件工程
Yyq1302086968214 小时前
MH2457,‌国产 32 位屏驱 MCU‌芯片,支持‌1080P 高清显示‌与‌以太网通信‌,广泛应用于两轮车仪表盘及工控屏等领域
单片机·嵌入式硬件
爱分享的阿Q14 小时前
STM32现代化AI开发环境搭建:从Keil到VSCode+AI的范式转移
人工智能·vscode·stm32
爱吃程序猿的喵14 小时前
南邮计科电工电子实验B《RLC串联谐振电路》实验报告
单片机·嵌入式硬件
独小乐14 小时前
009.中断实践之实现按键测试|千篇笔记实现嵌入式全栈/裸机篇
linux·c语言·驱动开发·笔记·嵌入式硬件·arm
XINVRY-FPGA15 小时前
XC7VX690T-2FFG1157I Xilinx AMD Virtex-7 FPGA
arm开发·人工智能·嵌入式硬件·深度学习·fpga开发·硬件工程·fpga
bubiyoushang88817 小时前
利用STM32实现Modbus通信(RTU从机方案)
stm32·单片机·嵌入式硬件
cmpxr_18 小时前
【单片机】常用设计模式
单片机·嵌入式硬件·设计模式