2. I2C通信时序:起始条件、停止条件、数据有效性、字节传输与ACK/NACK机制
I2C的时序,说白了就是一根时钟线(SCL)和一根数据线(SDA)之间跳的「双人舞」。很多初学者觉得I2C协议简单,不就是两根线嘛。但我在项目中踩过的坑告诉我——时序不对,一切白费。
咱们先把这个舞蹈的基本舞步拆开来看。
2.1 起始条件(START Condition)与停止条件(STOP Condition)
I2C总线上,SCL和SDA在空闲时都是高电平(由上拉电阻拉高)。什么时候算一次传输开始?什么时候算结束?协议里定义得很清楚。
| 条件 | SCL状态 | SDA变化 | 说明 |
|---|---|---|---|
| 起始条件(S) | 高电平 | 从高→低跳变 | SCL为高时,SDA由高变低 |
| 停止条件(P) | 高电平 | 从低→高跳变 | SCL为高时,SDA由低变高 |
| 重复起始(Sr) | 高电平 | 从高→低跳变 | 不发送停止,直接再发一次起始 |
关键记忆点:起始和停止的唯一区别,就是SDA跳变的方向。起始是「高→低」,停止是「低→高」。而且这两个跳变都必须在SCL为高电平时发生。
我曾经在一个项目里,用GPIO模拟I2C时,起始条件写成了SCL低电平时拉低SDA。结果总线上的从设备根本不响应。查了两天才发现——起始条件必须在SCL高电平时采样SDA的下降沿。这个细节,示波器一看就明白,但代码里很容易忽略。
2.2 数据有效性:SCL低电平时准备,高电平时采样
I2C的数据传输有一个铁律:
- SCL为低电平时:SDA允许变化(数据准备阶段)
- SCL为高电平时:SDA必须稳定(数据采样阶段)
为什么这样设计?你想想看,如果SCL高电平时SDA还在跳变,那接收方到底采到的是0还是1?没法确定。所以协议强制要求:SCL高电平期间,SDA不能动。
⚠️ 避坑提醒:我曾经在驱动里为了「提速」,在SCL上升沿之后立刻改变SDA。结果从设备偶尔能读到正确数据,偶尔读错。后来用逻辑分析仪一看,SDA在SCL高电平期间有毛刺。记住:SCL高电平期间,SDA必须纹丝不动。
2.3 字节传输与ACK/NACK机制
I2C每次传输一个字节(8位),数据从最高位(MSB)开始发送。发完8位之后,第9个时钟周期用来做应答。
应答机制是这样的:
- ACK(应答):接收方在第9个时钟周期将SDA拉低,表示「收到,请继续」
- NACK(非应答):接收方在第9个时钟周期保持SDA高电平,表示「收完了,别发了」或「我没准备好」
| 场景 | 发送方 | 接收方 | 第9个SCL的SDA状态 |
|---|---|---|---|
| 主机发送地址 | 主机 | 从机 | 从机拉低 = ACK,从机不拉 = NACK |
| 主机发送数据 | 主机 | 从机 | 从机拉低 = ACK,从机不拉 = NACK |
| 主机接收数据 | 从机 | 主机 | 主机拉低 = ACK(还要继续读),主机不拉 = NACK(读够了) |
💡 个人经验:我习惯在驱动里把NACK当作一种「通信状态码」来处理。比如读从机寄存器时,如果从机返回NACK,大概率是地址不对或者从机没准备好。这时候不要盲目重试,先检查硬件连接和地址配置。
2.4 完整字节传输时序
一个完整的字节传输,包含9个SCL时钟周期:
- 第1-8个SCL周期:传输数据位(D7→D0),SCL低电平时SDA变化,SCL高电平时采样
- 第9个SCL周期:应答位,发送方释放SDA(拉高),接收方决定拉低(ACK)还是保持高(NACK)
这里有个容易搞混的点:谁释放SDA?
- 如果是主机发送数据给从机:主机在8位数据发完后释放SDA,从机拉低应答
- 如果是主机从从机读取数据:从机在8位数据发完后释放SDA,主机拉低应答(表示还要继续读)
核心原则:谁发数据,谁控制SDA;发完8位后,谁发数据谁释放SDA,让对方控制应答。
2.5 时序知识体系总览
下面这张图把I2C时序的核心要素串起来了。我建议你把它存下来,写驱动时对照着看。
2.6 常见时序问题与调试方法
我在调试I2C驱动时,遇到过几个典型问题,分享给你:
- 从机无应答:最常见。先查地址对不对(7位还是10位?左对齐还是右对齐?),再查SCL频率是否在从机支持范围内
- 数据错位:大概率是SCL高电平时SDA发生了跳变。用示波器看SDA在SCL高电平期间有没有毛刺
- 总线死锁:从机把SDA拉低后不释放。这时候可以发9个时钟脉冲让从机复位,或者直接硬件复位从机
💡 调试小技巧:我习惯在驱动里加一个「时序检查模式」——每次发送起始条件后,读一下SDA和SCL的电平状态,确认总线状态符合预期。虽然会慢一点,但调试阶段能省大量时间。