I2C协议基础:从物理层到应答机制

各位同学,今天我们正式进入I2C协议的核心地带。说实话,I2C这玩意儿看起来简单,但坑不少。我当年第一次调I2C时,以为不就是两根线嘛,结果折腾了一整天——后来才发现是起始条件没搞对。好,咱们从头捋一遍。

1. I2C总线物理层

I2C总线只用两根线:SDA(串行数据线)和SCL(串行时钟线)。所有设备都挂在这两根线上,主从设备共用。

关键特性:

  • SDA和SCL都是开漏输出,必须外接上拉电阻
  • 空闲时,两根线都被上拉至高电平
  • 支持多主机,但同一时刻只能有一个主机控制总线

上拉电阻怎么选?我一般用4.7kΩ,标准模式100kHz没问题。如果总线电容大,比如挂了十几个设备,我会降到2.2kΩ。你想想看,电阻太小会拉低功耗,太大又会让上升沿变缓——这是个平衡。

模式 最大频率 典型上拉电阻 最大总线电容
标准模式 100 kHz 4.7 kΩ 400 pF
快速模式 400 kHz 2.2 kΩ 200 pF
快速模式+ 1 MHz 1.0 kΩ 100 pF

注意:开漏结构意味着任何设备都可以将SDA或SCL拉低。如果某个设备故障一直拉低总线,整个I2C通信就瘫痪了。我曾经在产线上遇到过——一个从机芯片虚焊,SDA被拉死,排查了半小时才发现。

2. 起始条件(Start Condition)

起始条件是I2C通信的“发令枪”。它的时序很简单:SCL为高电平时,SDA从高电平跳变到低电平。

为什么这么设计?因为空闲时两根线都是高,SDA的下降沿就是一个明确的“我要开始通信了”的信号。所有从机检测到这个边沿后,就会准备接收地址。

// 起始条件的C语言实现(软件模拟I2C)
void i2c_start(void) {
    SDA_HIGH();          // 确保SDA为高
    SCL_HIGH();          // SCL先拉高
    delay_us(5);         // 保持一段时间
    SDA_LOW();           // SDA拉低,产生起始条件
    delay_us(5);         // 保持建立时间
    SCL_LOW();           // 准备开始传输数据
}

个人经验:起始条件之后,SCL必须先拉低,否则后续的数据位会乱。我见过有人把SCL拉高后直接开始发数据,结果从机根本不认。

3. 停止条件(Stop Condition)

停止条件正好相反:SCL为高电平时,SDA从低电平跳变到高电平。从机检测到这个上升沿后,就知道通信结束了。

void i2c_stop(void) {
    SDA_LOW();           // 确保SDA为低
    SCL_HIGH();          // SCL拉高
    delay_us(5);         // 保持
    SDA_HIGH();          // SDA拉高,产生停止条件
    delay_us(5);         // 保持总线释放时间
}

这里有个细节:停止条件之后,总线进入空闲状态,SDA和SCL都保持高电平。如果连续发送多个数据包,每个包之间必须有一个停止条件,否则从机会认为数据还在继续。

4. 数据有效性

I2C的数据传输规则很简单:SCL为高电平时,SDA上的数据必须稳定;SCL为低电平时,SDA可以变化。

说白了,从机只在SCL的上升沿采样SDA。所以你在SCL低电平时随便折腾SDA都没事,但一到高电平,SDA必须稳住。

数据有效性三原则:

  1. 数据在SCL高电平时被采样
  2. 数据在SCL低电平时可以改变
  3. 每个SCL时钟周期传输1位数据

我记得有一次调试,示波器上看SDA波形很漂亮,但就是通信失败。后来发现是SCL的上升沿太缓,导致从机采样点偏移了。解决办法是减小上拉电阻,或者降低时钟频率。

5. 应答机制(ACK/NACK)

应答是I2C协议里最巧妙的设计之一。每发送完一个字节(8位),接收方必须在第9个SCL周期拉低SDA,表示“我收到了”。

  • ACK(应答):接收方在第9个时钟拉低SDA
  • NACK(非应答):接收方在第9个时钟保持SDA高电平

什么时候会出现NACK?我总结了几种常见情况:

场景 原因 处理方式
从机地址不匹配 总线上没有该地址的设备 主机发送停止条件,重试
从机忙 从机正在处理内部操作 主机等待后重试
从机不支持该操作 例如只写设备收到读命令 主机发送停止条件
数据接收完毕 从机不想再收数据 主机发送停止条件

避坑指南:我曾经在读取传感器数据时,从机连续返回NACK,我以为是硬件坏了。后来仔细看手册才发现,那个传感器在每次读取后需要一段处理时间,期间不响应。解决方案是在读取之间加一个10ms的延时。

6. 完整数据传输流程

把上面这些拼起来,一次完整的I2C数据传输就是:

  1. 主机发送起始条件
  2. 主机发送7位从机地址 + 1位读写位(共8位)
  3. 从机发送ACK(第9位)
  4. 主机或从机发送数据字节(8位),每字节后跟ACK
  5. 主机发送停止条件
// 完整的数据发送示例
uint8_t i2c_write_byte(uint8_t data) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        if (data & 0x80) {
            SDA_HIGH();
        } else {
            SDA_LOW();
        }
        data <<= 1;
        SCL_HIGH();
        delay_us(5);
        SCL_LOW();
        delay_us(5);
    }
    // 释放SDA,等待从机应答
    SDA_HIGH();
    SCL_HIGH();
    delay_us(5);
    uint8_t ack = SDA_READ();  // 读取应答位
    SCL_LOW();
    return ack;  // 0=ACK, 1=NACK
}

7. 知识结构总览

下面这张图把I2C协议基础的核心逻辑串起来了,从物理层到应答机制,一目了然。

I2C协议基础核心结构 物理层 起始条件 停止条件 数据有效性 应答机制 完整传输流程 SDA + SCL 开漏输出 外接上拉电阻 多主机仲裁 SCL高 → SDA下降沿 所有从机开始监听地址 SCL高 → SDA上升沿 总线释放,进入空闲 SCL高时SDA必须稳定 SCL低时SDA可变化 第9个SCL周期:ACK/NACK ACK=拉低,NACK=保持高

嗯,以上就是I2C协议基础的全部内容。物理层决定了你怎么接线,起始和停止条件决定了通信的边界,数据有效性决定了你怎么采样,应答机制决定了通信的可靠性。把这五个点吃透,I2C通信的基本功就算打牢了。