3. I2C地址与寻址:7位地址格式、10位地址格式、广播地址、保留地址、地址冲突检测
I2C总线采用从设备地址来区分总线上的多个设备。主设备在每次通信开始时,都会先发送一个地址字节(或两个字节)来指定要通信的从设备。理解地址格式、保留地址以及冲突检测机制,是进行I2C驱动开发和调试的基础。
3.1 7位地址格式
7位地址是I2C最常用的地址格式,地址范围从 0x00 到 0x7F(共128个地址),但其中 0x00 到 0x07 和 0x78 到 0x7F 被保留用于特殊用途,实际可用的地址约为112个。
在7位地址模式下,主设备发送的第一个字节由7位地址和1位读写标志位组成:
| 起始条件 | 7位地址 (A6..A0) | R/W# | 应答位 (ACK) | 数据字节... | 停止条件 |
- R/W# = 0:主设备写数据到从设备
- R/W# = 1:主设备从从设备读数据
例如,一个7位地址为 0x50(EEPROM AT24C02的典型地址)的写操作,主设备发送的字节为 0xA0(即 0x50 << 1 | 0)。
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字节:
1111 0 10+ R/W#(高2位10放在前缀后) - 等待从设备应答
- 发送第2字节:
1011 0100(低8位) - 等待从设备应答
- 开始数据传输
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 |
— | 保留 | 未来扩展用途 |
当主设备发送地址 0x00 + W(写操作)时,所有支持广播的从设备都应应答。广播地址后的第一个字节用于指示广播类型:
- 0x04:从设备复位(通过硬件复位引脚或软件复位)
- 0x05:从设备锁存地址(用于可编程地址设备)
- 其他值:由具体从设备自定义
在ARM驱动中,广播地址通常用于系统初始化阶段,统一配置所有I2C从设备的默认状态。
3.4 地址冲突检测
I2C总线上的地址冲突是指两个或多个从设备使用了相同的7位或10位地址。由于I2C从设备不会主动发起通信,地址冲突不会立即导致总线错误,但会导致主设备无法正确与目标设备通信。
地址冲突的典型表现:
- 无应答(NACK):主设备发送地址后,总线上没有设备应答(所有从设备都处于高阻态)
- 数据错误:多个设备同时应答,导致数据线(SDA)被同时拉低和释放,产生不确定的逻辑电平
- 总线挂死:冲突导致SDA或SCL被异常拉低,总线无法释放
在驱动开发中,检测地址冲突的常用方法:
- 扫描探测:主设备依次向每个可能的地址发送“伪读”请求(发送地址 + R,然后立即发送停止条件),检查是否有ACK应答
- 寄存器回读验证:向设备写入已知数据,再读取同一寄存器,比较读写值是否一致
- 设备ID读取:部分I2C设备支持读取设备ID寄存器,可用于唯一标识设备
在设备树中,如果两个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总线上的完整寻址流程,以及地址冲突的检测点:
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 |