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位地址传输过程:
- 第一个字节: 高5位固定为
11110,作为10位地址的标识;接着是10位地址的最高两位(A9、A8);最低位为R/W位。 - 第二个字节: 剩余的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设备共享同一总线时,地址冲突是常见问题。以下为典型场景及解决方案:
冲突场景:
- 相同型号设备: 两个相同的传感器(如两个BMP280气压计)默认地址相同。
- 不同型号设备: 不同厂商的设备可能使用相同的地址(如0x48被多个温度传感器使用)。
- 地址范围重叠: 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;
}
扫描注意事项: 上述扫描方法会向每个地址发送一个字节,可能干扰某些设备。在生产驱动中,应使用设备树或硬件配置表来获取地址,而不是运行时扫描。