3. I2C地址与寻址:7位地址格式、10位地址格式、广播地址、保留地址、设备地址分配规则
I2C的地址寻址,说白了就是总线上的“门牌号”系统。主设备要跟谁说话,就得先喊对谁的门牌号。这个门牌号怎么编、怎么用、有哪些坑,是每个调I2C的工程师必须啃下的硬骨头。
我刚开始做嵌入式那会儿,就因为在地址上栽过跟头——一个温湿度传感器死活读不到数据,折腾了两天,最后发现是7位地址和8位地址搞混了。嗯,从那以后我再也不敢小看这个“门牌号”了。
3.1 7位地址格式
7位地址是I2C最常用的格式。总线上最多挂127个设备(地址0x00到0x7F),但实际能用的没这么多,因为有一部分被保留了。
通信时,主设备发送的地址字节其实是这样的:
| 7-bit Address | R/W |
| bit7-bit1 | bit0 |
高7位是设备地址,最低位是读写标志位:0表示写,1表示读。所以你在数据手册里看到的地址,通常是7位值左移1位后的结果。举个例子:
- 数据手册写:设备地址 0x68(7位)
- 实际发送写操作:0xD0(0x68 << 1 | 0)
- 实际发送读操作:0xD1(0x68 << 1 | 1)
3.2 10位地址格式
7位地址不够用怎么办?I2C规范定义了10位地址格式。说实话,我在实际项目中用到10位地址的场景不多,但一旦遇到,处理起来确实有点绕。
10位地址的发送分两个阶段:
- 第一个字节:高5位固定为11110,接着是10位地址的高2位,最后是R/W位
- 第二个字节:10位地址的低8位
格式如下:
第一个字节: | 1 | 1 | 1 | 1 | 0 | A9 | A8 | R/W |
第二个字节: | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
我记得有一次调试一个高清摄像头模组,它的I2C地址就是10位的。当时我用的逻辑分析仪默认只解析7位地址,波形看起来全是乱码。后来手动按10位格式去解析,才把问题定位到。
3.3 广播地址与保留地址
I2C协议里有一些特殊地址,不能分配给普通设备。我把它们整理成了表格,方便你对照:
| 地址(7位) | 用途 | 说明 |
|---|---|---|
| 0x00 | 广播地址/通用呼叫地址 | 主设备向总线上所有设备发送数据 |
| 0x01 | 起始字节 | 用于慢速设备检测总线起始 |
| 0x02 | CBUS地址 | 保留给CBUS兼容设备 |
| 0x03 | 保留 | 未来扩展用途 |
| 0x04 - 0x07 | 高速模式地址 | 用于高速模式(Hs-mode) |
| 0x78 - 0x7F | 保留 | 10位地址格式标识、测试等 |
广播地址(0x00)是个有意思的东西。主设备发送地址0x00时,理论上所有从设备都应该应答。但实际中,很多设备默认不响应广播地址,需要配置寄存器才能开启。
我曾经用广播地址做过一次批量固件升级——主设备先发广播地址通知所有传感器进入升级模式,再逐个用具体地址下发固件。这个方案省了不少时间,但前提是所有设备都支持广播地址响应。
3.4 设备地址分配规则
实际项目中,设备地址怎么分配?我总结了几条经验:
- 硬件引脚配置:很多传感器有ADDR引脚,通过拉高或拉低来改变地址。比如MPU6050,AD0接GND时地址0x68,接VCC时地址0x69。
- 地址冲突检测:同一总线上不能有两个相同地址的设备。我建议上电后先扫描一遍总线,确认所有设备地址唯一。
- 地址偏移量:有些芯片的地址是固定的,比如0x48、0x4A等。设计电路时就要规划好,避免冲突。
- 软件可配置地址:部分高级传感器支持通过命令修改地址,但掉电会丢失,需要每次上电重新配置。
避坑指南: 我曾经在一个项目里用了两个相同型号的温湿度传感器,它们的地址都是0x44。硬件上没法改,软件上也没做地址切换。结果两个传感器数据互相干扰,读出来的值忽高忽低。最后只能加一个I2C多路复用器(如TCA9548A)来解决。
3.5 地址寻址流程图
下面这张图展示了I2C地址寻址的完整流程,从主设备发起开始,到从设备应答结束:
3.6 地址扫描与调试技巧
调试I2C设备时,我习惯先做一次地址扫描。说白了就是让主设备逐个发送地址,看哪个设备会应答。这个方法在排查地址冲突或设备未响应问题时特别管用。
下面是一个简单的地址扫描代码示例:
// I2C地址扫描函数
void i2c_scan_addresses(void) {
uint8_t address;
uint8_t ack;
printf("I2C地址扫描结果:\n");
printf(" ");
for (address = 0x00; address < 0x80; address++) {
// 跳过保留地址
if (address <= 0x07 || address >= 0x78) {
continue;
}
// 发送START + 地址 + R/W(写)
ack = i2c_master_start();
ack |= i2c_master_send_byte(address << 1 | 0);
i2c_master_stop();
if (ack == 0) {
printf("0x%02X ", address);
}
}
printf("\n");
}
调试小技巧: 扫描地址时,记得跳过保留地址(0x00-0x07和0x78-0x7F)。另外,有些设备在收到非匹配地址后会复位,所以扫描速度不要太快,建议每个地址间隔5ms以上。
嗯,关于I2C地址与寻址,核心就是这些。记住一句话:地址对了,通信就成功了一半。下次调I2C设备时,先确认地址格式,再检查地址冲突,最后用扫描工具验证——这套流程能帮你省下大量排查时间。