1. UART 基础概念:从物理层看懂串口通信
大家好,我是你们的嵌入式讲师。今天咱们来聊聊 UART——这个在嵌入式世界里几乎无处不在的通信接口。
UART 的全称是 Universal Asynchronous Receiver/Transmitter,通用异步收发器。说白了,它就是一套让两个设备之间通过一根或两根线互相传数据的硬件模块。我刚开始做嵌入式那会儿,总觉得 UART 太简单,不就是发个串口数据吗?后来踩了不少坑才明白,越是基础的东西,越值得把每个细节吃透。
1.1 UART 到底是什么?
UART 不是一个协议,而是一个硬件外设。它负责把并行数据转成串行数据发出去,再把收到的串行数据转回并行数据交给 CPU。你想想看,CPU 内部的数据总线是 8 位、16 位甚至 32 位并行的,但通信线缆只有一根 TX 和一根 RX,所以必须串行化。
核心要点:UART 是硬件层的数据格式转换器 + 时序控制器。它不关心数据内容是什么,只关心怎么把比特流按时序发出去。
我在一个工业控制项目里遇到过这样的问题:两个板子用 UART 通信,数据偶尔会错位。查了半天发现是其中一块板子的 UART 时钟源配置错了,导致波特率偏差超出容限。从那以后,我每次做 UART 驱动都会先确认时钟源和分频系数。
1.2 异步传输原理:没有时钟线怎么同步?
UART 是异步的,意思是收发双方没有独立的时钟线。那它们怎么对齐比特流呢?答案是:靠约定的波特率和帧格式。
异步传输的核心思想是:发送方在数据帧的开始插入一个起始位,接收方检测到这个电平跳变后,就按约定的波特率采样后续的每一位。说白了,就是发方说「我开始了」,收方说「好,我按这个节奏读」。
我画了一张图,帮你理解异步传输的时序关系:
1.3 帧结构详解:起始位、数据位、校验位、停止位
一个完整的 UART 数据帧由四部分组成。我习惯用「起数校停」四个字来记,你试试看,挺顺口的。
| 字段 | 长度 | 电平状态 | 作用 |
|---|---|---|---|
| 起始位 | 1 位 | 高→低跳变 | 同步信号,通知接收方开始采样 |
| 数据位 | 5~9 位(常用 8 位) | LSB 先发,MSB 后发 | 实际传输的有效数据 |
| 校验位 | 0 或 1 位 | 奇校验/偶校验/无校验 | 检错(只能检测奇数个错误) |
| 停止位 | 1 / 1.5 / 2 位 | 高电平 | 帧结束标志,给接收方处理时间 |
起始位(Start Bit)
起始位永远是 1 位,电平从空闲的高电平跳变到低电平。接收方检测到这个下降沿后,就开始按波特率采样后续位。嗯,这里要注意:起始位的下降沿是异步通信的「发令枪」,如果这个沿被噪声干扰了,整帧数据就全废了。
我的经验:在噪声环境中(比如电机驱动板附近),我会在 UART 的 RX 线上加一个 10kΩ 上拉电阻,确保空闲时电平稳定。另外,软件里可以加一个起始位确认逻辑——检测到下降沿后延迟半个位时间再采样,避开跳变沿附近的抖动区。
数据位(Data Bits)
数据位是帧的核心,长度可以是 5、6、7、8 或 9 位。8 位数据位最常用,因为它刚好对应一个字节。发送顺序是 LSB(最低有效位)先发,MSB(最高有效位)最后发。
举个例子,你要发送 0x55(二进制 01010101),LSB 是 1,所以线上先发 1,然后依次是 0、1、0、1、0、1、0。你想想看,如果示波器抓到的波形是 10101010,那说明你接反了位顺序——我当年就犯过这个错,调了两天才发现。
校验位(Parity Bit)
校验位是可选的,用于简单的错误检测。有三种模式:
- 无校验(None):不发送校验位,帧长度最短,效率最高。
- 奇校验(Odd):校验位 + 数据位中 1 的总数为奇数。
- 偶校验(Even):校验位 + 数据位中 1 的总数为偶数。
我曾经在一个数据采集项目里用过奇校验。当时传感器数据偶尔会跳变,加了奇校验后能检测到约 50% 的单比特错误。为什么只有 50%?因为如果两个比特同时翻转,奇偶性不变,校验就失效了。所以校验位只能防君子不防小人,真正要求高的场景得用 CRC。
停止位(Stop Bit)
停止位是帧的结束标志,电平必须为高。长度可以是 1、1.5 或 2 位。1 位停止位最常用,2 位停止位用于低速设备或需要更多处理时间的场景。
注意:停止位长度不是随便选的。收发双方必须一致,否则接收方会认为帧格式错误。我见过有人把发送端设成 2 位停止位,接收端设成 1 位,结果每帧都报帧错误(Frame Error)。
1.4 常见帧格式组合
实际项目中,帧格式通常用「数据位-校验位-停止位」的简写来表示。比如 8N1 就是 8 位数据、无校验、1 位停止位。这是最常用的组合,没有之一。
| 简写 | 数据位 | 校验位 | 停止位 | 帧总长度 | 典型场景 |
|---|---|---|---|---|---|
| 8N1 | 8 | 无 | 1 | 10 位 | 通用数据通信,最常用 |
| 8E1 | 8 | 偶校验 | 1 | 11 位 | Modbus RTU 等工业协议 |
| 8O1 | 8 | 奇校验 | 1 | 11 位 | 部分老式终端设备 |
| 7E1 | 7 | 偶校验 | 1 | 10 位 | ASCII 字符传输(7 位够用) |
| 8N2 | 8 | 无 | 2 | 11 位 | 低速设备,需要更多处理时间 |
1.5 波特率:通信的节拍器
波特率(Baud Rate)定义了每秒传输的符号数。在 UART 里,一个符号就是一位,所以波特率等于比特率。常见的波特率有 9600、19200、38400、115200 等。
波特率决定了每一位的持续时间:
位时间 = 1 / 波特率
例如:
波特率 9600 → 位时间 ≈ 104.17 μs
波特率 115200 → 位时间 ≈ 8.68 μs
收发双方的波特率必须一致,误差通常要求在 ±2% 以内。为什么会有误差?因为 UART 的时钟源来自系统时钟分频,分频系数不一定是整数,导致实际波特率和标称值有偏差。
避坑指南:我曾经在一个项目里用了 250000 波特率,结果接收端用的是 230400。两个值看起来差不多,但位时间差了约 8%。通信偶尔正常,偶尔乱码,查了三天才发现是波特率不匹配。所以我的建议是:优先用标准波特率(9600、19200、115200 等),非标波特率一定要仔细算误差。
1.6 本章小结
UART 的基础概念其实不复杂,但每个细节都影响通信的可靠性。我总结一下今天讲的核心点:
- UART 是硬件外设,负责并串转换和时序控制
- 异步传输靠起始位同步,没有时钟线
- 帧结构 = 起始位 + 数据位 + 校验位(可选) + 停止位
- 8N1 是最常用的帧格式,10 位一帧
- 波特率必须匹配,误差控制在 ±2% 以内