3. 串口通信协议:UART硬件层协议、波特率与帧格式、流控机制
串口,UART,这东西做嵌入式的没人能绕开。我入行那会儿调试第一块板子,就是靠一根串口线打印log活下来的。这么多年过去了,SPI、I2C、CAN换了一茬又一茬,但UART始终是那个最朴实、最可靠的“老伙计”。
这一节,咱们就把UART的硬件层协议、波特率、帧格式和流控机制彻底聊透。不扯虚的,全是实战里用得上的东西。
3.1 UART硬件层协议:说白了就是两根线
UART全称是Universal Asynchronous Receiver/Transmitter。注意“Asynchronous”这个词——异步。什么意思?收发双方没有独立的时钟线,全靠约定好的速率和对齐方式来沟通。
硬件上,最基本的UART只需要两根信号线:
- TXD (Transmit Data):发送数据线
- RXD (Receive Data):接收数据线
再加上一根共用的GND。三根线就能跑起来。
关键点:交叉连接
设备A的TXD接设备B的RXD,设备A的RXD接设备B的TXD。这个我见过太多新手接反了,结果死活调不通。嗯,别笑,我自己也犯过。
UART在空闲时,信号线保持高电平。开始传输时,先拉低一个位时间,这叫“起始位”。然后从低位到高位依次发送数据位,最后是“停止位”(高电平)。有些场景还会加一个“校验位”。
说白了,UART的物理层就是靠电平变化来传递信息的。没有时钟线,全靠收发双方对“一个位的时间”有相同的理解——这个理解,就是波特率。
3.2 波特率:通信的“节拍器”
波特率(Baud Rate)定义了一秒钟传输多少个符号。对于UART来说,一个符号就是一个bit,所以波特率就等于比特率。
常见的波特率有:
| 波特率 (bps) | 一个位的时间 | 常见应用场景 |
|---|---|---|
| 9600 | 104.17 µs | 调试终端、老旧设备 |
| 115200 | 8.68 µs | 通用调试、模组AT指令 |
| 921600 | 1.09 µs | 高速数据传输、固件升级 |
| 2M / 3M | 0.5 µs / 0.33 µs | 高速模组、FPGA调试 |
我的习惯:调试阶段一律用115200。9600太慢,log刷起来像看PPT。921600以上对线缆和MCU时钟精度要求高,容易出问题。等产品定型了再根据实际需求调整。
波特率误差是个大坑。UART允许的误差一般在±2%以内。为什么?因为接收端是在每个位的中间采样,如果误差累积,采样点会逐渐偏移,最终采到错误的电平。
我曾经在一个项目里,MCU主频是16MHz,怎么配都配不出精确的115200。算下来误差是3.2%,结果通信时好时坏。后来换了个晶振,问题就解决了。所以,配波特率时一定要算误差。
计算公式很简单:
实际波特率 = 系统时钟频率 / (16 × 分频系数)
误差 = |(实际波特率 - 目标波特率) / 目标波特率| × 100%
误差超过2%,就别指望稳定通信了。
3.3 帧格式:数据怎么打包
UART的一帧数据,结构是这样的:
起始位(1bit) + 数据位(5~9bit) + 校验位(0/1bit) + 停止位(1/1.5/2bit)
最常见的配置是:8N1——8位数据、无校验、1位停止位。
来,拆开看:
- 起始位:固定为低电平,告诉接收端“我要开始发了”。
- 数据位:从LSB(最低有效位)开始发送。比如要发0x55(二进制01010101),先发最低位1,再发0,再发1……
- 校验位:可选。奇校验保证数据位+校验位里1的个数为奇数;偶校验保证为偶数。
- 停止位:固定为高电平,给接收端时间处理刚收到的字节。
注意:有些老设备用5位或6位数据位,比如某些工业仪表。如果你用8N1去配,数据会错位。我遇到过一台德国产的温控器,数据位是7位,带偶校验,折腾了我半天才找到原因。
帧格式不匹配,通信就是鸡同鸭讲。两边必须完全一致,一个bit都不能差。
3.4 流控机制:数据太多怎么办?
UART没有时钟线,收发双方各自按自己的节奏跑。如果发送端一直发,接收端来不及处理,数据就会被覆盖——这就是“溢出”。
解决这个问题,需要流控(Flow Control)。
流控分两种:
3.4.1 硬件流控(RTS/CTS)
用额外的两根信号线:
- RTS (Request to Send):发送请求。接收端告诉发送端“我准备好了,你可以发了”。
- CTS (Clear to Send):清除发送。发送端看到CTS有效,才开始发数据。
流程是这样的:
- 接收端拉高RTS,表示“我有缓冲区,可以收”。
- 发送端检测到RTS有效,开始发数据。
- 接收端缓冲区快满了,拉低RTS,表示“停!我消化一下”。
- 发送端检测到RTS无效,停止发送。
硬件流控效率高,不浪费带宽。但需要MCU和模组都支持,而且要多接两根线。
3.4.2 软件流控(XON/XOFF)
不用额外引脚,靠发送特殊字符来控制:
- XOFF (0x13):接收端发这个字符,告诉发送端“暂停”。
- XON (0x11):接收端发这个字符,告诉发送端“继续”。
软件流控的优点是省引脚,但缺点也很明显:
- 如果传输的是二进制数据,0x13和0x11可能出现在数据里,造成误判。
- 响应有延迟,不适合高速通信。
我的建议:能用硬件流控就用硬件流控。尤其是和4G/5G模组通信时,模组发数据的速度可能远超MCU的处理能力。没有流控,丢数据是家常便饭。
3.5 知识体系总览
下面这张图,把UART的核心知识点串起来了。你可以把它当作一个快速索引:
3.6 实战中的几个坑
最后,分享几个我踩过的坑,希望能帮你省点时间:
- 波特率误差:配完一定要算。尤其是用内部RC振荡器的时候,误差可能比手册上写的还大。
- 电平不匹配:MCU是3.3V,模组是1.8V,直接连会烧引脚。电平转换芯片(比如TXS0108)该用就用。
- 共地问题:两个设备之间GND必须连。不连地,信号飘忽不定,什么怪问题都可能出现。
- 中断优先级:UART接收中断里别做耗时操作。我曾经在中断里做协议解析,结果丢了一堆数据。后来改成DMA + 环形缓冲区,世界清净了。