1. UART协议基础:从历史到实战

各位同学,咱们今天聊聊UART。说实话,这玩意儿在嵌入式领域太常见了,几乎每个做硬件的工程师都跟它打过交道。但越是常见的东西,越容易出幺蛾子。我见过不少项目,最后卡在串口通信上,查半天发现是协议理解不到位。

UART全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。名字挺长,说白了就是让两个设备能通过一根线互相说话,而且不需要额外的时钟线。

1.1 UART的历史与演进

UART的历史可以追溯到上世纪60年代。那时候计算机之间通信,用的还是并行接口,线又多又贵。后来人们发现,很多场景下不需要那么高的速度,串行通信更划算。

我记得最早接触UART是在大学实验室,用的还是MAX232这种电平转换芯片。那时候的UART芯片是独立的,比如8250、16550这些。现在嘛,基本上都集成到MCU内部了。

UART的演进有几个关键节点:

  • 1960s:早期UART作为独立芯片出现,用于连接电传打字机和计算机
  • 1980s:IBM PC使用8250 UART,成为事实标准
  • 1990s:16550 UART引入FIFO缓冲区,解决了高速通信丢数据的问题
  • 2000s至今:UART集成到SoC和MCU中,成为标配外设
个人经验: 我做过一个项目,用了某款国产MCU,它的UART模块没有FIFO。波特率一高,中断频繁触发,CPU根本忙不过来。后来只能降速处理。所以选型时,FIFO深度是个重要指标。

1.2 异步串行通信原理

异步串行通信,核心就一句话:没有独立的时钟线,靠约定的速率和帧格式来同步。

你想想看,两个设备之间只有一根数据线,怎么知道什么时候开始收数据?什么时候算一个字节结束?这就是UART协议要解决的问题。

异步通信的关键在于:

  • 空闲状态:数据线保持高电平
  • 起始位:拉低电平,告诉接收方"我要开始发数据了"
  • 数据位:按位发送,LSB(最低有效位)在前
  • 停止位:拉回高电平,表示一个字节传输结束

这里有个容易踩的坑:收发双方必须约定好完全相同的参数。波特率、数据位、校验位、停止位,一个都不能错。我曾经帮一个同事排查问题,折腾了两天,最后发现是发送端设了偶校验,接收端设了无校验。

1.3 数据帧格式详解

一个完整的UART数据帧长这样:

空闲状态 | 起始位 | 数据位(5~8位) | 校验位(可选) | 停止位(1/1.5/2位) | 空闲状态
  高电平  |   0   |  LSB...MSB    |  0/1/无      |     1/1.5/2       |  高电平

咱们拆开来看:

起始位

起始位永远是低电平,持续一个位时间。接收方检测到电平从高变低,就知道数据要来了。这个下降沿就是同步信号。

数据位

数据位可以是5、6、7或8位。现在绝大多数场景都用8位。发送顺序是LSB在前,MSB在后。举个例子,发送0x55(二进制01010101),实际线上先发1,再发0,交替进行。

校验位

校验位用于简单的错误检测,有三种模式:

校验模式 说明 典型应用
无校验(None) 不发送校验位,帧长度最短 大多数嵌入式场景
奇校验(Odd) 数据位+校验位中1的个数为奇数 老式设备、某些工业协议
偶校验(Even) 数据位+校验位中1的个数为偶数 Modbus RTU等
避坑指南: 我曾经在一个项目中,设备A用奇校验,设备B用偶校验,两边都能收到数据,但偶尔会出错。因为校验位不匹配时,接收方会认为数据错误而丢弃。这种问题很难复现,排查起来特别头疼。

停止位

停止位是高电平,持续1、1.5或2个位时间。停止位的作用是给接收方一个缓冲,处理完刚收到的字节。现在大多数场景用1位停止位就够了。

1.4 波特率的概念与计算

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

常见的波特率有:9600、19200、38400、115200、921600等。115200是目前最常用的,速度适中,稳定性好。

波特率的计算公式:

波特率 = 系统时钟频率 / (16 × 分频系数)

以STM32为例,如果系统时钟72MHz,要得到115200波特率:

分频系数 = 72000000 / (16 × 115200) ≈ 39.0625
取整为39,实际波特率 = 72000000 / (16 × 39) ≈ 115384
误差 = (115384 - 115200) / 115200 ≈ 0.16%

误差在±2%以内通常没问题。但要注意,如果系统时钟不是整数倍,误差会累积。我见过一个案例,某款MCU用8MHz晶振,要产生250000波特率,误差达到3.5%,结果通信时断时续。

关键点: 波特率误差超过±2%,通信就可能不稳定。尤其是连续传输大量数据时,误差会累积,最终导致帧错误。

1.5 UART帧结构SVG图

下面这张图展示了完整的UART数据帧结构,我建议你把它存下来,调试时对照着看:

UART数据帧结构图 空闲 起始位 数据位(5~8位) 校验位 停止位 空闲 电平波形: 高 低 高低交替 高/低 高 高 说明: 起始位固定为低电平,停止位固定为高电平。数据位LSB在前,MSB在后。校验位可选,停止位可为1/1.5/2位。

1.6 常见波特率与误差表

下面这张表列出了常用波特率在不同系统时钟下的分频系数和误差,调试时可以直接参考:

目标波特率 系统时钟16MHz 系统时钟72MHz 系统时钟100MHz
9600 104 (0.16%) 468 (0.08%) 651 (0.02%)
19200 52 (0.16%) 234 (0.08%) 325 (0.02%)
115200 8 (0.00%) 39 (0.16%) 54 (0.08%)
921600 1 (0.00%) 4 (0.00%) 6 (0.00%)
实战建议: 选波特率时,优先选系统时钟能整除的。比如16MHz时钟下,115200和921600都是整数分频,误差为0。如果必须用非整数分频,误差控制在±1%以内比较稳妥。