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

I2C协议说白了就是一套“谁说话、谁听话、什么时候说、说完怎么确认”的规矩。刚开始学的时候,我觉得这东西不就是两根线嘛,能有多复杂?结果第一次调EEPROM,示波器抓出来的波形乱七八糟,才发现时序细节一个都不能错。

这一章咱们就把I2C最核心的五个时序要素掰开揉碎讲清楚。你把这些搞明白了,后面写驱动、调bug心里就有底了。

2.1 起始条件(START Condition)

起始条件标志着一次I2C通信的开始。它由主机产生,所有从机都会检测到这个信号并准备接收后续数据。

时序定义:

  • SCL保持高电平
  • SDA从高电平切换到低电平
  • 切换完成后SCL变为低电平

这里有个关键点:SDA的下降沿必须在SCL高电平时发生。我刚开始做的时候,有次代码里SCL和SDA的时序没配合好,SDA下降沿落在了SCL低电平区间,结果从机根本不响应。查了半天才发现是起始条件没满足。

我的习惯:写驱动时我会把起始条件封装成一个独立函数,每次调用前先确保SCL和SDA都处于释放状态(高电平),然后再执行下降沿操作。这样不容易出错。

2.2 停止条件(STOP Condition)

停止条件标志着通信结束。主机告诉所有从机:“我说完了,你们可以休息了。”

时序定义:

  • SCL先变为高电平
  • SDA从低电平切换到高电平
  • SDA上升沿必须在SCL高电平时完成

你想想看,起始和停止其实是对称的——一个是高到低的跳变,一个是低到高的跳变,而且都发生在SCL高电平期间。

我曾经踩过的坑:有一次在停止条件之后马上又发起始条件(这叫“重复起始”,后面会讲),结果因为停止条件的释放时间不够,SDA还没完全拉高就被拉低了,从机直接懵掉。后来我在停止条件后面加了至少1us的延时,问题就解决了。

2.3 数据有效性(Data Validity)

I2C协议规定:SDA上的数据只有在SCL低电平时才能变化,在SCL高电平时必须保持稳定。从机就是在SCL的上升沿采样SDA的。

这个规则其实很直观——SCL高电平是“采样窗口”,SCL低电平是“数据准备时间”。

SCL状态 SDA行为 说明
高电平 必须保持稳定 从机在此阶段采样数据
低电平 允许变化 主机在此阶段更新数据

我记得有次调试一个温湿度传感器,读出来的数据偶尔会跳变。用示波器一看,发现是SDA在SCL高电平时有个毛刺,导致从机采样到了错误值。后来在硬件上加了个小电容滤波,软件上也做了多次读取取平均,才算稳定下来。

2.4 字节传输格式

I2C传输数据的基本单位是字节(8位),而且是从最高位(MSB)开始传。每传完一个字节,紧接着就是一个应答位。

传输顺序:

  1. 主机驱动SCL产生8个时钟脉冲
  2. 在每个SCL低电平期间,主机(或从机)更新SDA
  3. 在每个SCL高电平期间,接收方采样SDA
  4. 第9个时钟脉冲用于应答

关键点:I2C协议没有规定字节之间的间隔时间。你可以连续发,也可以每发一个字节等一会儿。但要注意,有些从机需要处理时间,发太快了它可能来不及应答。

我习惯在每两个字节之间加一个微小的延时,尤其是跟慢速EEPROM通信的时候。虽然标准I2C可以跑到400kHz甚至1MHz,但实际项目中稳定比速度重要。

2.5 应答机制(ACK/NACK)

应答是I2C协议里最巧妙的设计之一。每传完一个字节,接收方必须用第9个时钟周期告诉发送方:“我收到了”或者“我没收到”。

ACK(应答成功):

  • 接收方在第9个时钟周期将SDA拉低
  • 表示“数据已收到,请继续”

NACK(非应答):

  • 接收方在第9个时钟周期保持SDA高电平
  • 表示“我没准备好”或“别再发了”

这里有个容易混淆的地方:谁发数据,谁控制应答时钟?

  • 主机发送数据时:从机应答(ACK/NACK)
  • 主机接收数据时:主机应答(ACK表示还要继续读,NACK表示够了,停止)
我曾经犯过的错:读EEPROM的时候,主机读完最后一个字节后忘了发NACK,结果从机以为主机还要继续读,就一直发数据,总线被占死了。后来我养成了习惯:读操作的最后一定要发NACK+停止条件。

2.6 完整时序图

下面这张SVG图把起始条件、字节传输、应答、停止条件串在一起,你可以对照着看。

I2C完整时序图(起始→字节传输→应答→停止) SCL SDA 起始 停止 8位数据(MSB→LSB) ACK 1 2 3 4 5 6 7 8 9 注意:SDA只在SCL低电平时变化,SCL高电平时采样

2.7 常见问题与避坑

做I2C驱动开发,下面这几个问题我几乎每次都会遇到:

  • 总线卡死:从机没释放SDA,导致主机无法产生起始条件。解决办法是发9个以上的时钟脉冲让从机复位。
  • 应答超时:从机没拉低SDA。可能是地址错了,或者从机正在忙。我一般会加超时处理,超过一定时间就报错重试。
  • 时序不匹配:主机跑太快,从机跟不上。降低I2C时钟频率通常能解决。

我的调试小技巧:手边常备一个逻辑分析仪,抓一次波形就能看出起始条件、数据、应答、停止条件是否都对。比盯着示波器一行行看代码快多了。

嗯,这一章的内容就这些。起始、停止、数据有效性、字节格式、应答——五个要素串起来就是完整的I2C通信过程。你把这些搞清楚了,后面写驱动就是按部就班的事。