1. I2C总线基础:从历史到实战

各位工程师朋友,咱们今天聊聊I2C总线。说实话,这玩意儿我用了快二十年了,从最早的8051单片机到现在的ARM Cortex-M7,几乎每个项目都离不开它。你想想看,两根线就能挂一堆设备,多省事啊。

1.1 I2C的历史与标准演进

I2C是Philips(现在的NXP)在1982年搞出来的。初衷很简单——减少PCB上的走线。那时候的电视、音响里,芯片之间连来连去,线多得跟蜘蛛网似的。

我最早接触I2C是在2005年,当时用AT24C02存配置参数。那会儿的标准还是100kHz的标准模式(Standard Mode)。后来慢慢有了400kHz的快速模式(Fast Mode),再到1MHz的快速模式+(Fast Mode Plus)。

嗯,这里有个坑要提醒大家:不是所有I2C设备都支持高速。我曾经在一个项目里,把一颗只支持100kHz的传感器硬挂在400kHz总线上,结果数据读出来全是乱的。排查了整整两天才找到原因。

模式 最大速率 推出年份
标准模式(Sm) 100 kHz 1982
快速模式(Fm) 400 kHz 1992
快速模式+(Fm+) 1 MHz 2007
高速模式(Hs) 3.4 MHz 1998
超快速模式(UFm) 5 MHz 2007
个人建议:除非你特别清楚自己在做什么,否则别轻易用高速模式。3.4MHz下,PCB走线稍微长一点,信号反射就能让你怀疑人生。

1.2 物理层特性:开漏输出与上拉电阻

I2C的物理层,说白了就是两根线:SDA(数据线)和SCL(时钟线)。这两根线都是开漏输出结构。

什么叫开漏?就是芯片只能把线拉低到GND,不能主动拉高到VDD。要拉高怎么办?靠外部上拉电阻。这就是为什么I2C总线上必须要有上拉电阻。

上拉电阻的取值很关键。我见过不少新手,随便焊个10kΩ上去就完事了。结果总线频率一高,波形就变成正弦波了。

上拉电阻计算公式(经验版):

R_min = (VDD - VOL_max) / IOL_max

R_max = tr / (0.8473 × C_bus)

其中:tr是上升时间,C_bus是总线电容

实际项目中,我一般这样选:

  • 100kHz:4.7kΩ ~ 10kΩ
  • 400kHz:2.2kΩ ~ 4.7kΩ
  • 1MHz以上:1kΩ ~ 2.2kΩ
避坑指南:我曾经在一个项目里,把上拉电阻从4.7kΩ换成了1kΩ,结果功耗从0.5mA飙到了2.3mA。电池供电的设备,这点电流差别可能就是几天的续航差距。所以别一味追求小电阻。

1.3 协议层概述:时序与握手

I2C的协议层,其实就几个关键动作。搞懂了它们,波形分析就成功了一半。

起始条件(Start Condition)

SCL为高电平时,SDA从高电平跳变到低电平。就这么简单。我习惯叫它「S信号」。

// 起始条件的C语言实现(伪代码)
void i2c_start(void) {
    SDA_HIGH();
    SCL_HIGH();
    delay_us(5);  // 保持时间
    SDA_LOW();    // SCL高时,SDA拉低
    delay_us(5);  // 建立时间
    SCL_LOW();    // 准备发送数据
}

停止条件(Stop Condition)

SCL为高电平时,SDA从低电平跳变到高电平。这是「P信号」。

void i2c_stop(void) {
    SDA_LOW();
    SCL_HIGH();
    delay_us(5);
    SDA_HIGH();   // SCL高时,SDA拉高
    delay_us(5);
}

数据有效性

这个规则很简单:SCL高电平时,SDA上的数据必须稳定。数据变化只能在SCL低电平时发生。

为什么?因为接收方是在SCL的上升沿采样SDA。如果SDA在SCL高时乱跳,采到的数据就是错的。

实战经验:我调试过一块板子,SCL上有个毛刺,导致数据采样错位。用示波器一看,原来是SCL走线太长,信号反射造成的。加了个33pF的小电容到地,问题就解决了。

应答机制(ACK/NACK)

每发送完一个字节(8位数据),发送方会释放SDA,接收方在第9个时钟周期拉低SDA表示应答(ACK),或者保持高电平表示非应答(NACK)。

这里有个细节:主机发送完地址后,从机必须应答。如果从机没应答,说明地址不对、设备没上电、或者总线被锁死了。

我曾经遇到过最奇葩的情况:从机偶尔不应答,但重新上电就好了。查了三天,发现是电源纹波太大,从机内部复位了。加了个100μF的电解电容搞定。

场景 应答行为 含义
主机发送地址 从机拉低SDA 设备在线,准备通信
主机发送数据 从机拉低SDA 数据接收成功
主机读取数据 主机拉低SDA 继续读取下一字节
主机读取数据 主机保持高电平 读取结束,发送停止条件

好了,I2C的基础就这些。说白了就是两根线、一个起始、一个停止、8位数据加1位应答。但就是这简单的协议,在实际项目中能玩出各种花样来。

下一章咱们聊聊波形分析,我会拿几个真实案例出来,看看那些「看起来正常但就是不通」的波形到底是怎么回事。