3. I2C设备地址:7位地址与10位地址格式、广播地址、保留地址、地址分配机制

I2C总线上的每个从设备都有一个唯一的地址,用于主设备进行寻址和通信。地址格式、保留地址以及分配机制是驱动开发中必须掌握的基础知识。本章节将详细解析7位与10位地址格式、广播与保留地址的规则,以及地址分配的实际机制。

3.1 7位地址格式

7位地址是I2C总线最常用的地址格式,也是绝大多数I2C设备所采用的寻址方式。在7位地址模式下,主设备发送一个字节的地址帧,其中高7位为设备地址,最低位为读写方向位(R/W)。

关键点: 7位地址空间理论上支持128个设备(0x00 ~ 0x7F),但其中16个地址被保留用于特殊用途,实际可用的地址为112个。

地址帧结构:

| Bit 7 (MSB) | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 (LSB) |
|-------------|-------|-------|-------|-------|-------|-------|-------------|
|   A6        |  A5   |  A4   |  A3   |  A2   |  A1   |  A0   |    R/W      |
  • A6 ~ A0: 7位设备地址,由器件引脚电平或内部配置决定。
  • R/W: 0表示主设备将要写入(写操作),1表示主设备将要读取(读操作)。

例如,一个7位地址为 0x48(二进制 1001000)的温度传感器,写操作时发送的地址字节为 0x90(10010000),读操作时发送 0x91(10010001)。

驱动开发提示: 在Linux内核中,I2C设备地址通常以7位形式表示(如 0x48),内核会自动处理R/W位。用户态驱动通过 ioctl 或 read/write 操作时,也遵循此规则。

3.2 10位地址格式

10位地址格式是为了扩展I2C总线可寻址设备数量而设计的,允许最多1024个设备。10位地址的传输需要两个字节的地址帧。

10位地址传输过程:

  1. 第一个字节: 高5位固定为 11110,作为10位地址的标识;接着是10位地址的最高两位(A9、A8);最低位为R/W位。
  2. 第二个字节: 剩余的8位地址(A7 ~ A0)。

地址帧结构:

第一个字节:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|-------|-------|-------|-------|-------|-------|-------|-------|
|   1   |   1   |   1   |   1   |   0   |  A9   |  A8   |  R/W  |

第二个字节:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|-------|-------|-------|-------|-------|-------|-------|-------|
|  A7   |  A6   |  A5   |  A4   |  A3   |  A2   |  A1   |  A0   |
兼容性注意: 并非所有I2C控制器都支持10位地址模式。在Android设备中,绝大多数传感器、PMIC、音频编解码器等外设仍使用7位地址。10位地址多见于需要大量从设备的工业或服务器场景。

3.3 广播地址与保留地址

I2C规范定义了一组保留地址,用于特殊总线管理功能。这些地址不能被分配给普通从设备。

保留地址表:

7位地址 方向 用途 说明
0x00 写 广播/通用呼叫地址 主设备向所有从设备发送数据
0x01 — 起始字节 用于慢速MCU的启动序列
0x02 — CBUS地址 保留给CBUS兼容设备
0x03 — 保留 未来扩展用途
0x04 ~ 0x07 — 高速模式主设备码 用于高速模式(Hs-mode)的握手
0x78 ~ 0x7B — 10位地址标识 高5位为11110,用于10位地址
0x7C ~ 0x7F — 保留 未来扩展用途

广播地址(0x00):

  • 当主设备向地址 0x00 发送写操作时,所有支持广播的从设备都会接收数据。
  • 广播数据通常用于全局复位、状态查询或固件升级等场景。
  • 从设备可以选择忽略广播地址(通过配置寄存器或硬件引脚)。
驱动开发注意: 在用户态驱动中,不要将广播地址 0x00 分配给任何从设备。如果误用,可能导致总线冲突或意外复位。

3.4 地址分配机制

I2C设备地址的分配方式决定了驱动开发中如何配置和识别设备。主要有以下几种机制:

地址分配方式对比:

分配方式 描述 优点 缺点 典型示例
硬件引脚配置 通过芯片的ADDR引脚电平设置地址 简单、无需软件配置 占用GPIO,地址数量有限 LM75温度传感器(A0/A1/A2)
内部寄存器配置 通过写入配置寄存器改变地址 灵活,支持动态地址 需要初始化序列,增加复杂度 某些MEMS加速度计
固定地址 芯片出厂时地址已固化 无需配置,即插即用 无法更改,可能冲突 EEPROM(如24C02,地址0x50)
动态分配(如SMBus ARP) 通过地址解析协议(ARP)动态分配 支持热插拔,避免冲突 实现复杂,需要主机支持 SMBus设备

Android设备中的典型地址分配:

  • PMIC(电源管理芯片): 通常使用固定地址,如 0x34、0x36。
  • 触控控制器: 通过硬件引脚配置,常见地址 0x38、0x39。
  • 环境光/接近传感器: 固定地址或引脚配置,如 0x23、0x5C。
  • 音频编解码器: 固定地址,如 0x1A、0x1B。
驱动开发最佳实践: 在设备树(Device Tree)中,通过 reg 属性指定I2C设备地址。例如:compatible = "bosch,bma220"; reg = <0x0a>;。内核在枚举I2C总线时会自动匹配地址。

3.5 地址冲突与解决策略

当多个I2C设备共享同一总线时,地址冲突是常见问题。以下为典型场景及解决方案:

冲突场景:

  1. 相同型号设备: 两个相同的传感器(如两个BMP280气压计)默认地址相同。
  2. 不同型号设备: 不同厂商的设备可能使用相同的地址(如0x48被多个温度传感器使用)。
  3. 地址范围重叠: 10位地址与7位地址的保留区域重叠。

解决策略:

  • 硬件引脚重映射: 通过改变ADDR引脚电平,为设备分配不同地址。
  • 使用I2C多路复用器: 如PCA9548,将总线分割为多个子段,每个子段挂载不同设备。
  • 软件地址重配置: 如果设备支持,通过写入配置寄存器更改地址。
  • 更换设备型号: 选择地址不冲突的替代器件。
调试警告: 在用户态驱动中,如果 i2c-dev 检测到地址冲突,open() 或 ioctl 调用可能返回 -EBUSY 或 -EADDRINUSE。务必检查返回值并处理错误。

3.6 地址格式与驱动开发的关系

在Android驱动开发中,地址格式直接影响用户态驱动的实现方式:

7位地址驱动示例:

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>

int fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) {
    perror("open");
    return -1;
}

// 设置7位从设备地址为0x48
int addr = 0x48;
if (ioctl(fd, I2C_SLAVE, addr) < 0) {
    perror("ioctl I2C_SLAVE");
    close(fd);
    return -1;
}

// 发送数据
unsigned char buf[] = {0x00, 0xFF};
write(fd, buf, 2);

10位地址驱动示例:

// 设置10位从设备地址为0x01FF
int addr_10bit = 0x01FF;
if (ioctl(fd, I2C_SLAVE_FORCE, addr_10bit) < 0) {
    perror("ioctl I2C_SLAVE_FORCE");
    close(fd);
    return -1;
}

// 注意:I2C_SLAVE_FORCE 会强制设置地址,即使地址已被占用
// 通常用于调试或特殊场景
内核接口说明: I2C_SLAVE 用于设置7位地址,I2C_SLAVE_FORCE 可用于7位或10位地址。10位地址需要内核配置 CONFIG_I2C_SLAVE_10BIT 支持。

3.7 地址格式与总线仲裁

I2C总线是多主设备总线,地址格式在总线仲裁中起关键作用:

  • 仲裁过程: 多个主设备同时发送起始条件时,通过SDA线的电平比较决定谁获得总线控制权。
  • 地址优先级: 地址值较小的主设备在仲裁中获胜(因为SDA线是线与逻辑,低电平优先)。
  • 10位地址仲裁: 10位地址的第一个字节(高5位为11110)在仲裁中与7位地址的保留区域重叠,因此10位设备在仲裁时需特殊处理。

地址与仲裁关系表:

地址类型 仲裁优先级 说明
7位地址(0x00 ~ 0x07) 最高 保留地址,广播和起始字节
7位地址(0x08 ~ 0x77) 中等 普通设备地址
10位地址(0x78 ~ 0x7B) 低 10位地址标识,仲裁时视为保留
7位地址(0x7C ~ 0x7F) 最低 保留地址
驱动开发提示: 在用户态驱动中,通常不需要关心总线仲裁细节,因为I2C控制器硬件会自动处理。但在多主设备系统中,驱动应避免长时间占用总线,以免影响其他主设备的通信。

3.8 地址格式与设备树配置

在Android设备中,I2C设备地址通常在设备树(Device Tree)中配置。以下为典型配置示例:

// 7位地址设备
i2c@f9923000 {
    compatible = "samsung,s3c2410-i2c";
    reg = <0xf9923000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;

    // 温度传感器,7位地址0x48
    temp_sensor@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;
    };

    // 加速度计,7位地址0x18
    accel@18 {
        compatible = "bosch,bma220";
        reg = <0x18>;
    };
};

// 10位地址设备(需要特殊处理)
i2c@f9924000 {
    compatible = "samsung,s3c2410-i2c";
    reg = <0xf9924000 0x1000>;
    #address-cells = <2>;  // 10位地址需要2个cell
    #size-cells = <0>;

    // 10位地址设备,地址0x01FF
    device@1,ff {
        compatible = "vendor,device";
        reg = <0x1 0xFF>;  // 高2位 + 低8位
    };
};
设备树注意事项: 10位地址在设备树中需要 #address-cells = <2>,且 reg 属性包含两个值。大多数Android设备树默认使用7位地址,10位地址支持需要内核和引导加载程序配合。

3.9 地址格式与用户态驱动设计

在用户态驱动中,地址格式的选择直接影响驱动代码的复杂度和兼容性:

设计建议:

  • 优先使用7位地址: 兼容性最好,所有I2C控制器都支持。
  • 避免10位地址: 除非设备强制要求,否则不要使用10位地址,因为会增加驱动复杂度和调试难度。
  • 地址扫描: 在驱动初始化时,可以通过 I2C_RDWR 或 I2C_SMBUS 扫描总线上的设备地址,但注意不要干扰其他设备。
  • 地址验证: 在驱动中应验证设备返回的地址是否与预期一致,防止地址冲突或硬件故障。

地址扫描示例(用户态):

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>

int scan_i2c_bus(int bus_num) {
    char filename[20];
    snprintf(filename, sizeof(filename), "/dev/i2c-%d", bus_num);
    int fd = open(filename, O_RDWR);
    if (fd < 0) return -1;

    printf("Scanning I2C bus %d...\n", bus_num);
    for (int addr = 0x08; addr <= 0x77; addr++) {
        if (ioctl(fd, I2C_SLAVE, addr) < 0) continue;
        unsigned char buf[] = {0x00};
        if (write(fd, buf, 1) >= 0) {
            printf("Found device at 0x%02X\n", addr);
        }
    }
    close(fd);
    return 0;
}
扫描注意事项: 上述扫描方法会向每个地址发送一个字节,可能干扰某些设备。在生产驱动中,应使用设备树或硬件配置表来获取地址,而不是运行时扫描。

3.10 本章小结