4. 串口通信协议详解:UART协议基础,波特率、数据位、停止位、校验位,硬件流控
串口通信,说白了就是两个设备之间,用一根或两根线,按约定好的节奏,一位一位地传数据。UART(Universal Asynchronous Receiver/Transmitter)是其中最经典的协议。我刚开始做嵌入式时,觉得串口太简单了,不就是TX、RX两根线嘛。直到有一次,一个产品在产线上批量烧录时,10台里有3台烧录失败……查了两天才发现,是波特率偏差和流控没处理好。从那以后,我对串口协议的每一个参数都格外较真。
这一章,我们就来把UART协议的底裤扒干净。你理解了这些基础,后面讲串口烧录和OTA升级时,才能知道为什么有时候会卡住、为什么会丢包。
4.1 UART协议的本质:异步、全双工、帧结构
UART是异步通信,意思是收发双方没有独立的时钟线。那它们怎么同步?靠的是双方事先约定好相同的波特率,然后通过起始位来对齐。
一个完整的UART数据帧,长这样:
空闲状态(高电平) → 起始位(低电平) → 数据位(LSB先发) → 校验位(可选) → 停止位(高电平)
我习惯把UART帧比作一列火车:起始位是火车头,告诉接收方“注意,数据要来了”;数据位是车厢,装着你要传的信息;校验位是货物清单,用来检查车厢有没有被篡改;停止位是车尾,告诉接收方“这一趟结束了”。
核心要点:UART是异步的,所以收发双方的波特率必须一致。偏差超过2%-3%,就会出现误码。我在项目中见过有人用115200和1152000(少打了个0),结果数据全是乱码,查了半天才反应过来。
UART也是全双工的,TX和RX独立工作。这意味着你可以同时发送和接收数据。这在OTA升级中很重要——设备一边接收固件包,一边可以回复ACK。
4.2 波特率:通信的节拍器
波特率,就是每秒传输的符号数。对于UART来说,一个符号就是一个bit,所以波特率就等于比特率。单位是bps(bits per second)。
常见的波特率有:
| 波特率(bps) | 每位时长(μs) | 典型应用场景 |
|---|---|---|
| 9600 | 104.17 | 调试输出、低速传感器 |
| 19200 | 52.08 | GPS模块、工业仪表 |
| 38400 | 26.04 | 蓝牙模块、短距离通信 |
| 57600 | 17.36 | WiFi模块、中等速率 |
| 115200 | 8.68 | 串口烧录、OTA升级(最常用) |
| 230400 | 4.34 | 高速烧录、内部调试 |
| 921600 | 1.09 | 高速烧录(需硬件支持) |
你想想看,为什么115200最常用?因为它在速度和稳定性之间取得了很好的平衡。我做过测试,在普通MCU上,115200的误码率远低于230400,而速度比9600快了12倍。
我的经验:在串口烧录场景下,如果MCU的时钟精度不够(比如内部RC振荡器),建议不要用超过115200的波特率。我曾经在一个项目里用内部时钟跑230400,结果烧录成功率只有60%。换成外部晶振后,问题立刻解决。
4.3 数据位:一次传多少数据
数据位,就是每个帧里实际承载的数据比特数。常见的有5、6、7、8位。现在绝大多数场景都用8位数据位,因为一个字节正好8位,方便处理。
为什么会有7位?那是历史遗留问题。早期的ASCII码只需要7位,第8位可以用来做校验。但现在,除非你在跟古董设备通信,否则一律选8位。
数据位的发送顺序是LSB(最低有效位)先发。比如你要发送0x55(二进制01010101),先发最低位1,再发0,再发1……最后发最高位0。接收方收到后,再按LSB优先拼回去。
注意:有些设备支持9位数据位模式,用于多机通信或地址帧识别。但这不是标准UART,需要MCU的USART模块支持。我建议初学者先别碰,容易把自己绕晕。
4.4 停止位:帧与帧之间的间隔
停止位,就是每个数据帧结束后的高电平持续时间。常见的有1位、1.5位、2位。
- 1位停止位:最常用,效率最高。每传8位数据,只需要1位开销。
- 2位停止位:给接收方更多时间处理数据。适合低速设备或噪声环境。
- 1.5位:很少见,一般只在某些特殊设备上出现。
我个人习惯,只要不是跟特别老的设备通信,一律用1位停止位。为什么?因为效率高。在OTA升级中,每帧多1位停止位,意味着总传输时间增加约10%。如果固件有1MB,那差距就很明显了。
但有一种情况我会用2位停止位——当通信线路很长(比如超过10米)或者电磁干扰严重时。多一位停止位,相当于给接收方多了一个采样窗口,能有效降低误码率。
4.5 校验位:数据完整性的第一道防线
校验位,用来检测数据在传输过程中是否出错。UART支持以下几种校验方式:
| 校验方式 | 原理 | 特点 |
|---|---|---|
| 无校验(None) | 不发送校验位 | 效率最高,但无检错能力 |
| 奇校验(Odd) | 数据位+校验位中1的个数为奇数 | 能检测奇数个bit错误 |
| 偶校验(Even) | 数据位+校验位中1的个数为偶数 | 能检测奇数个bit错误 |
| 标记校验(Mark) | 校验位固定为1 | 基本不用,无实际检错能力 |
| 空格校验(Space) | 校验位固定为0 | 基本不用,无实际检错能力 |
在实际项目中,我推荐用无校验+上层CRC校验的组合。为什么?因为UART的奇偶校验只能检测奇数个bit错误,如果两个bit同时翻转,它检测不出来。而CRC(循环冗余校验)能检测出几乎所有错误。
避坑指南:我曾经在一个项目中,用了偶校验,结果数据偶尔出错,但校验位每次都通过。查了很久才发现,是线路上的噪声导致两个bit同时翻转。从那以后,我再也不依赖UART的硬件校验了。串口烧录和OTA升级,一定要在应用层做CRC校验。
4.6 硬件流控:RTS/CTS
硬件流控,就是通过额外的信号线,让收发双方互相告诉对方“我准备好了”或者“我忙,你先别发”。常用的信号线是RTS(请求发送)和CTS(清除发送)。
工作流程是这样的:
- 设备A想发送数据,先拉高RTS,告诉设备B“我要发了”。
- 设备B如果准备好了,就拉高CTS,告诉设备A“来吧,我准备好了”。
- 设备A看到CTS为高,开始发送数据。
- 如果设备B的接收缓冲区快满了,就拉低CTS,设备A看到后暂停发送。
- 设备B处理完数据后,再拉高CTS,设备A继续发送。
你可能会问:什么时候需要硬件流控?
- 高速通信时:比如921600波特率,MCU处理不过来,需要流控来防止数据溢出。
- 与PC通信时:PC端的USB转串口芯片通常有缓冲区,但缓冲区满了就会丢数据。硬件流控可以避免这个问题。
- 长距离通信时:线路上的延迟可能导致数据重叠,流控可以协调节奏。
但说实话,在大多数MCU的串口烧录和OTA升级场景中,我很少用硬件流控。为什么?因为MCU的处理速度足够快,而且我们会在应用层做分包和应答机制。硬件流控反而多占用了两个GPIO,增加了布线复杂度。
我的建议:如果你用的是STM32这类主流MCU,串口烧录时基本不需要硬件流控。但如果你在做高速数据采集,或者用ESP32通过串口传输音频流,那硬件流控就很有必要了。
4.7 知识体系总览
下面这张图,把UART协议的核心参数和它们之间的关系梳理清楚了。你可以把它当作一个速查表。
4.8 实战中的参数选择建议
说了这么多理论,最后给点实际建议。你在做串口烧录或OTA升级时,参数怎么配?
| 场景 | 波特率 | 数据位 | 停止位 | 校验位 | 硬件流控 |
|---|---|---|---|---|---|
| 调试输出 | 115200 | 8 | 1 | 无 | 关 |
| 串口烧录(内部晶振) | 115200 | 8 | 1 | 无 | 关 |
| 串口烧录(外部晶振) | 921600 | 8 | 1 | 无 | 开 |
| OTA升级(WiFi模块) | 115200 | 8 | 1 | 无 | 关 |
| 长距离通信(>10米) | 9600 | 8 | 2 | 偶校验 | 开 |
| 高速数据采集 | 230400 | 8 | 1 | 无 | 开 |
嗯,这张表你可以直接拿去用。我自己的项目,90%的情况都是115200、8N1(8数据位、无校验、1停止位)。简单、稳定、够用。