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有效,才开始发数据。

流程是这样的:

  1. 接收端拉高RTS,表示“我有缓冲区,可以收”。
  2. 发送端检测到RTS有效,开始发数据。
  3. 接收端缓冲区快满了,拉低RTS,表示“停!我消化一下”。
  4. 发送端检测到RTS无效,停止发送。

硬件流控效率高,不浪费带宽。但需要MCU和模组都支持,而且要多接两根线。

3.4.2 软件流控(XON/XOFF)

不用额外引脚,靠发送特殊字符来控制:

  • XOFF (0x13):接收端发这个字符,告诉发送端“暂停”。
  • XON (0x11):接收端发这个字符,告诉发送端“继续”。

软件流控的优点是省引脚,但缺点也很明显:

  • 如果传输的是二进制数据,0x13和0x11可能出现在数据里,造成误判。
  • 响应有延迟,不适合高速通信。

我的建议:能用硬件流控就用硬件流控。尤其是和4G/5G模组通信时,模组发数据的速度可能远超MCU的处理能力。没有流控,丢数据是家常便饭。

3.5 知识体系总览

下面这张图,把UART的核心知识点串起来了。你可以把它当作一个快速索引:

UART串口通信协议知识体系 UART 协议 硬件层协议 波特率 帧格式 流控机制 TXD / RXD GND 共地 交叉连接 常见速率 误差计算 ±2% 容限 起始位 数据位 (5~9) 校验位 / 停止位 硬件流控 (RTS/CTS) 软件流控 (XON/XOFF) 核心原则:收发双方波特率、帧格式必须完全一致,否则通信失败

3.6 实战中的几个坑

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 波特率误差:配完一定要算。尤其是用内部RC振荡器的时候,误差可能比手册上写的还大。
  • 电平不匹配:MCU是3.3V,模组是1.8V,直接连会烧引脚。电平转换芯片(比如TXS0108)该用就用。
  • 共地问题:两个设备之间GND必须连。不连地,信号飘忽不定,什么怪问题都可能出现。
  • 中断优先级:UART接收中断里别做耗时操作。我曾经在中断里做协议解析,结果丢了一堆数据。后来改成DMA + 环形缓冲区,世界清净了。