4、通信协议基础:UART、I2C、SPI协议的基本帧结构解析
做模组联调,说白了就是让两个芯片“说话”。但芯片之间没有嘴巴,也没有耳朵。它们靠的是几根金属线上的电平变化来传递信息。UART、I2C、SPI,就是最常见的三种“说话方式”。
我刚开始做嵌入式那会儿,总觉得协议是理论课的东西。直到第一次调Wi-Fi模组,死活收不到数据,拿逻辑分析仪一看——波特率配错了。嗯,从那以后我再也不敢小看帧结构了。
这一章,咱们就把这三个协议扒开来看。不扯太多历史,只看帧结构、时序、以及联调时最容易踩的坑。
4.1 UART:最朴素的“你一句我一句”
UART(Universal Asynchronous Receiver/Transmitter)是异步串行通信。什么叫异步?就是没有单独的时钟线。收发双方得事先约定好“语速”——也就是波特率。
UART的帧结构很简单,一条数据线(TX),一条接收线(RX),再加一条共地线(GND)。
4.1.1 帧结构
一个完整的UART数据帧长这样:
起始位(1bit) + 数据位(5~8bit) + 校验位(0/1bit) + 停止位(1/1.5/2bit)
举个例子,发送一个字节 0x55(二进制 01010101),8N1格式(8数据位、无校验、1停止位):
空闲(高) → 起始位(低) → 0 → 1 → 0 → 1 → 0 → 1 → 0 → 1 → 停止位(高) → 空闲(高)
注意:数据位是LSB first,也就是最低位先发。0x55的LSB是1,所以第一个数据位是1,不是0。
联调要点:UART通信双方必须完全一致:波特率、数据位长度、校验模式、停止位位数。任何一个不匹配,收到的就是乱码。
4.1.2 我踩过的坑
我曾经调一个4G模组,波特率设的是115200,模组默认也是115200。但就是收不到正确数据。拿示波器一看,波形宽度不对。后来发现模组内部晶振精度不够,实际波特率偏了2.3%。连续传几十个字节后,累积误差就把帧同步丢了。
解决办法:要么换高精度晶振,要么降低波特率。降到9600后,稳如老狗。
避坑指南:联调时如果UART数据偶尔错一两个字节,先别怀疑代码。用示波器或逻辑分析仪看波形,数一数每个bit的宽度对不对。我曾经花了一整天查代码,最后发现是地线没接好。
4.2 I2C:多设备共享两根线
I2C(Inter-Integrated Circuit)是同步半双工通信。它只有两根线:SCL(时钟线)和SDA(数据线)。但可以挂多个设备,每个设备有唯一的地址。
我刚开始用I2C时觉得挺神奇——两根线怎么区分谁在说话?后来明白了,靠的是“主从”关系和“地址”。
4.2.1 帧结构
I2C的帧结构比UART复杂一些:
起始条件 + 7位从机地址 + R/W位 + 应答位 + 数据字节(8位) + 应答位 + ... + 停止条件
关键时序点:
- 起始条件:SCL高电平时,SDA从高变低
- 停止条件:SCL高电平时,SDA从低变高
- 数据采样:SCL高电平时,SDA上的数据必须稳定
- 应答位:接收方在第9个SCL周期拉低SDA表示ACK
个人习惯:写I2C驱动时,我习惯把起始条件、停止条件、发送字节、接收字节都封装成独立函数。这样调试时可以在每个步骤加打印,定位问题快很多。
4.2.2 常见问题
I2C联调时最常遇到的是“总线锁死”。SDA被某个设备一直拉低,主设备发不了起始条件。
为什么会这样?从设备在应答位之后没释放SDA,或者主设备在通信过程中异常复位了,但从设备还在等后续时钟。
解决办法:给从设备硬件复位,或者连续发9个SCL时钟脉冲让从设备退出异常状态。我一般在初始化代码里加一段“总线恢复”逻辑:
// 伪代码:I2C总线恢复
void i2c_bus_recover(void) {
// 先发9个时钟脉冲
for (int i = 0; i < 9; i++) {
SCL = 1; delay();
SCL = 0; delay();
}
// 再发停止条件
SDA = 0; delay();
SCL = 1; delay();
SDA = 1; delay();
}
避坑指南:I2C的上拉电阻不能省。我见过有人把上拉电阻焊成0欧,结果SDA和SCL波形方波变三角波,通信距离稍远一点就出错。4.7kΩ是通用值,高速模式用2.2kΩ。
4.3 SPI:全双工,速度快
SPI(Serial Peripheral Interface)是同步全双工通信。它需要4根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。
SPI的速度通常比I2C快很多。我调过的一个LCD屏,SPI时钟跑到40MHz,刷一帧画面只要几毫秒。I2C的话,同样数据量得几百毫秒。
4.3.1 帧结构
SPI的帧结构相对简单:
CS拉低 → 每个SCLK时钟沿移出一位数据 → CS拉高
但SPI有4种模式,由CPOL(时钟极性)和CPHA(时钟相位)决定:
| 模式 | CPOL | CPHA | 采样沿 | 空闲时钟电平 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 上升沿 | 低 |
| 模式1 | 0 | 1 | 下降沿 | 低 |
| 模式2 | 1 | 0 | 上升沿 | 高 |
| 模式3 | 1 | 1 | 下降沿 | 高 |
联调时,主设备和从设备的SPI模式必须一致。我见过有人模式0和模式3混用,数据能通但偶尔错位,查了两天才发现。
4.3.2 多从机连接
SPI支持一主多从。每个从机独立占用一个CS引脚。但要注意:从机的MISO引脚通常是三态输出,只有自己被选中时才驱动MISO。否则多个从机同时驱动MISO,会打架。
联调要点:SPI通信时,如果MISO波形异常(比如出现不该有的低电平),先检查CS信号是否正常。我遇到过CS引脚虚焊,从机一直处于选中状态,结果和另一个从机抢MISO总线。
4.4 三种协议对比
做个表格,方便你选型时参考:
| 特性 | UART | I2C | SPI |
|---|---|---|---|
| 线数 | 2(TX/RX) | 2(SCL/SDA) | 4(SCLK/MOSI/MISO/CS) |
| 同步/异步 | 异步 | 同步 | 同步 |
| 全双工/半双工 | 全双工 | 半双工 | 全双工 |
| 多设备支持 | 点对点 | 多设备(地址寻址) | 多设备(片选寻址) |
| 典型速率 | 9600~115200 bps | 100kHz~3.4MHz | 10MHz~80MHz |
| 硬件复杂度 | 低 | 中 | 中高 |
| 联调常见问题 | 波特率不匹配、地线干扰 | 总线锁死、上拉电阻不当 | 模式不匹配、CS时序错误 |
4.5 知识体系总览
下面这张图,把三种协议的帧结构、关键参数和联调要点串起来了。你可以把它当作联调时的速查图。
4.6 联调时的通用思路
不管用哪种协议,联调步骤其实差不多。我总结了一个“五步法”:
- 确认硬件连接:用万用表量通断,确认TX/RX没接反,SCL/SDA没短路,CS引脚电平正常。
- 配置协议参数:波特率、模式、地址,主从两端必须一致。
- 发送测试数据:先发固定模式(如0x55、0xAA),方便看波形。
- 抓波形验证:逻辑分析仪或示波器,看起始条件、数据位、停止条件是否完整。
- 确认通信:收发数据一致,才算联调通过。
我的习惯:联调时先在从设备端把收到的原始数据通过另一个UART打印出来。这样能快速判断是主设备没发对,还是从设备没收到。省得两边互相怀疑。
好了,这一章的内容就到这里。UART、I2C、SPI的帧结构,说白了就是“约定好怎么说话”。你只要把起始、数据、结束这三个阶段搞清楚,联调时再配合波形验证,基本不会出大问题。