UART 协议基础:从历史到实战
各位同学,咱们今天正式开始啃 UART 这块硬骨头。说实话,UART 是我入行嵌入式时接触的第一个串行协议,十几年过去了,它依然活跃在每一块开发板上。你想想看,一个上世纪 60 年代诞生的协议,到现在还没被淘汰,本身就说明它有多实用。
1.1 UART 的前世今生
UART 的全称是 Universal Asynchronous Receiver/Transmitter,通用异步收发器。它最早由 DEC 公司在 1960 年代为电传打字机设计的。那时候的计算机还跟房间一样大,通信靠的是纸带和机械打字机。
我个人习惯把 UART 的发展分成三个阶段:
- 机械时代(1960s-1970s):用继电器和电机实现,速率只有 110bps,传输距离靠电压高低硬扛。
- 芯片时代(1980s-1990s):出现了 8250、16550 这类专用 UART 芯片,PC 主板上必带。16550 的 16 字节 FIFO 在当时可是革命性的设计。
- SoC 时代(2000s 至今):UART 被集成进 MCU 和 MPU 内部,变成片内外设。我们现在的 STM32、i.MX、全志芯片里都内置了 UART 控制器。
我记得刚入行那会儿,公司还在用 8250 芯片做多串口卡,调试时得拿示波器一根一根量信号线。现在想想,那时候的硬件工程师是真不容易。
1.2 物理层特性:三根线走天下
UART 的物理层其实很简单,就三根线:TX(发送)、RX(接收)、GND(地)。全双工通信,收发互不干扰。
| 信号线 | 方向 | 说明 |
|---|---|---|
| TX | 设备 A → 设备 B | 发送数据,空闲时为高电平 |
| RX | 设备 A ← 设备 B | 接收数据,与对方 TX 交叉连接 |
| GND | 公共参考地 | 必须共地,否则电平参考点不一致 |
这里有个坑,我必须要提醒你:TX 和 RX 要交叉连接。设备 A 的 TX 接设备 B 的 RX,设备 A 的 RX 接设备 B 的 TX。我曾经见过一个同事,把 TX-TX、RX-RX 直连了,调了一整天没通,最后发现是线接反了。
常见的 UART 电平有 TTL(0~3.3V/5V)和 RS-232(±12V)两种。TTL 电平的 UART 不能直接接 RS-232 接口,必须加电平转换芯片(如 MAX3232)。我遇到过不少新手直接把 3.3V 的 TX 接到 RS-232 的 DB9 座上,结果烧了 IO 口。
1.3 数据帧格式:一帧一帧聊
UART 是异步通信,没有时钟线。那收发双方怎么同步?靠的是数据帧的起始位和停止位。说白了,就是约定好一帧数据长什么样。
标准 UART 数据帧包含以下部分:
- 起始位(Start Bit):1 位,逻辑 0。告诉接收方「我要开始发数据了」。
- 数据位(Data Bits):5~9 位,常用 8 位。低位在前,高位在后。
- 校验位(Parity Bit):可选,0 或 1 位。用于简单的错误检测。
- 停止位(Stop Bit):1、1.5 或 2 位,逻辑 1。表示一帧结束。
8 位数据位 + 无校验 + 1 位停止位。这是嵌入式开发中最常用的配置,没有之一。Linux 内核的串口驱动默认也是 8N1。
为什么数据位常用 8 位?因为一个字节就是 8 位,方便和内存、寄存器对接。你想想看,如果数据位是 7 位,那传一个字节还得拆成两次,多麻烦。
校验位有三种模式:
- 无校验(N):不校验,省带宽,适合短距离可靠通信。
- 奇校验(O):数据位 + 校验位中 1 的个数为奇数。
- 偶校验(E):数据位 + 校验位中 1 的个数为偶数。
我在项目中遇到过一个问题:两个设备通信,一方配了偶校验,另一方配了无校验。结果数据偶尔对、偶尔错,查了三天才发现是校验位不匹配。所以啊,通信双方的帧格式必须完全一致,差一个 bit 都不行。
1.4 波特率:通信的节拍器
波特率(Baud Rate)定义的是每秒传输的符号数。对于 UART 来说,一个符号就是一个 bit,所以波特率就等于比特率。
常用波特率有:9600、19200、38400、115200、921600。其中 115200 是嵌入式开发中最常用的,因为它在速度和稳定性之间取得了很好的平衡。
| 波特率(bps) | 每字节传输时间(8N1) | 适用场景 |
|---|---|---|
| 9600 | 约 1.04 ms | 调试输出、GPS 模块 |
| 115200 | 约 86.8 μs | 通用串口通信、Bootloader |
| 921600 | 约 10.9 μs | 高速数据传输、固件升级 |
波特率怎么算出来的?以 115200 为例,一帧 10 位(1 起始 + 8 数据 + 1 停止),每秒传 115200 位,也就是 11520 帧/秒。每帧 1 字节,所以有效数据速率是 11520 字节/秒。
实际系统中,晶振频率和分频系数会导致波特率有微小误差。一般要求误差不超过 ±2%。Linux 内核的串口驱动在初始化时会自动计算最佳分频值,但如果你用外部晶振,最好用示波器量一下实际波特率。我习惯用逻辑分析仪抓一个字节的波形,量一下位宽,反推实际波特率。
1.5 知识结构总览
下面这张图把 UART 协议的核心知识点串起来了。你可以把它当作本章的思维导图。
这张图把 UART 协议的四个核心维度串在了一起。从历史演进到物理层,从数据帧格式到波特率计算,每一块都是后面章节的基础。你先把这张图印在脑子里,后面讲中断处理和多设备通信时,你会回来感谢我的。
1.6 本章小结
好了,第一章的内容就到这里。我们聊了 UART 的历史、物理层的三根线、数据帧的组成结构,还有波特率怎么算。这些东西看起来简单,但都是后面写驱动、调中断、做多设备通信的基石。