2. I2C时序分析:起始条件、停止条件、数据有效性、字节格式与应答机制
各位同学,今天我们来啃I2C协议最核心的部分——时序。说实话,很多工程师写I2C驱动时喜欢直接抄代码,一旦遇到波形异常就抓瞎了。我个人习惯是先把时序图刻在脑子里,再去写代码。你想想看,连协议怎么握手都不知道,怎么调得通呢?
2.1 起始条件与停止条件
I2C总线上所有的通信,都从起始条件(S)开始,到停止条件(P)结束。这两个信号是总线的“开关”。
| 信号 | SCL状态 | SDA变化方向 | 说明 |
|---|---|---|---|
| 起始条件(S) | 高电平 | 下降沿(高→低) | SCL为高时,SDA由高变低 |
| 停止条件(P) | 高电平 | 上升沿(低→高) | SCL为高时,SDA由低变高 |
这里有个关键点:SCL在高电平时,SDA绝对不能变化(除了起始和停止)。我在项目中遇到过一位同事,他在SCL低电平时没注意SDA的毛刺,结果从机一直误判成重复起始条件,整条总线直接锁死。嗯,排查了整整一个下午。
2.2 数据有效性
I2C的数据采样规则其实很简单,就一句话:SCL高电平时读数据,SCL低电平时换数据。
- SCL = 1(高电平):SDA上的数据必须稳定,主机或从机在此阶段采样。
- SCL = 0(低电平):允许SDA变化,发送方可以切换下一位数据。
说白了,SCL就是一把锁。高电平锁住数据,低电平释放数据。我刚开始做驱动时,总想着在SCL上升沿瞬间去读SDA,结果发现不同从机的建立时间(tSU)和保持时间(tHD)有差异,读出来的数据偶尔会错位。后来我改成在SCL高电平中间段采样,问题就解决了。
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时遇到过一种情况:写入一个字节后,从机需要时间来完成内部编程,期间它不会应答。如果主机不管不顾地继续发数据,写入就会失败。
2.5 时钟拉伸(Clock Stretching)
时钟拉伸是从机的一种“刹车”机制。当从机来不及处理数据时,它会主动拉低SCL,迫使主机等待。主机检测到SCL被拉低后,必须停止时钟,直到从机释放SCL。
为什么会这样?因为有些从机(比如EEPROM在写周期内)需要时间完成内部操作。如果主机不管不顾地继续发时钟,数据就会丢失。
我曾在某款传感器驱动中踩过这个坑。从机在启动测量后需要几十毫秒,期间它会拉伸时钟。我的模拟I2C代码没做超时处理,结果主机一直死等,整个系统卡死。后来我加了一个超时机制:如果SCL被拉低超过10ms,就主动复位总线。
2.6 完整时序图
下面这张SVG图把整个I2C单字节写入的时序串起来了。你可以对照着看,从起始条件到停止条件,每一步都标清楚了。
这张图里,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了。