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

时序,是I2C协议的命脉。很多工程师调不通I2C,说白了就是时序没吃透。我刚开始接触I2C时也吃过亏——波形看着对,从机就是不响应。后来才发现,是起始条件保持时间差了那么几十纳秒。

这一节,我们把I2C时序拆开揉碎。你搞懂了这些,后面写驱动、调bug都会顺手很多。

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

I2C总线空闲时,SCL和SDA都是高电平。这是前提。

起始条件(Start Condition):SCL为高电平时,SDA从高电平切换到低电平。
停止条件(Stop Condition):SCL为高电平时,SDA从低电平切换到高电平。

记住这个口诀:“高切低是起始,低切高是停止”。

关键时序参数(标准模式100kHz)

  • tHD;STA(起始条件保持时间):最小4.0µs
  • tSU;STA(起始条件建立时间):最小4.7µs
  • tSU;STO(停止条件建立时间):最小4.0µs
  • tBUF(停止与起始间总线空闲时间):最小4.7µs

我曾经遇到一个案子:从机偶尔不响应,抓波形发现起始条件保持时间只有3.2µs。手册要求4.0µs,差了0.8µs。把I2C时钟从400kHz降到100kHz,问题消失。嗯,这就是时序裕量不够的典型表现。

2.2 数据有效性

I2C的数据采样规则很简单:

  • SCL高电平期间,SDA数据必须稳定
  • SCL低电平期间,SDA允许变化

换句话说,从机只在SCL高电平时采样SDA。你在SCL低电平时随便折腾SDA都没事,但SCL一拉高,SDA就得稳住。

避坑指南:我曾经在STM32上用GPIO模拟I2C,SCL拉高后立刻读SDA,结果偶尔读到错误数据。后来发现是SDA电平还没稳定。加了个空指令延时(约100ns),问题解决。硬件上,SDA的上拉电阻和总线电容会形成RC延迟,SCL上升沿后需要等一小会儿再采样。

2.3 字节格式与数据传输

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

一个完整的数据帧结构如下:

阶段 位数 说明
起始条件 1 S
从机地址 7 或 10 7位地址模式最常见
读写位 1 0=写,1=读
应答位 1 ACK/NACK
数据字节 8 × N N个字节,每个后跟应答
停止条件 1 P

举个例子,主机向地址0x50的从机写一个字节0xA5:

S 0x50 0 ACK 0xA5 ACK P

其中:

  • S = 起始条件
  • 0x50 = 7位地址左移1位(实际发送0xA0)
  • 0 = 写操作
  • ACK = 从机应答
  • 0xA5 = 数据字节
  • P = 停止条件

2.4 应答机制(ACK/NACK)

应答是I2C协议的“握手信号”。每发送完一个字节(8位),第9个SCL时钟周期用于应答。

  • ACK(应答):接收方在第9个SCL时钟拉低SDA。表示“收到了,继续发”。
  • NACK(非应答):接收方在第9个SCL时钟保持SDA高电平。表示“没收到/不想收/发完了”。

我个人的经验:调试I2C时,第一个要看的信号就是应答位。如果地址发送后没有ACK,90%是地址错了或者从机没上电。

实用技巧:用逻辑分析仪抓I2C波形时,把光标放在第9个SCL时钟的下降沿,看SDA电平。低=ACK,高=NACK。我习惯在代码里加个调试打印,把每次传输的ACK状态打出来,这样不用每次都接仪器。

2.5 时序综合流程图

下面这张图把I2C单字节写操作的完整时序串起来了。你对照着看,每个阶段在波形上长什么样,心里就有数了。

I2C单字节写操作时序流程 起始条件 S 发送地址+读写位 等待应答 ACK 发送数据字节 停止条件 P SCL高电平时SDA从高→低 SCL高电平时SDA从低→高 波形示意(SCL & SDA) SCL SDA S 地址+读写 ACK 数据 P 注意:每个字节后都有ACK/NACK位(第9个SCL时钟) NACK表示传输结束或从机异常

2.6 常见时序问题与排查

调试I2C时,我总结了一套“三板斧”排查法:

  1. 看起始条件:SDA下降沿是否在SCL高电平时发生?保持时间够不够?
  2. 看地址+ACK:地址对不对?从机有没有拉低SDA应答?
  3. 看停止条件:SDA上升沿是否在SCL高电平时发生?总线是否恢复空闲?

我曾经踩过的坑:用GPIO模拟I2C时,起始条件后忘了释放SDA为输入模式,导致从机无法拉低SDA应答。折腾了两小时才发现是GPIO方向没切换。嗯,细节决定成败。

还有一个常见问题:时钟拉伸(Clock Stretching)。有些从机处理数据慢,会在应答位后拉低SCL,迫使主机等待。如果你的主机不支持时钟拉伸,通信就会失败。我建议:写驱动时默认支持时钟拉伸,兼容性会好很多。

2.7 小结

这一节的内容,是I2C协议的“语法”。起始条件、停止条件、数据有效性、字节格式、应答机制——这五个概念构成了I2C通信的底层规则。你写驱动也好,调硬件也罢,最终都是在跟这五个东西打交道。