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调试翻车最多的原因就是:
- 波特率对不上(最常见)
- 校验位设置不一致
- 停止位位数不匹配
- 电平逻辑搞反了(RS232和TTL电平的区别)
下一章咱们会讲UART的硬件设计,包括电平转换、流控制、以及如何用FPGA实现一个UART收发器。到时候我会分享一些FPGA实现上的坑,保证让你少走弯路。