2. I2C通信协议详解:起始条件、停止条件、数据有效性、应答机制(ACK/NACK)
I2C协议,说白了就是一套“谁说话、谁听话、什么时候说、说完了没有”的规矩。别看它只有两根线——SCL(时钟)和SDA(数据),但里面的门道不少。我刚开始调I2C的时候,总觉得协议简单,结果被一个应答位的时序卡了两天。嗯,咱们今天就把这几个核心动作掰开揉碎了讲清楚。
2.1 起始条件(START Condition)
总线空闲时,SCL和SDA都是高电平。主设备想开始通信,就得先发一个起始信号。
时序定义:
- SCL保持高电平
- SDA从高电平切换到低电平
- 这个下降沿必须在SCL高电平期间完成
关键点:起始条件是一个“电平跳变”,不是时钟边沿。SCL高电平时,SDA的下降沿就是起始信号。所有从设备都在监听这个信号。
我在项目中遇到过一个问题:主设备发送起始条件后,从设备没反应。后来用示波器一看,SDA的下降沿不够陡,SCL高电平时间太短,从设备根本没识别到。所以,起始条件的建立时间(tSU;STA)和保持时间(tHD;STA)一定要满足规格书要求。
| 参数 | 标准模式(100kHz) | 快速模式(400kHz) | 高速模式(3.4MHz) |
|---|---|---|---|
| tSU;STA(建立时间) | ≥ 4.7μs | ≥ 0.6μs | ≥ 0.16μs |
| tHD;STA(保持时间) | ≥ 4.0μs | ≥ 0.6μs | ≥ 0.16μs |
2.2 停止条件(STOP Condition)
通信结束,主设备要释放总线。停止信号和起始信号正好相反:
- SCL保持高电平
- SDA从低电平切换到高电平
- 这个上升沿同样在SCL高电平期间完成
个人习惯:我写驱动时,会在发送停止条件后加一个小的延时(比如1μs),确保总线完全释放。有些从设备反应慢,连续操作时容易卡住。
你想想看,如果停止条件没发对,总线会一直处于“被占用”状态。我曾经调试一个温湿度传感器,每次读完数据后总线就锁死了。查了半天,发现是停止条件发送后,SCL又多拉了一个时钟脉冲。从设备以为还有数据要传,就一直拉着SDA不放。
2.3 数据有效性(Data Validity)
I2C的数据传输有个硬性规定:SDA上的数据必须在SCL低电平时变化,在SCL高电平时被采样。
说白了就是:
- SCL低电平:SDA可以随便变(准备下一个bit)
- SCL高电平:SDA必须稳定(主设备或从设备读这个bit)
注意:起始条件和停止条件是个例外——它们是在SCL高电平时改变SDA的。这也是为什么协议要把它们单独拎出来,作为“控制信号”而不是“数据信号”。
数据是以字节为单位传输的,每个字节8位,高位在前(MSB first)。我刚开始写驱动时,总搞不清字节顺序,结果读出来的寄存器值全是反的。后来养成习惯,每次发数据前先在心里默念一遍:bit7 → bit6 → ... → bit0。
2.4 应答机制(ACK/NACK)
每发送完一个字节,接收方必须给一个应答位。这是I2C协议里最容易被忽视、但也最容易出问题的地方。
应答流程:
- 发送方释放SDA(拉高)
- 接收方在第9个时钟脉冲的低电平期间拉低SDA(表示ACK)
- 如果接收方不拉低SDA(保持高电平),就是NACK
| 应答类型 | SDA状态(第9个时钟) | 含义 |
|---|---|---|
| ACK(应答) | 低电平 | 字节接收成功,可以继续发送 |
| NACK(非应答) | 高电平 | 接收失败,或传输结束 |
避坑指南:我曾经在驱动里忘了在发送完地址后检查ACK位。结果从设备根本没响应,我还傻乎乎地继续发数据。总线上一片混乱。记住:每次发送完一个字节,必须检查ACK。如果收到NACK,要么从设备不存在,要么它正忙,要么你发错了地址。
NACK在两种情况下是“正常”的:
- 主设备作为接收方时,读完最后一个字节后发NACK,告诉从设备“别再发了”
- 从设备不支持某个命令或寄存器地址时,会回NACK拒绝
2.5 完整通信时序图
下面这张图把起始条件、数据字节、应答位、停止条件串在一起。我建议你把它存下来,写驱动时对照着看。
2.6 常见问题与排查思路
写I2C驱动时,大部分bug都出在这几个地方。我列个清单,你调试时可以直接对照:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 起始条件后无响应 | tSU;STA或tHD;STA不满足 | 示波器测量时序,检查I2C时钟频率 |
| 数据读出来全是0xFF | 从设备没应答,SDA被上拉电阻拉高 | 检查设备地址是否正确,用逻辑分析仪看ACK位 |
| 总线锁死(SCL一直低) | 从设备时钟拉伸(Clock Stretching) | 主设备应支持时钟拉伸,或加超时处理 |
| 停止条件后总线仍忙 | 停止条件时序错误,或从设备未释放SDA | 检查停止条件后的SDA上升沿是否在SCL高电平期间 |
我的调试习惯:先用逻辑分析仪抓一次完整的通信波形,对照协议手册逐bit检查。别一上来就怀疑代码,很多时候是硬件时序问题。我曾经花了一整天改驱动,最后发现是板子上拉电阻焊错了——4.7kΩ焊成了47kΩ,信号上升沿太慢。
2.7 小结
I2C协议的四个核心动作——起始条件、停止条件、数据有效性、应答机制——构成了所有通信的基础。你写驱动时,本质上就是在操作这几个动作的组合。记住:
- 起始和停止是“控制信号”,在SCL高电平时改变SDA
- 数据在SCL低电平时变化,高电平时采样
- 每个字节后面跟一个应答位,必须检查
- NACK不一定是错误,也可能是通信结束的信号
把这些基础打牢了,后面配置通信速率、处理多主设备冲突、实现DMA传输,都会顺手很多。