3. I2C协议层:开始条件、停止条件、数据位传输规则、ACK/NACK应答机制

好了,前面我们聊了I2C的物理层,搞清楚了SCL和SDA怎么接线,上拉电阻怎么选。现在咱们进入协议层——这才是真正好玩的地方。

说白了,协议层就是一套“握手暗号”。主设备和从设备之间,什么时候开始说话,什么时候结束,每一句话怎么说,对方听没听清楚,全得靠这套规则来保证。我当年第一次调I2C,以为只要把数据扔到总线上就行,结果示波器一看,波形乱成一团。嗯,从那以后我老老实实把协议层的每一条规则都背了下来。

3.1 开始条件(START Condition)

一切通信的开始,都得有个明确的信号。在I2C总线上,这个信号叫开始条件。

规则很简单:

  • SCL保持高电平时,SDA从高电平跳变到低电平。

只有主设备能发起开始条件。一旦总线上出现了这个信号,所有从设备都会知道:“嘿,有消息要来了,准备好接收。”

注意,开始条件必须是SDA在SCL高电平期间变化。SCL低电平时SDA怎么变都不算数。这个细节很多人第一次会忽略,结果仿真通过了,板子上死活跑不通。

核心要点: 开始条件是SDA在SCL高电平时的下降沿。唯一且不可替代。

3.2 停止条件(STOP Condition)

有开始就得有结束。停止条件就是告诉大家:“好了,话讲完了,总线释放了。”

规则也很明确:

  • SCL保持高电平时,SDA从低电平跳变到高电平。

停止条件同样由主设备发出。停止后,总线进入空闲状态,SDA和SCL都变回高电平,等待下一次开始。

我见过一个坑:有人在一次传输结束后忘了发停止条件,结果下一个开始条件发不出来,总线卡死在那了。所以写驱动时,每次传输完毕后一定要记住发STOP。

信号 SCL状态 SDA变化方向 谁发起
开始条件 高电平 下降沿(高→低) 主设备
停止条件 高电平 上升沿(低→高) 主设备

3.3 数据位传输规则

开始条件和停止条件之间的部分,就是数据的主体了。I2C的数据传输,遵循一个非常朴素的规则:SCL高电平时采样数据,SCL低电平时改变数据。

为什么?你想想看,如果数据和时钟同时变化,接收方根本分不清哪个才是有效的。所以协议规定:SDA只能在SCL低电平期间变化。SCL一拉高,SDA就必须稳定住,保持不动,这时候接收方去读SDA的电平。

每次传输一个字节(8位),从最高位MSB开始。意思是先发bit7,再发bit6……一直发到bit0。我习惯在脑子里把它想象成一个移位寄存器:每次时钟脉冲过来,移出去一位。

对了,还有一点:重复开始条件(Repeated START)。有时候主设备不想释放总线,但需要改变通信方向。比如在读操作之前,先发个地址,然后不发停止,再次发开始条件。这在协议里是允许的,而且很常用。我做过一个项目,需要连续读取传感器的多个寄存器,就用重复开始条件来避免总线被别的设备抢走。

一个小技巧: 如果是软件模拟I2C,建议在每次SCL拉高之前加一个短暂延时,确保SDA已经稳定。否则容易采样到毛刺。

3.4 ACK/NACK应答机制

数据发出去,对方收到了没有?总得有个反馈吧。I2C用了一个非常巧妙的应答机制。

主设备每发送完8位数据(一个字节)后,会释放SDA的控制权。然后在第9个时钟脉冲期间,从设备会把SDA拉低,表示“我收到了,请继续”。这个动作叫应答(ACK)。

如果从设备没有拉低SDA,也就是SDA保持高电平,那就表示“我没收到,或者我不想再收了”,这叫非应答(NACK)。主设备收到NACK后,通常会发送停止条件,结束本次传输。

应答规则:

  • ACK:SDA在第9个SCL脉冲期间为低电平。
  • NACK:SDA在第9个SCL脉冲期间为高电平。

应答由接收方发出。发送方只负责在第9个时钟周期释放SDA,并检查电平。

注意:地址字节发送后也需要等待应答。如果总线上没有设备响应这个地址,从设备就不会拉低SDA,这时候主设备收到NACK就得知道——要么地址写错了,要么设备没在线。

避坑指南: 我曾经遇到过一种情况,从设备处理数据需要一点时间,结果它来不及拉低SDA,导致主设备误判为NACK。解决方案是:在主设备发送完第8位后,适当延迟一点再拉高SCL,给从设备留出准备应答的时间。这就是所谓的“时钟拉伸”,有些从设备会主动拉低SCL来要求主设备等待。

3.5 完整的帧结构

把上面这些串起来,一个完整的I2C帧结构是这样的:

  1. 主设备发出开始条件
  2. 主设备发送7位从设备地址 + 1位读写位(0=写,1=读)
  3. 从设备发送应答位(ACK/NACK)
  4. 主设备发送/接收8位数据(多个字节)
  5. 每次数据字节后跟着应答位
  6. 主设备发出停止条件

读写位是紧跟在地址后面的第8位。0表示主设备要写数据给从设备,1表示主设备要从从设备读数据。这在后面章节我们会细讲。

I2C 协议帧结构 开始条件 7位从设备地址 + 1位读写位 ACK 8位数据 (可重复多字节) ACK 停止条件 图注: • 每个数据字节(8位)后紧跟1位应答位(ACK/NACK),总计9个SCL脉冲。 • 读写位:0 = 主写从读,1 = 主读从写。 • 重复开始条件可以在任何数据字节后出现,此时不发送停止条件。

3.6 应答的几种特殊情况

实际项目中,应答机制还会遇到几种特殊情况:

  • 从设备ACK后不响应:有时候从设备应答了,但后续数据处理不过来。这时候它会拉低SCL(时钟拉伸),逼主设备等它。主设备需要检测SCL电平,如果被拉低就继续等待。
  • 主设备主动发NACK:在读取模式下,主设备想结束传输,可以在最后一个字节之后不发ACK,而是发NACK。这表示“行了我不要了,结束吧”。然后主设备接着发送停止条件。
  • 广播地址(0x00):有些支持广播的设备,应答地址0x00。一般用得不多,但知道有这个功能就好。
经验之谈: 在写I2C驱动时,我一般把应答检测做成一个单独的函数。每次发送完地址或数据,都调这个函数去检查,如果连续3次NACK就直接报错退出。这样调试起来省心很多。