4. I2C通信协议:起始条件、停止条件、地址字节、读写位归纳总结
I2C协议在嵌入式世界里随处可见。传感器、EEPROM、ADC,几乎每个低速外设都用它。但你有没有用逻辑分析仪抓过一帧I2C数据?我自己第一次看波形时,说实话有点懵——怎么知道哪个是高电平哪个是低电平?哪个是起始哪个是停止?
这一章我们就把I2C帧的四个核心部分掰开揉碎了讲。你只要搞懂起始条件、停止条件、地址字节和读写位,以后用逻辑分析仪看I2C波形就像看手机号码一样清楚。
4.1 一帧I2C数据长什么样
裸板子上I2C只有两根线:SCL(时钟)和SDA(数据)。所有通信都是主设备发起,从设备回应。一帧完整的I2C消息帧包含:起始条件 → 从设备地址(7位或10位)+ 读写位 → 应答位(ACK)→ 数据字节(可多个)→ 应答位 → … → 停止条件。
我曾经碰到过一个情况:逻辑分析仪抓到波形但解析不出来,最后发现是因为从设备地址的7位和读写位顺序搞反了。你想想看,地址字节的高7位先发,最低位才是读写位。很多人以为读写位在最高位,那肯定对不上。
下面这张图把整个帧结构拆成模块,你看一眼就明白。
4.2 起始条件(Start Condition)
SCL为高电平时,SDA从高电平跳变到低电平。这就是起始条件。我建议你在逻辑分析仪上直接设一个下降沿触发,专门抓起始条件。为什么?因为很多错误就是起始条件没检测到,后面全部乱套。
有一次我调试一个温湿度传感器,死活读不到数据。用逻辑分析仪一看,主设备发了起始条件,但SCL还没完全拉高就开始跳SDA了。后来查手册才发现,那个传感器的时序要求非常严格——SCL高电平必须保持至少4.7μs才能变SDA。嗯,这里要注意,不同从设备的起始保持时间可能不一样。
4.3 停止条件(Stop Condition)
停止条件正好反过来:SCL为高电平时,SDA从低电平跳变到高电平。说白了,这就是总线释放的信号。停止条件之后,总线进入空闲状态,其他主设备才能发起通信。
你可能会问:如果传输了一半突然想中止怎么办?I2C协议里其实有一种叫“总线仲裁”的机制,但简单来说,主设备可以在任何字节之后发送停止条件,从设备会回到初始状态。我自己的习惯是,每次发现ACK超时,就立刻发停止条件复位总线,然后重新尝试。这比发复位序列要快得多。
4.4 地址字节与读写位
地址字节是紧跟起始条件之后的8位数据。高7位是从设备地址,最低位是读写位。举个例子:你的加速度计地址是0x68,二进制就是1101000。如果你想写寄存器,那么地址字节就是11010000(0xD0);如果想读,就是11010001(0xD1)。
很多新手会混淆0x68和0xD0。我建议你记住一个公式:写地址 = 从设备地址 << 1,读地址 = 写地址 | 1。写成代码就是:
#define DEV_ADDR 0x68 // 7位地址
#define DEV_WRITE_ADDR (DEV_ADDR << 1) // 0xD0
#define DEV_READ_ADDR ((DEV_ADDR << 1) | 1) // 0xD1
下面这个表格把7位地址与8位地址字节的对应关系列清楚,你以后查手册可以对照着看。
| 7位设备地址(常见) | 写字节(8位) | 读字节(8位) | 典型器件 |
|---|---|---|---|
| 0x48 | 0x90 | 0x91 | LM75温度传感器 |
| 0x68 | 0xD0 | 0xD1 | MPU6050加速度计 |
| 0x50 | 0xA0 | 0xA1 | 24C系列EEPROM |
| 0x3C | 0x78 | 0x79 | SSD1306 OLED |
4.5 读写位的实际应用
读写位只是地址字节的最低位,但它决定了后续数据传输方向。如果主设备发的是写地址(读写位=0),那么后续每个字节都是从主设备发往从设备。如果发的读地址(读写位=1),那么后续每个字节都是从从设备发往主设备。
实际操作中经常出现“读之前先写”的场景,比如向传感器发送寄存器编号,然后再读取数值。这时候就需要一个复合操作:先发一个写帧(包含寄存器地址),紧接着发一个重复起始条件,再发一个读帧。逻辑分析仪上你会看到:起始 → 写地址+ACK → 寄存器编号+ACK → 重复起始 → 读地址+ACK → 数据+NACK → 停止。
我第一次用逻辑分析仪看这个复合操作时,看到两个起始条件连在一起,还以为波形坏了。后来才知道重复起始条件(Repeated Start)是I2C协议允许的特殊情况——它不释放总线,在同一个主设备连续传输中替代停止+起始。
4.6 抓帧实战:如何快速定位错误
现在我们拿逻辑分析仪抓一帧I2C数据,从头到尾说一遍。你打开软件,设置成上升沿/下降沿混合触发,或者干脆设成SDA下降沿触发一次,因为起始条件肯定有SDA下降沿。
- 找起始条件:SCL高时SDA下降沿。这就是帧起点。
- 读地址字节:8个时钟周期内SDA的电平顺序。最高位(MSB)先出来,最低位是读写位。记得对照芯片手册的7位地址。
- 看ACK:第9个时钟周期,如果SDA被从设备拉低,说明从设备回应了正常。如果一直高,就是NACK——从设备没收到或无法响应。
- 看数据:8位数据+1位ACK,重复检查。通常第一个数据字节是寄存器地址或命令。
- 找停止条件:SCL高时SDA上升沿。总线释放。
我曾遇到一个EEPROM读写不稳定的问题,用这个步骤逐一检查,发现停止条件后SDA从高变低的过程有毛刺,导致下一个主设备误以为起始条件。最后在SDA线上加了一个4.7kΩ上拉电阻才解决。你想想看,一个电阻就能搞定的事,要是没分析仪得排查多久。
把这四个核心元素搞清楚,I2C通信的轮廓就完整了。下一层我们才敢深入谈CRC校验怎么嵌入到I2C帧中——毕竟要先能认帧,才能谈检错嘛。