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被另一个主机拉低了,它就检测到冲突,自动退出。说白了,就是「低电平优先」。

仲裁的规则:

  1. 所有主机同时产生自己的SCL时钟
  2. 每个主机在SDA上发送自己的数据位
  3. 如果某个主机发送1(高电平),但检测到SDA是0(低电平),它就输了,立即释放总线
  4. 赢的主机继续完成通信

重要:仲裁过程中不会丢失数据,也不会破坏任何一帧。输掉的主机可以在总线空闲后重试。这就是I2C优雅的地方——硬件自动处理冲突,软件几乎不用操心。

知识体系总览

下面这张图把I2C协议的核心脉络串起来了。我建议你保存下来,调试时对照着看。

I2C协议知识体系 物理层 SDA + SCL 两根线 开漏输出 + 上拉电阻 多设备共享总线 100kHz / 400kHz / 1MHz 通信协议 起始条件:SCL高→SDA下降沿 停止条件:SCL高→SDA上升沿 数据帧:8位 + ACK/NACK MSB first,字节传输 写操作:地址+寄存器+数据 地址与仲裁 7位地址:0x00~0x7F 10位地址:扩展模式 广播地址:0x00 仲裁:低电平优先 冲突检测 + 自动退让 核心要点 物理层决定总线能力 → 协议层定义通信规则 → 地址与仲裁解决多设备冲突 I2C写操作时序示意 SCL SDA S 地址 R/W ACK 数据 ACK P S = START P = STOP R/W = 0(写) / 1(读)

小结

I2C协议说复杂也复杂,说简单也简单。物理层记住开漏+上拉,协议层记住起始/停止+ACK,地址层记住7位为主+低电平优先仲裁。这三层搞明白了,I2C基本就通了。

我个人习惯是把I2C的时序图打印出来贴在工位上。调试时对着图看波形,哪里不对一眼就能发现。你试试看,比光看手册管用多了。