STM32 DMA 串口长时间运行后响应失效要检查 UART 错误状态

接手维护一个低功耗项目后,遇到串口长时间运行后失效的问题。MCU 向外部通信模组发起查询,模组按请求返回数据,但 MCU 侧收不到响应。接收链路已经用了 DMA、中断、乒乓缓冲区和外部 FIFO,仍然会在连续运行数天后偶发失效。

串口长时间失效不一定只由 ORE 触发,也可能来自 FE、NE、PE 等其他 UART 错误状态。这个案例最后定位到的是 ORE 接收溢出:固件没有清掉这个错误标志,后续 DMA/HAL 接收链路被错误状态卡住。

现象

故障表现很窄:

测试方法

为了缩短复现时间,测试时把这个通信模块单独拎出来做压力测试,并缩短低功耗休眠间隔。单位时间内执行更多次“进入低功耗、唤醒、查询模组、接收响应”的循环,让原本几天才暴露的问题更快出现。

问题暴露后,保存两个时间点的状态:

这些 dump 再交给一个临时 HTML 解析工具做字段级对比。对比结果直接指向 UART 错误状态:失效现场出现了 ORE。

后续验证不再每次从零开始挂机。下一轮测试会把故障时 dump 出来的内存状态重新加载回去,让系统直接回到问题现场,再观察修复后的接收链路能不能恢复。

原因

ORE 是 Overrun Error,含义是接收溢出。典型过程如下:

  1. UART 收到一个字节,硬件把数据放入接收数据寄存器,并置位 RXNE。
  2. CPU 或 DMA 还没有及时搬走 RDR 或 DR 里的数据。
  3. 下一个字节已经到达。
  4. 接收寄存器没有空位,硬件置位 ORE。

接手时,已有代码里有一段注释为“玄学操作”的处理:先读 SR 寄存器,再读 DR 寄存器。这个动作并不玄学,它正是部分 STM32 系列用于清除 ORE 的规定序列。问题在于这段代码放在一个不够可靠的位置,只覆盖了部分正常路径;特殊时序下没有走到这段处理,ORE 就没有按预期清掉。

在这个项目里,外部模组的响应只发生在 MCU 查询之后,不存在模组长时间主动灌数据。ORE 更可能来自接收链路的短暂不可服务区间:

这些条件单次测试未必出现。运行时间足够长后,查询响应、低功耗切换、任务调度、中断屏蔽和 DMA 接收状态总会碰到一次不利组合。

后果

ORE 置位后,至少已经丢过一个字节。对 AT 指令、Modbus、帧头帧尾协议或二进制数据流来说,当前帧可能已经不完整。

ORE 也不能当成普通告警忽略。很多 STM32 工程里,ORE 未清除会让 UART 停在错误状态;HAL 可能记录 HAL_UART_ERROR_ORE,进入错误回调,或者停掉原来的 DMA/中断接收流程。最终现象就是 MCU 继续发查询,但响应数据进不了正常接收路径。

修复

修复遵循最小职责原则:UART/DMA 层只负责清外设错误、恢复接收链路。已经进入外部 FIFO 或协议缓存的数据,继续交给后面的协议层处理。

这次修复沿用已有的清除机制,但把 ORE 清除动作放到可靠位置。旧 STM32 系列常见方式是读 SR 后读 DR;新系列可能需要写 ICR 的 ORECF 位,或者按参考手册要求组合读取 ISR/RDR。检测到 UART 错误状态后,按目标芯片要求清外设错误、恢复 UART/DMA 接收、记录错误事件,并向上层暴露状态。

HAL 工程里还要确认 DMA 是否仍在运行,必要时按乒乓缓冲状态重新启动。协议是否丢帧、重同步、继续等待下一包,应由协议层根据校验、帧边界和业务语义决定。

清除 ORE 是恢复接收链路的止血动作,不等于根因闭环。如果时间允许,还应该继续追查触发 ORE 的具体时序:是哪一次低功耗切换、DMA 重启、调度延迟或中断屏蔽,让接收寄存器没有及时被服务。

预防

后续同类项目至少补这些检查:

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。