目录
[一、 物理层与通信协议基础 (底层时序)](#一、 物理层与通信协议基础 (底层时序))
[1. 硬件连接规则](#1. 硬件连接规则)
[2. 通信时序与数据帧 (以最常用的 10 位标准帧 8N1 为例)](#2. 通信时序与数据帧 (以最常用的 10 位标准帧 8N1 为例))
[二、 UART 底层硬件架构 (双缓冲机制)](#二、 UART 底层硬件架构 (双缓冲机制))
[1. 接收双缓冲:移位寄存器 & RDR (接收数据寄存器)](#1. 接收双缓冲:移位寄存器 & RDR (接收数据寄存器))
[2. 发送双缓冲:TDR (发送数据寄存器) & 移位寄存器](#2. 发送双缓冲:TDR (发送数据寄存器) & 移位寄存器)
[3. 波特率发生器 (Baud Rate Generator)](#3. 波特率发生器 (Baud Rate Generator))
[三、 底层硬件的四大核心中断机制](#三、 底层硬件的四大核心中断机制)
[四、 软件生态演进:标准库 vs HAL 库](#四、 软件生态演进:标准库 vs HAL 库)
[五、 终极奥义:HAL 库 API 与底层硬件中断的映射关系](#五、 终极奥义:HAL 库 API 与底层硬件中断的映射关系)
[1. 基础系列 HAL_UART_... (已知长度处理)](#1. 基础系列 HAL_UART_... (已知长度处理))
[2. 扩展系列 HAL_UARTEx_... (不定长数据处理)](#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 (断流截断,工业首选) |