3. I2C设备地址与寻址:7位地址与10位地址格式、广播地址、保留地址、设备地址分配规则
I2C总线采用地址-响应机制实现多设备通信。每个从设备拥有唯一地址,主设备通过地址选择通信对象。本章深入解析I2C地址格式、特殊地址及分配规则,帮助你在驱动开发中正确配置设备地址。
3.1 7位地址格式
标准I2C总线使用7位地址,地址范围0x00~0x7F(共128个)。主设备在起始条件后发送7位地址+读写位(共8位),从设备匹配地址后应答ACK。
关键点:7位地址在Linux内核中通常以
u16类型存储,但有效位仅为低7位。驱动中配置地址时需注意左移1位(8位表示)与7位表示的区别。
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位地址模式。
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 设备地址分配规则
实际项目中,设备地址分配需遵循以下原则:
- 唯一性:同一条I2C总线上每个从设备地址必须唯一,不能冲突。
- 硬件可配置:多数I2C芯片通过引脚(如A0、A1、A2)设置地址,驱动需读取硬件配置。
- 地址范围合规:避开保留地址(0x00~0x07、0x78~0x7F),使用0x08~0x77范围内的地址。
- Linux内核注册:通过
i2c_board_info或设备树reg属性指定地址。
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”。