2. I2C协议时序:起始条件、停止条件、数据有效性、字节格式与应答机制

说实话,搞I2C这么多年,我见过太多工程师上来就调代码,结果波形一塌糊涂。时序这东西,看着简单,但坑特别多。咱们今天就把I2C最核心的五个时序要素掰开揉碎了讲清楚。

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

I2C总线的起始和停止,说白了就是SCL和SDA两根线的电平跳变游戏。我习惯用一句话记:SCL高电平时,SDA跳变才算数。

条件 SCL状态 SDA跳变方向 谁发起
起始条件(S) 高电平 高 → 低 主机
停止条件(P) 高电平 低 → 高 主机
重复起始(Sr) 高电平 高 → 低 主机(不发送P)
我的习惯:写驱动时,我会把起始和停止封装成两个独立函数。这样调试的时候,逻辑分析仪上一看S和P的位置,就知道主机在干什么。

这里有个细节——重复起始条件(Sr)。我在项目中遇到过有人用停止+起始来实现多字节读写,结果总线效率直接砍半。其实你想想看,Sr就是在不释放总线的情况下再发一次S,省掉中间的P时间。这在EEPROM随机读、传感器多寄存器访问时特别有用。

2.2 数据有效性

I2C的数据采样规则其实很反直觉:SCL低电平时SDA可以变,SCL高电平时SDA必须稳。

为什么会这样?因为从机是在SCL的上升沿采样SDA。如果你在SCL高电平期间动了SDA,从机可能采到错误电平,甚至误判成起始或停止条件。

我曾经踩过的坑:有一次调试一颗加速度计,读出来的数据总是跳变。用示波器一看,SDA在SCL高电平期间有毛刺。后来发现是GPIO驱动能力不够,加上拉电阻从4.7kΩ换成2.2kΩ就解决了。嗯,硬件问题有时候比软件更隐蔽。

数据有效性的核心约束就两条:

  • SCL低电平期间:SDA允许变化,主机可以准备下一位数据
  • SCL高电平期间:SDA必须保持稳定,从机在此窗口采样

2.3 字节格式与传输顺序

I2C的字节传输格式非常固定:8位数据 + 1位应答。每次传输9个SCL时钟周期。

字节内的位顺序是MSB first(最高位在前)。我记得刚入行时写了个驱动,把LSB first的SPI习惯带过来了,结果地址对不上,折腾了一下午才发现是位序搞反了。

// 一个典型的I2C字节发送流程(伪代码)
void i2c_send_byte(uint8_t data) {
    for (int i = 7; i >= 0; i--) {  // MSB first
        SDA = (data >> i) & 0x01;
        SCL = 1;  // 从机采样
        delay_half_period();
        SCL = 0;  // 允许SDA变化
        delay_half_period();
    }
    // 第9个时钟:释放SDA,等待应答
    SDA = 1;  // 释放总线
    SCL = 1;
    ack = SDA;  // 读取应答位
    SCL = 0;
}
关键点:地址字节的7位地址左移1位,最低位是R/W#。比如地址0x48写操作就是0x90(0x48 << 1 | 0),读操作是0x91。

2.4 应答机制(ACK/NACK)

应答是I2C协议里最优雅的设计之一。每发完一个字节,接收方要在第9个时钟周期拉低SDA表示ACK,保持高电平表示NACK。

我总结了几种常见场景:

  • 主机发送地址后:从机ACK表示存在,NACK表示无应答(设备不在线或地址错误)
  • 主机发送数据后:从机ACK表示接收成功,NACK表示无法处理(比如缓冲区满)
  • 主机接收数据后:主机ACK表示还要继续读,NACK表示够了,发停止
调试技巧:我习惯在驱动里把ACK/NACK状态打印出来。如果连续出现NACK,先别急着改代码——用示波器抓一下波形,看看SCL频率是不是超了从机规格。很多从机在400kHz下会丢应答。

2.5 完整时序图

下面这张图把上面讲的五个要素串起来了。我画图时习惯把SCL和SDA分开画,这样每个时钟周期对应什么数据一目了然。

I2C完整时序图(单字节写操作) SCL SDA S P 8位数据(MSB first) ACK 1 2 3 4 5 6 7 8 9 SCL高电平期间SDA必须稳定,从机在SCL上升沿采样

2.6 时序参数速查

不同速率模式下,时序参数差异很大。我整理了一张常用对照表,调试时直接查:

参数 标准模式(100kHz) 快速模式(400kHz) 快速+模式(1MHz)
SCL低电平时间 ≥4.7μs ≥1.3μs ≥0.5μs
SCL高电平时间 ≥4.0μs ≥0.6μs ≥0.26μs
起始保持时间 ≥4.0μs ≥0.6μs ≥0.26μs
停止建立时间 ≥4.0μs ≥0.6μs ≥0.26μs
数据保持时间 ≥0ns ≥0ns ≥0ns
上升/下降时间 ≤1000ns ≤300ns ≤120ns
注意:数据保持时间虽然是≥0ns,但实际项目中我建议留50ns以上的余量。有些从机芯片的采样窗口比较窄,保持时间太短会导致数据建立不充分。

2.7 常见时序问题排查

最后分享几个我实际项目中遇到的时序问题,你调试时可以参考:

  • 起始条件不满足:SDA先于SCL拉低,但时间太短。检查代码中SDA和SCL的GPIO操作顺序
  • 应答位丢失:从机没拉低SDA。先确认从机供电和地址,再用示波器看第9个时钟的SDA电平
  • 数据错位:字节内位序搞反。打印原始数据,对比MSB/LSB顺序
  • 总线卡死:从机一直拉低SDA。发9个SCL时钟强制复位从机状态机

嗯,时序这部分就这些。记住一句话:SCL高电平看跳变(S/P),SCL低电平看数据。把这个原则刻在脑子里,写驱动时基本不会出大问题。