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

I2C总线采用地址-响应机制实现多设备通信。每个从设备拥有唯一地址,主设备通过地址选择通信对象。本章深入解析I2C地址格式、特殊地址及分配规则,帮助你在驱动开发中正确配置设备地址。

3.1 7位地址格式

标准I2C总线使用7位地址,地址范围0x00~0x7F(共128个)。主设备在起始条件后发送7位地址+读写位(共8位),从设备匹配地址后应答ACK。

关键点:7位地址在Linux内核中通常以u16类型存储,但有效位仅为低7位。驱动中配置地址时需注意左移1位(8位表示)与7位表示的区别。
7位地址帧格式 S 7位从设备地址 R/W ACK 8位数据(可多个) P S=起始条件,R/W=0写/1读,ACK=从设备应答,P=停止条件 7位地址范围:0x00~0x7F,其中0x00~0x07和0x78~0x7F为保留地址

3.2 10位地址格式

为扩展地址空间,I2C规范定义了10位地址。主设备通过特殊序列发送10位地址:前5位固定为11110,后跟高2位地址+读写位,再发送低8位地址。

特性 7位地址 10位地址
地址范围 0x00~0x7F 0x000~0x3FF
可用地址数 112(扣除保留地址) 1024
帧长度 1字节地址+读写位 2字节地址序列
兼容性 所有设备支持 需主从设备均支持
Linux内核支持 默认支持 需配置I2C_TENBIT标志
实战建议:绝大多数I2C传感器、EEPROM、ADC/DAC芯片使用7位地址。10位地址主要用于高端多媒体芯片或复杂SoC间通信。在Linux驱动中,通过i2c_client的flags字段设置I2C_CLIENT_TEN启用10位地址模式。
10位地址帧格式 S 11110 A9 A8 R/W ACK A7 A6 A5 A4 A3 A2 A1 A0 (低8位地址) ACK 10位地址发送流程: 步骤1:发送起始条件 + 11110 A9 A8 R/W + ACK 步骤2:发送低8位地址 A7~A0 + ACK 步骤3:后续传输数据字节(可多个)

3.3 广播地址与保留地址

I2C规范保留部分地址用于特殊功能,驱动开发中需避免将这些地址分配给普通设备。

地址(7位) 名称 用途 响应要求
0x00 广播地址 主设备向所有从设备发送通用呼叫 支持广播的从设备必须应答
0x01 起始字节 用于慢速MCU的起始条件检测 不应答
0x02 CBUS地址 保留给CBUS兼容设备 视设备而定
0x03~0x07 保留 未来扩展用途 不应答
0x78~0x7B 10位地址前缀 10位地址高5位固定为11110 仅10位地址设备应答
0x7C~0x7F 保留 测试/特殊模式 不应答
避坑指南:广播地址(0x00)在Linux内核中通过i2c_smbus_write_byte()发送时需谨慎。某些从设备在收到广播后会复位或进入特殊模式,可能导致总线冲突。建议仅在系统初始化或调试阶段使用广播地址。

3.4 设备地址分配规则

实际项目中,设备地址分配需遵循以下原则:

  1. 唯一性:同一条I2C总线上每个从设备地址必须唯一,不能冲突。
  2. 硬件可配置:多数I2C芯片通过引脚(如A0、A1、A2)设置地址,驱动需读取硬件配置。
  3. 地址范围合规:避开保留地址(0x00~0x07、0x78~0x7F),使用0x08~0x77范围内的地址。
  4. Linux内核注册:通过i2c_board_info或设备树reg属性指定地址。
I2C设备地址分配流程 1. 硬件设计:确定地址引脚电平 2. 计算7位地址值 3. 检查是否与保留地址冲突 4. 在设备树或board_info中配置

3.5 Linux内核中的地址配置

在Linux驱动中,设备地址通过以下方式配置:

设备树方式(推荐):
// 设备树节点示例
&i2c1 {
    status = "okay";
    clock-frequency = <100000>;

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

    eeprom: at24c02@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;  // 7位地址0x50
        pagesize = <8>;
    };
};
board_info方式(传统板级代码):
static struct i2c_board_info i2c_devs[] = {
    {
        I2C_BOARD_INFO("tmp102", 0x48),
        .platform_data = &tmp102_pdata,
    },
    {
        I2C_BOARD_INFO("at24c02", 0x50),
        .platform_data = &eeprom_pdata,
    },
};

3.6 地址冲突检测与调试

开发中常遇到地址冲突问题,可通过以下方法排查:

现象 可能原因 排查方法
i2c_transfer返回-ENXIO 从设备未应答,地址错误或设备不存在 使用i2cdetect扫描总线确认地址
数据错乱 两个设备地址相同,同时响应 检查硬件地址引脚,确保唯一性
总线挂死 从设备地址冲突导致总线状态异常 断开可疑设备,逐设备排查
驱动probe失败 设备树中reg地址与实际硬件不匹配 核对原理图,确认地址引脚电平
调试命令:使用i2cdetect -y <bus_number>扫描总线,返回的地址值即为7位地址(十六进制)。若扫描结果中出现多个设备响应同一地址,则存在冲突。

3.7 地址格式转换实战

在驱动代码中,经常需要在7位地址和8位地址之间转换:

// 7位地址与8位地址转换示例
#include <linux/i2c.h>

// 假设硬件手册给出8位地址0xD0(写地址)
#define DEVICE_ADDR_8BIT_WRITE  0xD0
#define DEVICE_ADDR_8BIT_READ   0xD1

// 转换为7位地址(右移1位)
#define DEVICE_ADDR_7BIT        (DEVICE_ADDR_8BIT_WRITE >> 1)

// 在驱动中配置client->addr
static int my_probe(struct i2c_client *client)
{
    // client->addr 已经是7位地址
    dev_info(&client->dev, "Device address: 0x%02x\n", client->addr);
    
    // 手动构造8位地址用于调试
    u8 write_addr = (client->addr << 1) & 0xFE;  // 写地址
    u8 read_addr  = write_addr | 0x01;            // 读地址
    
    return 0;
}
常见误区:许多工程师在数据手册中看到的是8位地址(如0xA0),但在Linux内核中配置时需使用7位地址(0x50)。务必确认手册标注的是7位还是8位地址,通常手册会在地址后标注“8-bit”或“7-bit”。