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_IT或ReceiveToIdle_DMA是否真的启动了
方案对比与如何选择
默认选择思路
这节不是新知识点,而是把前面的几套方案串起来,形成真正的“选法”。
当前可以形成这样一个选择框架:
-
简单、轻量、先跑通:普通中断接收 + 超时解析法 -
一波一波来、量更大、想减轻 CPU:DMA + 空闲中断 -
流式更强、生产消费解耦要求更高:再考虑 ringbuffer
现在的默认策略
-
先用最简单能跑通的方案
-
再按压力升级
这比一上来就堆最复杂方案更稳,因为:
-
先跑起来,才能知道问题到底在哪
-
先闭环,才能知道瓶颈是不是真的存在
-
复杂方案应该是为了解决具体压力,而不是为了“显得高级”
这节课对后续比赛/工程的价值
-
对比赛:
-
这节是典型的“框架课”
-
不只是学会串口,而是学会
怎么按复杂度选串口接收方案 -
对限时题很重要,因为你不可能每次都从零想起
-
-
对工程:
-
这节已经开始接触
HAL API 链路、DMA、空闲中断、ringbuffer -
这些东西会反复出现在更真实的数据流处理场景中
-
-
对你个人:
-
这一节特别适合练“老师的框架 → 自己的调用版”
-
因为它天然包含了
场景选择、误用边界、排查顺序
-
超短复习卡片
-
UART:MCU 和电脑 / 两设备串行异步通信 -
先排查:连线 → 资源是否真开 → 通信参数 -
超时解析:轻量、不定长、先求跑通 -
DMA + 空闲中断:一波一波来、量更大、减轻 CPU -
ringbuffer:持续流数据、读写解耦、避免搬移 -
HAL 链路问题:先看初始化和首次启动 -
总策略:先用最简单能跑通的,再按压力升级