4. I2C 地址机制:7 位地址与 10 位地址、广播地址归纳总结

说到 I2C 的地址,我脑子里一下就蹦出当年第一次调 I2C 时的场景。明明焊好了两个传感器,结果总线上就是没应答。折腾半天才发现 —— 地址冲突了。从那以后我就特别重视地址这块。

I2C 总线上每个从机都有唯一地址。主机发数据前,先要把从机地址“喊”出来。这个地址可以是 7 位,也可以是 10 位。你想想看,一条线上挂几十个器件,地址可不能搞乱。

4.1 从机地址到底怎么传的?

主机发送起始位后,紧跟着的就是 地址字节。对于 7 位地址模式,地址字节的高 7 位是地址,最低 1 位是读写位(R/W)。10 位地址模式则要两个字节。

我习惯把地址帧拆成两步看:
先发地址头,再等从机 ACK。

下面这张图帮你快速理清 7 位与 10 位地址的帧结构区别:

I2C 地址帧结构对比 7 位地址帧(第一个字节) S A6 A5 A4 A3 A2 A1 A0 R/W 10 位地址帧(第一字节 + 第二字节) S 1111 0 A9 A8 R/W ACK A7 A6 A5 ACK 起始位 / 控制位 地址位 读写位 应答位

4.2 7 位地址 —— 主流方案

绝大多数 I2C 器件用的都是 7 位地址。每个地址字节里,高 7 位是地址,最低位是 R/W。比如你发 0xA0(二进制 1010_0000),意味着地址是 0x50(101_0000),并且要进行写操作。读操作就是 0xA1。

我刚开始接触 I2C 时老搞混:明明手册上写着地址 0x48,结果发 0x48 却没反应。后来才发现,0x48 是左对齐的 7 位地址,实际要左移一位才是正确字节。嗯,这里要注意:7 位地址左移 1 位,再拼上 R/W 位,才是你写入 SDA 的字节。

关键点:7 位地址范围是 0x00 ~ 0x7F。但 0x00 保留给通用呼叫(广播地址),0x01~0x03 保留,0x04~0x77 可用,0x78~0x7B 保留给 10 位地址头,0x7C~0x7F 保留。

我曾经踩过的坑:用某厂家的温度传感器,手册写“地址 0x48”,我直接在代码里写 `I2C_Write(0x48, data);`,结果一直 NACK。查了半天,原来厂家给的地址是 7 位值,你需要自己左移。从那以后我每次看数据手册,都先确认它是“7 位地址值”还是“字节形式的地址”。

4.3 10 位地址 —— 不够用时的选择

当板子上器件太多,7 位地址不够用时,I2C 规范允许使用 10 位地址。说白了两步走:
第一步:发头字节,前 5 位固定为 11110,后面跟地址的最高 2 位(A9、A8),最后是 R/W。
第二步:发第二个字节,包含低 8 位地址(A7 ~ A0)。

看一下上面的 SVG 图,10 位地址的第一字节高 5 位必须是 11110,这是 10 位地址的标志。然后从机收到后,如果自己的高两位匹配,就发送 ACK。接着主机再发第二个完整地址字节,从机再次 ACK。总共两个应答周期。

我个人在量产项目中很少用 10 位地址,因为大部分传感器、存储芯片都只支持 7 位。但如果你在做 FPGA 从机或者复杂的多主系统,10 位地址就很有用了。

特性 7 位地址 10 位地址
地址位数 7 10
可用地址数 112(0x04~0x77) 1024(0x000~0x3FF,扣除保留)
帧字节数 1 个地址字节 2 个地址字节
ACK 次数 1 次 2 次
常见于 大多数 I2C 从机 FPGA、复杂多主系统

我建议:如果你在设计从机,优先支持 7 位地址。如果必须用 10 位,记得把头字节中的保留位(11110)和地址高两位搞清楚,别跟保留地址冲突。

4.4 广播地址与特殊地址

I2C 规范里还有一类特殊地址——广播地址(也叫通用呼叫地址,General Call Address)。地址 0x00(7 位),对应字节 0x00 用于写操作,所有支持广播的从机都会 ACK 并接收后续数据。

广播地址通常用来干嘛?我遇到过一种用法:主板上电后,主机先发一个广播命令,让所有从机复位或进入配置模式。当然,不是每个从机都支持广播,很多廉价传感器直接忽略 0x00。

还有几个保留地址值得记住:

  • 0x00:通用呼叫(广播地址)
  • 0x01:起始字节(Start Byte)—— 某些老器件用于唤醒总线
  • 0x02:CBUS 地址(已废弃)
  • 0x03~0x07:保留给未来扩展
  • 0x78~0x7B:10 位地址的头字节(11110xx)

千万别这么干:我见过有人用一个从机地址 0x00 来当普通器件地址,结果每次发广播命令时,那个器件也响应,导致总线混乱。广播地址是公共的,不要占用。

4.5 实际项目中的地址选择建议

给你几个实操经验:

  1. 先查数据手册的地址表。很多器件有多个可选地址,通过引脚电平(比如 AD0、AD1)来配置。我一般先看地址冲突表,再决定硬件焊接哪几个电阻。
  2. 7 位地址尽量避开保留区。首选 0x10~0x77 之间的地址,留出低区给特殊用途。
  3. 使用 10 位地址时,代码里要小心处理两次 ACK。很多库(比如 Linux 的 i2c-dev)默认只处理 7 位,需要额外配置。
  4. 调试时先发一个连读命令,看看有没有应答。我习惯写一个小函数,遍历 0x04~0x77 扫描所有地址,很快就能找到挂载的从机。
// 简易 I2C 地址扫描函数 (伪代码)
for (addr = 0x04; addr <= 0x77; addr++) {
    uint8_t byte_addr = (addr << 1) | 0; // 写操作
    i2c_start();
    i2c_write(byte_addr);
    if (i2c_get_ack()) {
        printf("Found device at 0x%02X\n", addr);
    }
    i2c_stop();
}

这个扫描方法,我几乎每个项目初期都会跑一遍。省得布线回来发现地址撞车,还得飞线改引脚电平,那就麻烦了。

总结一句话:地址机制是 I2C 通信的地基。7 位地址够用就用 7 位,不够再考虑 10 位;广播地址别乱占;写代码前先把地址偏移量搞清楚。地基稳了,后面的 CRC 和重传才能安心玩。