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协议里最容易被忽视、但也最容易出问题的地方。

应答流程:

  1. 发送方释放SDA(拉高)
  2. 接收方在第9个时钟脉冲的低电平期间拉低SDA(表示ACK)
  3. 如果接收方不拉低SDA(保持高电平),就是NACK
应答类型 SDA状态(第9个时钟) 含义
ACK(应答) 低电平 字节接收成功,可以继续发送
NACK(非应答) 高电平 接收失败,或传输结束

避坑指南:我曾经在驱动里忘了在发送完地址后检查ACK位。结果从设备根本没响应,我还傻乎乎地继续发数据。总线上一片混乱。记住:每次发送完一个字节,必须检查ACK。如果收到NACK,要么从设备不存在,要么它正忙,要么你发错了地址。

NACK在两种情况下是“正常”的:

  • 主设备作为接收方时,读完最后一个字节后发NACK,告诉从设备“别再发了”
  • 从设备不支持某个命令或寄存器地址时,会回NACK拒绝

2.5 完整通信时序图

下面这张图把起始条件、数据字节、应答位、停止条件串在一起。我建议你把它存下来,写驱动时对照着看。

I2C完整通信时序图 I2C完整通信时序图 SCL SDA START Bit7 Bit6 ... Bit0 ACK STOP 时序说明: 1. 总线空闲时,SCL和SDA均为高电平 2. SCL高电平时,SDA下降沿 → 起始条件 3. SCL低电平时,SDA变化;SCL高电平时,SDA被采样 4. 第9个时钟脉冲为应答位(ACK低电平 / NACK高电平) 5. SCL高电平时,SDA上升沿 → 停止条件

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传输,都会顺手很多。