STM32 串口通信 (UART) 全栈底层复习指南
目录
一、 物理层与通信协议基础 (底层时序)
1. 硬件连接规则
2. 通信时序与数据帧 (以最常用的 10 位标准帧 8N1 为例)
二、 UART 底层硬件架构 (双缓冲机制)
1. 接收双缓冲:移位寄存器 & RDR (接收数据寄存器)
2. 发送双缓冲:TDR (发送数据寄存器) & 移位寄存器
3. 波特率发生器 (Baud Rate Generator)
三、 底层硬件的四大核心中断机制
四、 软件生态演进:标准库 vs HAL 库
五、 终极奥义:HAL 库 API 与底层硬件中断的映射关系
1. 基础系列 HAL_UART_... (已知长度处理)
2. 扩展系列 HAL_UARTEx_... (不定长数据处理)
极简速记对照表
一、 物理层与通信协议基础 (底层时序)
串口(UART)是一种异步串行通信协议。它的根本在于**“双方约定相同的波特率,并用规定的速率去采样电平数据”**。
1. 硬件连接规则
交叉连接:设备 A 的 TX(发送数据线)连设备 B 的 RX(接收数据线),设备 A 的 RX 连设备 B 的 TX。
必须共地 (GND):串口是通过电压高低来判断 0 和 1 的。如果没有连接 GND 提供统一的电压参考零点,通信绝对会乱码或无法识别。
2. 通信时序与数据帧 (以最常用的 10 位标准帧 8N1 为例)
在总线空闲时,RX 和 TX 线的默认状态为高电平。发送一次常规数据需要 10 位(Bit):
起始位 (1位):把高电平拉低。这是极其重要的一步!接收方检测到电平由高变低的“下降沿”,才知道数据要来了,并开始按波特率计时。
数据位 (8位):真正的有效数据。低电平代表 0,高电平代表 1。(注意:串口底层是低位 LSB 先发,高位 MSB 后发)。
校验位 (可选):通常不用(配置为 None)。
停止位 (1位):【🚨 核心要求】必须将电平拉高!停止位必须是高电平,目的是强行让总线恢复到默认的高电平空闲状态,这样才能保证下一个起始位的“由高变低”能被成功检测到。
二、 UART 底层硬件架构 (双缓冲机制)
单片机内部到底长什么样?为什么会有“移位寄存器”和“数据寄存器”之分?其实串口内部采用的是**“双缓冲架构 (Double Buffer)”**。
1. 接收双缓冲:移位寄存器 & RDR (接收数据寄存器)
接收移位寄存器:这是一个在底层默默“接砖头”的临时工。外部 RX 线上的数据是一位一位(串行)进来的,移位寄存器就像一个抽屉,每来一个 Bit,它就把抽屉里的数据往左推一格,直到 8 个 Bit 全部收齐。
RDR (接收数据寄存器):当移位寄存器凑齐 8 个 Bit 后,它会瞬间把这 8 个 Bit并行(一次性)倒进 RDR 寄存器中。此时触发
RXNE中断,通知 CPU:“砖凑齐了,快拿走!”为什么搞双缓冲?如果没有 RDR,CPU 必须在第 8 个 Bit 刚收完的一瞬间立刻把数据拿走,否则第 9 个 Bit 进来就会把数据覆盖。有了双缓冲,移位寄存器可以继续收下一个字节,CPU 只要在下一个字节收完之前,把 RDR 里的数据读走就行,大大降低了 CPU 的压力。
2. 发送双缓冲:TDR (发送数据寄存器) & 移位寄存器
逻辑完全反过来:CPU 把一个字节写进 TDR。硬件会自动把 TDR 的数据倒入“发送移位寄存器”。
移位寄存器接管后,按照波特率,一位一位地通过 TX 引脚“挤”出去。
3. 波特率发生器 (Baud Rate Generator)
它是串口的“心脏起搏器”。内部有一个分频器,把单片机的系统时钟(如 72MHz)分频成你需要的采样频率(如 115200 bps)。
三、 底层硬件的四大核心中断机制
串口内部就像一个流水线,四大状态都会产生硬件标志位(Flag)。必须满足条件:“硬件标志位产生 (Flag=1) + 软件开启中断允许 (IE=1) + NVIC 配置放行”,CPU 才会真正跳入真正的中断函数。
| 中断类型 | 标志位 | 触发条件 | 底层白话解释 |
| 接收非空中断 | RXNE | 数据从移位寄存器转移到接收数据寄存器 (DR) 后触发。 | “收到了 1 个完整字节,放在 DR 里了,CPU 快来拿走!” |
| 发送为空中断 | TXE | CPU 将数据放入发送数据寄存器 (DR) 后,数据进入发送移位寄存器。此时 DR 被腾空触发。 | “我手里的这块砖(DR)扔进流水线了,CPU 可以给我下一块砖了!” |
| 发送完成中断 | TC | 不仅发送 DR 空了,连发送移位寄存器也空了(最后一个停止位彻底发完)。 | “全部砖头都彻底从引脚上丢出去了,打完收工!” |
| 总线空闲中断 | IDLE | 接收到字节后,后面连着 1 个完整字节的时间(10个Bit时长)RX线都是高电平。 | “运砖车断档了,外面没动静了,这批货发完了!” |
四、 软件生态演进:标准库 vs HAL 库
当硬件中断产生时,软件怎么处理?
标准库 (手工小作坊):所有硬件中断都会进入同一个入口函数
USARTx_IRQHandler。软件必须在里面自己写if判断(if(USART_GetITStatus(...) != RESET)),去查看到底是谁触发的警报,并且最后还需要手动清除标志位。HAL 库 (现代化工厂):我们只需要调用对应的 API。HAL 库底层替我们写好了那几百行的
if判断和清除标志位代码,它判断完之后,会自动呼叫对应的“回调函数 (Callback)”交给我们处理数据。
五、 终极奥义:HAL 库 API 与底层硬件中断的映射关系
在 HAL 库中,表面上你只是调用了一个函数,但底层其实是开启了不同的硬件警报器。根据是否使用Ex扩展函数,底层的逻辑有天壤之别。
1. 基础系列HAL_UART_...(已知长度处理)
这个系列处理的是死心眼的“定长”任务,它绝对不会去开启空闲中断 (IDLE)。
纯中断接收 (
HAL_UART_Receive_IT):底层开关:开启
RXNEIE。触发事件:仅靠
RXNE标志位。逻辑:外部每发来 1 个字节,打断 1 次 CPU。搬走数据后计数器减 1。减到 0 时关掉
RXNEIE并呼叫回调。
纯中断发送 (
HAL_UART_Transmit_IT):底层开关:先开
TXEIE,后开TCIE。触发事件:接力赛。先由
TXE触发塞数据,最后一个字节离开引脚时由TC触发回调。
DMA 接收 (
HAL_UART_Receive_DMA):底层开关:开启 DMA 控制器中断 (
TCIE,HTIE)。强制关闭串口RXNEIE!触发事件:只有 DMA 的
TC(满载) 或HT(过半) 能触发中断。串口本身装聋作哑。
2. 扩展系列HAL_UARTEx_...(不定长数据处理)
这个系列(带有ToIdle字眼)是专门为了对付“不知道对方发多少”而生的,它的核心灵魂就是必定会开启串口的 IDLE 中断。
纯中断空闲接收 (
HAL_UARTEx_ReceiveToIdle_IT):底层开关:
RXNEIE+IDLEIE。触发事件:
RXNE搬砖,IDLE负责断流截断结算。混合双打。
DMA 空闲接收 (
HAL_UARTEx_ReceiveToIdle_DMA) - 【终极神仙函数】:底层开关:DMA 控制器中断 (
TCIE,HTIE) + 串口的IDLEIE。依然强制关闭RXNEIE。触发事件 (跨部门竞速赛):
部门 A (DMA 控制器):数据刚好塞满,触发DMA 的
TC(或HT过半)。部门 B (串口门卫):数据没塞满但中途断流了,触发串口的
IDLE。
殊途同归:无论哪个硬件标志位触发,HAL 库都会聪明的计算出真实的字节数
Size,并最终统一导向同一个回调函数RxEventCallback。
极简速记对照表
| 你调用的 HAL 库 API | 数据搬运工 | 触发 CPU 中断的底层硬件标志 (Flag) |
HAL_UART_Receive_IT | CPU 亲力亲为 | 仅 RXNE(每字节 1 次) |
HAL_UART_Receive_DMA | DMA 硬件搬运 | 仅 DMA TC / HT(串口装聋作哑,容易死等) |
HAL_UARTEx_ReceiveToIdle_IT | CPU 亲力亲为 | RXNE(每字节搬运) +IDLE(断流截断) |
HAL_UARTEx_ReceiveToIdle_DMA | DMA 硬件搬运 | DMA TC / HT(满载保底) +串口 IDLE(断流截断,工业首选) |
