I2C协议基础:从物理层到仲裁机制
说实话,I2C这协议看起来简单,但坑不少。我最早接触它是在一个安全元件的项目里,当时觉得不就是两根线嘛,能有多复杂?结果调试了整整两天才把时序调对。今天咱们就把I2C的底裤扒干净,从物理层一路聊到仲裁机制。
I2C总线物理层:就两根线,但别小看它们
I2C总线只用两根线——SDA(串行数据线)和SCL(串行时钟线)。嗯,就两根。但这两根线是开漏输出,必须外接上拉电阻。
关键点:开漏结构意味着任何设备都可以把总线拉低,但没人能主动拉高。拉高靠的是上拉电阻。这就是I2C能实现多主设备仲裁的物理基础。
上拉电阻的取值很讲究。我见过不少新手随便焊个10kΩ上去,结果400kHz模式下波形惨不忍睹。给你个经验值:
| 总线电容 | 标准模式(100kHz) | 快速模式(400kHz) |
|---|---|---|
| ~50pF | 4.7kΩ | 2.2kΩ |
| ~100pF | 2.2kΩ | 1.0kΩ |
| ~200pF | 1.0kΩ | 470Ω |
我的习惯:做安全元件项目时,我一般先按2.2kΩ起步,然后用示波器看上升沿。如果上升沿太缓(超过时钟周期的10%),就换小一点的电阻。别盲目抄参考设计,板子不一样,电容也不一样。
I2C通信协议:起始、停止与数据帧
I2C的通信流程其实很清晰。咱们先看最核心的三个动作。
起始条件(START Condition)
SCL为高电平时,SDA从高电平跳变到低电平。这个下降沿告诉所有从设备:「注意,要开始通信了!」
停止条件(STOP Condition)
SCL为高电平时,SDA从低电平跳变到高电平。这个上升沿表示:「好了,结束。」
数据帧格式
每个字节8位,高位在前(MSB first)。每发完一个字节,接收方必须回一个ACK(低电平)或NACK(高电平)。
我刚开始做的时候老搞混:到底谁发ACK?规则很简单——
- 主机发送数据:从机每收完一个字节就回ACK
- 主机接收数据:主机每收完一个字节就回ACK,但最后一个字节回NACK告诉从机「够了,别发了」
我曾经踩过的坑:有一次从机死活不回ACK,我查了半天电路、时序、地址,最后发现是从机电源没起来。嗯,先检查电源,再怀疑协议——这是血的教训。
来看一个完整的写操作时序:
// I2C写操作帧格式(以7位地址为例)
// S = START, P = STOP, A = ACK, N = NACK
S | 从机地址(7位) | R/W=0 | A | 寄存器地址(8位) | A | 数据字节(8位) | A | P
// 实际波形上的样子(简化):
// SDA: 高→低 地址位 ACK 数据位 ACK 数据位 ACK 低→高
// SCL: 高 脉冲 脉冲 脉冲 脉冲 脉冲 脉冲 高
I2C地址与仲裁机制
7位地址 vs 10位地址
大多数I2C设备用7位地址。你想想看,7位能表示128个地址,但0x00是广播地址,0x01~0x07保留,实际可用的大概120个。10位地址是为了扩展,但用得不多。
地址怎么分配?每个设备厂商会分配一个固定的前缀,比如:
- AT24C02 EEPROM:0x50(1010000)
- PCF8574 I/O扩展:0x40(1000000)
- 安全元件(比如ATECC608A):0x60(1100000)
避坑指南:我曾经在一个项目里用了两个相同型号的传感器,结果地址冲突了。后来发现其中一个芯片的ADDR引脚可以改电平,把地址偏移一位才解决。选型时一定要确认地址引脚是否可配置。
仲裁机制:多主设备怎么不打架?
I2C支持多个主机同时发起通信。你可能会问:「两根线怎么同时发数据?」
答案是:谁先拉低谁赢。
仲裁发生在SDA线上。多个主机同时发送数据时,每个主机都会监控SDA的实际电平。如果某个主机想发高电平,但SDA被另一个主机拉低了,它就检测到冲突,自动退出。说白了,就是「低电平优先」。
仲裁的规则:
- 所有主机同时产生自己的SCL时钟
- 每个主机在SDA上发送自己的数据位
- 如果某个主机发送1(高电平),但检测到SDA是0(低电平),它就输了,立即释放总线
- 赢的主机继续完成通信
重要:仲裁过程中不会丢失数据,也不会破坏任何一帧。输掉的主机可以在总线空闲后重试。这就是I2C优雅的地方——硬件自动处理冲突,软件几乎不用操心。
知识体系总览
下面这张图把I2C协议的核心脉络串起来了。我建议你保存下来,调试时对照着看。
小结
I2C协议说复杂也复杂,说简单也简单。物理层记住开漏+上拉,协议层记住起始/停止+ACK,地址层记住7位为主+低电平优先仲裁。这三层搞明白了,I2C基本就通了。
我个人习惯是把I2C的时序图打印出来贴在工位上。调试时对着图看波形,哪里不对一眼就能发现。你试试看,比光看手册管用多了。