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

各位同学,今天我们来啃I2C协议最核心的部分——时序。说实话,很多工程师写I2C驱动时喜欢直接抄代码,一旦遇到波形异常就抓瞎了。我个人习惯是先把时序图刻在脑子里,再去写代码。你想想看,连协议怎么握手都不知道,怎么调得通呢?

2.1 起始条件与停止条件

I2C总线上所有的通信,都从起始条件(S)开始,到停止条件(P)结束。这两个信号是总线的“开关”。

信号 SCL状态 SDA变化方向 说明
起始条件(S) 高电平 下降沿(高→低) SCL为高时,SDA由高变低
停止条件(P) 高电平 上升沿(低→高) SCL为高时,SDA由低变高

这里有个关键点:SCL在高电平时,SDA绝对不能变化(除了起始和停止)。我在项目中遇到过一位同事,他在SCL低电平时没注意SDA的毛刺,结果从机一直误判成重复起始条件,整条总线直接锁死。嗯,排查了整整一个下午。

避坑指南:我曾经在STM32上用GPIO模拟I2C时,起始条件后忘记把SCL拉低,导致第一个时钟周期就产生了额外的停止条件。从机直接罢工,数据全丢。记住:起始条件完成后,立即将SCL拉低,这是铁律。

2.2 数据有效性

I2C的数据采样规则其实很简单,就一句话:SCL高电平时读数据,SCL低电平时换数据。

  • SCL = 1(高电平):SDA上的数据必须稳定,主机或从机在此阶段采样。
  • SCL = 0(低电平):允许SDA变化,发送方可以切换下一位数据。

说白了,SCL就是一把锁。高电平锁住数据,低电平释放数据。我刚开始做驱动时,总想着在SCL上升沿瞬间去读SDA,结果发现不同从机的建立时间(tSU)和保持时间(tHD)有差异,读出来的数据偶尔会错位。后来我改成在SCL高电平中间段采样,问题就解决了。

实战技巧:模拟I2C时,建议在SCL拉高后插入一个微小的延时(比如几百纳秒),再读取SDA。这个延时可以覆盖大多数从机的建立时间要求,避免采样到数据跳变沿。

2.3 字节格式与数据传输

I2C的数据是以字节(8位)为单位传输的,而且高位在前(MSB first)。每个字节后面紧跟着一个应答位(ACK/NACK)。

一个完整的数据帧长这样:

起始条件 | 7位从机地址 | R/W位 | 应答位 | 数据字节1 | 应答位 | ... | 数据字节N | 应答位 | 停止条件

这里有个容易搞混的地方:地址字节其实是8位——7位地址 + 1位读写标志。读写标志为0表示主机要写,为1表示主机要读。我见过有人把地址左移一位后忘了设置读写位,结果从机根本不搭理他。

字段 位数 说明
从机地址 7位 高7位,如0x50(AT24C02的地址)
R/W位 1位 0=写,1=读
数据字节 8位 每个字节后跟一个应答位

2.4 应答机制(ACK/NACK)

应答机制是I2C协议的“握手信号”。每发送完一个字节,接收方必须在第9个时钟周期内将SDA拉低(ACK)或保持高(NACK)。

  • ACK(应答):接收方拉低SDA,表示“我收到了,请继续”。
  • NACK(非应答):接收方释放SDA(由上拉电阻拉高),表示“我没收到”或“别再发了”。

为什么需要应答?说白了,就是让发送方知道数据有没有成功送达。我在调试EEPROM时遇到过一种情况:写入一个字节后,从机需要时间来完成内部编程,期间它不会应答。如果主机不管不顾地继续发数据,写入就会失败。

核心要点:应答位是由接收方控制的。主机发送地址后,从机应答;主机发送数据后,从机应答;从机发送数据后,主机应答(或NACK表示读结束)。

2.5 时钟拉伸(Clock Stretching)

时钟拉伸是从机的一种“刹车”机制。当从机来不及处理数据时,它会主动拉低SCL,迫使主机等待。主机检测到SCL被拉低后,必须停止时钟,直到从机释放SCL。

为什么会这样?因为有些从机(比如EEPROM在写周期内)需要时间完成内部操作。如果主机不管不顾地继续发时钟,数据就会丢失。

我曾在某款传感器驱动中踩过这个坑。从机在启动测量后需要几十毫秒,期间它会拉伸时钟。我的模拟I2C代码没做超时处理,结果主机一直死等,整个系统卡死。后来我加了一个超时机制:如果SCL被拉低超过10ms,就主动复位总线。

注意:不是所有从机都支持时钟拉伸。EEPROM通常支持,但一些简单的I2C设备(如I/O扩展芯片)可能不支持。写驱动前,一定要看数据手册的“Clock Stretching”章节。

2.6 完整时序图

下面这张SVG图把整个I2C单字节写入的时序串起来了。你可以对照着看,从起始条件到停止条件,每一步都标清楚了。

I2C单字节写入时序图 I2C单字节写入完整时序 SCL SDA S 起始 从机地址 (7位) R/W ACK 数据字节 (8位) ACK P 停止 时间轴 →

这张图里,S和P分别对应起始和停止条件。地址字节、R/W位、数据字节后面都跟着一个ACK。你写模拟I2C代码时,就按这个节奏来:拉低SDA→拉低SCL→发数据→释放SCL→采样ACK。每一步都不能乱。

2.7 常见时序问题排查

最后,我把自己这些年遇到的I2C时序问题整理成了一张表,你可以当个速查手册用。

现象 可能原因 排查方向
起始条件后无应答 地址错误或从机未上电 用示波器抓SDA,检查地址位和R/W位
数据字节后一直NACK 从机忙或写保护 检查EEPROM的WP引脚,或等待内部写周期
SCL被拉低后不释放 从机时钟拉伸超时 加超时处理,或检查从机是否死锁
数据错位或乱码 SCL高电平时SDA变化 检查建立/保持时间,增加延时
总线一直忙(SDA低) 从机未释放总线或总线锁死 发送9个时钟脉冲复位从机

嗯,时序这东西,光看理论是不够的。我建议你拿逻辑分析仪抓一段真实的I2C波形,对照着这张图一个一个字节去对。等你亲手抓到一次起始条件、看到ACK的波形,你就真正理解I2C了。