3. UART 帧格式详解
说实话,搞 UART 这么多年,最基础也最容易出错的,就是帧格式。很多人一上来就调波特率,结果数据死活不对,回头一看——校验位配错了,或者停止位少了一位。我自己的习惯是:先把帧格式画在纸上,再动手写代码。这一节咱们就把四个基本部分掰开揉碎了讲清楚。
起始位 —— 同步的起点
空闲时,TX 线一直保持高电平(逻辑 1)。接收方怎么知道什么时候开始一帧?靠的是起始位。发送方把 TX 线拉低,保持一个位的时间。这个下降沿就是同步信号。
你想想看,如果线上有毛刺,下降沿过早或者过晚,整帧就全歪了。我在一个电机控制项目里就碰到过:电机一启动,电源干扰在 RX 线上制造了几十纳秒的毛刺,接收方误以为收到了起始位,结果后面数据全乱了。后来在接收中断里加了连续三次采样确认才稳住。
数据位 —— 真正的信息载体
起始位之后就是数据位。通常 5~9 位可选,最常用 8 位。注意传输顺序:LSB 先发(最低有效位最先)。比如你要发 0x55 (二进制 01010101),先发的不是最高位的 0,而是最低位的 1(LSB)。
为什么要 LSB first?历史原因——早期电传打字机就是这样的。后来所有 UART 都沿用了这个习惯。你只要记住:发送顺序是反直觉的就行。我曾经教过一个新人,他死活不理解为什么用示波器抓到的数据顺序跟程序里赋的值不一样,那就是没搞明白 LSB 优先。
| 位序号 | 0 (LSB) | 1 | 2 | 3 | 4 | 5 | 6 | 7 (MSB) |
|---|---|---|---|---|---|---|---|---|
| 线电平 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
看到没?实际线上先出现的是 1,然后 0,然后 1…… 跟二进制数值 0x55 从左到右看完全反着。
校验位 —— 错误检测的选择
数据位后面可以跟一个校验位(也可以没有)。校验位分三种:无校验、奇校验、偶校验。说白了就是让数据位加上校验位里“1”的个数凑成奇数或偶数。接收方收到后自己算一遍,对不上就报错。
我用过最多的场景是:短距离板内通信,直接关校验,省一个位的时间。但如果是工业现场、长线传输,我建议至少开偶校验——因为大部分噪声会引入奇数个错误,偶校验正好能抓住。曾经在一个 RS-485 项目里,现场有变频器干扰,我选了奇校验,结果丢帧率比偶校验高了一倍。为什么?因为干扰经常翻转两个位,奇校验反而漏检了。
| 模式 | 计算规则 | 校验位值 |
|---|---|---|
| 无校验 | 不发送校验位 | — |
| 偶校验 | 1的个数加校验位应为偶数 | 0 (4个1已经是偶数) |
| 奇校验 | 1的个数加校验位应为奇数 | 1 (4+1=5,奇数) |
停止位 —— 帧结束的标志
校验位(如果有)之后,发送方把 TX 线拉高,保持 1 位、1.5 位或 2 位的时间。这就是停止位。它在告诉接收方:这一帧完事了,准备下一帧吧。
为什么要有不同长度?早期机械 UART 处理速度慢,需要更长的停止位让接收器复位。现在虽然芯片快多了,但两线异步通信中,停止位还承担着“帧间隔”的角色。如果你数据量不大,我建议用 1 位停止位,节省开销;如果波特率很高(比如 115200 以上),或者你要连接多个从机,可以考虑 2 位停止位,给接收方更多处理时间。
一张图说清帧结构
下面这个 SVG 把一帧完整的 UART 数据画出来了。从左到右看,你会发现其实就五个部分:空闲→起始→数据(LSB→MSB)→校验(可选)→停止→回到空闲。
帧格式参数总结
如果要用一句话概括:一帧 = 1 位起始 + 5~9 位数据 + 0/1 位校验 + 1~2 位停止。所有参数必须收发双方完全一致,否则就是“鸡同鸭讲”。
| 字段 | 长度 | 电平 | 作用 |
|---|---|---|---|
| 起始位 | 1 bit | 低(逻辑0) | 提供帧同步下降沿 |
| 数据位 | 5~9 bit | LSB first | 传输有效负载 |
| 校验位 | 0 或 1 bit | 奇/偶计算结果 | 简单错误检测 |
| 停止位 | 1 / 1.5 / 2 bit | 高(逻辑1) | 帧结束并准备下一帧 |