串口通信协议详解:UART协议原理、波特率设置、数据帧格式、流控机制
串口,说白了就是嵌入式世界里最基础的“电话线”。你想想看,一个MCU要跟PC通信,或者两个MCU之间要聊天,最直接、最省引脚的方式就是UART。我做Bootloader这么多年,串口烧录始终是最后一道防线——哪怕芯片里程序跑飞了,只要Bootloader还在,串口就能把固件灌进去。
这一章我们就把UART的底裤扒干净。从协议原理到波特率怎么算,从数据帧里每个bit的含义到流控要不要加,我都会结合项目里的实际坑点来讲。
UART协议原理:异步,但并不同步
UART全称是Universal Asynchronous Receiver/Transmitter。注意“Asynchronous”这个词——异步。什么意思?就是收发双方没有独立的时钟线。不像SPI有SCLK,I2C有SCL,UART就靠两根线:TX和RX。
那没有时钟怎么对齐数据?靠的是双方约定好的波特率,以及起始位和停止位。我打个比方:两个人约好每秒钟说一个字,说之前先咳嗽一声(起始位),说完之后点个头(停止位)。这样哪怕没有节拍器,也能听懂对方在说什么。
核心要点:UART是异步串行通信,数据以帧为单位传输,每帧由起始位、数据位、可选的校验位和停止位组成。收发双方必须使用相同的波特率、数据位长度、校验方式和停止位位数。
我在一个工业项目里遇到过这样的问题:两块板子用UART通信,一块是5V的STM32,另一块是3.3V的ESP32。直接连TX和RX,结果ESP32的IO口烧了。嗯,这里要注意——电平不匹配是串口通信最常见的硬件坑之一。
波特率设置:快与稳的博弈
波特率(Baud Rate)就是每秒传输的符号数。对于UART来说,一个符号就是一个bit,所以波特率就等于比特率。常见的波特率有:9600、19200、38400、115200、921600等。
怎么选?我个人习惯遵循一个原则:在满足时间要求的前提下,选最低的稳定波特率。为什么?因为波特率越高,每个bit的宽度越窄,对时钟精度和信号质量的要求就越高。
| 波特率 | bit宽度 | 典型应用场景 | 注意事项 |
|---|---|---|---|
| 9600 | 104.17 µs | 调试日志、低速率传感器 | 几乎任何MCU都能稳定跑 |
| 115200 | 8.68 µs | Bootloader烧录、文件传输 | 需要晶振误差<2% |
| 921600 | 1.09 µs | 高速固件升级 | 线长建议<10cm,需硬件流控 |
波特率误差怎么算?以STM32为例,UART时钟来自APB总线,通过USARTDIV分频得到目标波特率。公式是:
BaudRate = fCK / (16 * USARTDIV)
如果fCK=72MHz,想要115200,算出来USARTDIV=39.0625。但分频寄存器只能取整数或半整数,实际只能设39或39.5。对应的实际波特率分别是115384和113924。误差分别是0.16%和-1.1%。
避坑指南:我曾经在一个项目里用了内部RC振荡器(精度约±1%),波特率设到460800,结果通信时好时坏。后来换成外部晶振,问题立刻消失。记住:波特率越高,对时钟源精度要求越苛刻。Bootloader场景下,我建议115200封顶,除非你确认晶振误差在±0.5%以内。
数据帧格式:每个bit都有它的使命
一个完整的UART数据帧长这样:
起始位(1bit) + 数据位(5~9bit) + 校验位(0/1bit) + 停止位(1/1.5/2bit)
我们一条一条拆开看。
起始位
TX线从高电平拉低,持续1个bit时间。接收端检测到这个下降沿,就知道“要发数据了”。这是帧同步的关键。
数据位
通常是8位,LSB(最低有效位)在前。为什么LSB在前?因为早期UART硬件设计上,先发低位可以更快地开始移位操作。这个习惯一直保留到现在。
校验位
可选。奇校验:数据位+校验位中1的个数为奇数。偶校验:1的个数为偶数。我个人在Bootloader场景下很少用校验位——因为数据包层面已经有CRC校验了,再加一个bit的奇偶校验意义不大,反而浪费带宽。
停止位
TX线拉高,持续1/1.5/2个bit时间。这是给接收端处理数据的时间窗口。停止位越长,抗干扰能力越强,但传输效率越低。
经验之谈:我习惯用8N1——8位数据、无校验、1位停止位。这是UART通信的“普通话”,几乎所有设备都支持。如果你不确定对方的配置,先试8N1,大概率能通。
流控机制:硬件流控 vs 软件流控
流控(Flow Control)解决的是“发送方太快,接收方来不及处理”的问题。UART的接收缓冲区通常只有1个字节(FIFO深的除外),如果CPU没来得及读,下一个字节就来了,直接覆盖——这就是溢出错误。
两种流控方式:
硬件流控(RTS/CTS)
需要额外两根线:RTS(请求发送)和CTS(允许发送)。接收方准备好接收时,拉高RTS;发送方检测到CTS为高,才发送数据。这是最可靠的流控方式。
我记得在一个批量烧录项目中,用115200波特率通过串口给1000块板子烧固件。没有硬件流控时,大约有3%的板子烧录失败。加上RTS/CTS后,失败率降到0。为什么?因为每块板子的Flash擦写时间不一样,硬件流控能自动适配最慢的那块。
软件流控(XON/XOFF)
不需要额外引脚,通过发送特殊字符来控制:XOFF(0x13)让发送方暂停,XON(0x11)让发送方继续。问题在于:如果传输的是二进制数据,0x13和0x11可能出现在数据中,造成误判。
| 流控方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 无流控 | 最简单,只需TX/RX两根线 | 高速传输易丢数据 | 调试日志、低速通信 |
| 硬件流控 | 可靠,不影响数据内容 | 需要额外两根线 | Bootloader烧录、文件传输 |
| 软件流控 | 不需要额外引脚 | 二进制数据可能误触发 | 纯文本通信 |
避坑指南:我曾经在一个产品里用了软件流控传输固件包,结果固件里恰好有一个字节是0x13,接收端以为是XOFF,直接卡死。从那以后,只要传输二进制数据,我坚决不用软件流控。要么不加流控,要么加硬件流控,没有中间选项。
UART通信知识体系
下面这张图把UART的核心知识点串起来了。从物理层到协议层,再到实际应用中的注意事项,一目了然。
写在最后
UART看起来简单,但真正用好它,需要理解每一层的工作原理。从物理层的电平匹配,到协议层的帧格式,再到时序层的波特率误差,最后到流控机制的选择——任何一个环节出问题,通信就会失败。