I2C协议详解:从物理层到数据帧
I2C协议,说白了就是一根时钟线加一根数据线,把一堆芯片串起来聊天的办法。我最早接触I2C是在一个步进电机驱动项目里,当时觉得这东西简单,结果被地址冲突坑了一把——嗯,后面我会细说。
I2C总线的物理层
I2C总线只有两条线:SCL(串行时钟线)和SDA(串行数据线)。所有设备都挂在这两条线上,主设备负责产生时钟,从设备响应数据。
物理层有几个关键点:
- 开漏输出:SCL和SDA都是开漏结构,只能拉低,不能主动拉高。高电平靠上拉电阻实现。
- 上拉电阻:阻值一般在1kΩ~10kΩ之间。我习惯用4.7kΩ,除非总线电容特别大才换2.2kΩ。
- 线与逻辑:多个设备可以同时拉低总线,谁先拉低谁占主导。这是仲裁的基础。
- 最大电容:标准模式下总线电容不能超过400pF。线长了、设备多了,信号就变形。
起始条件与停止条件
I2C总线的数据传输,由两个特殊信号控制:
| 信号 | SCL状态 | SDA变化 | 说明 |
|---|---|---|---|
| 起始条件(S) | 高电平 | 从高到低跳变 | 总线从空闲变为忙碌 |
| 停止条件(P) | 高电平 | 从低到高跳变 | 总线从忙碌变为空闲 |
你想想看,为什么要在SCL高电平时检测SDA的变化?因为数据采样也是在SCL高电平时进行的。如果SDA在SCL高电平时变化,就会被误认为数据位。所以起始和停止条件故意选在SCL高电平时改变SDA,就是为了和普通数据位区分开。
数据帧格式
I2C的数据帧格式很规整。每个字节8位,高位在前(MSB first)。每发完一个字节,从设备必须回复一个ACK位。
完整的数据传输流程是这样的:
- 主设备发送起始条件
- 主设备发送7位从设备地址 + 1位读写位
- 从设备回复ACK
- 主设备发送/接收数据字节(每字节后跟ACK)
- 主设备发送停止条件
这里有个细节:ACK位是接收方在第9个SCL周期拉低SDA。如果接收方没拉低,就表示NACK。NACK通常意味着:地址不对、设备忙、或者数据接收完毕。
地址与读写位
I2C的7位地址,最多支持112个设备(保留地址占16个)。地址由设备厂商固定,但有些芯片可以通过引脚配置。
读写位紧跟在地址后面:
- 0:主设备写数据到从设备
- 1:主设备从从设备读数据
举个例子:一个I2C温度传感器的地址是0x48(二进制1001000)。如果要读它的温度值,主设备先发0x48左移1位+读位1,也就是0x91。从设备匹配到地址后回复ACK,然后开始传数据。
我遇到过最头疼的问题是什么?地址冲突。两个不同型号的传感器用了同一个地址,挂在同一根总线上。解决办法要么换芯片,要么加一个I2C多路复用器(比如PCA9548)。从那以后,我选型时第一件事就是看地址能不能改。
I2C协议核心流程
下面这张图把I2C的完整通信流程画出来了,从起始到停止,每一步都标清楚了:
常见问题与避坑指南
做I2C开发,有几个坑我几乎每次都会遇到:
- 总线锁死:从设备拉低SDA不放,主设备无法产生起始条件。解决办法是给SCL发9个时钟脉冲,让从设备复位。
- 上拉电阻选错:阻值太大,信号上升沿太慢;阻值太小,功耗高且可能拉不低。我一般先按4.7kΩ试,不行再调。
- 地址冲突:两个设备地址一样,谁都不理你。选型时一定要确认地址是否可配置。
- 速率不匹配:主设备跑400kHz,从设备只支持100kHz,通信就会出错。降速到100kHz最保险。