3. UART协议详解:起始位、数据位、校验位、停止位、波特率计算
UART,说白了就是串口通信的物理层规矩。很多工程师一上来就调库函数,遇到通信乱码就抓瞎。我个人习惯是先把协议帧掰开揉碎了看,这样定位问题才快。
UART 的帧结构其实很简单,就 5 个部分:空闲位、起始位、数据位、校验位、停止位。咱们一个一个说。
3.1 帧结构拆解
先看一张完整的 UART 数据帧结构图,心里有个全局印象。
3.2 起始位与停止位
起始位:一个位宽的低电平。它告诉接收方「注意,数据要来了」。空闲时总线是高电平,突然拉低,这就是起始信号。
停止位:一个或两个位宽的高电平。它告诉接收方「这帧数据结束了」。停止位之后总线恢复高电平,等待下一帧。
我的经验:停止位长度可以配成 1、1.5 或 2 位。大多数场景用 1 位停止位就够了。如果通信距离远、干扰大,我建议用 2 位停止位,给接收方多留点处理时间。
3.3 数据位
数据位可以是 5、6、7、8 位,最常见的是 8 位。传输顺序是 LSB 先发,MSB 后发。
举个例子,你要发 0x55(二进制 01010101):
- LSB 是 1(最低位)
- MSB 是 0(最高位)
- 实际线上顺序:1 → 0 → 1 → 0 → 1 → 0 → 1 → 0
你想想看,如果接收方按 MSB 先收,那 0x55 就变成 0xAA 了,全乱套。所以协议规定必须 LSB first,这是硬规矩。
3.4 校验位
校验位是可选的,用来做简单的错误检测。常见三种模式:
| 校验模式 | 逻辑 | 适用场景 |
|---|---|---|
| 无校验 (None) | 不发送校验位 | 近距离、干扰小、追求速度 |
| 奇校验 (Odd) | 数据位中 1 的个数 + 校验位 = 奇数 | 对数据完整性有一定要求 |
| 偶校验 (Even) | 数据位中 1 的个数 + 校验位 = 偶数 | 与奇校验对称,看系统习惯 |
注意:校验位只能检测奇数个比特错误。如果两个比特同时翻转,校验位是看不出来的。我曾经在一个工业项目里遇到过这种情况,后来加了 CRC 才搞定。
3.5 波特率计算
波特率就是每秒传输的比特数,单位是 bps(bits per second)。
一帧的总位数 = 1(起始位)+ 数据位位数 + 校验位(0 或 1)+ 停止位位数
举个例子,8 数据位 + 无校验 + 1 停止位:
总位数 = 1 + 8 + 0 + 1 = 10 位
波特率 9600 bps 时:
每帧时间 = 10 / 9600 ≈ 1.042 ms
每秒传输帧数 = 9600 / 10 = 960 帧/秒
常用波特率有:9600、19200、38400、115200、921600。我个人习惯是:
- 调试阶段:用 115200,速度快,省时间
- 量产产品:用 9600 或 19200,抗干扰能力强
避坑指南:我曾经在一个项目里,主控和从机的波特率都配成 115200,但主控的晶振是 16MHz,从机是 12MHz,结果两边算出来的实际波特率差了 2.3%。通信偶尔丢字节,查了两天才发现是波特率误差超标了。记住,波特率误差必须控制在 ±2% 以内,最好在 ±1% 以内。
3.6 波特率发生器配置
以 STM32 为例,波特率计算公式:
波特率 = fCK / (16 × USARTDIV)
其中:
fCK = 外设时钟频率
USARTDIV = 一个 16 位寄存器值,包含整数和小数部分
实际配置时,我们一般用 CubeMX 或直接算好 USARTDIV 值写寄存器。但理解原理很重要——波特率误差主要来自时钟分频的取整误差。
| 目标波特率 | 时钟频率 | 实际波特率 | 误差 |
|---|---|---|---|
| 115200 | 16 MHz | 115200 | 0% |
| 115200 | 8 MHz | 114943 | -0.22% |
| 9600 | 16 MHz | 9615 | +0.16% |
小技巧:选时钟频率时,尽量选能被目标波特率 × 16 整除的值。比如 115200 × 16 = 1.8432 MHz,很多 UART 芯片专门用这个频率做时钟源,误差为 0。
3.7 常见配置速查表
下面是我整理的最常用配置组合,直接拿去用:
| 场景 | 数据位 | 校验位 | 停止位 | 波特率 |
|---|---|---|---|---|
| 调试输出 | 8 | 无 | 1 | 115200 |
| 传感器通信 | 8 | 无 | 1 | 9600 |
| 工业现场总线 | 8 | 偶校验 | 1 | 19200 |
| 长距离传输 | 7 | 奇校验 | 2 | 4800 |