3. I2C地址与寻址:7位地址格式、10位地址格式、广播地址、保留地址、地址冲突检测

I2C总线采用从设备地址来区分挂在同一条总线上的多个器件。主设备在每次通信前必须先发送目标从设备的地址,并附带读写标志位。理解地址格式、特殊地址以及冲突检测机制,是编写正确I2C设备驱动的基石。

3.1 7位地址格式

7位地址是I2C最常用的地址模式,地址范围从 0x00 到 0x7F(共128个地址),但其中一部分被保留用于特殊用途,实际可用地址约为112个。

7位地址传输时序:主设备在起始条件(S)后,先发送地址字节的高7位(A6~A0),最低位为读写标志位(R/W)。

关键点: 7位地址在总线上实际占用1个字节(8位),其中高7位为地址,最低位为方向位。0表示写,1表示读。
驱动开发提示: 在Linux内核中,struct i2c_client 的 addr 字段存储的是7位地址(左对齐,范围0~127)。设备树中通常使用 reg = <0x48>; 直接指定7位地址值。
7位地址格式 — 地址字节结构 S A6 A5 A4 A3 A2 A1 A0 R/W ACK 起始条件 7位从设备地址 (MSB first) 0=写
1=读 从设备
应答
示例:地址 0x48 (A6~A0 = 1001000) → 地址字节 = 0x90 (写) / 0x91 (读)

3.2 10位地址格式

10位地址格式扩展了地址空间,允许挂接更多从设备。它使用两个字节来传输地址,第一个字节的高5位固定为 11110,作为10位地址的标志。

字节 位 含义
第1字节 (高8位) Bit[7:3] = 11110
Bit[2] = A9
Bit[1] = A8
Bit[0] = R/W
10位地址标志 + 地址高2位 + 读写方向
第2字节 (低8位) Bit[7:0] = A7~A0 地址低8位
兼容性注意: 并非所有I2C从设备都支持10位地址。在驱动开发中,如果从设备不支持10位地址,主设备发送 11110xx 头字节时,从设备不会应答,导致通信失败。Linux内核通过 I2C_CLIENT_TEN 标志来标识10位地址设备。
10位地址格式 — 双字节传输 S 11110 (标志) A9 A8 R/W ACK A7 A6 A5 A4 A3 A2 A1 A0 ACK 地址高2位 地址低8位 示例:10位地址 0x2A3 → 第1字节 = 11110 10 0 (写) = 0xF0,第2字节 = 10100011 = 0xA3

3.3 广播地址与保留地址

I2C规范定义了一些特殊地址,用于总线管理和通用调用。这些地址不能分配给具体的从设备。

地址 (7位) 名称 用途 驱动注意事项
0x00 广播地址 / 通用调用地址 主设备向所有从设备发送广播消息。从设备可选择性应答。 驱动中通常忽略广播;若需响应,需在 probe 中特殊处理。
0x01 起始字节地址 用于低速MCU检测总线起始条件。 现代I2C控制器很少使用,驱动无需处理。
0x02 CBUS地址 保留给CBUS兼容设备。 Linux I2C子系统不支持CBUS协议。
0x03 保留 未来扩展用途。 驱动不应使用。
0x04 ~ 0x07 高速模式主设备代码 用于高速模式(Hs-mode)的地址分配。 标准驱动无需关注,由I2C控制器硬件处理。
0x78 ~ 0x7F 保留地址 10位地址格式的预留范围。 7位模式下不可用。
驱动开发关键: 在注册 i2c_board_info 或设备树节点时,必须确保 reg 属性不落在保留地址范围内。Linux内核在 i2c-core-base.c 中会校验地址合法性,非法地址会导致 probe 失败。
I2C地址空间分配图 (7位地址) 0x00~0x07 保留/广播 0x08 ~ 0x77 (112个可用地址) 标准从设备地址范围 0x78~0x7F 10位地址预留 地址分配原则 • 广播地址 (0x00):所有从设备都应监听,但可选择性不应答。 • 保留地址 (0x01~0x07, 0x78~0x7F):驱动开发中禁止使用。 • 可用地址 (0x08~0x77):实际设备地址,常见如 0x48 (温度传感器)、0x3C (OLED)。

3.4 地址冲突检测

当同一条I2C总线上挂接了多个相同地址的从设备时,会发生地址冲突。这会导致主设备无法正确寻址目标设备,通信行为不可预测。

3.4.1 冲突场景

  • 硬件设计冲突: 两个相同型号的传感器使用了相同的地址引脚配置(如ADDR引脚都接GND)。
  • 地址复用: 不同厂商的设备恰好使用了相同的7位地址(如某些温度传感器和加速度计都默认使用 0x48)。
  • 总线扩展: 通过I2C多路复用器(如PCA9548)切换子总线时,子总线上的地址重叠。

3.4.2 检测方法

方法 操作 驱动实现
总线扫描 主设备依次发送每个地址,检查是否有ACK应答。 使用 i2c_smbus_read_byte() 探测;Linux提供 i2cdetect 工具。
设备ID读取 向疑似地址发送读ID命令,比对返回的设备ID寄存器值。 在 probe 函数中调用 i2c_smbus_read_word_data() 读取ID。
时序冲突检测 监测总线上的SCL/SDA电平异常(如多设备同时拉低SDA)。 由I2C控制器硬件检测,驱动通过 -EAGAIN 或 -EREMOTEIO 错误码感知。
驱动开发最佳实践: 在 probe 函数中,不要仅依赖地址匹配就认为设备存在。应通过读取设备ID寄存器或执行特定握手命令来二次确认设备身份,避免因地址冲突导致驱动绑定到错误的设备上。
地址冲突检测流程 I2C主设备 (Master) I2C总线 (SCL / SDA) 从设备 A 地址: 0x48 从设备 B 地址: 0x48 (冲突!) ⚠ 冲突检测与处理步骤 1. 主设备发送地址 0x48 + 写位 → 两个从设备都应答 (ACK),SDA被同时拉低。 2. 主设备检测到总线异常 (SDA无法正常释放) → 返回 -EREMOTEIO 错误。 3. 驱动应记录错误日志,并尝试使用 i2c_mux 或 地址重映射 解决冲突。

3.5 Linux内核中的地址处理

在Linux I2C驱动框架中,地址处理涉及以下几个关键点:

/* i2c_client 结构体中的地址字段 */
struct i2c_client {
    unsigned short addr;    /* 7位地址 (0~127) 或 10位地址 */
    unsigned short flags;   /* 包含 I2C_CLIENT_TEN 等标志 */
    // ... 其他字段
};

/* 设备树中指定地址示例 */
&i2c1 {
    status = "okay";
    clock-frequency = <100000>;

    temperature_sensor: tmp102@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;          /* 7位地址 0x48 */
    };

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;          /* 7位地址 0x50 */
        pagesize = <8>;
    };
};

/* 内核地址合法性检查 (i2c-core-base.c) */
int i2c_check_addr_validity(unsigned int addr, unsigned short flags)
{
    if (flags & I2C_CLIENT_TEN) {
        /* 10位地址范围: 0 ~ 1023 */
        if (addr > 0x3FF)
            return -EINVAL;
        return 0;
    }
    /* 7位地址范围: 0x08 ~ 0x77 (排除保留地址) */
    if (addr < 0x08 || addr > 0x77)
        return -EINVAL;
    return 0;
}
常见驱动错误: 初学者容易将设备树中的 reg 值写为8位地址(如 0x90 代替 0x48)。内核期望的是7位地址,而不是左移后的8位地址字节。错误配置会导致 i2c_check_addr_validity 返回 -EINVAL,设备无法被正确绑定。

3.6 本章小结

  • 7位地址是最常用的格式,范围 0x08~0x77,驱动中直接使用7位值。
  • 10位地址通过两个字节传输,第一个字节高5位固定为 11110,需要设置 I2C_CLIENT_TEN 标志。
  • 广播地址 (0x00) 和 保留地址 (0x01~0x07, 0x78~0x7F) 不能分配给具体设备。
  • 地址冲突会导致通信失败,驱动应通过读取设备ID等方式进行二次确认。
  • Linux内核提供 i2c_check_addr_validity() 进行地址合法性校验,设备树中的 reg 必须使用7位地址。