2. I2C协议时序:起始条件、停止条件、数据有效性、字节传输格式、应答机制(ACK/NACK)

I2C协议,说白了就是一套“谁说话、谁听话、什么时候说、什么时候停”的规矩。很多刚入行的工程师觉得I2C简单,不就是两根线嘛。但我在项目里见过太多因为时序踩坑的案例——从设备不响应到数据错位,根子往往就出在这些基础时序上。

咱们今天就把I2C协议的五个核心要素掰开揉碎讲清楚。你把这些搞明白了,调试时至少能少走一半弯路。

2.1 起始条件(S)与停止条件(P)

I2C总线的空闲状态是:SCL高电平,SDA高电平。所有操作都从起始条件开始,以停止条件结束。

条件 时序定义 谁产生
起始条件(S) SCL高电平时,SDA从高电平切换到低电平 主机(Master)
停止条件(P) SCL高电平时,SDA从低电平切换到高电平 主机(Master)
重复起始条件(Sr) 在未产生停止条件前,再次产生起始条件 主机(Master)

关键点:起始和停止条件都是唯一的。SDA的跳变必须发生在SCL高电平期间,否则会被误判为数据位。我曾经调试一个温湿度传感器,死活读不到数据,用逻辑分析仪一看——主机在SCL低电平时就拉低了SDA,从机根本不认这个“起始条件”。

重复起始条件(Sr)是个好东西。比如你要读一个寄存器,先写寄存器地址,再读数据,中间不需要释放总线。用Sr代替“停止→再起始”,效率更高,也避免其他设备趁机抢占总线。

我的习惯:在写驱动时,我会把起始条件和停止条件封装成两个独立的函数。这样代码清晰,调试时也方便在关键位置加打印或逻辑分析仪触发。

2.2 数据有效性

I2C的数据采样规则很简单,但很多人会搞反:

  • SCL低电平期间:SDA允许变化(数据准备阶段)
  • SCL高电平期间:SDA必须稳定(数据采样阶段)

为什么这样设计?你想想看,如果SCL高电平时SDA还在跳变,从机就不知道你传的到底是0还是1。这个规则保证了数据传输的确定性。

避坑指南:我曾经遇到一个国产加速度传感器,它的数据手册里时序图画错了——SCL高电平时SDA还在变化。我按照手册写驱动,死活读不对。后来用示波器实测才发现,手册的保持时间(tHD:DAT)标反了。所以,永远不要100%相信数据手册的时序图,用示波器或逻辑分析仪实测才是王道。

2.3 字节传输格式

I2C每次传输一个字节(8位),顺序是MSB first(高位在前)。

一个完整的字节传输流程:

  1. 主机在SCL低电平时逐位设置SDA(从高位到低位)
  2. 每设置一位,主机产生一个SCL脉冲(低→高→低)
  3. 从机在SCL高电平时采样SDA
  4. 重复8次,完成一个字节传输
  5. 第9个SCL脉冲用于应答位
// 伪代码:主机发送一个字节
void i2c_send_byte(uint8_t data) {
    for (int i = 7; i >= 0; i--) {  // MSB first
        if (data & (1 << i))
            SDA_HIGH();
        else
            SDA_LOW();
        SCL_HIGH();  // 从机采样
        delay_us(4); // 保持时间
        SCL_LOW();   // 准备下一位
        delay_us(4);
    }
    // 第9位:释放SDA,等待应答
    SDA_INPUT();
    SCL_HIGH();
    // 读取SDA电平判断ACK/NACK
    ack = SDA_READ();
    SCL_LOW();
}

注意:标准模式(100kHz)下,SCL高电平和低电平的保持时间通常需要4~5μs。快速模式(400kHz)下缩短到1μs左右。别把时序跑得太极限,留点余量——我一般按数据手册典型值的1.5倍来设延时。

2.4 应答机制(ACK/NACK)

这是I2C协议里最巧妙的设计。每传输完一个字节,接收方必须给出回应:

  • ACK(应答):接收方在第9个SCL脉冲期间将SDA拉低,表示“收到,请继续”
  • NACK(非应答):接收方在第9个SCL脉冲期间保持SDA高电平,表示“收完了/出错了/别发了”
场景 谁发ACK/NACK 含义
主机发送地址+方向位 从机 从机存在且准备好通信 → ACK;否则NACK
主机发送数据到从机 从机 从机成功接收 → ACK;接收失败或缓冲区满 → NACK
主机从从机读取数据 主机 主机还想继续读 → ACK;读完最后一个字节 → NACK

我的经验:调试I2C时,第一个要看的就是地址阶段有没有ACK。如果没有,90%的情况是地址写错了(比如7位地址忘了左移一位),或者从机地址线配置不对。我曾经花了一下午查一个气压计,最后发现是芯片的ADDR引脚电平没焊好。

主机读数据时的ACK/NACK逻辑很多人会搞混。记住一句话:主机想继续读,就发ACK;主机想结束读取,就发NACK。发完NACK后,主机再产生停止条件,从机就知道数据传输结束了。

2.5 完整传输序列示例

把上面这些串起来,一个典型的I2C写操作时序是这样的:

// 向从机地址0x48的寄存器0x05写入0xA3
[S] + [0x48<<1 | 0] + [ACK] + [0x05] + [ACK] + [0xA3] + [ACK] + [P]

// 分解:
// [S]          : 起始条件
// [0x48<<1 | 0]: 7位地址0x48左移1位,最低位0表示写
// [ACK]        : 从机回应
// [0x05]       : 寄存器地址
// [ACK]        : 从机回应
// [0xA3]       : 要写入的数据
// [ACK]        : 从机回应
// [P]          : 停止条件

读操作稍微复杂一点,需要先写后读:

// 从从机地址0x48的寄存器0x05读取一个字节
[S] + [0x48<<1 | 0] + [ACK] + [0x05] + [ACK] + 
[Sr] + [0x48<<1 | 1] + [ACK] + [data] + [NACK] + [P]

// 注意中间的[Sr]:重复起始条件,不释放总线
// 最后的[NACK]:主机告诉从机“我只读一个字节,够了”

常见坑:有些从机在写寄存器地址后,需要一小段“处理时间”才能响应读操作。如果你紧接着发Sr,从机可能还没准备好,返回的数据全是0xFF。我遇到过一颗触摸芯片就是这样,必须在写地址后加一个微秒级的延时。数据手册里不会写这个,全靠实测发现。

2.6 知识结构图

下面这张图把I2C协议时序的核心要素串起来了,建议你保存下来,调试时对照着看:

I2C协议时序核心要素 起始条件 (S) 停止条件 (P) 数据有效性规则 字节传输格式 (MSB First) 应答机制 (ACK/NACK) 完整传输序列 (写/读操作组合) 调试要点: • 起始/停止条件:SCL高电平时SDA跳变,否则无效 • 数据有效性:SCL高电平采样,低电平变化 • 应答检查:地址阶段无ACK → 查地址/硬件连接;数据阶段NACK → 查从机状态 • 读操作最后必须发NACK,否则从机会一直发数据

2.7 调试速查表

最后给你一张速查表,调试时对照着排查问题:

现象 可能原因 排查方法
起始条件后无任何响应 SDA被拉死、从机地址错误、总线电容过大 用示波器看SDA/SCL波形,检查地址位
地址阶段收到NACK 从机地址错误、从机未上电、地址线配置不对 核对数据手册地址,检查硬件连接
数据阶段收到NACK 寄存器地址不存在、从机忙、写保护 确认寄存器地址范围,检查从机状态寄存器
读到的数据全是0xFF 从机未准备好、时钟延展未处理、上拉电阻失效 加延时等待,检查SCL是否被从机拉低
总线死锁(SDA一直低) 从机未释放SDA、时钟延展超时、噪声干扰 发9个SCL脉冲复位从机,检查上拉电阻

最后说一句:I2C的时序看起来简单,但实际调试时坑不少。我的建议是——逻辑分析仪是你的好朋友。花几百块买个带I2C解码的,能帮你省下大把抓头发的时间。别问我怎么知道的。