1. I2C协议基础:从物理层到地址格式
I2C协议,说白了就是两根线搞定一堆设备通信。我第一次接触I2C时觉得这玩意儿太简单了,结果调试时被时序坑得死去活来。嗯,咱们从最基础的东西说起。
1.1 I2C总线物理层特性
I2C总线只需要两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上,主从设备共用总线。
| 参数 | 标准模式 | 快速模式 | 高速模式 |
|---|---|---|---|
| 最大时钟频率 | 100 kHz | 400 kHz | 3.4 MHz |
| 最大总线电容 | 400 pF | 400 pF | 100 pF |
| 上拉电阻典型值 | 4.7 kΩ | 2.2 kΩ | 1.0 kΩ |
| 最大从设备数量 | 127(7位地址) | 127 | 127 |
关键点:I2C总线是开漏输出,必须加上拉电阻。上拉电阻选大了信号上升沿太慢,选小了功耗太大。我个人习惯100kHz用4.7kΩ,400kHz用2.2kΩ。
总线电容会影响信号质量。线越长、设备越多,电容越大。我曾经在一个项目里挂了8个传感器,总线长度超过30cm,结果400kHz死活跑不稳,降到100kHz才正常。
1.2 SCL与SDA时序
I2C的时序其实不复杂,但细节决定成败。咱们先看几个基本概念。
- SCL:时钟线,由主设备控制。高电平期间采样数据,低电平期间数据可以变化。
- SDA:数据线,双向传输。SCL高电平时SDA必须稳定,SCL低电平时SDA才能变化。
- 数据有效性:SCL高电平时SDA上的数据被采样,每个时钟周期传输1位。
你想想看,如果SCL高电平时SDA变了,那就不叫数据了,那是起始或停止条件。这是I2C协议最核心的规则。
调试技巧:用逻辑分析仪抓I2C波形时,先看SCL高电平时SDA有没有跳变。如果有,要么是起始/停止条件,要么就是时序出错了。我调试时第一件事就是看这个。
1.3 起始条件与停止条件
起始条件和停止条件是I2C通信的"开关"。没有它们,总线上的设备不知道什么时候开始、什么时候结束。
起始条件(START):SCL高电平时,SDA从高电平跳变到低电平。
停止条件(STOP):SCL高电平时,SDA从低电平跳变到高电平。
注意:起始条件和停止条件只能由主设备产生。从设备不能主动发起或终止通信。我曾经见过新手工程师让从设备发停止条件,结果总线直接挂死。
重复起始条件(Repeated START)是个好东西。它允许主设备在不释放总线的情况下切换通信方向。比如读一个寄存器,先写寄存器地址,再发重复起始条件,然后读数据。这样效率高,而且不会让其他设备抢到总线。
1.4 7位地址与10位地址格式
I2C地址是用来区分总线上不同设备的。7位地址最多支持127个设备,10位地址最多支持1023个设备。不过实际项目中7位地址基本够用。
7位地址格式:
起始条件 | 7位地址 | 读/写位 | 应答位 | 数据... | 停止条件
地址字节的高7位是设备地址,最低位是读写方向:0表示写,1表示读。所以发送地址时,实际发送的是 (地址 << 1) | R/W。
| 地址类型 | 地址范围 | 保留地址 | 常用场景 |
|---|---|---|---|
| 7位地址 | 0x00 - 0x7F | 0x00 - 0x07 | 传感器、EEPROM、ADC |
| 10位地址 | 0x000 - 0x3FF | 0x000 - 0x00F | 复杂系统、多设备场景 |
10位地址格式:
起始条件 | 11110_AA9_AA8 | R/W | 应答 | A7...A0 | 应答 | 数据... | 停止条件
10位地址分两字节发送。第一字节高5位固定为11110,接着是地址的高2位,然后是读写位。第二字节是地址的低8位。
避坑指南:我曾经遇到一个传感器,数据手册说地址是0x68,结果怎么都通信不上。后来发现手册写的是7位地址0x68,但发送时要左移一位变成0xD0(写)或0xD1(读)。很多新手都会在这里栽跟头。
1.5 I2C通信流程全景图
下面这张图展示了I2C一次完整通信的流程。从起始条件开始,到停止条件结束,中间包含地址发送、数据收发和应答机制。
1.6 应答机制
I2C的应答机制是保证通信可靠性的关键。每发送完一个字节,接收方必须回复一个应答位。
- ACK(应答):接收方在第9个时钟周期将SDA拉低,表示"收到,继续"。
- NACK(非应答):接收方在第9个时钟周期保持SDA高电平,表示"收完了"或"出错了"。
实用技巧:读数据时,主设备在最后一个字节回复NACK,告诉从设备"别再发了"。这是标准做法。我见过有人一直发ACK,结果从设备不停发数据,总线被撑爆。
1.7 常见问题与避坑
做I2C驱动这么多年,踩过的坑不少。分享几个最常见的:
- 地址不对:数据手册上的地址是7位还是8位?一定要搞清楚。7位地址0x68发送时要变成0xD0(写)。
- 上拉电阻不合适:总线电容大时用太小电阻,信号上升沿太慢;电阻太大时信号拉不下来。用示波器看波形最靠谱。
- 时钟延展:从设备忙时会拉低SCL,让主设备等一等。很多MCU的I2C外设不支持时钟延展,得用GPIO模拟。
- 总线冲突:两个主设备同时发数据,总线会乱。多主场景要做好仲裁。
严重警告:千万不要在I2C通信过程中拔插设备!热插拔会导致总线电平异常,轻则通信失败,重则烧毁IO口。我有个同事就是这么烧了一块开发板。