2. I2C协议时序基础:起始条件、停止条件、数据有效性、字节格式与ACK/NACK机制

各位同学,咱们今天聊I2C协议最核心的东西——时序。说白了,就是总线上那些高低电平怎么跳变,主设备和从设备怎么“对话”。我刚开始搞驱动时,总觉得时序图看着头疼,后来发现只要抓住几个关键点,这东西其实挺直观的。

2.1 起始条件与停止条件

I2C总线在空闲时,SCL和SDA都是高电平。这是“静默”状态。任何通信,都得先打破这个静默。

起始条件(S): SCL为高电平时,SDA从高电平切换到低电平。

停止条件(P): SCL为高电平时,SDA从低电平切换到高电平。

你想想看,这两个条件都是SCL在高电平时发生的。为什么?因为数据采样也是在SCL高电平时,所以起始和停止必须“插空”在SCL高电平期间改变SDA,才能被所有设备识别到。

我在项目中遇到过一个问题:某个从设备偶尔不响应,抓波形发现是主设备在发送停止条件时,SDA上升沿太慢,导致从设备没识别到。后来调整了上拉电阻的阻值,问题就解决了。嗯,这里要注意,上拉电阻不是随便选的,它直接影响边沿斜率。

条件 SCL状态 SDA变化 说明
起始(S) 高电平 高 → 低 通信开始,总线被占用
停止(P) 高电平 低 → 高 通信结束,总线释放
重复起始(Sr) 高电平 高 → 低 不停止直接开始新传输

个人习惯: 我写驱动时,会把起始和停止条件封装成两个内联函数。这样代码读起来清晰,而且方便调试时加断点。

2.2 数据有效性

I2C协议规定:SCL为高电平时,SDA必须保持稳定;SCL为低电平时,SDA允许变化。

这个规则很关键。你想想,如果SCL高电平时SDA乱跳,从设备就会误判数据。所以主设备必须在SCL低电平时准备好下一个bit,然后在SCL高电平时让从设备采样。

我见过一些新手写的软件模拟I2C,在SCL高电平时去改变SDA,结果通信时好时坏。说白了,就是时序没卡对。

我曾经踩过的坑: 在STM32上用GPIO模拟I2C时,忘了加延时,SCL高电平持续时间太短,从设备来不及采样。后来查手册才发现,标准模式下SCL高电平最小时间是4微秒。别小看这几微秒,差一点都不行。

2.3 字节格式与数据传输

I2C传输数据时,是以字节为单位的。每个字节8位,高位在前(MSB first)。

传输流程是这样的:

  1. 主设备发送起始条件
  2. 主设备发送7位从设备地址 + 1位读写位(R/W)
  3. 从设备回复ACK
  4. 主设备发送/接收数据字节(每字节后跟ACK)
  5. 主设备发送停止条件

这里有个细节:地址是7位的,但实际发送时是8位——高7位是地址,最低位是读写方向。0表示写,1表示读。

位序 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
内容 A6 A5 A4 A3 A2 A1 A0 R/W
示例 1 1 0 1 0 0 0 0

上面这个例子,地址是0x68(1101000),R/W=0表示写操作。所以发送的字节是0xD0。

2.4 ACK/NACK机制

这是I2C协议的“握手”环节。每传输完一个字节(8位),接收方必须在第9个SCL时钟周期内拉低SDA,表示“收到了”。这就是ACK。

如果接收方没拉低SDA(保持高电平),那就是NACK,表示“没收到”或“不想收”。

ACK/NACK规则:

  • 主设备发送地址后,从设备必须回复ACK,否则通信终止
  • 主设备发送数据后,从设备回复ACK表示可以继续发
  • 主设备接收数据后,主设备回复ACK表示“我还要读”,回复NACK表示“够了,停止”

我建议你在调试时,重点看ACK位。很多通信问题都是卡在这里——地址不对、从设备没上电、总线被拉死,都会导致NACK。

调试技巧: 用逻辑分析仪抓波形时,先看起始条件对不对,再看地址和ACK。如果地址对了但没ACK,八成是从设备没响应。如果地址错了,查查你的地址是不是左移了一位。

2.5 完整时序流程(SVG图)

下面这张图把整个I2C单字节写操作的时序画出来了。你可以对照着看,每个时钟周期发生了什么。

I2C单字节写操作时序图 I2C单字节写操作时序图 SCL SDA S 起始 地址 (7位) 从设备地址 R/W 读写位 ACK 从设备应答 数据 (8位) 寄存器地址或数据 P 停止 每个字节传输后,接收方在第9个SCL时钟周期拉低SDA作为应答

2.6 常见时序问题与避坑

搞I2C驱动,说白了就是跟时序打交道。我总结几个常见问题:

  • 起始条件不满足: SCL高电平时SDA没拉低,从设备不认。检查代码中GPIO操作的顺序。
  • ACK丢失: 从设备没拉低SDA。可能原因:地址错误、从设备忙、总线被其他设备占用。
  • 时钟拉伸: 从设备拉低SCL表示“我还没准备好”。主设备必须等待SCL变高才能继续。很多MCU的硬件I2C会自动处理,但软件模拟时容易忽略。
  • 总线死锁: 从设备把SDA拉低后不释放。我遇到过这种情况,最后只能复位从设备。预防措施是在驱动中加入超时处理。

我曾经踩过的坑: 有一次调试温度传感器,读回来的数据总是0xFF。抓波形发现,主设备发送读命令后,从设备回复了ACK,但数据字节全是1。后来查手册才知道,这个传感器在转换期间不响应读请求,需要先写寄存器触发转换,等一段时间再读。嗯,不看手册就写驱动,迟早要还的。

2.7 小结

这一章的内容,是I2C协议的“语法”。起始条件、停止条件、数据有效性、字节格式、ACK/NACK,这五个概念构成了所有I2C通信的基础。你写驱动时,不管是用MCU的硬件外设还是GPIO模拟,最终都是在跟这些时序打交道。

我个人习惯是,拿到一个新器件,先看它的时序图,对照着协议标准过一遍。哪里是起始,哪里是ACK,数据什么时候采样,心里有数了再写代码。这样调试起来会省很多时间。