2. UART协议帧结构:起始位、数据位、校验位、停止位与空闲位
UART通信说白了就是一根线上一比特一比特地串着走。没有时钟线,全靠收发双方约定好速率,然后从帧结构里把数据“抠”出来。我早年刚接触嵌入式时,总觉得这玩意儿简单,结果调试时波形对不上,折腾了一整天才发现是停止位配错了。嗯,咱们今天就把这个帧结构掰开揉碎了讲清楚。
2.1 一帧数据长什么样?
UART的帧结构是固定的套路:空闲位 → 起始位 → 数据位 → 校验位(可选) → 停止位。平时线上没有数据传输时,信号线保持高电平,这就是空闲状态。一旦要发数据,先把电平拉低一个位的时间,告诉对方“注意,我要开始了”——这就是起始位。然后跟着5到8个数据位,低位在前高位在后。接着可以带一个校验位做简单的错误检测。最后拉高电平保持1位、1.5位或2位的时间,告诉对方“这帧结束了”,这就是停止位。
2.2 起始位:通信的“发令枪”
起始位的作用就是让接收端知道数据要来了。它把空闲时的高电平拉低,持续一个位时间。接收端检测到这个下降沿后,就开始按约定的波特率采样后续的位。
我在项目中遇到过一个问题:两个设备波特率差了2%,结果起始位之后的采样点逐渐偏移,到最后一个数据位时已经采到边沿上了,数据全是错的。所以起始位不仅是“发令枪”,也是接收端做时钟同步的起点。
2.3 数据位:真正的“货物”
数据位是帧里真正要传的信息,通常是5到8位。最常见的是8位数据,对应一个字节。发送时低位在前(LSB first),这是UART的铁律。
举个例子,要发送0x55(二进制01010101),实际线上先发最低位1,然后0、1、0、1、0、1,最后发最高位0。为什么这样设计?我猜早期串口设备处理低位更方便,反正现在大家都这么用,你记住就行。
| 数据位配置 | 常见场景 | 说明 |
|---|---|---|
| 8位 | 最常用,传输ASCII字符或二进制数据 | 几乎所有的MCU默认配置 |
| 7位 | 纯ASCII文本通信(如老式终端) | 节省1位,但现代系统很少用 |
| 5位或6位 | 特殊工业协议或老设备 | 遇到时注意核对数据手册 |
2.4 校验位:简单的“防错机制”
校验位是可选的,用来检测传输过程中有没有发生单比特错误。它有两种模式:
- 奇校验(Odd Parity): 数据位中1的个数加上校验位,总共有奇数个1。
- 偶校验(Even Parity): 数据位中1的个数加上校验位,总共有偶数个1。
举个例子,数据位是0x55(二进制01010101),里面有4个1(偶数)。如果配偶校验,校验位就是0,保持总1数为偶数;如果配奇校验,校验位就是1,让总1数变成奇数。
说实话,校验位只能检测奇数个比特错误,如果两个比特同时翻转,校验位就发现不了。但胜在实现简单,硬件自动计算,不占CPU。我个人的习惯是:短距离板内通信不用校验位,长距离或工业现场环境一定开奇校验。
2.5 停止位:帧的“结束符”
停止位是发送端拉高电平,持续1位、1.5位或2位的时间。它给接收端留出处理上一帧数据的时间,也为下一帧的起始位下降沿做准备。
为什么会有1.5位这种奇怪的配置?这主要是早期UART芯片的遗留问题,现在基本只用1位或2位。我建议:默认用1位停止位,如果通信距离远或干扰大,可以改成2位,牺牲一点速度换取可靠性。
2.6 空闲位:线上的“沉默期”
空闲位不是帧的一部分,但它很重要。两帧之间,信号线保持高电平,这就是空闲位。它的作用有两个:
- 区分帧边界: 接收端通过检测高电平到低电平的跳变来识别起始位,空闲位保证了起始位之前一定是高电平。
- 给设备喘息时间: 有些低速MCU处理完一帧数据需要时间,空闲位就是天然的缓冲期。
你想想看,如果没有空闲位,两帧背靠背发送,接收端怎么知道前一帧结束、后一帧开始?全靠停止位的高电平和起始位的低电平之间的跳变来区分。所以空闲位虽然不传数据,但它是协议能正常工作的前提。
2.7 完整帧结构图解
下面我用一张SVG图把整个帧结构画出来,你一看就明白了。
2.8 常见配置速查表
实际项目中,UART配置通常用一个字符串表示,比如“9600 8N1”。我来解释一下这个格式:
| 配置字符串 | 波特率 | 数据位 | 校验 | 停止位 | 典型应用 |
|---|---|---|---|---|---|
| 9600 8N1 | 9600 | 8 | 无 | 1 | 最通用,MCU默认配置 |
| 115200 8N1 | 115200 | 8 | 无 | 1 | 高速调试,蓝牙模块 |
| 19200 8E1 | 19200 | 8 | 偶校验 | 1 | 工业Modbus协议 |
| 4800 7O2 | 4800 | 7 | 奇校验 | 2 | 老式终端设备 |
“8N1”里的8是数据位,N表示无校验(None),1是停止位位数。同理“8E1”就是偶校验(Even),“7O2”就是7位数据、奇校验(Odd)、2位停止位。这个命名法很直观,我建议你记牢,以后看设备手册一眼就能读懂。
2.9 避坑指南
最后分享几个我踩过的坑,你遇到了能少走弯路:
- 起始位误判: 我曾经把噪声的毛刺当成起始位,导致接收端不断触发中断。后来在GPIO上加了硬件滤波,问题解决。
- 校验位与数据位混淆: 有次调试一个传感器,手册写的是“8位数据+偶校验”,我配成了“8位数据+无校验”,结果校验位被当成数据位处理,每个字节都多了一位,解析全乱。用逻辑分析仪逐位看才找到问题。
- 停止位不足: 在高速率下(如1Mbps以上),如果接收端处理速度跟不上,1位停止位可能不够。我建议高速通信时用2位停止位,或者降低波特率。
- 空闲位电平反转: 有些老设备用RS232电平(±12V),空闲位是负电压,起始位是正电压。和TTL电平(3.3V/5V)正好相反。接转换芯片时一定要注意电平极性。