2. I²C通信协议详解:起始条件、停止条件、数据有效性、应答机制

各位同学,今天我们来啃I²C协议最核心的骨头。说实话,很多工程师调了几年I²C,遇到波形异常还是只会抓瞎——说白了就是协议底层没吃透。这一节我带你从波形图到寄存器,把起始、停止、数据有效性、ACK/NACK这四个基本动作彻底搞明白。

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

先看最基础的两个信号。I²C总线空闲时,SCL和SDA都是高电平(由上拉电阻拉高)。什么时候算一次传输开始?什么时候算结束?协议规定得很清楚。

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

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

我刚开始做驱动时,总觉得这个定义太简单,直到有一次在瑞芯微RK3568上调试一个温湿度传感器,示波器抓到的起始条件总是不对——原来是GPIO的 slew rate 设置太慢,SDA下降沿不够陡,主设备根本不认。嗯,硬件细节往往就卡在这种地方。

这里有个关键点:起始和停止条件只能由主设备产生。从设备永远不能主动拉低SDA来发起传输。你想想看,如果从设备也能随便拉总线,那不乱套了?

我的习惯:调试时先用示波器看SDA和SCL的静态电平。如果空闲时SDA不是高电平,先查上拉电阻和GPIO配置,别急着调软件。

2.2 数据有效性:SCL高电平时采样

数据怎么传?协议规定:SDA上的数据必须在SCL低电平时变化,在SCL高电平时被采样。换句话说,SCL高电平期间,SDA必须保持稳定。

为什么这么设计?说白了就是为了抗干扰。SCL高电平时采样,如果SDA有毛刺,只要不在采样窗口内,就不会被误判。我在一个项目中遇到过总线长度超过30厘米的情况,SCL高电平期间的SDA抖动特别大,最后不得不降低速率到50kHz才稳定——这就是数据有效性的物理限制。

我曾经踩过的坑:在RK3588上配置I²C时钟时,忘了检查SCL高电平的最小保持时间。结果传感器在高温下偶尔丢数据,查了两天才发现是SCL高电平太窄,从设备来不及采样。记住:数据有效性不仅看SDA,还要看SCL的占空比。

数据位的传输顺序是MSB first(最高位在前)。每个字节8位,之后必须跟一个应答位。下面这个表总结了数据有效性的核心规则:

信号 SCL低电平 SCL高电平
SDA 允许变化(数据更新) 必须稳定(数据采样)
SCL 从设备准备接收/发送 主设备采样SDA

2.3 应答机制:ACK与NACK

每个字节传完后,接收方必须给一个应答。这是I²C协议里最容易被忽视、也最容易出问题的地方。

ACK(应答):在第9个SCL时钟周期,接收方将SDA拉低。
NACK(非应答):在第9个SCL时钟周期,接收方保持SDA为高电平。

谁发应答?分两种情况:

  • 主设备发送数据给从设备:从设备发ACK/NACK。如果从设备忙不过来或地址不对,就发NACK。
  • 主设备读取从设备数据:主设备在每字节后发ACK,表示“我还要继续读”;最后一个字节发NACK,表示“够了,停止”。

我记得有一次调试一个音频编解码芯片,读寄存器总是返回0xFF。示波器一看,从设备明明发了数据,但主设备在应答位把SDA拉低了——我配置成了主设备发ACK,但读操作时主设备应该发NACK来结束传输。这种细节,手册上写得很清楚,但实际写代码时太容易搞反了。

核心原则:谁接收数据,谁发应答。主设备发送时,从设备应答;主设备接收时,主设备应答。

2.4 完整传输时序:从起始到停止

把上面几个概念串起来,一次完整的I²C传输是这样的:

  1. 主设备发送起始条件(S)
  2. 主设备发送7位从设备地址 + 1位读写位(R/W)
  3. 从设备发ACK(如果地址匹配)
  4. 主设备发送寄存器地址/数据(每字节后跟ACK)
  5. 如果是读操作,主设备切换为接收模式,从设备发送数据
  6. 主设备发送停止条件(P)

下面这张图把整个流程画清楚了:

I²C完整传输时序(主设备写入) 起始条件 S 从设备地址 R/W 位 ACK 数据字节 停止 P 每个数据字节后必须跟一个ACK/NACK位(第9个SCL时钟) 波形示意(简化) SCL SDA S 地址(7位) R/W ACK 数据(8位) P 注意:SDA在SCL高电平时必须稳定,只在SCL低电平时变化

2.5 常见问题与调试技巧

最后分享几个我在瑞芯微平台上实际遇到过的坑:

问题1:起始条件后无ACK

现象:发送从设备地址后,SDA在第9个时钟没有被拉低。
原因:地址错误、从设备未上电、总线被其他设备占用。
我的做法:先量从设备VDD,再量SDA/SCL波形,确认地址对不对。RK平台上经常有人把7位地址左移一位当8位发,结果地址对不上。

问题2:停止条件后总线仍被拉低

现象:停止条件发出后,SDA一直低电平。
原因:从设备没有释放SDA总线,或者主设备GPIO配置成了开漏输出但没拉高。
我曾经在RK3566上遇到过,原因是I²C控制器在发送停止条件时,SDA释放时序不对,最后在dts里加了 i2c-scl-falling-time-ns 参数才解决。