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个阶段:起始、地址传输、读写位、数据字节、停止。
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高电平时进行,丢失仲裁的主设备自动转为从模式。这个机制保证了多主设备系统不会冲突。