4. 异步通信原理:时钟同步机制、采样窗口、起始位检测、停止位容错
异步通信,说白了就是收发双方没有独立的时钟线。你想想看,UART 只用了两根数据线(TX 和 RX),怎么保证两边能对齐节奏?这就是本章要聊的核心问题。
我刚开始学串口时,总觉得异步通信很玄乎。后来调了几个项目,踩过几次坑,才真正理解它背后的设计逻辑。其实没那么复杂,咱们一步步拆开看。
4.1 时钟同步机制:没有时钟线,怎么对齐?
异步通信的关键在于——收发双方约定好相同的波特率。波特率就是每秒传输的比特数,单位是 bps(bits per second)。
举个例子:波特率 9600,意味着每 bit 的时长是 1/9600 ≈ 104.17 μs。发送方按这个节奏发,接收方按这个节奏采。
但问题来了:双方晶振不可能完全一样,总会有误差。我遇到过用内部 RC 振荡器做串口通信的情况,误差大了直接丢数据。所以时钟同步机制的核心就是:用起始位做“对齐锚点”,然后靠本地时钟连续采样。
时钟同步的关键点:
- 接收方持续检测 RX 线的电平变化
- 检测到起始位(高→低跳变)后,启动本地波特率计数器
- 后续每个 bit 的采样点,都基于这个起始时刻计算
- 误差累积不能超过半个 bit 宽度,否则采样点漂移
我曾经在一个项目中,发送端用 8MHz 晶振,接收端用 7.3728MHz 晶振,波特率都是 115200。算下来误差只有 0.16%,通信很稳定。但如果换成 1% 误差的 RC 振荡器,长帧数据就容易出错。
4.2 采样窗口:什么时候“看”数据最准?
接收方怎么知道什么时候该采样?不是随便找个时间点就采的。硬件串口通常采用过采样技术,比如 16 倍过采样。
什么意思?就是每个 bit 时长内,接收方采样 16 次。然后取中间那几次的值做判决。我习惯用下面这张图来理解:
为什么取中间那几次?因为 bit 边沿可能有抖动,靠近边沿采样容易出错。取中间位置最稳。我习惯把采样窗口理解为“在信号最稳定的时刻看一眼”。
实际开发技巧:
如果使用 8 倍过采样,采样点通常取第 4、5、6 次。16 倍过采样抗干扰能力更强,但需要更高的系统时钟。我在 STM32 上常用 16 倍过采样,波特率 115200 以下都很稳。
4.3 起始位检测:通信的“握手信号”
起始位是异步通信的起点。没有起始位,接收方不知道什么时候开始读数据。
起始位的检测逻辑其实很简单:
- 空闲状态:RX 线保持高电平
- 检测下降沿:RX 线从高变低,表示可能有起始位
- 确认采样:在下降沿后的半个 bit 位置再采样一次,如果还是低电平,确认是起始位
- 启动接收:开始按波特率采样后续数据位
这里有个坑:如果线上有噪声毛刺,也会产生下降沿。我遇到过电源纹波大的板子,串口时不时收到乱码,就是噪声触发了虚假起始位。
避坑指南:
我曾经在一个电机驱动项目中,串口频繁误触发。后来发现是电机启停时电源波动,在 RX 线上产生了毛刺。解决办法有两个:
- 硬件上:RX 线加 RC 低通滤波(100Ω + 100pF)
- 软件上:使能串口硬件的噪声滤波功能
双管齐下,问题解决。
4.4 停止位容错:给接收方一点“喘息空间”
停止位的作用是什么?很多人以为只是“帧结束标志”。其实停止位还有一个重要功能——提供容错空间。
你想想看,如果收发双方的时钟有误差,每个 bit 的采样点会逐渐偏移。停止位就是用来吸收这个累积误差的。
| 停止位长度 | 容错能力 | 适用场景 |
|---|---|---|
| 1 bit | 标准容错 | 大多数通用场景 |
| 1.5 bit | 中等容错 | 时钟误差较大的系统 |
| 2 bit | 高容错 | 长帧传输、低速通信 |
停止位越长,接收方对时钟误差的容忍度越高。但代价是传输效率降低。我一般默认用 1 位停止位,如果通信距离远或者时钟不准,就改成 2 位。
停止位的检测也很简单:接收方在采样完最后一个数据位后,等待一个停止位时长,检查 RX 线是否为高电平。如果是高,说明帧结束正常;如果是低,说明发生了帧错误。
停止位容错的计算:
假设波特率误差为 δ,一帧数据有 N 个 bit(起始位 + 数据位 + 校验位 + 停止位)。
累积误差 = δ × N × 100%
要求:累积误差 < 50%(采样窗口在 bit 中间 ±25%)
例如:8N1 格式(起始位1 + 数据8 + 停止位1 = 10 bit),允许最大误差 = 50% / 10 = 5%
实际上,为了保证可靠,我建议误差控制在 2% 以内。
4.5 波特率误差的影响:一个实际案例
我记得有一次调试一个 GPS 模块,波特率设的是 9600。发送端用内部 RC 振荡器,标称 8MHz,实际测出来 7.92MHz,误差 1%。
按 8N1 格式算:
- 每帧 10 bit
- 累积误差 = 1% × 10 = 10%
- 采样窗口偏移 = 10% × 104.17μs ≈ 10.4μs
- 半个 bit 宽度 = 52μs
- 10.4μs < 52μs,理论上能通信
实际测试确实能通,但偶尔丢字节。换成外部晶振后,误差降到 0.1%,通信就非常稳定了。
所以我的建议是:能不用内部振荡器就别用,尤其是波特率高于 115200 时。
4.6 本章小结
异步通信的时钟同步,说白了就是“起始位对齐 + 本地时钟保持”。采样窗口选在 bit 中间,用多数表决抗干扰。起始位检测要防噪声误触发。停止位是容错缓冲区,越长越稳但效率低。
这些原理看起来简单,但实际项目中每个细节都可能出问题。我建议你在做驱动开发时,先把这些基础概念吃透,后面调试起来会顺手很多。