4、UART异步串口时序:起始位/数据位/校验位/停止位、波特率误差容限、采样点位置
UART,说白了就是串口。这玩意儿太经典了,从8051时代一直用到现在的Cortex-M7,几乎每个MCU都标配。我刚开始做嵌入式那会儿,调试手段匮乏,串口就是我的眼睛和耳朵。直到现在,我拿到一块新板子,第一件事就是先把串口调通。
UART是异步通信,没有单独的时钟线。收发双方得靠约定好的波特率来同步。这就引出了我们今天要聊的核心问题:时序到底怎么对齐?误差能容忍多少?采样点又该选在哪儿?
4.1 一帧数据的完整结构
先看一个标准的数据帧长什么样。UART一帧数据,通常包含这么几部分:
- 空闲位:总线空闲时,保持高电平
- 起始位:一个位宽的低电平,标志数据传输开始
- 数据位:5~9位,常见的是8位,低位在前
- 校验位:可选,奇校验或偶校验
- 停止位:1位、1.5位或2位的高电平
举个例子,我们最常用的配置:8N1。意思是8个数据位,无校验,1个停止位。加上起始位,一帧总共10个位宽。
关键点:起始位是下降沿,这是接收端同步的起点。接收端检测到下降沿后,就开始按波特率采样后续的每一位。
4.2 波特率误差容限
这个问题我踩过坑。有一次做485总线通信,节点多了之后,偶尔出现乱码。查了半天,发现是板子上晶振精度不够,波特率偏了2.3%。
UART的容限到底是多少?我们来算一笔账。
假设我们配置9600波特率,8N1。每个位的时间是:
T_bit = 1 / 9600 ≈ 104.17 μs
接收端在起始位下降沿触发后,会在每个数据位的中间位置采样。如果收发双方的时钟有偏差,这个偏差会随着采样的位数累积。
对于8N1格式,一帧有10个位。接收端在第10个位(停止位)采样时,累积误差最大。如果采样点正好在位的中间,那么允许的最大误差是半个位宽的一半,也就是25%。
但实际工程中,我们不会卡这个极限。我个人习惯留多少余量?
| 波特率 | 推荐误差容限 | 极限容限 |
|---|---|---|
| 9600及以下 | ±2% | ±4.5% |
| 38400 | ±1.5% | ±3.5% |
| 115200 | ±1% | ±2.5% |
| 921600及以上 | ±0.5% | ±1.5% |
注意:我曾经遇到过用内部RC振荡器跑115200波特率的情况。手册上写RC精度±3%,实际测下来在温度变化时能漂到±5%。结果就是,常温下能通信,一进高低温箱就丢包。所以高速串口,我建议用外部晶振。
4.3 采样点位置与过采样技术
接收端怎么知道什么时候该采样?这涉及到过采样技术。
大多数MCU的UART外设,内部会用一个比波特率快16倍或8倍的时钟来采样。以16倍过采样为例:
采样时钟频率 = 波特率 × 16
检测到起始位的下降沿后,接收端会等待8个采样时钟周期(也就是半个位宽),然后在第9、10、11个采样点连续采样三次,用多数表决的方式确定这一位的电平。
为什么要连续采三次?说白了就是抗干扰。总线上可能有毛刺,单次采样容易误判。三取二的方式,能滤掉大部分窄脉冲噪声。
经验之谈:我调试串口通信时,如果发现偶发误码,会先检查采样点位置。有些MCU允许配置采样点偏移,比如在位的1/4处或3/4处采样。如果总线容性负载大,信号边沿变缓,把采样点往后调一调,效果立竿见影。
4.4 实际工程中的时序调试
嗯,这里要讲点实战的东西。你拿到一块板子,串口不通,怎么排查?
- 先看电平:用示波器量TX和RX引脚。空闲时应该是高电平。如果一直是低,可能是IO配置错了,或者芯片进入了某种异常状态。
- 看帧格式:触发在下降沿,看一帧的长度对不对。9600 8N1,一帧应该是10个位宽,约1.04ms。如果长度不对,波特率配置肯定有问题。
- 看停止位:停止位应该是高电平。如果停止位被拉低了,说明接收端没来得及处理上一帧数据,产生了溢出错误。
- 看误差:用示波器的光标测量实际位宽,跟理论值对比。误差超过±3%就要警惕了。
我记得有一次,客户说他们的设备偶尔会丢第一个字节。我让他们抓波形,发现起始位之后的第一个数据位采样点正好落在信号跳变沿上。原因是他们用的晶振频率不是标准的整数倍,导致分频后的波特率有0.8%的误差。加上线缆较长,信号反射造成了边沿抖动。解决办法很简单,换了一个精度更高的晶振,问题就解决了。
4.5 波特率发生器的配置技巧
MCU的UART波特率发生器,通常是一个分频器。公式大致是:
波特率 = 外设时钟频率 / (16 × (UBRR + 1))
以AVR的USART为例,UBRR是波特率寄存器值。如果外设时钟是16MHz,想要9600波特率:
UBRR = 16000000 / (16 × 9600) - 1 = 103.17
取整后UBRR=103,实际波特率:
实际波特率 = 16000000 / (16 × 104) = 9615.38
误差只有0.16%,完全没问题。
我的习惯:配置波特率时,我会先算理论值,再算实际值,确保误差在±1%以内。如果误差偏大,我会考虑换一个晶振频率,或者用MCU内部的PLL产生更精确的时钟。
有些MCU支持分数波特率发生器,比如STM32的USART。它能通过小数部分来微调分频比,把误差压到0.1%以内。对于921600以上的高速串口,这个功能非常实用。
好了,UART时序的核心内容就这些。下一章我们聊I2C,那个协议比UART复杂不少,但掌握了套路也不难。