I2C协议时序:从起始到应答,手把手拆解

I2C协议,说白了就是两根线(SCL时钟线、SDA数据线)搞定一切通信。很多初学者觉得它简单,但实际调试时,时序上出问题的情况比比皆是。我个人习惯把I2C时序拆成五个核心动作来理解:起始、停止、数据有效性、字节格式、应答机制。一个一个来。

2.1 起始条件与停止条件

这两个条件是I2C通信的“开关”。总线空闲时,SCL和SDA都是高电平。谁先拉低SDA,谁就获得了总线的控制权。

起始条件(START): SCL为高电平时,SDA从高电平切换到低电平。

停止条件(STOP): SCL为高电平时,SDA从低电平切换到高电平。

我在项目中遇到过一个问题:从机偶尔不响应。排查了半天,发现是主机发送的停止条件不完整——SDA上升沿太慢,从机没识别到。嗯,这里要注意:停止条件的建立时间(tSU:STO)必须满足芯片手册要求,一般不低于4μs(100kHz模式)。

条件 SCL状态 SDA变化 典型时序要求
起始条件 高电平 高→低 tHD:STA ≥ 4μs
停止条件 高电平 低→高 tSU:STO ≥ 4μs

调试小技巧: 用逻辑分析仪抓起始和停止条件时,注意看SDA的跳变是否发生在SCL高电平期间。如果跳变发生在SCL低电平期间,那其实是数据位,不是起始/停止条件。

2.2 数据有效性:时钟高电平时采样

I2C的数据传输有个铁律:数据在SCL高电平时必须稳定。SDA的电平变化只允许在SCL低电平时发生。

为什么会这样?因为从机是在SCL的上升沿或高电平期间采样SDA的。如果你在SCL高电平时改变了SDA,从机采到的数据就是错的。我曾经调试一个温湿度传感器,读出来的数据总是跳变,后来发现是MCU的I2C引脚驱动能力不足,SDA在SCL高电平时有毛刺——从机采到了错误的位。

避坑指南: 我曾经在STM32F103上遇到过,GPIO配置成开漏输出时,外部上拉电阻选太大(10kΩ),导致SDA上升沿太慢。SCL高电平期间SDA还没稳定,从机就采错了。建议100kHz用4.7kΩ,400kHz用2.2kΩ。

2.3 字节传输格式:8位数据 + 1位应答

I2C传输的基本单位是字节。每个字节8位,高位在前(MSB first)。发送完8位数据后,第9个时钟周期用于应答。

你想想看,如果从机在第9个时钟周期没有拉低SDA,主机就知道出问题了。我习惯把I2C的字节传输想象成“8+1”的结构:8位数据,1位应答位。

// 伪代码:I2C发送一个字节
void I2C_SendByte(uint8_t data) {
    for (int i = 0; i < 8; i++) {
        // 数据位:高位先发
        if (data & 0x80) {
            SDA_HIGH();
        } else {
            SDA_LOW();
        }
        data <<= 1;  // 左移,准备下一位
        SCL_HIGH();   // 时钟高电平,从机采样
        delay_us(5);  // 保持高电平时间
        SCL_LOW();    // 时钟低电平,允许SDA变化
        delay_us(5);
    }
    // 第9个时钟:释放SDA,等待应答
    SDA_INPUT();     // 释放SDA,由从机控制
    SCL_HIGH();
    // 读取SDA电平,判断ACK/NACK
    if (SDA_READ() == 0) {
        // ACK:从机拉低了SDA
    } else {
        // NACK:从机没响应
    }
    SCL_LOW();
    SDA_OUTPUT();    // 重新控制SDA
}

2.4 应答机制:ACK与NACK

应答位是I2C通信的“握手信号”。主机发送完一个字节后,释放SDA,从机在第9个时钟周期拉低SDA表示ACK,保持高电平表示NACK。

不同的场景下,ACK/NACK的含义不同:

  • 主机发送地址后: ACK表示从机存在且准备好通信;NACK表示从机不存在或忙。
  • 主机发送数据后: ACK表示从机已接收;NACK表示从机无法处理(比如缓冲区满)。
  • 主机接收数据后: 主机发送ACK表示“我还要继续读”;主机发送NACK表示“够了,停止传输”。

关键点: 主机作为接收者时,最后一个字节必须发送NACK,然后跟停止条件。否则从机会以为你还要继续读,一直发数据。

我记得有一次调试一个OLED屏幕,读状态寄存器时一直返回0xFF。后来发现是主机在最后一个字节后发了ACK而不是NACK——从机以为还要继续,就把数据线拉低了,导致总线冲突。

2.5 完整时序图:把五个动作串起来

下面这张图把起始条件、字节传输、应答、停止条件串在一起。你可以看到,整个I2C通信就是“起始→地址+读写位→应答→数据+应答→...→停止”的循环。

I2C完整时序图 I2C完整通信时序(主机发送一个字节) 起始 D7 D6 D5 D4 D3 D2 D1 ACK 停止 SCL高电平时SDA必须稳定(数据采样),SCL低电平时SDA允许变化(数据切换) SCL SDA

2.6 常见时序故障与排查方法

调试I2C时,逻辑分析仪是你的好朋友。我总结了几个最常见的时序问题:

故障现象 可能原因 排查方法
从机无应答(NACK) 地址错误、从机未上电、总线被拉死 用逻辑分析仪抓地址字节,核对7位地址和读写位
数据错位 SCL高电平时SDA变化、时钟频率不匹配 检查SCL高电平期间SDA是否稳定,降低时钟频率测试
总线一直为低 从机死锁、主机未释放SDA 发送9个时钟脉冲让从机复位,检查GPIO配置
停止条件不识别 SDA上升沿太慢、时序不满足tSU:STO 减小上拉电阻,增加停止条件前的延时

我的调试习惯: 遇到I2C问题,先抓起始条件和从机地址的应答。如果地址阶段就NACK,别急着查数据——先确认从机活着没、地址对不对。地址对了还NACK,再查时钟频率和上拉电阻。

嗯,I2C时序说白了就是“什么时候该做什么事”。起始条件告诉从机“我要开始通信了”,停止条件说“结束了”,数据有效性保证从机不会采错,字节格式规定了数据怎么排列,应答机制让双方知道对方是否收到。把这五个点吃透,I2C调试就成功了一半。