1. I2C总线概述:历史、物理层与多主机仲裁
I2C(Inter-Integrated Circuit)总线由飞利浦半导体(现恩智浦)于1982年推出,最初用于电视接收机内部各IC之间的低速通信。其设计目标是以最少的引脚(仅需两根线)实现多主多从、同步、半双工的数据传输。经过40余年发展,I2C已成为嵌入式系统中最广泛使用的板级总线之一,被纳入几乎所有MCU和SoC的外设标准。
核心设计哲学: 用两根线(SDA + SCL)解决板级多设备互联,支持多主机仲裁,无需片选信号,地址由软件决定。
1.1 物理层特性:SDA与SCL
I2C总线仅使用两条双向信号线:
- SDA(Serial Data Line):串行数据线,用于传输地址、数据和控制位。
- SCL(Serial Clock Line):串行时钟线,由主机产生,同步所有数据传输。
两条线均为开漏(Open-Drain)输出结构,通过外部上拉电阻接至VDD(通常为3.3V或5V)。当总线空闲时,SDA和SCL均保持高电平。
为什么用开漏? 开漏结构允许多个设备同时驱动同一根线而不发生短路——任何设备拉低总线时,总线即被拉低;只有所有设备都释放(高阻),总线才被上拉至高电平。这是多主机仲裁和时钟拉伸的物理基础。
电气连接标准
| 参数 | 标准模式 (100 kHz) | 快速模式 (400 kHz) | 高速模式 (3.4 MHz) |
|---|---|---|---|
| 最大SCL频率 | 100 kHz | 400 kHz | 3.4 MHz |
| 总线最大电容 | 400 pF | 400 pF | 100 pF |
| 上拉电阻典型值 | 4.7 kΩ | 2.2 kΩ | 1.0 kΩ |
| 最大从设备数量 | 127(7位地址) | 127 | 127 |
| VIL 最大 | 0.3 × VDD | 0.3 × VDD | 0.3 × VDD |
| VIH 最小 | 0.7 × VDD | 0.7 × VDD | 0.7 × VDD |
上拉电阻选择关键: 电阻过小会导致功耗增大、上升沿变缓;电阻过大则无法在SCL周期内将总线拉至高电平。实际设计中需根据总线电容和SCL频率计算,常用公式:Rp(min) = (VDD - VOL(max)) / IOL,Rp(max) = tr / (0.8473 × Cb)。
1.2 电气连接拓扑
I2C总线采用总线型拓扑,所有设备的SDA引脚并联至SDA线,SCL引脚并联至SCL线。每个设备都有一个唯一的7位或10位地址。主机通过地址选择从设备,无需片选信号。
1.3 多主机仲裁机制
I2C总线支持多主机(Multi-Master)工作模式,即多个主机可以同时尝试控制总线。仲裁机制确保最终只有一个主机赢得总线控制权,且不会丢失数据。
仲裁过程
- 总线空闲检测: 每个主机在启动传输前检测SDA和SCL是否均为高电平(空闲状态)。
- 同时启动: 多个主机可能在检测到空闲后几乎同时产生START条件(SCL高电平时SDA下降沿)。
- 逐位仲裁: 在SCL的每个时钟周期,多个主机同时向SDA发送数据位。如果某个主机试图拉低SDA(输出0),而另一个主机试图释放SDA(输出1),则SDA实际被拉低(0)。
- 失败主机退出: 发送1但检测到SDA为0的主机知道自己失去了仲裁,立即停止驱动SDA和SCL,转为从模式监听总线。
- 获胜主机继续: 赢得仲裁的主机继续完成剩余的数据传输,整个过程对从设备透明。
关键特性: I2C仲裁不会丢失数据——失败主机在仲裁过程中发送的地址或数据位与获胜主机一致,因此从设备不会收到错误信息。仲裁既可在地址阶段发生,也可在数据阶段发生。
仲裁与时钟同步
多主机仲裁与时钟同步(Clock Synchronization)紧密配合。多个主机可能产生不同频率的SCL时钟,通过以下方式同步:
- 所有主机在SCL高电平期间检测总线状态。
- 如果某个主机拉低SCL(开始低电平期),其他主机检测到SCL被拉低后,也立即进入低电平等待状态。
- 所有主机释放SCL后,SCL才被上拉至高电平,开始下一个时钟周期。
- 最终,SCL的低电平时间由最慢的主机决定,高电平时间由最快的主机决定。
1.4 总线状态与信号时序
I2C总线定义了几种关键信号状态,所有设备必须严格遵守:
| 信号 | SDA | SCL | 说明 |
|---|---|---|---|
| 空闲(Idle) | 高电平 | 高电平 | 总线空闲,可启动传输 |
| START条件 | 下降沿 | 高电平 | 主机启动传输,SDA在SCL高电平时由高变低 |
| STOP条件 | 上升沿 | 高电平 | 主机结束传输,SDA在SCL高电平时由低变高 |
| 数据有效 | 稳定数据 | 高电平 | SCL高电平时SDA数据被采样,必须保持稳定 |
| 数据变化 | 允许变化 | 低电平 | SCL低电平时SDA可以变化,准备下一位数据 |
| 应答(ACK) | 低电平 | 高电平 | 接收方在第9个SCL周期拉低SDA表示应答 |
| 非应答(NACK) | 高电平 | 高电平 | 接收方不拉低SDA,表示未应答或结束 |
时钟拉伸(Clock Stretching): 从设备可以在SCL低电平期间拉低SCL,迫使主机等待,直到从设备准备好继续传输。这是I2C实现流控的唯一机制,在低速传感器和EEPROM中常见。Linux I2C子系统通过
i2c_adapter 的 quirks 字段支持时钟拉伸检测。
1.5 地址与传输格式
I2C使用7位或10位地址。7位地址最多支持127个从设备(地址0x00为广播地址,0x01~0x7F可用)。每个传输帧包含:
- START条件
- 7位从设备地址 + 读/写位(R/W):R/W=0表示主机写,R/W=1表示主机读
- 应答位(ACK/NACK):从设备应答
- 数据字节(1~N字节):每个字节后跟随应答位
- STOP条件
// I2C传输帧结构(主机写示例)
START | 7位地址 | R/W=0 | ACK | 数据字节1 | ACK | 数据字节2 | ACK | ... | STOP
// I2C传输帧结构(主机读示例)
START | 7位地址 | R/W=1 | ACK | 数据字节1 | ACK | 数据字节2 | ACK | ... | NACK | STOP
Linux内核中的I2C消息结构: 在Linux I2C子系统中,一次传输由多个
struct i2c_msg 组成,支持组合传输(Combined Transfer),即先写寄存器地址再读数据,无需STOP/START间隔。