I2C协议基础:从物理层到地址模式
各位同学,今天我们来聊聊I2C协议的基础。说实话,I2C是我在嵌入式开发中用得最多的总线之一。它简单、灵活,两根线就能挂一堆设备。但正因为简单,很多人反而忽略了它的底层细节。等出了问题,才抓耳挠腮。
我刚开始做I2C项目时,就吃过不少亏。有一次调试一个温湿度传感器,死活读不到数据。折腾了两天,最后发现是SDA线上的上拉电阻焊错了。嗯,从那以后,我再也不敢小看物理层了。
I2C总线物理层:SDA与SCL
I2C总线只有两根线:SDA(串行数据线)和SCL(串行时钟线)。所有设备都挂在这两根线上,通过唯一地址区分。
核心要点:I2C是开漏输出总线。这意味着设备只能把线拉低,不能主动拉高。高电平靠外部上拉电阻实现。
为什么用开漏?说白了,就是为了多设备共用总线。如果每个设备都能主动拉高,那一个设备输出高、另一个输出低,直接就短路了。开漏设计让所有设备只能拉低,谁想拉高就释放总线,靠上拉电阻恢复高电平。
上拉电阻怎么选?我一般遵循这个经验:
| 总线电容 | 推荐上拉电阻 | 适用场景 |
|---|---|---|
| < 100 pF | 10 kΩ | 低速、短距离、设备少 |
| 100 ~ 200 pF | 4.7 kΩ | 标准模式、常见场景 |
| 200 ~ 400 pF | 2.2 kΩ | 高速模式、长走线 |
我的习惯:ESP32开发板上默认用了4.7kΩ上拉电阻。如果你自己画板子,先按4.7kΩ起步。信号边沿太缓就换小一点,功耗太高就换大一点。
I2C通信时序:起始、停止与数据有效性
I2C的时序其实不复杂。你只要记住三个关键动作:起始条件、停止条件和数据有效性。
起始条件(START Condition)
SCL为高电平时,SDA从高电平跳变到低电平。这就是起始信号。我习惯叫它「握手开始」。
停止条件(STOP Condition)
SCL为高电平时,SDA从低电平跳变到高电平。这就是停止信号。总线释放,其他设备可以发起通信。
数据有效性
数据在SCL时钟的高电平期间必须保持稳定。只有在SCL低电平时,SDA才能变化。你想想看,如果数据在时钟高电平时乱跳,接收方根本不知道哪个位才是对的。
我曾经踩过的坑:有一次写I2C驱动,在SCL高电平时改变了SDA电平。结果从设备一直返回NACK。查了半天,发现是中断服务函数里改了SDA状态。从那以后,我写I2C时序代码时,都会加一个「SCL低电平时才允许改SDA」的断言。
下面这张图展示了I2C的基本时序流程:
7位地址模式
7位地址是I2C最常用的模式。每个从设备有一个7位的唯一地址,范围是0x00 ~ 0x7F。主机发送地址时,实际上会发送8位:7位地址 + 1位读写位(0表示写,1表示读)。
举个例子:如果从设备地址是0x3C(二进制 011 1100),主机要写数据,就发送0x78(0111 1000)。要读数据,就发送0x79(0111 1001)。
我建议:在代码里直接用左移1位再加读写标志的方式,别手动算。比如 addr = (0x3C << 1) | 0 表示写,addr = (0x3C << 1) | 1 表示读。这样不容易出错。
10位地址模式
7位地址最多挂127个设备。如果不够用,I2C规范还定义了10位地址模式。不过说实话,我在实际项目中几乎没用过。大部分传感器、存储器、IO扩展芯片都用7位地址。
10位地址的发送分两步:
- 第一个字节:1111 0 + 地址高2位 + R/W
- 第二个字节:地址低8位
| 特性 | 7位地址 | 10位地址 |
|---|---|---|
| 地址范围 | 0x00 ~ 0x7F | 0x000 ~ 0x3FF |
| 最大设备数 | 127 | 1023 |
| 兼容性 | 所有设备都支持 | 部分设备支持 |
| 传输开销 | 1字节地址 | 2字节地址 |
| 实际使用 | 绝大多数场景 | 极少使用 |
注意:10位地址模式下,从设备地址不能与7位地址的保留地址(0x00 ~ 0x07、0x78 ~ 0x7F)冲突。否则总线仲裁会出问题。我建议初学者先别碰10位地址,把7位地址玩熟了再说。
小结
I2C物理层和时序是后续所有调试工作的基础。你只要记住:开漏输出、上拉电阻、起始停止条件、SCL高电平时数据稳定,这四个点吃透了,I2C通信就成功了一半。