2. I2C协议基础:数据帧格式、起始条件、停止条件、ACK/NACK机制
I2C协议,说白了就是两根线(SCL时钟线、SDA数据线)搞定多个设备之间的通信。很多刚入行的工程师觉得它简单,不就是发个地址、读个数据嘛。但我在项目中吃过不少亏——有时候示波器抓出来的波形看着对,设备就是不响应。问题往往就出在最基础的帧格式和应答机制上。
这一节,咱们把I2C协议的“骨架”拆开来看。你搞懂了起始条件、停止条件、数据帧怎么排列、ACK/NACK怎么玩,后面调试的时候才能一眼看出波形哪里不对劲。
2.1 起始条件与停止条件
I2C总线上,所有通信都由主设备发起。主设备想说话,必须先发一个“起始条件”。
- 起始条件(S):SCL为高电平时,SDA从高电平切换到低电平。
- 停止条件(P):SCL为高电平时,SDA从低电平切换到高电平。
这两个条件,是总线上所有设备同步的“起跑线”和“终点线”。没有它们,从设备根本不知道什么时候该听、什么时候该停。
2.2 数据帧格式:地址+读写+数据
起始条件之后,主设备开始发送数据帧。标准I2C数据帧的格式是这样的:
- 7位从设备地址(或10位地址,咱们先讲7位)
- 1位读写标志:0表示写,1表示读
- 1位应答位(ACK):从设备拉低SDA表示收到
- 8位数据字节(可以连续发多个)
- 每个字节后跟1位应答位
- 停止条件结束通信
你想想看,整个通信过程就像一次“点名-应答-传纸条”的流程。主设备先喊名字(地址),从设备应一声(ACK),然后开始传数据。
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,从设备会继续发下一个字节,导致总线卡死。
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协议的基础框架画出来了。你可以把它当作调试时的“检查清单”。
2.6 常见故障与排查思路
搞懂了基础帧格式,咱们来看看实际调试中经常遇到的几个问题。
- 总线卡死(SDA一直被拉低):某个从设备没有释放总线。解决办法是给SCL发9个时钟脉冲,让从设备完成当前操作,释放SDA。
- 地址NACK:主设备发完地址后,SDA一直高。先检查地址对不对,再查从设备是否上电、复位引脚是否拉高。我遇到过一块板子,从设备的使能引脚没拉高,导致它一直“装死”。
- 数据NACK:地址应答正常,但写数据时从设备返回NACK。常见原因是写保护引脚被拉高(比如EEPROM的WP引脚),或者从设备内部正在忙(比如Flash写操作需要时间)。
- 时钟拉伸(Clock Stretching):从设备把SCL拉低,表示“我还没准备好,你等等”。很多主设备不支持这个特性,会直接超时报错。如果你用的从设备有时钟拉伸功能,记得确认主设备驱动是否兼容。
嗯,I2C协议的基础就这些。起始条件、停止条件、地址帧、数据帧、ACK/NACK,这五个要素构成了所有I2C通信的骨架。你只要把这张图印在脑子里,后面调试的时候,看到波形就能快速定位问题出在哪个环节。