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

I2C总线采用从设备地址来区分总线上的多个设备。主设备在每次通信开始时,都会先发送一个地址字节(或两个字节)来指定要通信的从设备。理解地址格式、保留地址以及冲突检测机制,是进行I2C驱动开发和调试的基础。

3.1 7位地址格式

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

在7位地址模式下,主设备发送的第一个字节由7位地址和1位读写标志位组成:

7位地址帧格式:
| 起始条件 | 7位地址 (A6..A0) | R/W# | 应答位 (ACK) | 数据字节... | 停止条件 |
  • R/W# = 0:主设备写数据到从设备
  • R/W# = 1:主设备从从设备读数据

例如,一个7位地址为 0x50(EEPROM AT24C02的典型地址)的写操作,主设备发送的字节为 0xA0(即 0x50 << 1 | 0)。

驱动开发提示:在Linux内核中,I2C从设备地址通常以7位格式存储在 struct i2c_client 的 addr 字段中。驱动开发者在设备树或板级文件中配置地址时,务必使用7位原始地址(如 0x50),而不是左移后的8位地址。

3.2 10位地址格式

当系统中需要挂载超过112个I2C设备时,可以使用10位地址格式。10位地址兼容7位地址,通过保留地址 0xF0 到 0xF7 来标识。

10位地址的传输需要两个字节:

阶段 字节内容 说明
第1字节(高5位) 1111 0XX + R/W# 11110 是10位地址前缀;XX 是10位地址的最高2位(A9..A8)
第2字节(低8位) A7..A0 10位地址的低8位

例如,10位地址 0x2B4(二进制 10 1011 0100)的传输过程:

  1. 主设备发送起始条件
  2. 发送第1字节:1111 0 10 + R/W#(高2位 10 放在前缀后)
  3. 等待从设备应答
  4. 发送第2字节:1011 0100(低8位)
  5. 等待从设备应答
  6. 开始数据传输
兼容性注意:并非所有I2C从设备都支持10位地址。在ARM嵌入式系统中,10位地址主要用于需要大量从设备的复杂系统,如服务器主板或FPGA扩展总线。驱动开发时,需检查从设备数据手册是否支持10位寻址。

3.3 广播地址与保留地址

I2C规范定义了一些保留地址,用于特殊总线管理功能:

7位地址 R/W# 用途 说明
0x00 0 广播地址(General Call) 主设备向所有从设备发送数据。从设备可选择应答并接收数据
0x01 — 起始字节 用于慢速MCU检测总线起始
0x02 — CBUS地址 保留给CBUS兼容设备
0x03 — 保留 未来扩展用途
0x04 – 0x07 — 高速模式主码 用于高速模式(Hs-mode)的地址编码
0x78 – 0x7B — 10位地址前缀 1111 0XX 格式
0x7C – 0x7F — 保留 未来扩展用途
广播地址(General Call)详解:

当主设备发送地址 0x00 + W(写操作)时,所有支持广播的从设备都应应答。广播地址后的第一个字节用于指示广播类型:

  • 0x04:从设备复位(通过硬件复位引脚或软件复位)
  • 0x05:从设备锁存地址(用于可编程地址设备)
  • 其他值:由具体从设备自定义

在ARM驱动中,广播地址通常用于系统初始化阶段,统一配置所有I2C从设备的默认状态。

3.4 地址冲突检测

I2C总线上的地址冲突是指两个或多个从设备使用了相同的7位或10位地址。由于I2C从设备不会主动发起通信,地址冲突不会立即导致总线错误,但会导致主设备无法正确与目标设备通信。

地址冲突的典型表现:

  • 无应答(NACK):主设备发送地址后,总线上没有设备应答(所有从设备都处于高阻态)
  • 数据错误:多个设备同时应答,导致数据线(SDA)被同时拉低和释放,产生不确定的逻辑电平
  • 总线挂死:冲突导致SDA或SCL被异常拉低,总线无法释放

在驱动开发中,检测地址冲突的常用方法:

  1. 扫描探测:主设备依次向每个可能的地址发送“伪读”请求(发送地址 + R,然后立即发送停止条件),检查是否有ACK应答
  2. 寄存器回读验证:向设备写入已知数据,再读取同一寄存器,比较读写值是否一致
  3. 设备ID读取:部分I2C设备支持读取设备ID寄存器,可用于唯一标识设备
Linux内核中的地址冲突处理:

在设备树中,如果两个I2C从设备节点使用了相同的 reg 属性值,内核在注册 i2c_client 时会返回 -EBUSY 错误。驱动开发者可以通过 i2cdetect 工具扫描总线,快速定位地址冲突:

# 扫描I2C总线0上的所有设备
i2cdetect -y 0

# 输出示例(地址冲突时,同一地址出现多个设备标记)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- 46 -- -- -- -- -- -- -- -- --
50: 50 50 -- -- -- -- -- -- -- -- -- -- -- -- -- --  ← 地址0x50出现两次,冲突!
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- --

3.5 地址格式与寻址流程SVG

下图展示了7位地址和10位地址在I2C总线上的完整寻址流程,以及地址冲突的检测点:

I2C地址格式与寻址流程 起始条件 (S) 7位地址 + R/W# 10位地址 (第1字节) 10位地址 (第2字节) 等待 ACK/NACK 等待 ACK/NACK 地址冲突检测点 多个设备同时应答? 正常通信 唯一设备应答,开始数据传输 广播地址 (0x00) 所有设备应答,接收广播数据 保留地址区域 0x00-0x07:广播、起始字节、CBUS、高速模式主码 0x78-0x7F:10位地址前缀、未来扩展保留 地址冲突解决建议 1. 检查设备数据手册,确保每个设备使用唯一的地址引脚配置 2. 使用I2C多路复用器(如PCA9548)扩展地址空间 3. 在驱动初始化时通过扫描探测验证地址唯一性

3.6 地址配置与驱动代码示例

在ARM Linux驱动中,I2C设备地址的配置通常通过设备树或板级文件完成。以下是一个典型的设备树节点示例:

/* 设备树中配置I2C从设备地址 */
&i2c0 {
    status = "okay";
    clock-frequency = <100000>;  /* 标准模式100kHz */

    /* 7位地址设备:温度传感器 */
    temp_sensor: lm75@48 {
        compatible = "national,lm75";
        reg = <0x48>;           /* 7位地址 0x48 */
    };

    /* 10位地址设备示例(假设支持) */
    eeprom@2b4 {
        compatible = "atmel,24c1024";
        reg = <0x2b4>;          /* 10位地址 0x2B4 */
        reg-names = "eeprom";
    };
};

在驱动代码中,通过 i2c_client 结构体获取地址并进行通信:

#include <linux/i2c.h>

static int my_driver_probe(struct i2c_client *client,
                           const struct i2c_device_id *id)
{
    struct i2c_adapter *adapter = client->adapter;
    u16 addr = client->addr;  /* 获取7位或10位地址 */

    dev_info(&client->dev, "设备地址: 0x%02x\n", addr);

    /* 检查地址是否在保留范围内 */
    if (addr < 0x08 || addr > 0x77) {
        dev_warn(&client->dev, "地址 0x%02x 在保留地址范围内\n", addr);
        return -EINVAL;
    }

    /* 检查是否支持10位地址 */
    if (i2c_check_functionality(adapter, I2C_FUNC_10BIT_ADDR)) {
        dev_info(&client->dev, "适配器支持10位地址\n");
    }

    /* 发送广播地址(复位所有设备) */
    if (addr == 0x00) {
        struct i2c_msg msg;
        u8 buf[] = {0x04};  /* 复位命令 */
        msg.addr = 0x00;
        msg.flags = 0;      /* 写操作 */
        msg.len = 1;
        msg.buf = buf;
        i2c_transfer(adapter, &msg, 1);
    }

    return 0;
}
地址配置避坑指南:
  • 地址左移问题:设备树中的 reg 属性必须使用7位原始地址(如 0x48),而不是左移后的8位地址(0x90)。内核会自动处理左移操作。
  • 10位地址标志:在设备树中配置10位地址时,内核会自动识别地址值大于 0x7F 的情况,并设置 I2C_CLIENT_TEN 标志。
  • 广播地址误用:不要将普通从设备的地址设置为 0x00,否则会导致总线上的所有设备都尝试应答,造成严重冲突。

3.7 本章小结

知识点 关键要点 驱动开发关注点
7位地址 范围 0x00-0x7F,可用地址 0x08-0x77 设备树 reg 属性使用原始7位值
10位地址 前缀 11110XX,需要2字节传输 检查适配器是否支持 I2C_FUNC_10BIT_ADDR
广播地址 0x00 + W,用于全局复位或配置 谨慎使用,避免误操作所有从设备
保留地址 0x00-0x07, 0x78-0x7F 驱动中应过滤保留地址,防止非法访问
地址冲突 多个设备使用相同地址,导致通信失败 使用 i2cdetect 扫描,设备树中避免重复 reg