2. UART协议详解:起始位、数据位、校验位、停止位、波特率的概念与计算

UART,说白了就是串口通信的底层规矩。我刚开始做嵌入式那会儿,总觉得这东西简单,不就是两根线收发数据嘛。结果第一次调板子,数据全是乱码,查了半天才发现是波特率对不上。嗯,从那以后我再也不敢小看这个协议了。

今天咱们就把UART协议扒开来看。你想想看,两个设备之间,就靠TX和RX两根线,怎么就能把数据传得明明白白?靠的就是一套固定的帧格式。

2.1 UART数据帧的组成

一个完整的UART数据帧,包含以下几个部分:

  • 起始位:1位,固定为低电平(逻辑0)
  • 数据位:5~9位,常见的是8位
  • 校验位:0或1位,可选
  • 停止位:1、1.5或2位,固定为高电平(逻辑1)

我习惯把UART帧比作一列火车。起始位是火车头,告诉接收方「注意,我要发数据了」。数据位是车厢,装着你真正想传的信息。校验位是安检员,检查数据有没有出错。停止位是最后一节车厢,告诉接收方「这趟发完了」。

关键点:UART是异步通信,没有时钟线。收发双方靠约定的波特率来同步。起始位就是那个「同步信号」。

2.2 起始位:通信的起跑线

起始位为什么是低电平?因为UART总线在空闲时保持高电平。从高电平跳变到低电平,这个下降沿就是接收方的「起跑信号」。

我在项目中遇到过一个问题:有些芯片的UART引脚默认是弱上拉,空闲时电平没问题。但如果你用了开漏输出,外部上拉电阻没焊,空闲电平就飘了。结果就是接收方不停地检测到「假起始位」,数据全乱套。

经验之谈:调试时用示波器看TX引脚的空闲电平。如果空闲时不是高电平,先查硬件上拉。

2.3 数据位:真正要传的内容

数据位通常有7位或8位。7位用于ASCII字符传输,8位用于二进制数据。我个人几乎只用8位数据位,除非是跟老设备通信。

数据位的传输顺序是LSB first,也就是最低有效位先发。举个例子,你要发送0x55(二进制01010101),实际在线上看到的顺序是:

起始位 -> 1 -> 0 -> 1 -> 0 -> 1 -> 0 -> 1 -> 0 -> 校验位 -> 停止位

为什么会这样?因为早期UART设计时,LSB first可以配合移位寄存器,一边收一边移,硬件实现简单。你想想看,如果MSB first,移位寄存器的逻辑就复杂了。

2.4 校验位:数据的安检员

校验位分三种:

  • 无校验(None):不发送校验位,帧长最短
  • 奇校验(Odd):数据位+校验位中,1的个数为奇数
  • 偶校验(Even):数据位+校验位中,1的个数为偶数

举个例子,数据位是0x55(二进制01010101),1的个数是4个(偶数)。

  • 奇校验:校验位为1,使总1个数变成5(奇数)
  • 偶校验:校验位为0,总1个数保持4(偶数)

注意:校验位只能检测奇数个位的翻转错误。如果两个位同时翻转,校验位检测不出来。我建议在可靠性要求高的场合,配合校验和或CRC使用。

我记得有一次调试一个工业设备,数据偶尔出错。开了奇校验后,错误率从1%降到了0.01%。虽然没完全消除,但至少能发现错误了,配合重传机制就解决了问题。

2.5 停止位:帧的结束符

停止位固定为高电平,持续1位、1.5位或2位的时间。为什么要有停止位?

  • 给接收方时间处理刚收到的数据
  • 为下一帧的起始位下降沿做准备

1.5位停止位比较少见,我只有在跟某些老式Modem通信时才见过。大多数情况下用1位停止位就够了。如果通信距离远、干扰大,可以考虑用2位停止位,给接收方更多缓冲时间。

2.6 波特率:通信的节拍器

波特率就是每秒传输的符号数。在UART中,一个符号就是一位,所以波特率等于比特率。

常见的波特率有:

波特率(bps) 每位时间(us) 常见用途
9600 104.17 调试终端、GPS模块
19200 52.08 工业设备
115200 8.68 常用高速通信
921600 1.09 高速数据传输

波特率怎么算?说白了就是:1 / 波特率 = 每位的时间。比如115200bps,每位时间是1/115200 ≈ 8.68微秒。

实际传输速率计算

有效数据速率 = 波特率 × (数据位 / 帧总位数)

以8N1(8数据位、无校验、1停止位)为例:

帧总位数 = 1起始 + 8数据 + 0校验 + 1停止 = 10位

有效数据速率 = 115200 × (8/10) = 92160 bps ≈ 11.25 KB/s

你想想看,如果波特率对不上,接收方采样到的位位置就全偏了。我见过最离谱的一次,有人把9600配成了19200,结果收到的数据看起来像乱码,但仔细看每个字节都翻了一倍。因为采样速率翻倍,每个位被采样了两次。

2.7 波特率误差与容限

实际系统中,晶振有误差,分频器有量化误差。UART接收方允许一定的波特率误差,通常要求在±2%以内

为什么是2%?因为UART在每位中间采样,如果误差累积,到最后一个数据位时采样点可能偏移到位的边缘,导致误码。

我曾经用STM32的HSE(8MHz晶振)产生115200波特率,分频系数是8MHz/115200 ≈ 69.44,取整69后实际波特率是8MHz/69 ≈ 115942,误差约0.64%,完全没问题。但如果用内部RC振荡器,误差可能到2%~3%,就得小心了。

避坑指南:我曾经在项目里用了内部RC振荡器,温度一变化,波特率就飘了。从那以后,只要涉及UART通信,我必用外部晶振。

2.8 实际波形分析

咱们来看一个实际波形。假设发送0x55(01010101),8N1格式,波特率115200:

空闲(高)  起始(低)  LSB  位1  位2  位3  位4  位5  位6  MSB  停止(高)
  1         0       1    0    1    0    1    0    1    0     1

用示波器看,你会看到一串方波。起始位的下降沿是明显的「标记」。我建议初学者用逻辑分析仪抓一下这个波形,比看一百遍文字描述都管用。

嗯,这里要注意:逻辑分析仪的采样率至少要是波特率的4倍以上,才能准确还原波形。我一般用8倍采样率,这样每个位能采到8个点,波形看起来更平滑。

2.9 总结

UART协议其实不复杂,但细节决定成败。起始位、数据位、校验位、停止位、波特率,这五个要素缺一不可。我做了这么多年嵌入式,UART调试翻车最多的原因就是:

  1. 波特率对不上(最常见)
  2. 校验位设置不一致
  3. 停止位位数不匹配
  4. 电平逻辑搞反了(RS232和TTL电平的区别)

下一章咱们会讲UART的硬件设计,包括电平转换、流控制、以及如何用FPGA实现一个UART收发器。到时候我会分享一些FPGA实现上的坑,保证让你少走弯路。