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

I2C总线,说白了就是两根线(SCL时钟线 + SDA数据线)搞定多个设备通信。很多刚入行的工程师觉得它简单,不就是拉高拉低嘛。但我在项目中吃过不少亏——有一次板子调了三天,最后发现是起始条件没满足,从机根本不搭理主机。

这一节,咱们把I2C时序的五个核心要素掰开揉碎。你把这些搞懂了,以后调I2C设备基本不会翻车。

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

I2C总线的空闲状态是:SCL和SDA都保持高电平。什么时候开始传输?什么时候结束?全靠这两个特殊时序。

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

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

我个人的习惯是,写驱动时先画个时序草图。起始和停止条件,说白了就是SDA在SCL高电平期间跳变。其他时候SDA只能在SCL低电平时变化,这个后面会讲。

条件 SCL状态 SDA跳变方向 作用
起始条件(S) 高电平 高 → 低 通知所有从机:主机要开始通信了
停止条件(P) 高电平 低 → 高 通知所有从机:本次传输结束

我曾经踩过的坑:有一次用逻辑分析仪抓波形,发现起始条件之后SDA没拉低到位,只降了0.8V左右。从机是3.3V器件,死活不认这个起始条件。后来查出来是上拉电阻阻值太大(10kΩ配了长走线),换4.7kΩ就好了。起始条件的边沿斜率很重要,别忽略。

2.2 数据有效性

I2C的数据采样规则其实很简单:SDA上的数据,必须在SCL高电平期间保持稳定。只有在SCL低电平时,SDA才能变化。

为什么这样设计?你想想看,如果SDA在SCL高电平时乱跳,从机怎么知道这是数据还是起始/停止条件?所以协议规定:

  • SCL高电平期间:SDA必须稳定,从机在这个窗口采样数据
  • SCL低电平期间:SDA可以变化,主机准备好下一个bit

我记得有一次调试一个温湿度传感器,读出来的数据偶尔跳变。抓波形发现,主机在SCL上升沿之后没等够保持时间就改变了SDA,从机采到了错误值。后来在代码里加了几个nop延时,问题就解决了。

小技巧:标准模式(100kHz)下,数据建立时间(tSU;DAT)至少250ns,保持时间(tHD;DAT)至少0ns。快速模式(400kHz)要求更严。写软件I2C时,建议在SCL跳变后加1μs左右的延时,兼容性最好。

2.3 字节格式与传输顺序

I2C每次传输一个字节(8位),而且是从最高位(MSB)开始传。这个顺序和SPI不一样,SPI可以配置MSB或LSB优先,但I2C固定MSB first。

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

  1. 主机在SCL低电平时,依次送出D7~D0共8个bit
  2. 每送完一个bit,SCL产生一个高电平脉冲,从机在上升沿采样
  3. 第8个bit(D0)送完后,主机释放SDA(拉高),等待从机应答
  4. 第9个SCL脉冲用于应答位
// 伪代码:I2C发送一个字节(软件模拟)
void i2c_send_byte(uint8_t data) {
    for (int i = 0; i < 8; i++) {
        // 先送最高位
        if (data & 0x80)
            SDA_HIGH();
        else
            SDA_LOW();
        SCL_HIGH();  // 从机采样
        delay_us(1);
        SCL_LOW();
        delay_us(1);
        data <<= 1;  // 左移,准备下一位
    }
    // 第9位:释放SDA,等待应答
    SDA_HIGH();
    SCL_HIGH();
    // 读取SDA电平判断ACK/NACK
    ack = SDA_READ();
    SCL_LOW();
}

2.4 应答机制(ACK/NACK)

这是I2C最巧妙的设计之一。每发完一个字节,接收方必须给一个回应:

  • ACK(应答):接收方在第9个SCL脉冲期间将SDA拉低,表示“收到,请继续”
  • NACK(非应答):接收方在第9个SCL脉冲期间保持SDA高电平,表示“收完了/出错了/别发了”

应答规则:

  • 主机发送地址+写方向:从机必须ACK,否则说明地址不对或设备离线
  • 主机发送数据:从机每收到一个字节都要ACK
  • 主机接收数据:主机在收到最后一个字节后发NACK,告诉从机“别再发了”

我在项目中遇到过一种情况:从机明明在线,但发地址后就是收不到ACK。查了半天,发现是从机地址搞错了——7位地址左移了一位。I2C的地址字节是7位地址+1位读写位,很多人在这里翻车。

场景 发送方 接收方 应答行为
主机发送从机地址 主机 从机 匹配则ACK,不匹配则NACK
主机发送数据 主机 从机 每字节ACK,除非出错
主机接收数据 从机 主机 需要更多数据则ACK,最后字节发NACK

注意:应答位是强制性的。如果从机不ACK,主机必须产生停止条件终止传输,或者发送重新起始条件(Sr)重试。强行继续发数据,从机也不会理你。

2.5 完整时序流程与SVG图解

下面这张图把I2C一次完整的写操作画出来了。从起始条件开始,到停止条件结束,中间包括地址字节、数据字节和应答位。

I2C 完整写操作时序(主机→从机) SCL SDA S 地址字节(7位地址 + R/W#) ACK 数据字节(8位) P 时序说明: 1. 起始条件(S):SCL高电平时,SDA从高→低跳变 2. 地址字节:主机发送7位从机地址 + 1位写方向(0),MSB first 3. 应答位(ACK):从机在第9个SCL脉冲拉低SDA,表示确认 4. 数据字节:主机发送8位数据,从机每字节后ACK 5. 停止条件(P):SCL高电平时,SDA从低→高跳变,释放总线

2.6 常见问题与避坑指南

调试I2C的实用技巧:

  • 先用逻辑分析仪抓波形,别靠猜。看起始条件、地址、ACK三个关键点
  • 如果收不到ACK,先检查地址对不对(7位地址左移1位才是地址字节)
  • 上拉电阻选4.7kΩ起步,总线电容大时换2.2kΩ
  • 软件模拟I2C时,SCL高低电平的占空比尽量接近50%

我曾经犯过的错:有一次做多主机系统,两个主机同时发起始条件,总线冲突了。I2C虽然有仲裁机制,但我的软件没做冲突检测,结果数据全乱了。后来加了总线忙检测,每次发起始条件前先确认总线空闲(SCL和SDA都是高电平),问题解决。

嗯,I2C时序的核心就这些。起始条件、停止条件、数据有效性、字节格式、应答机制——这五个要素你掌握了,I2C通信的基本原理就通了。实际项目中,80%的I2C问题都出在这几个点上,尤其是起始条件和应答位的时序细节。