6 Uart接收方案与排查框架

Uart模块

## 这节课解决什么问题

这一节的目标,是把串口通信从“知道概念”推进到“能真正跑通、能接收、能选择方案”。

核心主线有四条:

  • 建立 UART / USART 的基础认识

  • 打通板级连接、CubeMX 配置和基础发送链路

  • 理解超时解析法的接收框架

  • 理解 DMA + 空闲中断,以及后续 ringbuffer 的升级方向


核心结论

  • UART 是异步串行通信,常用于 MCU 和电脑通信两个设备之间串行收发数据

  • USART 是更完整的外设名,既支持同步也支持异步;课程里多数时候实际用的是它的异步能力。

  • 串口问题不要只盯代码,很多问题首先出在 连线、资源使能、首次启动、通信参数

  • 超时解析法 适合轻量、不定长、先求跑通的场景。

  • DMA + 空闲中断 适合一波一波来的更大数据量场景,重点是减轻 CPU 逐字节处理中断的负担。

  • ringbuffer 不是 UART 专属,而是处理持续流式数据时更稳的结构升级。

  • 现场默认策略应是:先用最简单能跑通的方案,再按压力升级


引入与 UART 基本概念

这部分讲什么

UART 用于串行异步通信,典型场景是:

  • MCU 和电脑通信

  • 两个设备之间串行通信

基础概念包括:

  • 串行 vs 并行:UART 属于串行通信,一位一位发送。

  • 波特率:通信双方必须一致,否则会乱码或通信失败。

  • 数据帧:UART 按固定帧格式收发,不是随意发比特流。

  • TX / RX:发送脚和接收脚需要交叉连接;供电系统不同通常还要共地。

看到什么信号会想到它

  • 看到 MCU 和电脑通信

  • 看到 两个设备之间串行异步收发

  • 就优先想到 UART / USART

什么情况下别乱用

  • 不是所有通信都该上 UART

  • 如果需求不是串行异步通信,或者场景本身更适合别的总线/接口,就不要盲目用 UART

第一排查顺序

  • 先查 物理连线

  • 再查 串口 / COM 口是否真的打开

  • 最后查 波特率、数据位、停止位、校验位 等参数是否一致


硬件连接与 CubeMX 基础发送

这部分讲什么

这一部分的目标,不是先深挖原理,而是先把 串口发送链路跑通

起手要先看:

  • 板子上是否已经板载串口转换芯片

  • UART 实际接到了哪个接口、哪个引脚

  • 是否还需要额外接 USB 转 TTL

然后再去做 CubeMX 配置:

  • 先确认引脚

  • 再确认串口资源是否真正使能

  • 注意引脚复用冲突

  • 配置波特率等基本参数

生成工程后,要重点看:

  • 串口初始化

  • 硬件初始化

发送层建议先跑最小闭环:

  • HAL_UART_Transmit

  • 再封成 my_printf(...)

  • 最后用串口调试助手验证

看到什么信号会先查板级与配置

  • 工程能编译通过

  • 但串口没有任何现象,或者现象明显不对

  • 这时不能只怀疑代码,要先回头查板级链路和串口资源是否真的打通

常错点

  • CubeMX 串口资源没真正使能

  • 初始化没调用,或者调用顺序不对

  • 如果使用 printf 重定向,Keil / C库链路没有配好

第一次独立跑通的起手顺序

  • 先看原理图和板载串口链路

  • 再确认接口和引脚

  • 再做 CubeMX 配置

  • 再检查工程侧打印配置

  • 最后写最小发送测试代码验证


超时解析法

这部分讲什么

超时解析法的核心思想是:

  • 每收到一个字节就先存起来

  • 同时刷新“最后一次收到数据的时间”

  • 如果一段时间没有新字节到来,就把这批数据当作一整帧

这套方法适合:

  • 不定长数据

  • 轻量通信

  • 协议不复杂

  • 可以接受用空闲一段时间来判断一帧结束

框架怎么分工

典型框架分成三部分:

  • HAL_UART_RxCpltCallback

    • 收一个字节

    • 更新时间戳

    • 增加计数

    • 重新挂下一次单字节接收

  • uart_task

    • 周期检查是否超时

    • 一旦超时,就把缓冲区中的数据作为一帧处理

  • my_printf

    • 负责辅助打印调试

真正重要的是职责划分:

  • 中断里只做轻量动作

  • 真正处理放在任务/主循环

  • 先把接收框架搭好,再往里面放业务逻辑

看到什么信号会想到它

  • 数据 不定长

  • 通信 轻量

  • 先求 快速跑通

  • 这时优先考虑超时解析法

什么情况下别优先用它

  • 如果数据吞吐更大

  • 收发更频繁

  • CPU 压力更敏感

  • 就不该优先选超时解析法,而应进一步考虑 DMA + 空闲中断

第一排查点

  • 如果现场没跑通,先查 第一次接收有没有手动启动

  • 因为第一次 HAL_UART_Receive_IT(...) 没挂上,后续接收链根本起不来


DMA + 空闲中断解析法

这部分讲什么

这套方案的目标是:减少 CPU 逐字节接收的负担

核心分工:

  • DMA:自动把串口收到的数据搬到内存缓冲区

  • 空闲中断:检测一段时间没有新数据到来,提示“一帧可能结束了”

相比超时解析法,它的关键变化是:

  • 不再每来一个字节都由 CPU 处理中断接收

  • 而是让 DMA 连续搬运

  • 等出现空闲事件后,再统一处理这一批数据

关键链路

  • CubeMX 中配置 UART RX 的 DMA

  • 使能 UART 中断

  • 启动 HAL_UARTEx_ReceiveToIdle_DMA(...)

  • HAL_UARTEx_RxEventCallback(...) 中使用 Size 获取本次实际接收长度

  • 将有效数据转入待处理缓冲区

  • 置位标志

  • 重新启动下一轮 DMA 接收

两个关键理解点

  • 回调里不要做太重的处理

  • Size 代表本次真正收到的字节数,不能直接拿整个 DMA 缓冲区长度去处理

看到什么信号会想到它

  • 数据是一波一波来的

  • 单次数据量可能更大

  • 不希望每个字节都触发 CPU 处理

  • 这时优先考虑 DMA + 空闲中断

什么情况下别一开始就上它

  • 场景简单

  • 数据量小

  • 当前目标只是先跑通

  • 这时超时解析法通常更直接

第一排查点

  • 现场跑不通时,先查 CubeMX 里的 DMA 配置

  • 包括 DMA 请求、模式、UART 中断、关联关系是否真的配对


环形缓冲区

这部分讲什么

环形缓冲区不是 UART 独有,而是处理 持续数据流 的通用结构。

它解决的核心问题是:

  • 数据持续到来时,普通线性数组不好长期管理

  • 接收速度和处理速度不一致时,线性数组容易低效或混乱

  • 反复搬移数据代价高,环形缓冲区可以避免这一点

核心机制是:

  • 写指针 负责写入新数据

  • 读指针 负责取出待处理数据

  • 指针走到尾部后回绕到开头,形成“环”

这一节当前阶段不要求吃透所有底层细节,重点是:

  • 知道它解决什么问题

  • 知道它什么时候比普通数组更合适

  • 知道以后移植到 UART 时应该先改哪条数据流

看到什么信号会想到它

  • 对实时性要求更高

  • 数据是持续流式到来

  • 接收和处理速度可能不一致

  • 不想频繁搬移数组内容

  • 这时会考虑 ringbuffer

什么情况下别急着上它

  • 如果数据量小

  • 逻辑简单

  • 线性数组已经够用

  • 就没必要急着上环形缓冲区

  • 是否真要升级,很多时候要结合实际运行表现来判断

如果以后要移植进 UART,先看哪里

  • 先看 当前数据流入口和出口

  • 也就是:

    • 回调里数据是怎么进入缓冲区的

    • 主循环 / 任务里数据是怎么被取走和解析的

  • 本质上不是平白塞进去,而是替换当前的“线性缓冲区接收 + 线性读取处理”链路


HAL 库关键 API 与中断链路

这部分讲什么

这一节重点不是背 HAL 函数名,而是看清:

  • 启动 API

  • 中断入口

  • HAL 内部处理

  • 用户回调

是怎么串成一条链的。

关键 API / 机制包括:

  • HAL_UART_Receive_IT

  • HAL_UART_RxCpltCallback

  • HAL_UARTEx_ReceiveToIdle_DMA

  • HAL_UARTEx_RxEventCallback

  • HAL_UART_IRQHandler

  • uwTick

  • weak

这一节真正的价值

以后遇到问题时,不只是抄代码,而是能大概判断问题可能落在哪一层:

  • CubeMX / 初始化层

  • HAL 启动 API 层

  • 中断入口层

  • HAL 内部处理层

  • 用户回调层

看到什么信号会往这条链里查

  • 应用代码看起来已经写了

  • 但串口现象完全不对

  • 尤其是 回调没进接收链像没启动

  • 这时就要往 HAL API 和中断链路里查

什么情况下别急着深挖底层

  • 如果回调已经正常工作

  • 现象也闭环了

  • 就没必要一开始就钻 HAL_UART_IRQHandler 这类底层细节

  • 先把应用层和框架层跑通更重要

第一排查点

  • 如果接收回调根本没进

  • 先查 初始化 / 首次启动

  • 例如串口初始化是否到位

  • 首次 Receive_ITReceiveToIdle_DMA 是否真的启动了


方案对比与如何选择

默认选择思路

这节不是新知识点,而是把前面的几套方案串起来,形成真正的“选法”。

当前可以形成这样一个选择框架:

  • 简单、轻量、先跑通:普通中断接收 + 超时解析法

  • 一波一波来、量更大、想减轻 CPU:DMA + 空闲中断

  • 流式更强、生产消费解耦要求更高:再考虑 ringbuffer

现在的默认策略

  • 先用最简单能跑通的方案

  • 再按压力升级

这比一上来就堆最复杂方案更稳,因为:

  • 先跑起来,才能知道问题到底在哪

  • 先闭环,才能知道瓶颈是不是真的存在

  • 复杂方案应该是为了解决具体压力,而不是为了“显得高级”


这节课对后续比赛/工程的价值

  • 对比赛:

    • 这节是典型的“框架课”

    • 不只是学会串口,而是学会 怎么按复杂度选串口接收方案

    • 对限时题很重要,因为你不可能每次都从零想起

  • 对工程:

    • 这节已经开始接触 HAL API 链路DMA空闲中断ringbuffer

    • 这些东西会反复出现在更真实的数据流处理场景中

  • 对你个人:

    • 这一节特别适合练“老师的框架 → 自己的调用版”

    • 因为它天然包含了 场景选择、误用边界、排查顺序


超短复习卡片

  • UART:MCU 和电脑 / 两设备串行异步通信

  • 先排查:连线 → 资源是否真开 → 通信参数

  • 超时解析:轻量、不定长、先求跑通

  • DMA + 空闲中断:一波一波来、量更大、减轻 CPU

  • ringbuffer:持续流数据、读写解耦、避免搬移

  • HAL 链路问题:先看初始化和首次启动

  • 总策略:先用最简单能跑通的,再按压力升级