本节摘要:芯片与芯片之间对话靠串行通信。UART 用两根线完成异步点对点通信,SPI 用四根线跑出高速同步传输,I2C 用两根线挂起一串低速设备。本节讲清三种总线的帧结构、时序要点与选型逻辑,重点拆解 UART 波特率误差的算法——它既是初学者最常撞的坑,也是理解"时钟精度如何影响通信"的最佳教具。
老一辈芯片间通信流行并行:八根数据线一次传一个字节。但线多了排线成本高、时序对齐难、抗干扰差,速率一上去各线到达时间不一致(偏斜)就成了灾难。串行通信反其道而行:一根线一位一位传,线少、偏斜问题轻、速率靠提高时钟弥补。如今芯片间通信的天下基本是串行三种总线的:UART 求简单,SPI 求速度,I2C 求省引脚。三种总线怎么选,先看它们各自怎么工作。
UART(通用异步收发器)异步的含义是通信双方没有共享时钟线,各自用自己的时钟按约定速率采样。约定就是波特率——每秒传多少位,常用 9600、115200。帧结构极简:空闲时线为高;起始位拉低宣告"一帧开始";随后 5 到 9 个数据位(低位先行);可选校验位;停止位拉高收尾,线回到空闲。接收方在起始位的下降沿处启动自己的采样计时,在每个位的中央采样——中央采样留出了半个位的时钟容差,这正是异步通信能工作的根本机制。

波特率误差怎么算。假设时钟 72MHz、目标波特率 115200:分频值等于 72000000 除以 16 再除以 115200,约等于 39.06——寄存器只能装整数 39,实际波特率变成 72000000 除以 16 再除以 39,约 115384,误差约 0.16%,安全。换时钟 36MHz 算同档波特率可能出现 3% 以上误差——接近或越过 2.5% 的总容限(收发双方各摊一半),通信就会间歇出错。2.3 节"串口乱码"案例的根因,用这里的公式一算便知。波特率计算是串口配置唯一需要动笔的地方,务必亲手算一遍而不是抄例程。
SPI(串行外设接口)是同步总线:主机额外提供一根时钟线(SCLK),数据在时钟边沿被双方同时采样,没有波特率误差问题,速率轻松上到几十 MHz。标准接线四根:SCLK 时钟、MOSI 主出从入、MISO 主入从出、CS 片选(每从机一根,低电平选中)。主机模式下单向节奏完全由主机掌控,简单粗暴、吞吐高,Flash 芯片、屏幕、高速 ADC 都爱用它。
SPI 的细节坑在"模式"上:时钟极性(空闲时是高是低)与时钟相位(第一个还是第二个边沿采样)组合出四种模式,外设芯片用哪种,数据手册写死。模式配错的现象很典型:数据整体移位或按位取反——能收到东西但内容不对。排查口诀:先查模式,再查片选极性,最后查字节序。
I2C(集成电路互连总线)用两根线(SDA 数据、SCL 时钟)挂起一串从设备,每个从机有 7 位地址,主机靠"先广播地址再通信"的方式点名。它有两个天生绝活:多从机省引脚——挂八个传感器也只占两根线;应答机制——每字节后接收方拉低 SDA 一拍表示"收到了",主机可感知设备在不在。代价是速率低(标准 100kHz、快速 400kHz)、时序复杂(起始条件、停止条件、地址帧、应答位)、需要上拉电阻(开漏输出配外部上拉,正是 4.1 节的知识)。
| 对比项 | UART | SPI | I2C |
|---|---|---|---|
| 线数 | 2(收发) | 4(含每从机一根片选) | 2(含上拉) |
| 时钟 | 异步,靠波特率约定 | 同步,主机供时钟 | 同步,主机供时钟 |
| 速率 | 常用至 1Mbps 内 | 数十 MHz | 100k 至 400kHz |
| 从机寻址 | 点对点 | 片选线 | 7 位地址广播 |
| 应答机制 | 无 | 无 | 每字节应答 |
| 典型场景 | 调试口、模块通信 | Flash、屏、高速采样 | 传感器、RTC、小容量存储 |
以 STM32 串口 1 为例的完整配置步骤,每一步对应一个硬件机制:
/* UART 配置五步:时钟、引脚复用、波特率、帧格式、使能 */ RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* 串口时钟 */ RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; /* 引脚端口时钟 */ GPIOA->CRH = 0x444444B4; /* PA9 复用推挽(发送)PA10 浮空输入(接收) */ USART1->BRR = 72000000 / 16 / 115200; /* 波特率分频:算准这一步 */ USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; /* 使能收发 */ while (!(USART1->SR & USART_SR_TXE)); /* 等发送数据寄存器空 */ USART1->DR = 'H'; /* 发出一个字符 */ while (!(USART1->SR & USART_SR_TC)); /* 等真正发完 */
I2C 的读取传感器流程值得用伪代码过一遍,理解"地址点名"的节奏:起始条件(SDA 在 SCL 高时拉低)→ 发 7 位地址加读写位 → 等从机应答(第 9 拍 SDA 被拉低)→ 发寄存器地址 → 重新起始 → 再发地址改读方向 → 连续读字节、每字节回一个应答 → 停止条件(SDA 在 SCL 高时拉高)。整套时序 HAL 库已封装,但懂时序的人看逻辑分析仪抓的波形,能一眼指出问题出在哪一拍——这是库函数用户与工程师的分界线。
串行通信是新手故障最密集的领域,三板斧能解决八成问题。第一斧,物理层:确认接线(TX 接 RX 交叉铁律)、共地(两板之间必须连 GND,只连信号线不共地是最隐蔽的错)、电平匹配(3.3V 与 5V 设备直连要看双方耐压)。第二斧,参数层:波特率、数据位、停止位、校验位四件套两端一致——一头 8N1 一头 9N1 的"能收到但全是乱码"是经典病。第三斧,协议层:参数全对还不通,抓波形(逻辑分析仪或示波器)看有没有信号、信号电平对不对、帧间隔是否合理。三板斧的顺序就是依赖链顺序:物理不通别调参数,参数不对别查协议。这套路数与 2.5 节的最小系统排查一脉相承,都是"沿依赖链逐级验证"。
9600、19200、115200 这串"怪数字"其实是历史遗产:早期电传终端的机械速度折算出 9600 这个基准,后续档位按整数倍往下排,115200 即 9600 的十二倍。工程上真正要关心的不是数字本身,而是分频误差——4.2 开头的波特率公式里,分频值常除不尽,余数累积成位宽偏差。经验阈值是总误差控制在正负百分之二以内(异步采样容差约百分之五,还要给两端各留余量)。所以低速档(9600)几乎总能配准,高速档(115200)在时钟频率不凑巧的芯片上反而可能超差——串口"高速档乱码、低速档正常"的怪病,病根就在这。换晶振或换波特率档位,是最省钱的修法。