3. I2C 通信时序:起始条件、停止条件、数据采样、ACK/NACK 归纳总结
说实话,搞 I2C 时序看起来眼花缭乱,但核心就四个动作:起始、停止、数据采样、应答(ACK/NACK)。我早年第一次调 I2C 时,就被示波器上的波形绕晕过——明明 SCL 和 SDA 都在跳,为什么读回来的数据全错?后来才发现是起始条件和停止条件没卡对位置。这种事,经历一次就记住了。
3.1 起始条件(Start Condition)与停止条件(Stop Condition)
这两个条件是 I2C 通信的“起跑线”和“终点线”。总线空闲时,SCL 和 SDA 都是高电平。主机要开始传输,必须产生一个起始条件;传输完毕,必须产生一个停止条件。
- 起始条件:SCL 保持高电平时,SDA 从高电平切换到低电平。
- 停止条件:SCL 保持高电平时,SDA 从低电平切换到高电平。
我记得在某个项目里,从机芯片对时序要求特别苛刻——停止条件之后必须等至少 4.7µs 才能发下一个起始条件。我当时没仔细看 datasheet,连续发了两个起始条件,结果从机直接不理人了。嗯,从那以后我养成了习惯:每次写 I2C 驱动前,先翻到电气特性表,把时序参数全抄下来贴在显示器上。
| 条件 | SCL 电平 | SDA 跳变方向 | 典型保持时间 |
|---|---|---|---|
| 起始条件 | 高 | 高→低 | tHD;STA ≥ 4µs (标准模式) |
| 停止条件 | 高 | 低→高 | tSU;STO ≥ 4µs (标准模式) |
3.2 数据采样:时钟的上升沿和下降沿
一个 I2C 数据位的传输,靠的是 SCL 时钟。每发一个时钟脉冲,就传输一个 bit。说白了,主机掌控时钟,从机跟随时钟。
采样时刻发生在 SCL 的上升沿(或者说高电平期间)。为什么?因为 I2C 规范要求:SDA 上的数据必须在 SCL 低电平时改变,在高电平时保持稳定。这样接收方就能在 SCL 高电平中间稳稳地读 SDA。
为什么要强调“高电平稳定”?你想想看,如果数据在 SCL 高电平时变化,接收方可能采样到错误的电平,甚至把数据变化误判为起始或停止条件。时序的严谨性就在这里。
3.3 ACK(应答)与 NACK(非应答)
每发送完 8 个数据位,第 9 个时钟周期就是应答位。主机释放 SDA(变成高阻/输入),从机拉低 SDA 表示 ACK,保持高表示 NACK。这个机制是整个 I2C 通信的握手基础。
| 应答类型 | SDA 在第 9 个 SCL 高电平时 | 含义 |
|---|---|---|
| ACK | 低电平(从机拉低) | 从机已接收字节,可以继续 |
| NACK | 高电平(从机不拉低) | 从机忙/不支持该操作/最后字节 |
我个人习惯把 ACK/NACK 当成 I2C 的“回执”。主机发完地址后,如果没收到 ACK,说明这个从机不存在或地址错了。我记得有一次调试一个温湿度传感器,怎么都读不到数据,万用表量 SDA 一直是高电平。我换了两个器件才发现,原来我把地址左移了一位没处理(I2C 地址是 7 位,需要进行左移+读写位),从机根本不认。NACK 就是它给我的态度。
3.4 时序组合:一帧完整的传输
把上面三个元素串起来,一次典型的 I2C 写操作长这样:
- 主机产生 起始条件(SDA 下降沿,SCL 高)。
- 主机发送 7 位从机地址 + 1 位写标志(0),共 8 bit。
- 主机释放 SDA,从机拉低 SDA 作为 ACK。
- 主机发送 8 bit 数据(比如寄存器地址)。
- 从机回复 ACK。
- 主机发送下一个 8 bit 数据(要写入的值)。
- 从机回复 ACK。
- 主机产生 停止条件(SDA 上升沿,SCL 高)。
你可能会问:如果中间某一步从机回复了 NACK 怎么办?我一般做法是:立即停止本次传输,发停止条件,然后重试或报错。别傻等着,因为从机 NACK 之后通常不会再回应任何数据。
3.5 时序参数速查(标准模式)
在实际项目中,你不可能每次都去翻 datasheet。我整理了一张常用参数表,建议你收藏——反正我自己是打印出来贴墙上的。
| 参数 | 符号 | 最小值 | 最大值 | 说明 |
|---|---|---|---|---|
| SCL 时钟频率 | fSCL | 0 | 100 kHz | 标准模式 |
| 起始条件保持时间 | tHD;STA | 4.0 µs | — | SCL 高后 SDA 低要保持 |
| 数据建立时间 | tSU;DAT | 250 ns | — | SDA 变化到 SCL 上升沿 |
| 数据保持时间 | tHD;DAT | 0 | 3.45 µs | SCL 下降后 SDA 要稳住 |
| 停止条件建立时间 | tSU;STO | 4.0 µs | — | SCL 高后 SDA 上升 |
| 总线空闲时间 | tBUF | 4.7 µs | — | 停止到下次起始的最小间隔 |
3.6 小结:时序就是通信的语言
起始、停止、数据采样、应答——这四件事构成了 I2C 的全部通信语法。你掌握的越扎实,写驱动、调 bug 就越快。说白了,你只要在示波器上看到 SCL 高电平时 SDA 发生了不该有的跳变,十有八九是时序错了。
我个人习惯每次写完 I2C 代码,先拿逻辑分析仪抓一帧波形,对照本文的流程检查一遍。不要嫌烦,这能省下后面好几个小时的排查时间。