4. I2C通信协议:起始条件、停止条件、地址字节、读写位归纳总结

I2C协议在嵌入式世界里随处可见。传感器、EEPROM、ADC,几乎每个低速外设都用它。但你有没有用逻辑分析仪抓过一帧I2C数据?我自己第一次看波形时,说实话有点懵——怎么知道哪个是高电平哪个是低电平?哪个是起始哪个是停止?

这一章我们就把I2C帧的四个核心部分掰开揉碎了讲。你只要搞懂起始条件、停止条件、地址字节和读写位,以后用逻辑分析仪看I2C波形就像看手机号码一样清楚。

调试小技巧:建议在接逻辑分析仪时,把采样率设为4倍于SCL频率,比如100kHz的I2C就用400kS/s。否则容易漏掉起始和停止的电平跳变。

4.1 一帧I2C数据长什么样

裸板子上I2C只有两根线:SCL(时钟)和SDA(数据)。所有通信都是主设备发起,从设备回应。一帧完整的I2C消息帧包含:起始条件 → 从设备地址(7位或10位)+ 读写位 → 应答位(ACK)→ 数据字节(可多个)→ 应答位 → … → 停止条件。

我曾经碰到过一个情况:逻辑分析仪抓到波形但解析不出来,最后发现是因为从设备地址的7位和读写位顺序搞反了。你想想看,地址字节的高7位先发,最低位才是读写位。很多人以为读写位在最高位,那肯定对不上。

关键认知:I2C协议中每个字节传输都是从MSB(最高位)到LSB(最低位)。地址字节的LSB就是读写位,1表示读,0表示写。

下面这张图把整个帧结构拆成模块,你看一眼就明白。

I2C数据帧结构(主设备写场景) 起始条件 地址字节(7+1) ACK 数据字节 (可重复) ACK 停止条件 可重复多个数据字节 说明: • 每个字节传输后从设备必须回复ACK(SDA低电平),否则主设备发送NACK后发停止 • 地址字节共8位:高7位为从设备地址,最低位为读写位(1=读,0=写)

4.2 起始条件(Start Condition)

SCL为高电平时,SDA从高电平跳变到低电平。这就是起始条件。我建议你在逻辑分析仪上直接设一个下降沿触发,专门抓起始条件。为什么?因为很多错误就是起始条件没检测到,后面全部乱套。

有一次我调试一个温湿度传感器,死活读不到数据。用逻辑分析仪一看,主设备发了起始条件,但SCL还没完全拉高就开始跳SDA了。后来查手册才发现,那个传感器的时序要求非常严格——SCL高电平必须保持至少4.7μs才能变SDA。嗯,这里要注意,不同从设备的起始保持时间可能不一样。

避坑指南:我曾经遇到过因为中断抢占导致起始条件违反时序的bug——主设备在发送起始条件时被高优先级中断打断,SDA拉低后SCL没及时拉高。结果从设备认为这是噪声直接忽略。解决方案是在发送起始条件前关闭中断,或者用硬件I2C外设。

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位)典型器件
0x480x900x91LM75温度传感器
0x680xD00xD1MPU6050加速度计
0x500xA00xA124C系列EEPROM
0x3C0x780x79SSD1306 OLED

4.5 读写位的实际应用

读写位只是地址字节的最低位,但它决定了后续数据传输方向。如果主设备发的是写地址(读写位=0),那么后续每个字节都是从主设备发往从设备。如果发的读地址(读写位=1),那么后续每个字节都是从从设备发往主设备。

实际操作中经常出现“读之前先写”的场景,比如向传感器发送寄存器编号,然后再读取数值。这时候就需要一个复合操作:先发一个写帧(包含寄存器地址),紧接着发一个重复起始条件,再发一个读帧。逻辑分析仪上你会看到:起始 → 写地址+ACK → 寄存器编号+ACK → 重复起始 → 读地址+ACK → 数据+NACK → 停止。

我第一次用逻辑分析仪看这个复合操作时,看到两个起始条件连在一起,还以为波形坏了。后来才知道重复起始条件(Repeated Start)是I2C协议允许的特殊情况——它不释放总线,在同一个主设备连续传输中替代停止+起始。

实用经验:在逻辑分析仪软件里(比如Saleae或Kingst),I2C协议解析器会自动识别重复起始条件,并用“Sr”或“Restart”标记。如果你看到两个起始之间没有停止,别慌,那是正常的复合传输。

4.6 抓帧实战:如何快速定位错误

现在我们拿逻辑分析仪抓一帧I2C数据,从头到尾说一遍。你打开软件,设置成上升沿/下降沿混合触发,或者干脆设成SDA下降沿触发一次,因为起始条件肯定有SDA下降沿。

  1. 找起始条件:SCL高时SDA下降沿。这就是帧起点。
  2. 读地址字节:8个时钟周期内SDA的电平顺序。最高位(MSB)先出来,最低位是读写位。记得对照芯片手册的7位地址。
  3. 看ACK:第9个时钟周期,如果SDA被从设备拉低,说明从设备回应了正常。如果一直高,就是NACK——从设备没收到或无法响应。
  4. 看数据:8位数据+1位ACK,重复检查。通常第一个数据字节是寄存器地址或命令。
  5. 找停止条件:SCL高时SDA上升沿。总线释放。

我曾遇到一个EEPROM读写不稳定的问题,用这个步骤逐一检查,发现停止条件后SDA从高变低的过程有毛刺,导致下一个主设备误以为起始条件。最后在SDA线上加了一个4.7kΩ上拉电阻才解决。你想想看,一个电阻就能搞定的事,要是没分析仪得排查多久。

一句话口诀:起始下降沿,停止上升沿;地址七加一,读写末位判;ACK第九拍,低电平回应;数据重复发,最后停止完。

把这四个核心元素搞清楚,I2C通信的轮廓就完整了。下一层我们才敢深入谈CRC校验怎么嵌入到I2C帧中——毕竟要先能认帧,才能谈检错嘛。