I2C协议深度解析:物理层、时序、起始/停止条件、数据有效性、应答机制
各位同学,咱们今天来啃I2C协议这块硬骨头。说实话,我见过不少工程师,写I2C驱动时全靠复制粘贴,一旦遇到波形异常就抓瞎了。我个人习惯是,先把协议底层的物理特性和时序搞透,再去写代码。这样出了问题,你心里有底,知道该查哪里。
2.1 物理层:两条线怎么工作的?
I2C总线就两条线:SCL(时钟线)和SDA(数据线)。但你别小看这两条线,它们的设计其实很巧妙。
开漏输出 + 上拉电阻,这是I2C物理层的核心。什么叫开漏?说白了,就是芯片的引脚只能拉低电平,不能主动拉高。高电平靠外部的上拉电阻来实现。
关键点: 总线空闲时,SCL和SDA都是高电平。谁想占用总线,谁就把SDA拉低。
我在项目中遇到过一个问题:两个从设备地址冲突了,结果总线一直被拉低,主设备根本发不了起始条件。排查了半天,才发现是地址没配对。
| 参数 | 标准模式 (100kHz) | 快速模式 (400kHz) | 高速模式 (3.4MHz) |
|---|---|---|---|
| 上拉电阻典型值 | 4.7kΩ | 2.2kΩ | 1kΩ |
| 总线电容上限 | 400pF | 400pF | 100pF |
| 最大从设备数量 | 127(7位地址) | 127 | 127 |
选电阻的小技巧: 总线长度越长、设备越多,上拉电阻就要选小一点。我一般先按4.7kΩ起步,如果波形上升沿太缓,再换2.2kΩ。千万别用10kΩ以上,否则高速模式下波形会变形。
2.2 时序:时钟是怎么控制数据的?
I2C的时序其实不复杂,但有几个关键参数你必须记住。嗯,这里我建议你用示波器实际抓一下波形,比看一百遍文档都管用。
SCL低电平时间(tLOW) 和 SCL高电平时间(tHIGH) 决定了时钟频率。标准模式下,tLOW ≥ 4.7μs,tHIGH ≥ 4.0μs。
你想想看,如果主设备时钟跑得太快,从设备跟不上,就会丢数据。我曾经调试一个温湿度传感器,读出来的数据总是跳变,最后发现是主设备时钟频率设成了400kHz,但那个传感器只支持100kHz。
避坑指南: 同一个I2C总线上挂多个从设备时,时钟频率要以最慢的那个为准。我曾经因为一个老旧的EEPROM拖慢了整条总线,排查了整整一天。
2.3 起始条件与停止条件
这两个条件是I2C通信的“开关”。没有它们,总线上的设备不知道什么时候开始、什么时候结束。
- 起始条件(S): SCL为高电平时,SDA从高电平跳变到低电平。
- 停止条件(P): SCL为高电平时,SDA从低电平跳变到高电平。
说白了,起始条件和停止条件都是SDA在SCL高电平期间发生的跳变。而正常数据传输时,SDA只能在SCL低电平期间变化。这个区别很重要,你写驱动时一定要保证时序正确。
注意: 起始条件之后,总线就被主设备占用了。直到停止条件发出,总线才释放。如果主设备挂了,没发停止条件,总线就死锁了。我遇到过这种情况,最后只能硬件复位。
2.4 数据有效性:什么时候读数据?
I2C协议规定:数据在SCL高电平期间必须保持稳定。也就是说,从设备只能在SCL低电平期间采样数据,在SCL高电平期间读取。
为什么会这样?因为SDA在SCL低电平时可以变化,高电平时必须稳定。这样从设备只需要在SCL的上升沿或高电平期间锁存数据就行了。
我记得刚学I2C时,总搞不清什么时候发数据、什么时候收数据。后来总结了一句话:SCL高电平读,SCL低电平写。简单吧?
2.5 应答机制:确认收到没?
I2C的应答机制是它的一大特色。每传输一个字节(8位)数据后,接收方必须发送一个应答位(ACK)或非应答位(NACK)。
- ACK(应答): 第9个SCL时钟周期,接收方将SDA拉低,表示“收到了,继续发”。
- NACK(非应答): 第9个SCL时钟周期,接收方释放SDA(保持高电平),表示“没收到”或“不想收了”。
实际应用: 读传感器数据时,主设备在最后一个字节发NACK,然后发停止条件。这样从设备就知道数据发完了,别再发了。我刚开始写驱动时忘了发NACK,结果从设备一直发数据,总线乱成一团。
2.6 完整通信流程
一个完整的I2C通信流程是这样的:
- 主设备发起始条件(S)
- 主设备发7位从设备地址 + 1位读写位(0=写,1=读)
- 从设备发ACK应答
- 主设备发寄存器地址(如果是写操作)
- 从设备发ACK应答
- 数据传输(每字节后跟ACK/NACK)
- 主设备发停止条件(P)
下面这张图把整个流程画清楚了,你对照着看:
2.7 常见问题与调试技巧
最后,我分享几个实际调试中遇到的问题,你以后肯定也会碰到:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 总线一直为低 | 某个设备拉死了总线 | 逐个断开从设备,找到问题设备 |
| 没有ACK应答 | 地址错误或设备未上电 | 用示波器抓地址波形,核对地址 |
| 数据错位 | 时钟频率太快 | 降低SCL频率,或加长上升时间 |
| 随机数据跳变 | 上拉电阻太大 | 换小阻值上拉电阻 |
重要提醒: 调试I2C时,示波器是你的好朋友。别光看代码,波形会告诉你真相。我曾经花了两天查一个bug,最后发现是PCB走线太长,导致信号反射。换了个短一点的飞线就好了。