1. I2C协议基础:物理层、协议层、时序图解析、起始条件与停止条件
各位同学,咱们今天正式开讲I2C。说实话,I2C这协议看起来简单,但坑不少。我最早接触I2C是在一个温湿度传感器项目上,当时觉得不就是两根线嘛,结果调试了整整两天才把数据读出来——后来发现是起始条件没给对。嗯,咱们今天就把这些细节掰开揉碎了讲清楚。
1.1 物理层:就两根线,但别小看它们
I2C总线物理上只需要两根线:SCL(时钟线)和SDA(数据线)。这两根线都是开漏输出,外部接上拉电阻。为什么用开漏?说白了,就是为了实现多设备共用总线——任何设备都可以把线拉低,但没人能主动拉高。
核心要点:开漏结构 + 上拉电阻 = 线与逻辑。谁拉低谁说了算,这就是I2C仲裁的基础。
我在项目中遇到过一个问题:上拉电阻选大了,总线上升沿太慢,高速模式下通信失败;选小了,功耗又高。一般4.7kΩ是万金油,100kHz标准模式没问题。400kHz快速模式建议用2.2kΩ左右。
| 参数 | 标准模式 (100kHz) | 快速模式 (400kHz) | 高速模式 (3.4MHz) |
|---|---|---|---|
| 上拉电阻推荐值 | 4.7kΩ ~ 10kΩ | 1.5kΩ ~ 4.7kΩ | 0.5kΩ ~ 1.5kΩ |
| 总线电容上限 | 400pF | 400pF | 100pF |
| 最大从设备数量 | 理论127个 | 理论127个 | 受限于电容 |
我的习惯:做原型时先用4.7kΩ,调试稳定后再根据实际总线长度和设备数量微调。别一上来就追求高速,先把数据读对再说。
1.2 协议层:地址、读写、应答,三板斧
I2C的协议层其实就三个核心概念:设备地址、读写方向、应答机制。你想想看,总线上挂了一堆设备,主设备怎么找到目标?靠的就是7位或10位地址。
一个典型的I2C通信帧长这样:
起始条件 | 7位地址 | R/W位 | 应答位 | 数据字节 | 应答位 | ... | 停止条件
这里有个细节:地址是7位,但发送时是8位——高7位是地址,最低位是读写标志。0表示写,1表示读。所以0x50这个地址,发送时其实是0xA0(写)或0xA1(读)。
我曾经踩过的坑:某款加速度传感器的数据手册上写的地址是0x1D,但那是7位地址。我直接拿0x1D去发,结果设备死活不应答。后来才发现,发送时要左移一位,变成0x3A。记住:数据手册上的地址通常是7位,发送时要左移1位加上R/W位。
1.3 时序图解析:别怕,其实就几个关键点
很多同学看到时序图就头疼。我教你一个方法:只看电平变化和时钟边沿。I2C的时序说白了就是SCL和SDA在不同时间点的组合状态。
咱们用一张SVG图把核心时序画出来:
1.4 起始条件与停止条件:通信的开关
这两个条件是I2C通信的"开门"和"关门"动作。我刚开始学的时候总觉得这俩条件很玄乎,其实记住一句话就行:SCL高电平时,看SDA的跳变方向。
- 起始条件(S):SCL为高时,SDA从高电平切换到低电平
- 停止条件(P):SCL为高时,SDA从低电平切换到高电平
为什么要在SCL高电平时判断?因为数据采样也是在SCL高电平时进行的。起始和停止条件本质上是一种"特殊的数据位",它们违反了正常数据在SCL低电平时变化的规则,所以能被总线上的所有设备识别出来。
实战要点:模拟I2C时,起始条件和停止条件的时序必须严格满足。我曾经在一个项目中,起始条件后SDA保持低电平的时间不够,导致从设备误判了地址位。后来加了2μs的延时才稳定。
1.5 数据有效性:SCL高电平时别乱动
I2C的数据有效性规则很简单:SCL高电平时,SDA上的数据必须稳定;SCL低电平时,SDA可以变化。这意味着,你要在SCL低电平时准备好下一个数据位,然后在SCL高电平时让从设备去读。
用代码模拟一下这个过程:
// 模拟I2C发送一个字节
void i2c_send_byte(uint8_t data) {
for (int i = 0; i < 8; i++) {
// SCL低电平,设置SDA
SCL_LOW();
if (data & 0x80)
SDA_HIGH();
else
SDA_LOW();
delay_us(1);
// SCL高电平,从设备采样
SCL_HIGH();
delay_us(1);
data <<= 1; // 准备下一位
}
// 释放SDA,等待应答
SCL_LOW();
SDA_INPUT();
SCL_HIGH();
// 读取ACK位...
}
我的经验:模拟I2C时,延时别用死等,最好用硬件定时器或者nop指令计数。不同MCU主频不同,同样的延时函数效果可能差很多。我习惯先写一个基准延时函数,然后用逻辑分析仪实测校准。
1.6 本章小结
I2C协议基础就这些内容。说白了,物理层就是两根开漏线加上拉电阻;协议层就是地址+读写+应答三板斧;时序上抓住起始条件、停止条件和数据有效性三个关键点。你把这些搞清楚了,后面模拟I2C实现传感器驱动就是水到渠成的事。