4. I2C总线协议基础

I2C(Inter-Integrated Circuit)总线由 Philips 公司(现 NXP)于 1982 年开发,是一种同步、多主机、串行通信总线。它仅需两根线(SCL 时钟线、SDA 数据线)即可连接多个外设,是嵌入式系统中连接传感器、EEPROM、ADC/DAC、实时时钟等低速外设的事实标准。在 Zephyr OS 中,I2C 驱动是 i2c.h API 的核心实现对象,理解其物理层与时序是编写正确设备驱动的第一步。

4.1 物理层与电气特性

I2C 总线使用开漏(Open-Drain)输出结构,通过外部上拉电阻将 SCL 和 SDA 线拉至高电平。任何设备都可以将线拉低,实现“线与”逻辑。

参数标准模式 (Standard)快速模式 (Fast)高速模式 (Fast+ / HS)
最大时钟频率100 kHz400 kHz1 MHz / 3.4 MHz
总线电容限制400 pF400 pF100 pF / 400 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
关键提示: 开漏结构决定了 I2C 总线必须包含上拉电阻。在 Zephyr 设备树中,若未正确配置 pinctrl 的上拉属性,总线可能无法正常产生高电平,导致 SCL 或 SDA 线卡在低电平。

4.2 时序与信号

I2C 通信以 SCL 时钟为同步基准,SDA 数据在 SCL 低电平时变化,在 SCL 高电平时被采样。以下为基本信号单元:

  • 起始条件 (START): SCL 高电平时,SDA 由高变低。
  • 停止条件 (STOP): SCL 高电平时,SDA 由低变高。
  • 数据位传输: 每 8 位数据后跟一个应答位(ACK/NACK)。
  • 应答 (ACK): 接收方在第 9 个 SCL 周期将 SDA 拉低。
  • 非应答 (NACK): 接收方在第 9 个 SCL 周期保持 SDA 高电平。
时序避坑: 在快速模式(400 kHz)下,SCL 高电平最小保持时间(tHIGH)为 0.6 μs,低电平最小保持时间(tLOW)为 1.3 μs。若 Zephyr 驱动中 I2C 时钟分频配置不当,可能导致时序违规,从设备无响应。
I2C 基本时序图:起始、数据位、应答、停止 SCL (时钟线) SDA (数据线) START STOP D7 D6 D5 D4 D3 D2 D1 D0 ACK 数据在 SCL 高电平时被采样,在 SCL 低电平时变化

4.3 设备地址与寻址

I2C 使用 7 位或 10 位地址。7 位地址是最常见的形式,每个从设备在总线上拥有唯一地址。地址由器件类型标识和引脚配置共同决定。

地址类型地址范围保留地址典型应用
7 位地址0x00 – 0x7F0x00 (通用呼叫), 0x01 (起始字节), 0x04-0x07 (HS 模式)EEPROM, 传感器, ADC
10 位地址0x00 – 0x3FF0x00 – 0x07 (保留)复杂外设, 多路复用器
地址冲突警告: 在 Zephyr 设备树中,reg = <0x48> 表示 7 位地址。若总线上两个设备使用相同地址,必须通过硬件引脚(如 ADDR0、ADDR1)或 I2C 多路复用器(如 TCA9548A)解决冲突。

4.4 读写操作流程

I2C 通信以主从模式进行。主机产生时钟、发起 START 条件、发送从机地址和读写位,从机应答后传输数据。

4.4.1 写操作

  1. 主机发送 START 条件。
  2. 主机发送 7 位从机地址 + 写位(0)。
  3. 从机发送 ACK。
  4. 主机发送寄存器地址(内部偏移)。
  5. 从机发送 ACK。
  6. 主机发送数据字节,每字节后等待 ACK。
  7. 主机发送 STOP 条件。

4.4.2 读操作

  1. 主机发送 START 条件。
  2. 主机发送 7 位从机地址 + 写位(0)。
  3. 从机发送 ACK。
  4. 主机发送寄存器地址。
  5. 从机发送 ACK。
  6. 主机发送重复起始条件(RESTART)。
  7. 主机发送 7 位从机地址 + 读位(1)。
  8. 从机发送 ACK。
  9. 从机发送数据字节,主机发送 ACK(继续读)或 NACK(最后一字节)。
  10. 主机发送 STOP 条件。
I2C 读操作流程(含重复起始) START 地址 + W (0) ACK 寄存器地址 ACK RESTART 地址 + R (1) ACK 数据字节 ACK 数据字节 NACK STOP 读操作关键点 1. 先写寄存器地址,再通过 RESTART 切换为读方向 2. 主机在最后一字节发送 NACK,通知从机停止发送

4.5 多主机与仲裁

I2C 支持多个主机共享同一总线。多主机仲裁机制基于开漏结构的“线与”特性:当多个主机同时尝试驱动总线时,检测到 SDA 电平与自己输出不一致的主机将主动退出。

4.5.1 仲裁过程

  • 每个主机在发送地址或数据时,持续监控 SDA 电平。
  • 若某主机输出高电平但检测到低电平(另一主机拉低),则该主机失去仲裁,立即释放 SCL 和 SDA。
  • 赢得仲裁的主机继续通信,不丢失任何数据位。

4.5.2 时钟同步

多主机模式下,所有主机共同参与 SCL 时钟同步。每个主机在 SCL 低电平时开始计时,最先结束低电平周期的主机将 SCL 拉高。其他主机若未完成低电平周期,会强制保持 SCL 低电平,实现时钟拉伸(Clock Stretching)。

场景仲裁结果Zephyr 处理建议
两个主机发送相同地址数据内容决定胜负确保不同主机访问不同从机地址
主机与从机冲突从机不会主动发起传输,不会参与仲裁标准 I2C 设计,无需额外处理
时钟拉伸超时从机拉低 SCL 超过 35 ms 视为超时在 Zephyr 驱动中配置 I2C_TIMEOUT 或使用 KConfig 调整
I2C 多主机仲裁流程 主机 1 发送 0x48 主机 2 发送 0x49 SDA 线与检测 主机 1 输出 0,主机 2 输出 1 主机 1 赢得仲裁 主机 2 释放总线 仲裁核心原则 “谁先输出低电平,谁就赢得仲裁”——低电平在开漏总线上具有更高优先级 Zephyr 多主机场景需启用 CONFIG_I2C_MULTI_HOST 并实现仲裁超时处理

4.6 Zephyr 中的 I2C 协议抽象

在 Zephyr OS 中,I2C 协议细节被封装在 struct i2c_driver_api 中。驱动开发者无需手动处理 START/STOP/ACK 时序,而是通过以下 API 完成操作:

// Zephyr I2C 核心 API 示例
#include <zephyr/drivers/i2c.h>

const struct device *i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c1));
uint8_t buf[4];
uint16_t reg_addr = 0x10;

// 写操作:向从机地址 0x48 的寄存器 0x10 写入 4 字节
i2c_burst_write(i2c_dev, 0x48, reg_addr, buf, 4);

// 读操作:从从机地址 0x48 的寄存器 0x10 读取 4 字节
i2c_burst_read(i2c_dev, 0x48, reg_addr, buf, 4);

// 自定义事务:使用 i2c_transfer 实现重复起始
struct i2c_msg msgs[2];
msgs[0].buf = &reg_addr;
msgs[0].len = 1;
msgs[0].flags = I2C_MSG_WRITE;
msgs[1].buf = buf;
msgs[1].len = 4;
msgs[1].flags = I2C_MSG_READ | I2C_MSG_RESTART;
i2c_transfer(i2c_dev, &msgs[0], 2, 0x48);
Zephyr 最佳实践: 使用 i2c_burst_read/write 时,驱动内部自动处理寄存器地址发送和重复起始条件。若需更精细控制(如 10 位地址、无寄存器地址的纯数据流),应使用 i2c_transfer 并手动构造 struct i2c_msg。

4.7 本章小结

  • 物理层: 开漏 + 上拉电阻,SCL/SDA 两线制,支持多设备。
  • 时序: START → 地址 → ACK → 数据 → ACK → STOP,数据在 SCL 高电平时采样。
  • 地址: 7 位为主,10 位用于扩展;设备树中 reg 属性定义地址。
  • 读写: 写操作直接发送数据;读操作需先写寄存器地址,再通过 RESTART 切换方向。
  • 多主机仲裁: 基于线与逻辑,低电平优先;Zephyr 需启用 CONFIG_I2C_MULTI_HOST。