I2C协议详解:起始条件、停止条件、数据有效性、应答机制与时钟同步

各位同学,咱们今天来聊聊I2C协议最核心的几个基础动作。说实话,我见过不少工程师,写I2C驱动能跑,但一问到起始条件和停止条件的具体时序,就开始含糊了。这不行。底层的东西不扎实,后面做安全通信、防攻击设计,你连门都摸不着。

这一节,我把I2C协议的五个关键机制掰开揉碎了讲:起始条件、停止条件、数据有效性、应答机制、时钟同步。每个我都会结合项目里踩过的坑来说。

2.1 起始条件(S)与停止条件(P)

先看最基础的。I2C总线空闲时,SCL和SDA都是高电平。这是规矩。

起始条件(Start Condition):SCL为高电平时,SDA从高电平切换到低电平。
停止条件(Stop Condition):SCL为高电平时,SDA从低电平切换到高电平。

说白了,就是SCL在高电平期间,SDA跳变一次。往哪跳?往下跳是开始,往上跳是结束。

关键点:起始条件和停止条件都是由主机产生的。从机只能被动响应,不能主动发起。

我记得有一次调试一个温湿度传感器,死活读不到数据。示波器一抓,发现起始条件之后SDA的电平没拉低到位——代码里SCL和SDA的延时没配好,导致从机没识别到起始信号。嗯,从那以后我写I2C驱动,第一件事就是拿示波器看起始条件的边沿质量。

另外,I2C还有一个重复起始条件(Repeated Start,Sr)。它的时序跟起始条件一模一样,但用在一次传输中间——不先发停止条件,直接再发一次起始条件。这个在读写切换的场景里非常常见,比如先写寄存器地址,再读数据。

我的习惯:能用重复起始条件就别先发Stop再发Start。少一次总线释放,少一次被其他设备抢占总线的风险。尤其是在多主机的系统里,这个细节能省不少麻烦。

2.2 数据有效性

数据怎么才算有效?I2C的规矩很简单:SCL为高电平时,SDA上的数据必须稳定。SDA只能在SCL为低电平时改变。

你想想看,如果SCL高电平的时候SDA变了,那会被识别成起始条件或停止条件。所以数据有效性的核心就是一句话:SCL高电平采样,SCL低电平换数据。

每个数据字节是8位,从最高位(MSB)开始传输。我刚开始学的时候老搞反,以为从LSB开始,结果读出来的数据全是反的。后来养成习惯,看数据手册时先确认位序。

阶段 SCL状态 SDA行为 说明
数据建立 低电平 允许变化 主机或从机更新数据位
数据采样 高电平 必须稳定 接收方读取SDA电平
起始/停止 高电平 发生跳变 SDA跳变表示S或P

这里有个坑:如果SCL高电平期间SDA因为干扰发生了毛刺,接收方会误判成起始或停止条件。我在一个工业现场的项目里遇到过,电机启动时电磁干扰直接把I2C总线干废了。后来加了软件滤波和硬件上拉电阻调整才稳住。

2.3 应答机制(ACK/NACK)

I2C的应答机制,是它可靠性的基石。每发送完一个字节(8位数据),接收方必须在第9个SCL时钟周期拉低SDA,表示应答(ACK)。如果接收方不拉低,SDA保持高电平,那就是非应答(NACK)。

应答是谁发的?

  • 主机发送地址或数据后:从机发ACK,表示“我收到了,继续”。
  • 主机接收数据后:主机发ACK,表示“我还要继续读”;主机发NACK,表示“够了,停止”。

注意:如果从机因为忙或者出错没来得及拉低SDA,主机检测到NACK后应该立即发送停止条件,然后重试。我曾经见过一个新手写的驱动,检测到NACK后还继续发数据,结果总线直接锁死。

我个人习惯在写驱动时,把ACK/NACK的检测单独封装成一个函数。每次传输完一个字节,先检查SDA电平,再决定下一步。这样代码结构清晰,调试也方便。

2.4 时钟同步与仲裁

时钟同步是多主机I2C系统里最巧妙的设计。说白了,就是多个主机同时想控制总线时,怎么协调。

I2C的SCL线是“线与”结构——任何一个设备拉低SCL,整条线就是低电平。时钟同步的原理是:所有主机同时产生自己的SCL时钟,但谁先拉低SCL,谁就决定了SCL的低电平时间;谁最后释放SCL,谁就决定了SCL的高电平时间。

结果就是,所有主机的时钟被同步到最慢的那个。

仲裁呢?发生在SDA上。多个主机同时发送数据时,如果某个主机试图拉高SDA,但检测到SDA实际是低电平(因为另一个主机在拉低),那它就输了,立即退出竞争。

仲裁的规则:谁先发送低电平,谁赢。赢了的主机继续传输,输了的主机转为从机模式,等待下一次机会。

我做过一个双主机的系统,两个MCU通过I2C共享一个EEPROM。刚开始没处理好仲裁逻辑,结果两个主机同时写数据时,数据全乱了。后来加了总线空闲检测和重试机制,才算稳定下来。

这里有个重要的点:起始条件和停止条件也会参与仲裁。如果两个主机同时发起始条件,谁先拉低SDA谁赢。如果同时发停止条件,那就看谁先释放SDA。

2.5 知识体系总览

下面这张图,把I2C协议这五个核心机制的关系画出来了。你仔细看,它们不是孤立的——起始条件启动传输,数据有效性保证采样正确,应答机制确保每字节可靠,停止条件结束传输,时钟同步和仲裁则解决多主机冲突。

I2C协议核心机制关系图 I2C总线传输 起始条件 (S) 数据有效性 应答机制 停止条件 (P) 时钟同步与仲裁 关键依赖关系 • 起始条件 → 数据有效性 → 应答机制 → 停止条件(顺序执行) • 时钟同步与仲裁贯穿整个传输过程,影响所有阶段 • 重复起始条件(Sr)可在不发送停止条件的情况下重新开始传输

2.6 实战中的几个要点

最后,我把自己这些年积累的几个要点列出来,你们写代码或者调板子的时候对照着看:

  • 起始条件保持时间:SCL高电平后,SDA拉低之前,需要至少4.7μs的建立时间(标准模式)。别省这点延时,省了就是给自己挖坑。
  • 停止条件后的总线空闲:停止条件发出后,需要至少4.7μs的总线空闲时间,才能发起下一次起始条件。有些从机需要这个时间来完成内部操作。
  • 应答超时处理:如果从机没发ACK,别死等。设置一个超时机制,比如等待10个SCL周期后还没检测到ACK,就主动发停止条件退出。
  • 时钟拉伸(Clock Stretching):有些从机在收到数据后,会拉低SCL来延长低电平时间,给自己争取处理时间。主机必须支持这个特性,否则会通信失败。

一个小技巧:调试I2C时,别光看逻辑分析仪的波形。把SCL和SDA的模拟电压也看一下——有时候电平没拉到标准值(比如SDA只拉到2.5V而不是0V),从机可能识别不到。我吃过这个亏,后来每次调I2C都带示波器。

好了,这一节的内容就到这里。起始条件、停止条件、数据有效性、应答机制、时钟同步——这五个东西是I2C协议的骨架。你把这五个搞透了,后面讲地址分配、多主机通信、安全协议,你才能跟得上。