3. I2C寻址机制:7位寻址、10位寻址、广播地址、保留地址、设备地址分配

I2C的寻址机制,说白了就是主设备怎么找到从设备。这个问题我刚开始接触I2C时也犯过迷糊——总觉得地址就是随便给一个数就行。后来踩了坑才明白,这里面的门道不少。

咱们先看一张整体结构图,把寻址的脉络理清楚。

I2C 寻址机制总览 I2C 寻址机制 7位寻址 10位寻址 广播地址 (0x00) 保留地址 11110xx 前缀 双字节帧 关键说明 • 7位寻址:标准模式,支持112个设备(0x01~0x7F) • 10位寻址:扩展模式,支持1024个设备,兼容7位 • 广播地址(0x00):所有从设备必须应答 • 保留地址:用于特殊协议功能,不可分配给设备

3.1 7位寻址——最常用的方式

7位寻址是I2C最基础的寻址方式。主设备发送一个字节,高7位是地址,最低位是读写标志(0表示写,1表示读)。

举个例子:你有一个温度传感器,地址是0x48(二进制1001000)。主设备要写数据时,发送的字节就是0x90(10010000);要读数据时,发送0x91(10010001)。

地址范围:0x01 ~ 0x7F,共127个可用地址。但0x00被用作广播地址,所以实际可用的是112个(0x01~0x7F中除去保留地址)。

我个人习惯在驱动代码里这样定义地址:

/* 7位地址定义,左对齐到高7位 */
#define SENSOR_ADDR_7BIT  0x48

/* 构造读写地址 */
#define I2C_WRITE_ADDR(addr)  ((addr) << 1)      /* 写地址:左移1位,最低位为0 */
#define I2C_READ_ADDR(addr)   (((addr) << 1) | 0x01)  /* 读地址:左移1位,最低位为1 */

这里有个坑——很多芯片手册里给的地址是7位值,但有些厂商给的是8位值(已经左移过的)。我在项目中就遇到过,一个加速度传感器手册写的是0xA6,我以为是7位地址,结果通信死活不通。后来才发现人家给的是8位地址,实际7位地址是0x53。嗯,这里要注意,拿到手册先确认地址格式。

3.2 10位寻址——什么时候用?

7位地址只有112个可用,对于复杂的系统来说确实不够。10位寻址就是为了解决这个问题。

10位寻址的格式有点特殊。主设备先发一个特殊前缀字节:11110xx + R/W,其中xx是10位地址的高2位。然后再发第二个字节,包含低8位地址。

阶段 SDA内容 说明
起始条件 S SCL高电平时SDA下降沿
第一字节 11110 A9 A8 R/W 前缀11110 + 地址高2位 + 读写位
从设备应答 ACK 匹配的从设备拉低SDA
第二字节 A7 A6 A5 A4 A3 A2 A1 A0 地址低8位
从设备应答 ACK 再次确认

兼容性提示:10位寻址和7位寻址可以混用。但要注意,10位地址的前缀11110xx可能会和某些7位地址冲突。我建议在设计系统时,要么全用7位,要么全用10位,混用容易出问题。

3.3 广播地址——0x00的特殊使命

广播地址(0x00)是所有从设备都必须响应的地址。主设备发送0x00时,总线上的每个从设备都要拉低SDA表示应答。

广播地址一般用在什么场景?

  • 系统复位:主设备发送广播,让所有从设备复位到初始状态。
  • 地址分配:某些可编程地址的从设备,通过广播地址进入配置模式。
  • 同步操作:同时触发多个从设备执行同一动作。

注意:广播地址是只写的。如果主设备发送0x01(读广播地址),从设备不会应答。我曾经在调试一个多传感器系统时,想用广播地址同时读取所有传感器的数据,结果发现根本行不通——广播地址只能写,不能读。

3.4 保留地址——这些地址别乱用

I2C协议保留了一些地址用于特殊功能。这些地址不能分配给普通设备,否则会冲突。

地址(7位) 用途 说明
0x00 广播地址 所有从设备必须应答
0x01 起始字节 用于慢速设备检测起始条件
0x02 CBUS地址 兼容CBUS协议
0x03~0x07 保留 未来扩展使用
0x78~0x7B 10位寻址前缀 11110xx格式
0x7C~0x7F 保留 未来扩展使用

你看,0x78~0x7B这4个地址就是10位寻址的前缀。如果你在7位模式下用了这些地址,恰好又有一个10位设备在总线上,那通信就会乱套。我记得有一次,一个同事把I2C扩展芯片的地址设成了0x78,结果总线上另一个10位设备怎么都调不通。查了两天才发现是地址冲突。

3.5 设备地址分配——实战中的选择

实际项目中,设备地址怎么分配?我总结了几条经验:

  1. 查手册:每个I2C器件都有固定的地址范围。比如AT24C02的地址是0x50~0x57,由A0/A1/A2引脚决定。
  2. 避免冲突:同一总线上不能有两个相同地址的设备。如果必须用,就加I2C多路复用器(如PCA9548)。
  3. 避开保留地址:0x00~0x07、0x78~0x7F这些地址别用。
  4. 留余量:我习惯在地址分配表里留几个空位,方便后期扩展。

地址分配表示例:

/* 系统I2C总线地址分配 */
#define ADDR_EEPROM_0     0x50  /* AT24C02, A0=0, A1=0, A2=0 */
#define ADDR_EEPROM_1     0x51  /* AT24C02, A0=1, A1=0, A2=0 */
#define ADDR_TEMP_SENSOR  0x48  /* LM75温度传感器 */
#define ADDR_ACCEL        0x53  /* ADXL345加速度计 */
#define ADDR_RTC          0x68  /* DS3231实时时钟 */
/* 0x70~0x77 预留 */
/* 0x78~0x7F 禁止使用(10位寻址前缀) */

嗯,这里还要提一句。有些芯片的地址引脚是悬空处理的,内部有上拉或下拉。我遇到过一款气压传感器,地址引脚悬空时默认是0x76,但手册上写的是0x77。后来发现是芯片版本不同。所以,拿到新芯片,先用逻辑分析仪抓一下地址,确认无误再写驱动。

总结一下:7位寻址够用就用7位,不够再上10位。广播地址和保留地址要避开。地址分配表一定要文档化,不然半年后你自己都忘了哪个地址给了谁。