Uart知识层详解
## 这份笔记回答什么
-
UART / USART 是什么
-
参数、连线、CubeMX 分别在管什么
-
timeout parse、DMA + IDLE、ringbuffer各解决哪一层问题 -
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 里经常看到
USART1、USART2,但课程里多数时候实际用的是它的异步能力
串行通信在说什么
-
数据不是多位并排一起走,而是一位一位按时间顺序发送
-
它的优势不是极限速度,而是省线、简单、适合调试和命令交互
波特率和数据帧为什么重要
-
波特率可以先理解成双方“说话的速度”
-
最常见的是
9600、115200 -
通信双方至少要在
波特率 + 数据位 + 校验位 + 停止位上匹配 -
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 -> 用户回调