6.2 Uart知识层

Uart知识层详解

## 这份笔记回答什么

  • UART / USART 是什么

  • 参数、连线、CubeMX 分别在管什么

  • timeout parseDMA + IDLEringbuffer 各解决哪一层问题

  • USARTx_IRQHandler -> HAL_UART_IRQHandler -> callback 这条链为什么能走通

如果你更想看:

  • 现场选型和 first check:回 6 Uart接收方案与排查框架.md

  • 真实复现与排障:回 6.1 Uart课程复现与排障记录模板.md


这节到底要会什么?

  • 知道 UART 是基础异步串行通信

  • 知道串口第一层先查链路和参数

  • 知道接收真正的难点是“帧边界”

  • 知道三类东西不要混层:

    • timeout parse = 边界判断法

    • DMA + IDLE = 搬运 + 边界事件

    • ringbuffer = 缓冲结构升级


UART / USART 基础认识

UART 和 USART 怎么区分

  • UART 可以先理解成异步串行收发

  • USART 是更完整的外设名,既支持同步,也支持异步

  • 在 STM32 里经常看到 USART1USART2,但课程里多数时候实际用的是它的异步能力

串行通信在说什么

  • 数据不是多位并排一起走,而是一位一位按时间顺序发送

  • 它的优势不是极限速度,而是省线、简单、适合调试和命令交互

波特率和数据帧为什么重要

  • 波特率可以先理解成双方“说话的速度”

  • 最常见的是 9600115200

  • 通信双方至少要在 波特率 + 数据位 + 校验位 + 停止位 上匹配

  • UART 发的是有结构的数据帧,不是随便把 0 和 1 推到线上

TX / RX 为什么总要交叉接

  • TX 是发送线,RX 是接收线

  • A 的 TX 要接 B 的 RX,A 的 RX 要接 B 的 TX

  • 电源体系分开时通常还要共地

  • 所以串口没反应时,先回头看连线和板级链路,不要只盯软件


板级链路和配置

为什么先看板级链路

  • 你工程里开了某个 USART,不代表它就真的连到了电脑那边

  • 要先搞清:板上有没有串口转换芯片、实际走哪一路外设、最终落到哪组引脚

  • 这一步没搞清,后面很容易出现“代码没错但串口助手没反应”

CubeMX 核心在做什么

  • 选定 UART / USART 实例

  • 绑定 TX / RX 引脚

  • 配置基本通信参数

  • 配置中断

  • 如果进入 DMA 方案,再补 DMA 关联

MX_USARTx_UART_Init()HAL_UART_MspInit() 分别在管什么

  • MX_USARTx_UART_Init() 更像把 UART 外设本体参数设好

  • HAL_UART_MspInit() 更像把 GPIO、时钟、中断、DMA 这些外围条件接好

  • 所以串口能不能工作,要同时看“外设本体”和“外围条件”


为什么先打通最小发送链

  • 发送先通,说明 UART 资源、板级链路、参数匹配、当前输出通道大体是活的

  • HAL_UART_Transmit 适合做最小闭环验证

  • my_printf 的本质是“格式化字符串 + 指定 UART 发送”

  • 它的价值不是替代标准 printf,而是让当前工程的串口输出路径更明确


接收的核心问题

接收链表面上是在“收字节”,真正难点通常是:

  • 我怎么知道这一帧什么时候结束

所以后面的很多方案,本质都在回答同一个问题:

  • 帧边界怎么判断

超时解析法

用时间差判断帧尾

  • 只要还在持续收到字节,就先认为这一帧没结束

  • 如果隔了一段时间没有新字节,就把前面那批数据当成一整帧

核心分工

  • 中断回调:收字节、记时间、继续挂下一字节接收

  • 主循环 / 任务:检查是否超时

  • 超时成立后:把这一批数据按整帧处理

两个关键点

  • uwTick 可以先理解成 HAL 提供的全局毫秒计时器

  • 第一次接收必须手动启动,否则整条接收链不会自己起来

优点和边界

  • 优点:简单、好理解、适合先跑通

  • 边界:高频数据下中断多,CPU 参与度更高


DMA + IDLE

把搬运和边界分给不同角色

  • DMA 在这里的价值是“把数据从 UART 自动搬到内存”

  • IDLE 在这里的价值是“给出这一波可能结束的信号”

  • 所以它的本质是“DMA 搬运 + 硬件事件判帧尾”

Size 为什么是关键

  • HAL_UARTEx_RxEventCallback(...) 里,Size 代表本次实际收到的有效字节数

  • 它不是整个 DMA 缓冲区长度

  • 后续处理必须按 Size 来看真实数据范围

为什么回调里仍然别做重活

  • 回调本质上仍然是中断上下文拉起来的处理点

  • 更稳的做法还是:回调里先交接数据,真正业务处理放到主循环 / 任务里


ringbuffer

升级的不是 UART,而是缓冲结构

  • 它不是替代 UART

  • 它不是替代 DMA

  • 它真正升级的是“数据收到以后怎么更稳地存和取”

为什么线性数组会开始不够用

  • 数据持续到来时,线性数组边界不好管

  • 接收和处理速度不一致时,结构容易变乱

  • 这时 ringbuffer 才真正有价值

最核心的结构思想

  • 固定容量缓冲区

  • 写指针负责写入

  • 读指针负责取出

  • 走到末尾后回绕

为什么空和满不好区分

  • 只看读写指针是否相等,会有“空”和“满”都可能相等的歧义

  • 所以常见要再配合:

    • 空出一个位置

    • 额外计数器

    • 镜像位

UART 场景下它为什么有价值

  • UART 很容易形成“前台持续收,后台按节奏处理”的结构

  • ringbuffer 更适合做生产者 / 消费者解耦


HAL 回调链

回调链为什么能走到你的函数

  • 第一层:硬件先触发中断,CPU 进入 USARTx_IRQHandler

  • 第二层:中断入口把处理交给 HAL_UART_IRQHandler(...)

  • 第三层:HAL 根据当前标志位、句柄状态、接收模式决定后面走哪条链

  • 第四层:你自己写的 callback 能生效,是因为 HAL 往往先给了 weak 弱定义版本


别再把三种东西混成一层

  • 超时解析法:更像“帧边界怎么判断”

  • DMA + 空闲中断:更像“数据由谁搬、边界由谁给”

  • ringbuffer:更像“收到以后怎么更稳地管”

这样拆开之后,就更容易看懂它们为什么既有关联,又不是同一个维度的问题。


封包、解包、粘包

  • UART 最后面对的通常不是“天然整整齐齐的一条消息”,而是字节流

  • 封包:发送方主动按规则组织数据

  • 解包:接收方按规则把字节流还原成完整消息

  • 粘包:多条数据连在一起到,或者一条数据分几次到


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

  • 对比赛:你以后遇到串口题,不会只停在“我记得有个 API”,而会开始按“链路、边界、缓冲结构”去理解

  • 对工程:串口经常承担日志、命令、模块通信三类角色

  • 对后续学习:这一课里的 DMA、回调链、ringbuffer 思维后面会反复复用


超短复习卡片

  • UART:异步串行通信;USART:更完整的同步/异步外设名

  • 先看链路:板级连接、引脚映射、参数匹配

  • 接收难点:不是收字节本身,而是判断一帧何时结束

  • 超时解析:逐字节接收 + 时间差判帧尾

  • DMA + Idle:DMA 搬运 + 空闲事件给边界

  • ringbuffer:不是另一种 UART,而是缓冲结构升级

  • HAL 回调链USARTx_IRQHandler -> HAL_UART_IRQHandler -> 用户回调