3. I2C寻址:7位地址、10位地址、广播地址与保留地址

地址,是I2C通信的灵魂。每次启动信号后,主机发出去的第一个字节就是“谁在听我说话”。假如地址搞混了,总线上的设备要么不理你,要么两家同时抢着应答——那画面,总线直接崩溃。我个人习惯把I2C地址理解为“门牌号”,但I2C的门牌号体系比想象中复杂一些,有7位的、10位的,还有几个不能用的“特殊号码”。今天咱们就把这摊子事捋清楚。

开始之前,先问一个问题:你平时写代码是给设备配7位地址还是10位地址?我遇到过不少初学者,在一个只支持7位地址的从设备上硬塞了0x20的10位地址格式,结果一直收不到ACK。嗯,地址差错会让人排查到怀疑人生。

3.1 7位地址——最常用的基础

7位地址,是I2C最经典的寻址方式。主机在START之后发送的第一个字节,高7位是地址,最低位是读/写方向(0表示写,1表示读)。这个字节叫做“地址字节”。

举个例子,你要往地址0x50(7位)的EEPROM里写数据,那发送的第一个字节就是0x50左移1位,再或上0(写方向),得到0xA0。如果是读取,则是0xA1。

关键公式:第一字节 = (7位地址 << 1) | (R/W)

R/W:0写,1读

一个总线上最多能挂多少个7位地址的设备呢?严格说是128个(0x00~0x7F),但其中有16个保留地址,实际可用的是112个。看看下面这个表,你就知道哪些地址是“禁区”。

7位地址范围用途说明
0x00通用呼叫地址广播地址,所有从机都应应答
0x01 – 0x03保留用于特殊总线协议,不作通用寻址
0x04 – 0x07保留(高速模式相关)HS模式控制码预留
0x08 – 0x77通用7位地址你常用的地址都在这个区间
0x78 – 0x7B10位地址扩展标识用于10位地址的高字节
0x7C – 0x7F保留未来扩展或其他协议用途

我曾经调试一款温度传感器,数据手册里写的地址是0x48,我直接用0x48左移一位去发,结果总是不通。后来才发现,手册里给的那个“0x48”其实是左移后的结果?不对,就是7位地址0x48,要左移。那次我栽在了一个低级错误上——没有仔细看数据手册的地址标注方式。有的厂商直接给出“8位写入地址”(例如0x90对应7位0x48),有的明确写7位地址。我的建议:拿到任何新器件,先确认手册里是7位还是8位,最好拿示波器抓一下第一个字节,心里才踏实。

3.2 10位地址——扩展的可行方案

当设备越来越多,7位地址就不够用了。I2C协议在标准里加入了10位地址模式。它不是简单地把7位扩展到10位,而是用了两个字节来承载地址信息。

10位地址的第一字节格式:高5位固定为11110xx,其中xx是10位地址的最高两位。最低位仍然是R/W。第二字节则是剩下的8位地址。

快速识别:第一字节高5位为11110(即0xF0掩码),说明这个传输是10位地址。

10位地址范围:0x000 – 0x3FF(共1024个),但实际也要扣掉保留地址。

举个例子:10位地址0x2BC(二进制10 1011 1100)。高两位是10,所以第一字节高5位11110拼上高两位10得到11110 10,即0x7A(算上R/W位就是0x7A或0x7B)。第二字节是剩下的8位:1011 1100 = 0xBC。

要注意,10位地址的兼容性。不是所有主机都支持,也不是所有从机都支持。我见过一个项目,主控MCU的I2C外设只支持7位,但非要挂一个10位地址的从机,最后只能用GPIO模拟I2C,改了两个月软件。你说冤不冤?

3.3 广播地址(通用呼叫地址)

广播地址,就是7位地址中的0x00。主机发出0x00+R/W(写方向),所有支持广播的从机都必须应答,并接收后续的数据。这个机制主要用于主机想一次性向所有设备发送指令——比如复位总线、全局配置等。

但注意,不是所有从机都响应广播。很多器件在出厂时默认不响应,需要额外设置一个控制位才能开启。

警告:广播地址不能用于读操作。读方向(0x01)被保留,没有定义具体行为。你如果尝试发广播读,总线上的设备要么不响应,要么出乱子。

3.4 保留地址——这些门牌号你不能用

I2C协议规定了一些地址是“特殊用途”的,普通设备不能用。我把它们整理成一个清单,方便你查表避坑。

  • 0x00:通用呼叫(广播地址),同上。
  • 0x01 – 0x03:保留,用于特殊总线协议(如CBUS等)。
  • 0x04 – 0x07:保留给HS模式(高速模式)控制码,用于在高速传输中切换模式。
  • 0x78 – 0x7B:10位地址扩展标识,不能当普通7位地址用。
  • 0x7C – 0x7F:保留,你可能永远用不到。

说白了,如果你在代码里给设备配置了0x00~0x07或者0x78~0x7F这些地址,那大概率要出问题。我曾有一次在调试一个I2C hub时,看到它有个配置寄存器可以设地址,手一抖写了个0x00,结果整个总线上的设备全部“失聪”。后来用示波器抓波形才发现,第一个地址字节发出去之后,所有从机都试图应答,总线状态变得一塌糊涂。

保留地址虽然少,但踩中的后果很严重。建议你写驱动的时候加一个地址校验函数,如果传入的是保留地址就返回错误。

整个寻址体系一览

为了帮你把刚才这些零散的信息串起来,我画了一张寻址流程图。它反映了主机从启动信号开始,如何根据地址决定下一步行动。

I2C寻址决策树 主机发送START 发送第一个字节(7位地址或10位标识) 高5位是否为11110? 是 10位地址模式 否 7位地址模式 判断是否保留地址? 是 保留/广播 特殊处理 否 正常 7位从机 发第二字节 10位从机 接着读/写数据 提示:7位地址占绝大多数,10位地址用于大数量设备 保留地址(0x00~0x07, 0x78~0x7F)不可分配给普通从机

3.5 避坑指南与实用建议

讲完理论,我给你三点实际项目中踩出来的经验:

  1. 地址冲突排查:如果总线上挂了两颗相同地址的器件,它们的SCL/SDA依然能正常工作,但拉低应答时就会“打架”。我曾经做一块四路温湿度采集板,四个同样的传感器,每个都设成了相同的默认地址0x44,一读数据全部乱套。后来加了地址选择引脚才解决。选型时最好预留地址可配置的器件。
  2. 10位地址的坑:大部分MCU的I2C外设硬件会帮你自动处理10位地址的两个字节,但如果你用GPIO模拟,一定要把两个字节完整发出去,而且注意第二字节后还要有一次应答。我见过模拟代码漏发了第二字节,结果从机永远不回ACK。
  3. 广播地址的副作用:除非你明确知道所有从机都支持广播并且你确实想控制全局,否则不要轻易使用0x00。你想想看,如果总线上有个不支持广播的器件,它可能忽略这个地址,但如果它恰好把0x00解释成自己的地址……后果不堪设想。

小技巧:写初始化代码时,先发送一个“START + 从机地址 + STOP”的探测脉冲(不传数据),看是否收到ACK。如果没收到,99%是地址错了或从机没上电。我每次都这么干,能快速定位地址问题。