I2C 寻址机制:7 位地址与 10 位地址、广播地址、保留地址、地址冲突检测
I2C 的寻址,说白了就是主设备怎么叫醒它想通信的那个从设备。总线上挂着一堆器件,主设备喊一声,只有地址匹配的那个才会应答。这个机制看起来简单,但坑不少。我早年调一块板子,I2C 总线上挂了 4 个传感器,结果死活只认出来 3 个。查了半天,发现是地址冲突——两个器件用了同一个地址。嗯,从那以后我每次画原理图都会先拉一张地址分配表。
7 位地址结构
标准 I2C 用 7 位地址。主设备发送起始条件后,先发 7 位地址 + 1 位读写标志。从设备收到后,如果地址匹配,就在第 9 个 SCL 周期拉低 SDA 作为应答。
7 位地址的范围是 0x00 ~ 0x7F。但别高兴太早,不是所有地址都能用。实际可用的地址大概在 0x08 ~ 0x77 之间,因为两头都被保留地址占了。
地址格式速记:
- 7 位地址 + R/W 位 = 第一个字节
- R/W = 0 表示写,R/W = 1 表示读
- 例如:地址 0x50(写)→ 发送 0xA0
10 位地址扩展
7 位地址最多挂 112 个器件,有些场景不够用。所以 I2C 规范定义了 10 位地址。怎么区分 7 位和 10 位?看第一个字节的高 5 位。如果高 5 位是 11110,那就是 10 位地址模式。
10 位地址的发送分两步:
- 第一个字节:11110 + 地址高 2 位 + R/W
- 第二个字节:地址低 8 位
我个人习惯,能用 7 位地址就别用 10 位。10 位地址的兼容性问题比较多,有些老器件或者廉价 MCU 的 I2C 外设根本不支持。我在一个项目里用过一次 10 位地址,结果发现某款国产 MCU 的 I2C 模块在硬件上就不支持,最后只能软件模拟。
| 特性 | 7 位地址 | 10 位地址 |
|---|---|---|
| 地址字节数 | 1 字节 | 2 字节 |
| 最大设备数 | 112 | 1024 |
| 兼容性 | 所有器件支持 | 部分器件不支持 |
| 传输效率 | 高 | 略低 |
广播地址与通用呼叫
地址 0x00 是广播地址,也叫通用呼叫地址。主设备发送 0x00 时,所有从设备都应该应答。这个功能一般用来让主设备广播一些全局信息,比如复位所有从设备、更新固件之类的。
但说实话,我在实际项目中很少用广播地址。为什么?因为不是所有从设备都实现了通用呼叫处理。有些器件收到 0x00 后直接忽略,有些会应答但什么都不做。你想想看,如果总线上挂了 5 个器件,只有 3 个应答了广播,那剩下的 2 个怎么办?
注意:使用广播地址前,务必确认所有从设备的数据手册都明确支持通用呼叫功能。否则可能出现部分设备响应、部分设备不响应的混乱局面。
保留地址一览
I2C 规范保留了一些特殊地址。这些地址不能分配给普通器件。我列个表,你调试时对照着看:
| 地址(7 位) | 用途 | 说明 |
|---|---|---|
| 0x00 | 通用呼叫 | 广播地址 |
| 0x01 | 起始字节 | 用于慢速 MCU 检测总线 |
| 0x02 ~ 0x07 | 保留 | 未来扩展用 |
| 0x78 ~ 0x7B | 10 位地址前缀 | 11110xx 模式 |
| 0x7C ~ 0x7F | 保留 | 测试/特殊用途 |
我曾经遇到过一个坑:某款温度传感器默认地址是 0x48,但数据手册没写清楚它其实占用了 0x48 和 0x49 两个地址(因为内部有两个通道)。结果我在总线上又挂了一个地址为 0x49 的 EEPROM,两个器件直接打架。所以看数据手册时,一定要确认器件到底占用了几个地址。
地址冲突检测
地址冲突是 I2C 调试中最常见的问题之一。怎么检测?我总结了几种方法:
- 逻辑分析仪抓波形:这是最直接的方法。抓取起始条件后的第一个字节,看看主设备发了什么地址,从设备有没有应答。
- 逐个断开测试:先把所有从设备断开,然后逐个接上,看哪个地址出现重复。
- 读器件 ID:很多现代 I2C 器件都有器件 ID 寄存器,可以读出来确认身份。
我的调试习惯:画原理图时,我会在每路 I2C 器件的 SDA/SCL 上预留测试点。调试时用示波器或逻辑分析仪直接勾上去看波形,比靠代码打印日志快得多。
地址冲突的典型表现:主设备发送地址后,SDA 没有被拉低(没有应答),或者应答位出现毛刺。为什么会这样?因为两个从设备都试图应答,一个拉低 SDA,另一个也拉低,结果 SDA 电平不确定。
SVG:I2C 寻址流程与地址空间结构
实际调试中的地址问题
我遇到过几种典型的地址问题,列出来给你参考:
- 地址写错了:数据手册给的地址可能是 7 位值,但代码里需要左移一位。比如器件地址是 0x50,发送时要发 0xA0。我见过有人直接发 0x50,结果总线一直没应答。
- 地址引脚配置不对:很多 I2C 器件有地址选择引脚(A0/A1/A2)。如果这些引脚悬空或者电平不对,地址就会偏移。我调一个音频编解码器时,发现 A0 引脚虚焊,地址从 0x1A 变成了 0x1B,折腾了半天。
- 多个器件地址相同:有些器件没有地址选择引脚,或者地址选择范围不够。比如某款加速度计只有 2 个可选地址,但板子上要放 3 个。这时候只能用 I2C 多路复用器(如 PCA9548)来扩展。
避坑指南:我曾经在一个项目里用了 4 片同样的温度传感器,每片通过 A0/A1 引脚设置不同地址。结果生产时贴片厂把其中两片的 A0 焊错了,导致地址冲突。从那以后,我要求产线在贴片后必须做 I2C 地址扫描测试,确保每个地址唯一。
地址扫描代码示例
调试时我经常写一段地址扫描代码,看看总线上到底有哪些器件。下面是一个简单的 I2C 扫描函数:
void i2c_scan(void)
{
uint8_t addr;
uint8_t data = 0;
printf("I2C Bus Scanning...\n");
for (addr = 0x01; addr < 0x7F; addr++)
{
// 跳过保留地址
if (addr < 0x08 || addr > 0x77)
continue;
i2c_start();
// 发送地址 + 写标志
if (i2c_write(addr << 1) == I2C_ACK)
{
printf("Found device at 0x%02X\n", addr);
i2c_stop();
}
else
{
i2c_stop();
}
}
printf("Scan complete.\n");
}
这段代码会遍历所有可用地址,发送地址字节后检查是否有应答。有应答就打印地址,没应答就跳过。我在调试新板子时,第一件事就是跑这个扫描函数,看看实际挂载的器件和原理图是否一致。
小技巧:扫描时注意保留地址区域(0x00~0x07 和 0x78~0x7F)。有些从设备对保留地址的访问会做出异常响应,甚至导致总线锁死。我一般只扫描 0x08~0x77 这个安全区间。
好了,I2C 寻址这块就聊这么多。记住一句话:地址对了,通信就成功了一半。下次你调 I2C 时,先拿逻辑分析仪看看地址字节对不对,能省下大把时间。