2. I2C协议基础:数据帧格式、起始条件、停止条件、ACK/NACK机制

I2C协议,说白了就是两根线(SCL时钟线、SDA数据线)搞定多个设备之间的通信。很多刚入行的工程师觉得它简单,不就是发个地址、读个数据嘛。但我在项目中吃过不少亏——有时候示波器抓出来的波形看着对,设备就是不响应。问题往往就出在最基础的帧格式和应答机制上。

这一节,咱们把I2C协议的“骨架”拆开来看。你搞懂了起始条件、停止条件、数据帧怎么排列、ACK/NACK怎么玩,后面调试的时候才能一眼看出波形哪里不对劲。

2.1 起始条件与停止条件

I2C总线上,所有通信都由主设备发起。主设备想说话,必须先发一个“起始条件”。

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

这两个条件,是总线上所有设备同步的“起跑线”和“终点线”。没有它们,从设备根本不知道什么时候该听、什么时候该停。

我的习惯:调试时先用示波器抓一下SDA和SCL的起始/停止边沿。如果SDA在SCL高电平时有毛刺抖动,大概率是上拉电阻没配好,或者总线电容太大。我曾经遇到一块板子,起始条件总是被从设备忽略,查了半天发现是SDA线上的寄生电容导致边沿变缓,主设备发出的起始条件根本没达到逻辑阈值。

2.2 数据帧格式:地址+读写+数据

起始条件之后,主设备开始发送数据帧。标准I2C数据帧的格式是这样的:

  1. 7位从设备地址(或10位地址,咱们先讲7位)
  2. 1位读写标志:0表示写,1表示读
  3. 1位应答位(ACK):从设备拉低SDA表示收到
  4. 8位数据字节(可以连续发多个)
  5. 每个字节后跟1位应答位
  6. 停止条件结束通信

你想想看,整个通信过程就像一次“点名-应答-传纸条”的流程。主设备先喊名字(地址),从设备应一声(ACK),然后开始传数据。

关键点:地址字节的发送顺序是高位在前(MSB first)。很多新手在软件里写地址时,容易把位顺序搞反。我建议你写驱动时,直接用芯片手册给出的7位地址值,左移1位再拼上读写位,这样最保险。

2.3 ACK/NACK机制:谁在说话?

ACK/NACK是I2C协议的“握手信号”。每个字节(包括地址字节和数据字节)发送完后,接收方必须在第9个SCL时钟周期内拉低SDA(ACK)或保持高电平(NACK)。

场景 SDA状态(第9个时钟) 含义
地址匹配成功 从设备拉低SDA ACK:我在,继续
地址无匹配 SDA保持高 NACK:没这个设备
数据写入成功 从设备拉低SDA ACK:收到了
从设备忙/数据无效 SDA保持高 NACK:等一下/我不收
主设备读数据结束 主设备不拉低SDA NACK:别发了,停止

这里有个容易踩的坑:主设备在读取最后一个字节后,必须发送NACK,告诉从设备“够了,别发了”。如果你发了ACK,从设备会继续发下一个字节,导致总线卡死。

我曾经踩过的坑:有一次调试温度传感器,读出来的数据总是多一个字节。后来用逻辑分析仪一看,发现我在读最后一个字节时发了ACK而不是NACK。从设备以为我还要数据,又多发了一个字节,结果整个数据帧错位了。从那以后,我写读操作代码时,都会在循环里单独处理最后一个字节的应答位。

2.4 完整数据帧示例:写一个字节到EEPROM

咱们看一个实际例子。假设你要往地址为0x50的EEPROM(24C02)里写一个字节0xAB到地址0x10。完整的帧是这样的:

主设备发送:
[S] + 0x50<<1 + 0 (写) + [ACK] + 0x10 (地址) + [ACK] + 0xAB (数据) + [ACK] + [P]

波形展开:
SCL: ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
SDA: ▔▔▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▔▔
      S  1  0  1  0  0  0  0  0  A  C  K  ...  P

注意看:地址0x50左移1位变成0xA0,再加上写标志0,得到0xA0。很多芯片手册直接给出的是“设备地址+读写位”的完整字节,比如0xA0是写地址,0xA1是读地址。你写代码时直接用它就行,不用再左移。

2.5 知识结构图:I2C协议核心要素

下面这张图把I2C协议的基础框架画出来了。你可以把它当作调试时的“检查清单”。

I2C协议基础框架 起始条件 (S) SCL高时SDA下降沿 地址帧 (7位) MSB first + R/W位 应答位 (ACK) 第9个SCL时钟 数据字节 (8位) 可连续多个 应答位 (ACK) 每字节后跟1位 循环 停止条件 (P) SCL高时SDA上升沿 调试要点: 1. 起始条件后,第一个字节必须是地址+R/W,否则从设备不会响应 2. 每个字节后必须等待ACK,超时或NACK都要做异常处理 3. 读操作最后一个字节发NACK,写操作所有字节都等ACK 4. 停止条件后,总线释放,主设备才能发起下一次通信 5. 如果从设备一直NACK,先检查地址对不对,再查上拉电阻

2.6 常见故障与排查思路

搞懂了基础帧格式,咱们来看看实际调试中经常遇到的几个问题。

  • 总线卡死(SDA一直被拉低):某个从设备没有释放总线。解决办法是给SCL发9个时钟脉冲,让从设备完成当前操作,释放SDA。
  • 地址NACK:主设备发完地址后,SDA一直高。先检查地址对不对,再查从设备是否上电、复位引脚是否拉高。我遇到过一块板子,从设备的使能引脚没拉高,导致它一直“装死”。
  • 数据NACK:地址应答正常,但写数据时从设备返回NACK。常见原因是写保护引脚被拉高(比如EEPROM的WP引脚),或者从设备内部正在忙(比如Flash写操作需要时间)。
  • 时钟拉伸(Clock Stretching):从设备把SCL拉低,表示“我还没准备好,你等等”。很多主设备不支持这个特性,会直接超时报错。如果你用的从设备有时钟拉伸功能,记得确认主设备驱动是否兼容。
我的调试工具箱:逻辑分析仪是I2C调试的利器。我习惯把采样率设到4倍于SCL频率以上,触发条件设为起始条件。这样每次通信开始,分析仪自动抓波形,一眼就能看到地址、数据、ACK/NACK的时序对不对。比用示波器一根一根数脉冲高效多了。

嗯,I2C协议的基础就这些。起始条件、停止条件、地址帧、数据帧、ACK/NACK,这五个要素构成了所有I2C通信的骨架。你只要把这张图印在脑子里,后面调试的时候,看到波形就能快速定位问题出在哪个环节。