1. I2C协议基础:物理层特性与协议层帧结构
做嵌入式这些年,我调试过的I2C设备少说也有上百种了。从温度传感器到EEPROM,从触摸屏到电源管理芯片,I2C几乎无处不在。但说实话,很多人对它的理解停留在「两根线、一主多从」这个层面。一旦波形异常、设备无应答,就抓瞎了。
这一章,咱们把I2C的底裤扒干净。物理层怎么工作的?协议层怎么通信的?搞懂了这些,后面用逻辑分析仪抓波形时,你才能一眼看出问题在哪。
1.1 物理层特性:SDA/SCL开漏与上拉电阻
I2C总线只有两根线:SDA(串行数据线)和SCL(串行时钟线)。但这两根线的驱动方式,跟SPI、UART完全不同——它们是开漏输出。
开漏是什么意思?
说白了,芯片的引脚只能把电平拉低到GND,不能主动拉高到VDD。要输出高电平,必须靠外部上拉电阻把线拉到VDD。
为什么会这样设计?我刚开始做硬件时也觉得奇怪。后来想明白了——开漏结构允许多个设备共用总线,而且不会打架。你想想看,如果两个设备同时输出,一个拉高一个拉低,那就短路了。但开漏结构下,谁想拉低就拉低,想输出高电平就释放总线,让上拉电阻去拉高。这就是I2C总线能挂多个设备的物理基础。
上拉电阻的取值很关键。我踩过这个坑——
我曾经在一个项目里用了10kΩ上拉电阻,总线长度30cm,速率400kHz。结果通信时好时坏,逻辑分析仪一看,SCL上升沿慢得像蜗牛爬。换成2.2kΩ后,问题立刻消失。
上拉电阻怎么选?记住这个经验公式:
| 总线速率 | 总线电容 | 推荐上拉电阻 | 我的经验 |
|---|---|---|---|
| 100kHz(标准模式) | <100pF | 4.7kΩ ~ 10kΩ | 板内通信用10kΩ没问题 |
| 400kHz(快速模式) | <100pF | 2.2kΩ ~ 4.7kΩ | 我习惯用2.2kΩ,余量大 |
| 1MHz(快速+模式) | <50pF | 1kΩ ~ 2.2kΩ | 注意走线要短,电容要小 |
选电阻时,还要考虑功耗。电阻越小,总线拉高时电流越大。比如VDD=3.3V、R=1kΩ,拉低时电流3.3mA,挂10个设备就33mA了。低功耗场景下,可以适当选大一点,但别超过10kΩ。
1.2 协议层帧结构:起始、停止、数据与应答
物理层搞清楚了,咱们来看协议层。I2C的帧结构其实很简洁,就几个要素:起始条件、从机地址、读写位、数据字节、应答位、停止条件。
我个人习惯把I2C通信比作打电话:
- 起始条件 = 拿起话筒,拨号
- 从机地址 = 拨电话号码
- 读写位 = 告诉对方「我找你说话」还是「我听你说」
- 数据字节 = 通话内容
- 应答位 = 对方说「收到」或「没听清」
- 停止条件 = 挂断电话
下面咱们逐个拆解。
起始条件(START)与停止条件(STOP)
这两个条件由主机产生,是I2C通信的「开关」。
- 起始条件:SCL为高电平时,SDA从高电平跳变到低电平
- 停止条件:SCL为高电平时,SDA从低电平跳变到高电平
注意看,这两个条件都发生在SCL高电平期间。正常数据传输时,SDA只能在SCL低电平时变化。所以起始和停止条件是一种「异常」状态,用来标记通信的开始和结束。
调试技巧:用逻辑分析仪抓波形时,先找起始条件。如果波形上SDA在SCL高电平时乱跳,那大概率是起始/停止条件被干扰了。我曾经遇到过上拉电阻虚焊,导致起始条件无法正常产生,从机根本不响应。
数据有效性:SCL高电平时采样
I2C的数据传输规则很简单:
- SCL低电平:SDA可以变化(发送方准备数据)
- SCL高电平:SDA必须稳定(接收方采样数据)
说白了,每个时钟脉冲传输1位数据。SCL上升沿之后、高电平期间,接收方读取SDA的电平。所以数据必须在SCL低电平时准备好,高电平时保持住。
这个规则看起来简单,但实际调试时经常出问题。我见过一个案例——
我曾经调试一块板子,从机总是返回错误数据。逻辑分析仪抓波形发现,从机在SCL上升沿之后才改变SDA电平,导致主机采到了错误位。后来查手册发现,从机对SCL上升沿的响应时间超标了。换了个批次的芯片就好了。
应答机制:ACK与NACK
I2C的应答机制是它的一大特色。每传输完一个字节(8位),接收方必须在第9个时钟周期给出应答:
- ACK(应答):接收方在第9个时钟拉低SDA,表示「收到,继续」
- NACK(非应答):接收方在第9个时钟释放SDA(保持高电平),表示「没收到」或「不想收」
应答位是I2C调试中最常遇到的问题。我总结了几种常见情况:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 从机地址后收到NACK | 地址错误、从机不存在、从机忙 | 核对地址、检查上电时序 |
| 数据字节后收到NACK | 从机不支持该命令、写保护 | 查手册、检查WP引脚 |
| 主机主动发NACK | 读操作结束、主机不想继续收 | 正常行为,无需处理 |
这里有个容易忽略的点:应答位是由接收方控制的。主机发送地址后,从机拉低SDA表示应答;主机发送数据后,从机拉低SDA表示应答;从机发送数据后,主机拉低SDA表示应答。谁接收谁应答,别搞反了。
1.3 完整帧结构:一次典型的I2C写操作
把上面这些要素串起来,一次完整的I2C写操作长这样:
START | 从机地址(7位) | R/W=0 | ACK | 寄存器地址(8位) | ACK | 数据(8位) | ACK | STOP
读操作稍微复杂一点,需要先写寄存器地址,再重新发起起始条件读:
START | 从机地址(7位) | R/W=0 | ACK | 寄存器地址(8位) | ACK |
RESTART | 从机地址(7位) | R/W=1 | ACK | 数据(8位) | NACK | STOP
注意读操作最后的NACK是主机发的,告诉从机「别发了,我要结束了」。然后主机再发停止条件。
1.4 知识结构总览
下面这张图把I2C协议的核心要素串起来了。我建议你把它存下来,调试时对照着看。
1.5 写在后面
I2C协议的基础就这些。物理层记住开漏+上拉,协议层记住起始、地址、数据、应答、停止这五个要素。搞懂了这些,你已经能看懂90%的I2C波形了。