2. I2C协议深度解析:从物理层到应答机制

I2C协议,说白了就是两根线搞定一堆设备通信。很多初学者觉得它简单,不就是SCL和SDA嘛。但真到调驱动的时候,时序不对、应答超时、总线锁死……这些问题我当年都踩过。今天咱们就把I2C的物理层、时序、起停条件、数据有效性和应答机制,一个一个掰开揉碎了讲。

2.1 I2C总线物理层

I2C总线只用两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上,主设备负责产生时钟,从设备响应数据。

关键特性:

  • 开漏输出 + 上拉电阻:SCL和SDA都是开漏结构,必须外接上拉电阻(典型值4.7kΩ,高速模式用1.5kΩ)
  • 线与逻辑:多个设备可以同时拉低总线,谁先释放谁后释放由硬件自动仲裁
  • 7位/10位地址:标准模式用7位地址,最多挂127个设备(实际受总线电容限制)
  • 多主设备支持:多个主设备可以共享总线,通过仲裁决定谁控制总线

我在项目中遇到过一个问题:上拉电阻选太大,总线上升沿太慢,400kHz高速模式直接跑不起来。后来换成1.5kΩ,波形才干净。嗯,这里要注意——上拉电阻不是随便选的,得根据总线电容和速率算。

参数 标准模式 (100kHz) 快速模式 (400kHz) 高速模式 (3.4MHz)
上拉电阻 4.7kΩ ~ 10kΩ 1.5kΩ ~ 4.7kΩ 0.5kΩ ~ 1.5kΩ
总线电容 ≤ 400pF ≤ 400pF ≤ 100pF
最大从设备数 127 127 127

2.2 I2C时序图深度拆解

I2C的时序,说白了就是SCL和SDA在时间轴上的配合。我习惯把一帧完整的I2C通信拆成5个阶段:起始、地址传输、读写位、数据字节、停止。

I2C完整通信时序图 SCL SDA 起始条件 地址传输 (7位) R/W ACK 数据字节 (8位) ACK 停止 时序要点: 1. SCL高电平时,SDA必须稳定(数据有效) 2. SCL低电平时,SDA可以变化(数据切换) 3. 起始条件:SCL高电平时,SDA从高→低 4. 停止条件:SCL高电平时,SDA从低→高

2.3 起始条件与停止条件

起始条件和停止条件是I2C通信的"开关"。没有它们,总线上的设备不知道什么时候开始、什么时候结束。

起始条件(S):SCL为高电平时,SDA从高电平切换到低电平。

停止条件(P):SCL为高电平时,SDA从低电平切换到高电平。

重复起始条件(Sr):在未发送停止条件的情况下,再次发送起始条件。用于改变数据传输方向。

我曾经在调试一个温湿度传感器时,发现读到的数据总是错位。用逻辑分析仪抓波形一看——停止条件没发完整,SCL还没拉高SDA就变了。从那以后,我写I2C驱动都会在停止条件后加一个微秒级的延时,确保总线稳定。

2.4 数据有效性

I2C的数据有效性规则其实就一句话:SCL高电平时,SDA必须保持稳定。只有在SCL低电平时,SDA才能变化。

你想想看,如果SCL高电平时SDA变了,那总线会怎么理解?要么当成起始条件,要么当成停止条件,数据肯定乱套。

数据有效性检查清单:

  • 每个数据位在SCL高电平时被采样
  • SDA在SCL低电平时才能切换
  • 数据以MSB(最高位)优先传输
  • 每个字节8位,后面紧跟一个ACK位

2.5 应答机制(ACK/NACK)

应答机制是I2C协议的"握手信号"。每次发送完一个字节(8位数据),接收方必须在第9个时钟周期拉低SDA表示应答(ACK),或者保持高电平表示非应答(NACK)。

应答类型 SDA状态(第9个SCL) 含义 典型场景
ACK 低电平 接收成功,继续传输 正常数据读写
NACK 高电平 接收失败或传输结束 从设备忙、地址错误、读操作结束

我遇到过最坑的情况:从设备在写寄存器时返回NACK,但驱动没检查应答位,继续发下一个字节。结果数据全写到了错误的地址。嗯,这里要记住——每次传输后必须检查ACK位,这是I2C驱动的基本功。

2.6 完整通信流程示例

咱们以读取一个I2C温度传感器为例,看看完整流程:

// 伪代码:I2C读温度传感器
1. 发送起始条件 (S)
2. 发送从设备地址 (0x48 << 1 | 0)  // 写操作
3. 等待ACK
4. 发送寄存器地址 (0x00)            // 温度寄存器
5. 等待ACK
6. 发送重复起始条件 (Sr)
7. 发送从设备地址 (0x48 << 1 | 1)  // 读操作
8. 等待ACK
9. 读取高字节数据                   // 主设备接收
10. 发送ACK                        // 主设备应答
11. 读取低字节数据
12. 发送NACK                       // 主设备非应答,表示结束
13. 发送停止条件 (P)

避坑指南:

  • 我曾经在重复起始条件后忘记切换读写位,结果读到的全是0xFF
  • 从设备地址左移1位再拼接读写位,这个细节很多人会搞错
  • 读操作最后一个字节必须发NACK,否则从设备会继续发数据

2.7 时钟拉伸与总线仲裁

这两个概念虽然不常用,但面试和实际项目中都可能遇到。

时钟拉伸(Clock Stretching):从设备可以拉低SCL,让主设备等待。常见于慢速从设备需要时间处理数据。我调试过一个EEPROM,写操作后必须等5ms,从设备会拉低SCL直到写入完成。

总线仲裁(Arbitration):多个主设备同时发送数据时,谁先拉低SDA谁就赢得仲裁。仲裁在SCL高电平时进行,丢失仲裁的主设备自动转为从模式。这个机制保证了多主设备系统不会冲突。