1. I2C 总线协议基础
I2C(Inter-Integrated Circuit)总线是由 Philips 公司(现 NXP)在 1980 年代推出的一种同步、多主从、串行通信总线。它仅需两根信号线即可完成板级芯片间的数据交换,是嵌入式系统中使用最广泛的低速外设总线之一。理解 I2C 的物理层特性、信号时序、拓扑结构和地址机制,是编写正确、健壮的 I2C 设备驱动的第一步。
1.1 I2C 总线物理层特性
I2C 总线在物理层上采用开漏(Open-Drain)结合上拉电阻的架构。所有设备的 SCL(时钟线)和 SDA(数据线)引脚均为开漏输出,通过外部上拉电阻连接到电源电压 VDD。
| 参数 | 标准模式 (100 kHz) | 快速模式 (400 kHz) | 高速模式 (3.4 MHz) |
|---|---|---|---|
| 最大时钟频率 | 100 kHz | 400 kHz | 3.4 MHz |
| 总线最大电容 | 400 pF | 400 pF | 100 pF |
| 上拉电阻典型值 | 4.7 kΩ | 2.2 kΩ | 1.0 kΩ |
| 逻辑低电平 (VIL) | ≤ 0.3 × VDD | ≤ 0.3 × VDD | ≤ 0.3 × VDD |
| 逻辑高电平 (VIH) | ≥ 0.7 × VDD | ≥ 0.7 × VDD | ≥ 0.7 × VDD |
驱动开发要点: 在 Linux 内核中,I2C 适配器驱动负责控制 SCL 频率和时序。设备驱动通常不需要关心物理层上拉电阻值,但必须确保从设备支持主控器所配置的总线速率。不匹配的速率可能导致通信失败或数据损坏。
1.2 SCL 与 SDA 信号
SCL(Serial Clock)和 SDA(Serial Data)是 I2C 总线上仅有的两根信号线。所有数据传输都遵循严格的时序规则:
- 空闲状态: SCL 和 SDA 均为高电平(由上拉电阻拉高)。
- 起始条件(START): SCL 为高电平时,SDA 由高电平切换到低电平。表示一次传输开始。
- 停止条件(STOP): SCL 为高电平时,SDA 由低电平切换到高电平。表示一次传输结束。
- 数据采样: 数据在 SCL 的上升沿被采样,在 SCL 的低电平期间允许 SDA 变化。
- 应答位(ACK/NACK): 每传输 8 位数据后,接收方在第 9 个 SCL 周期拉低 SDA(ACK)或保持高电平(NACK)。
关键信号时序图: 起始条件、数据位传输和停止条件的波形关系如下所示。
1.3 I2C 总线拓扑结构
I2C 总线采用多主从总线拓扑,所有设备通过 SCL 和 SDA 两条线并联连接。每个设备都有一个唯一的地址,主控器通过地址选择通信对象。
- 主控器(Master): 发起总线传输、产生时钟信号、发送起始/停止条件的设备。一个 I2C 总线上可以存在多个主控器,通过仲裁机制解决冲突。
- 从设备(Slave): 被主控器寻址并响应传输的设备。从设备不产生时钟,只被动接收或发送数据。
- 总线仲裁: 当多个主控器同时尝试控制总线时,通过 SDA 线的“线与”特性进行仲裁。输出低电平的主控器赢得仲裁。
1.4 I2C 设备地址
每个 I2C 从设备都有一个7 位或 10 位的芯片地址。在 Linux 内核中,绝大多数设备使用 7 位地址。地址由设备制造商固定或通过引脚电平配置。
- 7 位地址: 范围 0x00 ~ 0x7F。其中 0x00 为通用呼叫地址,0x01 ~ 0x03 保留,0x04 ~ 0x77 可用,0x78 ~ 0x7F 为 10 位地址预留。
- 10 位地址: 范围 0x00 ~ 0x3FF。通过第一个字节的前 5 位(11110xx)标识。
- 读写位: 地址字节的最低位为 R/W 位。0 表示主控器写,1 表示主控器读。
常见驱动错误: 设备数据手册中给出的地址通常是 7 位地址(如 0x48),但有些手册会给出 8 位地址(左移 1 位后的值)。在 Linux 内核中,
i2c_client 的 addr 字段必须使用 7 位地址。务必确认手册地址格式,否则驱动将无法正确寻址设备。
1.5 数据传输流程
一次完整的 I2C 数据传输遵循以下步骤:
- 主控器发送起始条件(START)。
- 主控器发送 7 位从设备地址 + R/W 位。
- 从设备应答(ACK): 地址匹配的从设备拉低 SDA 表示应答。
- 数据传输: 主控器(写模式)或从设备(读模式)逐字节发送数据,每字节后跟一个应答位。
- 主控器发送停止条件(STOP)或重复起始条件(Repeated START)。
内核驱动视角: Linux 内核的 I2C 核心层封装了上述流程。驱动开发者只需填充
i2c_msg 结构体数组,指定地址、方向、缓冲区和长度,然后调用 i2c_transfer() 即可。无需手动操作 SCL/SDA 时序。
// Linux 内核 I2C 消息结构体定义
struct i2c_msg {
__u16 addr; // 从设备 7 位地址
__u16 flags; // 传输标志:I2C_M_RD 表示读,0 表示写
__u16 len; // 消息数据长度(字节数)
__u8 *buf; // 数据缓冲区指针
};
通过本章的学习,你已经掌握了 I2C 总线的物理层特性、信号时序、拓扑结构和地址机制。这些基础知识是后续编写 I2C 设备驱动、配置设备树和调试通信问题的前提。