2. I2C的应答机制:ACK与NACK的原理归纳总结
调试I2C的人,几乎都栽过在应答位上。我自己刚入门时也觉得“不就是个拉低拉高嘛”,直到一次量产中发现整批设备间歇性死机,用逻辑分析仪抓到从机偶尔不回ACK,才真正重视起来。这个9位的传输窗口,藏着整个协议稳定性的命门。
2.1 什么是应答?为什么需要应答?
I2C协议中,每传输一个字节(8位数据)后,接收方必须在第9个SCL时钟周期给出一个应答位。说白了,这就像你给对方寄了封信,必须等对方回个“收到了”或“不收了”的信号。没有这个环节,主机永远不知道数据是否正确送达。
- ACK(Acknowledge):接收方在第9个SCL拉低SDA,表示“收到,请继续”。
- NACK(Not Acknowledge):接收方在第9个SCL保持SDA高,表示“没收到”或“不想再收了”。
我个人习惯把应答位叫做“协议的血压计”——血压不对,整个系统肯定有问题。你想想看,一个没有应答的协议会怎样?只能靠超时猜状态,这在实时控制中是灾难。
2.2 ACK与NACK的电气特征
时序上非常直观:第1~8个SCL传输数据位,第9个SCL传输应答位。数据位由发送方控制SDA,应答位由接收方控制SDA。我曾在项目里用逻辑分析仪抓过一段漂亮的时序,完全符合标准:
- ACK:SDA在第9个SCL低电平阶段被从机拉低,并保持到SCL高电平结束。
- NACK:SDA在第9个SCL保持高,主机看到这个高电平后,通常会发出停止信号。
关键指标:应答位必须在SCL高电平期间采样。逻辑分析仪上要关注第9个SCL上升沿后SDA的状态——这才是真正的应答值。很多新手把整个第9个时钟都算进去,结果看错。
为了更直观地理解整个流程,我画了一张图,展示I2C写操作中应答位如何嵌入在传输序列里:
2.3 主机与从机的角色
应答的职责分配,很多人会搞混。我记了个口诀:“谁发数据,谁被应答;谁收数据,谁发应答”。具体来说:
- 主机发送(写操作):主机发地址 + 数据字节,从机在每个字节后发ACK/NACK。
- 主机接收(读操作):主机发地址后,从机开始发数据,此时主机需要发ACK(表示“还要”)或NACK(表示“够了”)。
| 操作方向 | 发送方 | 接收方 | 应答发出者 |
|---|---|---|---|
| 写 | 主机 | 从机 | 从机 |
| 读 | 从机 | 主机 | 主机 |
我曾经调试一块SPI转I2C桥接芯片,读操作一直卡死。逻辑分析仪一看,主机在收完第一个字节后发了个NACK,从机直接释放了总线。问题出在主机的接收缓冲区只能装一个字节,读完就发NACK——但我的应用层期望读更多。这里要记住:读操作中,主机会在你想要的数据字节之后发NACK。
2.4 NACK的常见触发场景
NACK不一定是错误,它有时是正常协议终止。但数据手册和逻辑分析仪上出现的NACK,十有八九是问题。我总结了几种典型情况:
- 地址不匹配:总线上没有从机响应该地址,SDA保持高,主机收到NACK。
- 从机忙:从机正在处理内部事务(比如EEPROM正在写页),此时会NACK。
- 数据无效:从机发现收到的字节格式错误或校验失败,主动回NACK。
- 主机主动结束:读操作中主机发NACK表示“不再读”。
- 总线短路或上拉不足:SDA被意外拉高,导致本该是ACK的位变成了NACK。
| 场景 | NACK出现在哪个字节后? | 根本原因 |
|---|---|---|
| 地址不匹配 | 地址字节后 | 从机不存在或地址错误 |
| 从机忙 | 任意字节后 | 从机内部处理未完成 |
| 数据错误 | 数据字节后 | 校验失败或协议违规 |
| 主机主动停止 | 最后一个数据字节后(读) | 主机预期结束 |
| 硬件故障 | 随机位置重复出现 | 上拉电阻、电平冲突或干扰 |
2.5 实战中的应答观察:逻辑分析仪视角
用逻辑分析仪抓应答,我最关注两个点:第九个SCL的上升沿和SDA的稳定电平。有一次量产返修率突然飙到30%,看波形全是正常ACK,但偶尔在地址字节后的第九个时钟SDA是“半高半低”——那其实是总线电容过大、SDA没来得及拉到低。我加了几根飞线临时降低了线路寄生电容,问题立刻消失。
实用技巧:在逻辑分析软件里设置“ACK检测”触发条件,抓取第一个NACK时自动暂停。这样你不需要手动翻看几万帧数据。我调试I2C设备时,一定会把这个触发条件配好,比事后逐帧扫描高效十倍。
另一个常见陷阱:重复起始条件(Sr)前的应答。一些从机在接收到读命令后会先发一个NACK表示“还没准备好”,你必须重新发起Sr再试。这时候如果软件没有重试机制,就可能被误判为设备坏了。嗯,这里要注意区分“真的坏了”和“忙但活着”。
掌握ACK/NACK的电气行为和协议角色,是诊断I2C异常的第一步。下功夫把这张应答时序图刻在脑子里,以后抓波形能一眼看出问题出在第几个字节。