UART协议详解(上):起始位、数据位、校验位、停止位,波特率的概念与计算
各位工程师朋友,咱们今天来聊聊UART。说实话,这可能是嵌入式开发里最基础、也最常用的接口了。我刚开始做项目那会儿,总觉得UART太简单,不就是发个数据嘛。结果有一次调试一个GPS模块,死活收不到正确数据,折腾了两天才发现是波特率配错了。从那以后,我再也不敢小看这个“老朋友”了。
UART数据帧结构:一个字节的旅行
UART通信的本质,就是在一根线上串行地发送比特。你想想看,一根线怎么传一个字节?它得把8个比特排成一队,一个个地发出去。这个“队伍”就是数据帧。
一个标准的UART数据帧长这样:
| 起始位 | 数据位(5~9位) | 校验位(可选) | 停止位(1/1.5/2位) |
|---|---|---|---|
| 1位,低电平 | LSB先发 | 奇/偶校验 | 高电平 |
我习惯把空闲时的电平叫“休息状态”。UART空闲时,TX线保持高电平。当要发数据时,先把电平拉低一个位的时间——这就是起始位。接收端一看到这个下降沿,就知道:“来了,准备接收!”
关键点:起始位是同步信号。没有它,接收端不知道什么时候开始采样。这也是UART为什么叫“异步”的原因——没有单独的时钟线,全靠起始位对齐。
数据位紧跟在起始位后面。通常是8位,但也可以配成5、6、7或9位。我一般用8位,因为一个字节正好8位,省心。数据位的发送顺序是LSB(最低有效位)在前,MSB(最高有效位)在后。举个例子,要发0x55(二进制01010101),先发最低位的1,再发0,再发1……最后发最高位的0。
校验位是可选的。它的作用是做简单的错误检测。奇校验要求数据位加上校验位里“1”的总数是奇数;偶校验要求是偶数。说实话,这个功能现在用得少了,因为UART的误码率本身不高,而且更可靠的CRC校验在更高层协议里实现。我个人的习惯是:短距离通信(板内、机箱内)不用校验位;长距离或者干扰大的环境,我会加上。
停止位是帧的结束标志。它把电平拉回高电平,持续1位、1.5位或2位的时间。为什么要有停止位?两个作用:一是给接收端时间处理刚收到的字节;二是为下一帧的起始位下降沿做准备。我遇到过一些老设备,必须配2位停止位才能稳定通信,否则丢数据。嗯,兼容性问题,没办法。
实战建议:如果你不确定对方的配置,先用8N1(8数据位、无校验、1停止位)试。这是最通用的配置,90%的设备都支持。
波特率:通信的节拍器
波特率,说白了就是每秒传多少个比特。单位是bps(bits per second)。9600bps就是一秒传9600个比特。注意,这里说的是“比特”,不是“字节”。一个字节加上起始位、停止位,实际要传10个比特(8N1配置)。所以9600bps的理论最大字节速率是9600/10 = 960字节/秒。
我见过不少新手犯这个错:以为9600bps就是9600字节/秒,结果算出来的传输时间全不对。
| 波特率 | 位时间 | 字节时间(8N1) | 典型应用 |
|---|---|---|---|
| 9600 | 104.17 μs | 1.04 ms | 调试终端、GPS |
| 115200 | 8.68 μs | 86.8 μs | 高速传感器、WiFi模块 |
| 921600 | 1.09 μs | 10.9 μs | 固件升级、大数据传输 |
波特率怎么算出来的?它由系统时钟分频得到。以STM32为例,UART的波特率计算公式是:
波特率 = fCK / (16 × USARTDIV)
其中:
- fCK:UART外设时钟频率
- USARTDIV:分频系数(可以是小数)
- 16:过采样率(标准模式)
举个例子,fCK = 72 MHz,想要115200 bps:
USARTDIV = 72,000,000 / (16 × 115200) = 39.0625
整数部分:39
小数部分:0.0625 × 16 = 1
所以USARTDIV = 39.1(十六进制0x271)
避坑指南:我曾经在一个项目里用8 MHz晶振配STM32,想跑921600 bps。算出来分频系数是5.43,小数部分精度不够,实际波特率偏差超过3%。结果就是通信时好时坏。后来换了16 MHz晶振,偏差降到0.2%,问题解决。记住:波特率偏差超过2%就可能丢数据。
波特率误差与容限
为什么会有误差?因为分频系数是离散的,不可能完全精确。误差计算公式:
误差 = (实际波特率 - 目标波特率) / 目标波特率 × 100%
UART的容限是多少?理论上,接收端在每位的中点采样,允许的误差是±半个位时间。但实际中要考虑晶振精度、温度漂移等因素。我一般按这个标准:
- 误差 < 1%:非常可靠,放心用
- 1% ~ 2%:短帧(8~10位)没问题,长帧可能有风险
- 2% ~ 3%:只能传短帧,而且环境要稳定
- > 3%:别用,等着丢数据吧
你可能会问:为什么有时候误差2.5%也能用?因为实际通信中,起始位的下降沿会重新同步接收时钟。但如果连续传很多字节,误差会累积,最终采样点偏移到错误的位置。
我的习惯:设计电路时,优先选能让UART分频系数接近整数的晶振频率。比如11.0592 MHz是经典的UART晶振,因为它能精确分频出几乎所有常用波特率。
UART帧结构流程图
下面这张图展示了UART发送一个字节(0x55,即二进制01010101)的完整过程:
常见配置速查表
为了方便你快速配置,我整理了一份常用参数组合:
| 配置名称 | 数据位 | 校验位 | 停止位 | 典型波特率 |
|---|---|---|---|---|
| 8N1 | 8 | 无 | 1 | 9600 / 115200 |
| 8E1 | 8 | 偶校验 | 1 | 9600 / 19200 |
| 8O1 | 8 | 奇校验 | 1 | 9600 / 19200 |
| 7E1 | 7 | 偶校验 | 1 | 1200 / 2400 |
| 8N2 | 8 | 无 | 2 | 9600 / 115200 |
避坑指南:我曾经调试一个工业仪表,对方手册写的是“8位数据、无校验、1停止位”,但死活不通。用逻辑分析仪抓波形才发现,实际配置是7位数据、偶校验。手册写错了!所以我的建议是:别完全相信手册,用示波器或逻辑分析仪确认实际波形才是王道。