1. I2C协议基础:总线结构、时序、寻址归纳总结

做嵌入式这些年,跟I2C打交道是最多的。老实说,它看起来简单,但坑是真不少。锁死故障就是最常见的一种——总线莫名其妙拉低,SCL还正常,SDA就是抬不起来。要排查这类问题,第一步得把协议底子扎牢。这章我们就聊聊I2C的总线结构、时序和寻址,把基础打结实。

1.1 总线结构:两根线,多个设备

I2C只有两条线:SCL(时钟线)和SDA(数据线)。所有设备挂在这两条线上,靠地址区分谁跟谁说话。主设备发起通信,从设备响应。标准模式下最高100kHz,快速模式400kHz,高速模式能到3.4MHz。

我个人习惯用上拉电阻把两根线拉到VDD。阻值常见4.7kΩ,但总线电容大了就得换小一点。我记得有一次项目里接了10个从设备,总线长度一米多,4.7kΩ死活拉不起来——换成2.2kΩ就好了。

参数 标准模式 快速模式 高速模式
最高时钟频率 100 kHz 400 kHz 3.4 MHz
上拉电阻典型值 4.7 kΩ 2.2 kΩ 1 kΩ
最大总线电容 400 pF 400 pF 100 pF

关键点:两根线是开漏输出+上拉电阻。任何设备都能拉低总线,但没人能主动拉高。这个开漏结构既是I2C的优点(多机冲突检测),也是锁死故障的根源。

1.2 时序:起始、数据、应答、停止

I2C通信靠SCL的边沿对齐数据。时序上就那么几个动作:起始条件、数据位传输、应答位、停止条件。我们来一个个拆。

起始条件(START)

SCL高电平时,SDA从高跳低。主设备发出这个信号,告诉总线上所有设备:我要开始通信了。

数据位传输

每个数据字节8位,先传最高位(MSB)。SCL低电平时SDA可以变化,SCL高电平时采样SDA。这个我强调一下:SCL高电平期间SDA必须稳定。锁死故障很多时候就是SDA在SCL高时被拉死,主设备等不到它释放。

应答位(ACK/NACK)

第9个时钟周期,接收方把SDA拉低表示应答。如果不拉低(保持高),就是非应答(NACK)。主设备读数据时,每收到一个字节必须主动应答;如果不想读了,就发NACK然后发停止。

停止条件(STOP)

SCL高电平时,SDA从低跳高。一次传输结束。

// 伪代码示例:I2C 发送一个字节  
void i2c_send_byte(uint8_t data) {
    for (int i = 7; i >= 0; i--) {
        // SCL 低
        SCL_LOW();
        // 设置SDA (MSB first)
        if (data & (1 << i))
            SDA_HIGH();
        else
            SDA_LOW();
        // SCL 高,从设备采样
        SCL_HIGH();
        delay_half_period();
        SCL_LOW();
    }
    // 释放SDA,等待应答
    SDA_HIGH();
    SCL_HIGH();
    // 检查ACK
    if (SDA_READ() == 0) {
        // ACK received
    } else {
        // NACK: 处理错误
    }
    SCL_LOW();
}

我的小技巧:调试I2C锁死时,先用示波器看SCL和SDA的关系。如果SCL还在正常翻转,但SDA一直低,大概率是从设备把SDA锁死了。我曾经遇到过一颗温湿度传感器,上电后如果SCL线先拉高,它的内部状态机就卡死,必须重新上电才能释放。

1.3 寻址:7位和10位地址

主设备每次通信前先发一个地址字节。地址字节高7位是从设备地址,最低位是读/写位(0写,1读)。加上应答位,正好9位。

标准I2C用7位地址,理论上能挂128个设备,但保留地址占掉一些,实际可用100个左右。10位地址模式扩展了地址空间,实际项目里用得少。

地址模式 地址字节数 最大设备数 常见场景
7位地址 1字节(高7位 + R/W) 112 大多数传感器、EEPROM
10位地址 2字节(首字节11110xx + R/W,第二字节8位) 1024 需要大量从设备的复杂系统

举个例子:AT24C02 EEPROM的7位地址是0x50,写操作发送0xA0,读操作发送0xA1。这个地址是硬件引脚决定的,A0/A1/A2三个引脚接VCC或GND来配置。

1.4 整个通信流程

一次完整的写操作大概长这样:

  1. 主设备发送起始条件
  2. 主设备发送从设备地址 + 写位(0)
  3. 从设备应答
  4. 主设备发送寄存器地址
  5. 从设备应答
  6. 主设备发送数据字节,每字节后从设备应答
  7. 主设备发送停止条件

读操作类似,只是中间会多一个重复起始条件(Restart)和地址+读位。

注意锁死风险点:如果在步骤3或步骤5,从设备没有应答(NACK),主设备应该及时发停止条件释放总线。有些主设备会傻等,导致SDA被从设备拉死。我现在设计代码时,每个字节传输后都设一个超时,超过一定时间没有应答就强制发停止并复位I2C外设。

1.5 本章知识结构图

下面这张图把I2C协议三要素的关系串起来,方便你快速回顾。

I2C协议基础三要素 总线结构 • SCL(时钟线) • SDA(数据线) • 开漏输出+上拉 • 多主设备仲裁 时序规则 • 起始条件 • 数据位传输(MSB) • 应答位 (ACK/NACK) • 停止条件 寻址方式 • 7位地址(常用) • 10位地址 • 地址+R/W位 • 保留地址 锁死故障(SCL正常,SDA被拉低) 理解三要素才能定位问题 说明: 总线结构决定物理层行为(开漏、上拉) 时序规则决定通信步骤(起始、数据、应答、停止) 寻址决定设备选择(7位/10位,R/W位)

1.6 本章小结

基础不牢,地动山摇。I2C总线只有两根线,但锁死故障往往就出在时序细节或地址冲突上。记住三点:

  • 开漏结构——任何设备拉低后只能靠上拉恢复,从设备卡住就不放
  • 时序中的应答位——没等到ACK要果断收手,别死等
  • 地址必须唯一——两条总线不能挂两个相同地址的从设备

我个人每次画板前都会用表格列一遍所有I2C器件的地址、速率和引脚映射,提前排除冲突。这活儿不复杂,但能省掉后期一堆debug时间。