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)的完整过程:

UART数据帧结构(发送0x55,8N1配置) 空闲 高电平 起始位 低电平 D0 1 (LSB) D1 0 D2 1 D3 0 D4 1 D5 0 D6 1 D7 0 (MSB) 停止位 高电平 下一帧 起始位 说明: • 空闲时TX线保持高电平 • 起始位下降沿通知接收端开始采样 • 数据位LSB在前,MSB在后 • 停止位高电平,为下一帧做准备

常见配置速查表

为了方便你快速配置,我整理了一份常用参数组合:

配置名称 数据位 校验位 停止位 典型波特率
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位数据、偶校验。手册写错了!所以我的建议是:别完全相信手册,用示波器或逻辑分析仪确认实际波形才是王道。