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

I2C总线的时序,说白了就是一套“谁在什么时候干什么”的规矩。你想想看,一条SCL时钟线,一条SDA数据线,主设备从设备都挂在上面,要是没个统一的时序约定,那不乱套了?

这一节我们把I2C时序拆成五个核心单元来讲。每个单元我都结合自己调试时的踩坑经历,帮你把那些容易忽略的细节抠清楚。

核心观点: I2C的时序不是死记硬背的,而是理解“谁控制时钟、谁驱动数据、什么时候采样”这三个问题。搞懂了这三点,任何I2C设备的时序你都能自己分析。
I2C时序五单元关系图 I2C总线时序 起始条件 停止条件 数据有效性 字节格式 应答机制 五个单元环环相扣:起始/停止定义传输边界,数据有效性规定采样窗口, 字节格式决定数据组织方式,应答机制实现传输确认。

2.1 起始条件(START Condition)

起始条件是什么?就是主设备告诉总线上所有设备:“我要开始通信了,大家注意!”

电气定义:SCL为高电平时,SDA从高电平切换到低电平。

我刚开始学I2C时,总觉得这个定义很简单。直到有一次调试一个温湿度传感器,逻辑分析仪抓出来的波形怎么看都不对。后来发现,是我在SCL还没稳定在高电平时就拉低了SDA——这其实是个“准起始条件”,很多从设备不认。

我的习惯:写起始条件代码时,我会先确保SCL和SDA都被拉高,然后加一个微小的延时(几百纳秒),再拉低SDA,最后拉低SCL。这个“先高后低”的顺序,一个都不能少。

起始条件的时序参数,我整理了一个表格,方便你对照:

参数 符号 标准模式(100kHz) 快速模式(400kHz) 高速模式(3.4MHz)
起始条件保持时间 tHD;STA 4.0 μs 0.6 μs 160 ns
起始条件建立时间 tSU;STA 4.7 μs 0.6 μs 160 ns

2.2 停止条件(STOP Condition)

停止条件和起始条件正好相反。SCL为高电平时,SDA从低电平切换到高电平。从设备看到这个信号,就知道本次传输结束了。

这里有个坑,我曾经踩过:重复起始条件(Repeated START)。它是在没有停止条件的情况下,直接再发一个起始条件。很多新手会误以为必须先发停止再发起始,结果把一次完整的读写操作拆成了两次,中间还被其他设备抢占了总线。

注意:重复起始条件常用于“先写寄存器地址,再读数据”的场景。如果你在写地址后发了停止条件,总线就释放了,其他主设备可能插进来,导致你的读操作失败。

2.3 数据有效性(Data Validity)

数据有效性这个规则,说白了就一句话:SDA上的数据必须在SCL为高电平时保持稳定,只有在SCL为低电平时才能变化。

为什么会这样?因为从设备只在SCL的上升沿或高电平期间采样SDA。如果你在SCL高电平时改变了SDA,从设备可能采到错误的数据,甚至把数据变化误判成起始或停止条件。

我记得有一次调试一个加速度计,读出来的数据总是跳变。用示波器一看,发现是主设备在SCL高电平时SDA有毛刺。后来在SDA上加了一个小电容滤波,问题就解决了。

避坑指南:我曾经在GPIO模拟I2C时,为了省代码,在SCL高电平期间直接写SDA寄存器。结果数据总是不对。后来老老实实按“SCL低时改SDA,SCL高时读SDA”的规矩来,一次通过。

2.4 字节格式(Byte Format)

I2C传输数据时,每次发8位(1个字节),高位在前(MSB first)。发完8位后,紧接着跟一个应答位。所以一个完整的字节传输,其实是9个SCL时钟周期。

字节格式的组成:

  • 7位从机地址:高7位,决定跟谁通信
  • 1位读写标志:0表示写,1表示读
  • 1位应答位:从设备回应

举个例子,如果主设备要往地址0x50的设备写数据,发送的第一个字节就是:

二进制:1010 000 0
解释:高7位 1010000 = 0x50(从机地址)
      第8位 0 = 写操作
      第9位 等待从设备ACK
小技巧:很多数据手册会把从机地址写成7位形式(如0x50),但实际发送时要左移1位,再拼上读写位。我习惯在代码里用宏定义:#define ADDR_WRITE(addr) ((addr) << 1) 和 #define ADDR_READ(addr) ((addr) << 1) | 0x01。

2.5 应答机制(ACK/NACK)

应答机制是I2C协议里最巧妙的设计之一。每发完一个字节,接收方要在第9个SCL周期拉低SDA,表示“我收到了,继续发”。如果接收方不拉低(SDA保持高),那就是NACK,表示“我不想要了”或“出错了”。

应答机制有三种常见场景:

  1. 主设备写,从设备应答:每发一个字节,从设备都要ACK。如果从设备NACK,说明地址不对、寄存器不存在或设备忙。
  2. 主设备读,从设备发数据:主设备每收到一个字节,要主动发ACK表示“我还要继续读”。当主设备想停止读取时,发NACK,然后发停止条件。
  3. 从设备NACK:我遇到过最典型的情况是,从设备正在处理内部操作(比如EEPROM在写页),这时它会对主设备的访问NACK。解决办法就是等一会儿再重试。
曾经踩过的坑:有一次我读一个ADC芯片,读出来的数据全是0xFF。查了半天,发现是主设备在读最后一个字节时忘了发NACK,直接发了停止条件。从设备以为主设备还要继续读,就一直等,结果总线卡死了。记住:读操作的最后一个字节,主设备必须发NACK!

2.6 完整时序示例:一次写操作

把上面五个单元串起来,一次完整的I2C写操作时序是这样的:

1. 主设备发起始条件(S)
2. 主设备发7位地址 + 写标志(0)
3. 从设备发ACK
4. 主设备发8位数据(寄存器地址)
5. 从设备发ACK
6. 主设备发8位数据(要写的值)
7. 从设备发ACK
8. 主设备发停止条件(P)

用逻辑分析仪看,波形应该是:S → 地址+W → ACK → 数据 → ACK → 数据 → ACK → P。每个字节9个时钟,整个流程清晰明了。

总结一下:起始条件和停止条件定义了传输的边界,数据有效性保证了采样的可靠性,字节格式规定了数据的组织方式,应答机制实现了流量控制。这五个单元,就是I2C协议的“语法”。你写驱动时,本质上就是在实现这套语法。