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

I2C总线上设备那么多,主控怎么找到它想对话的那个?

答案就是地址。每个从设备都有一个唯一的地址。主设备在发起通信时,先发地址,告诉总线上的所有设备:“我要找的是谁”。

地址不对,数据就传不到正确的设备上。我见过不少新手,焊好板子发现I2C不通,查了半天,最后发现是设备地址搞错了。这种问题,示波器一抓就能看到,但提前搞清楚地址机制,能省很多排查时间。

3.1 7位地址模式

这是I2C最常用的地址模式。说白了,地址就是7个bit,范围从0x00到0x7F。

主设备发送地址时,实际上会发送8个bit:高7位是地址,最低位是读写标志位(0表示写,1表示读)。

关键点:7位地址模式下,总线上最多可以挂 2^7 = 128 个设备。但实际可用地址远少于这个数,因为有一部分地址被保留了。

举个例子。你买一个I2C温度传感器,数据手册上写“设备地址为0x48”。那主设备要读它时,发送的字节就是 0x48 << 1 | 1 = 0x91。写操作则是 0x48 << 1 | 0 = 0x90。

我习惯在代码里这样定义:

#define TEMP_SENSOR_ADDR  0x48
#define TEMP_SENSOR_WRITE (TEMP_SENSOR_ADDR << 1)
#define TEMP_SENSOR_READ  ((TEMP_SENSOR_ADDR << 1) | 0x01)

这样写,代码可读性高,也不容易算错。

3.2 10位地址模式

7位地址最多128个设备,去掉保留地址,实际能用的也就一百来个。对于复杂的系统,比如服务器主板、基站设备,这就不够用了。

所以I2C规范定义了10位地址模式。地址范围扩展到0x000到0x3FF,最多支持1024个设备。

10位地址的发送过程有点绕。主设备需要发两个字节:

  1. 第一个字节:高5位固定为11110,接着是10位地址的高2位,最后1位是读写标志。
  2. 第二个字节:10位地址的低8位。

举个例子,地址0x2C4(二进制10 1100 0100):

  • 高2位:10
  • 低8位:1100 0100
  • 第一个字节:1111 0 10 R/W = 0xF4(写)或0xF5(读)
  • 第二个字节:1100 0100 = 0xC4

我的经验:实际项目中,10位地址用得并不多。大部分消费级、工业级传感器和外围芯片都用7位地址。我只有在做FPGA和某些高端ADC驱动时,才遇到过10位地址的设备。如果你刚开始学I2C,先把7位地址吃透,10位地址了解原理就够了。

3.3 广播地址

广播地址是0x00。主设备发送地址0x00时,总线上的所有从设备都应该响应。

广播一般用于主设备发送“通用命令”,比如“所有设备复位”、“所有设备进入低功耗模式”。

注意:不是所有从设备都支持广播地址。有些设备会忽略0x00地址。设计系统时,不要依赖广播功能,除非你确认所有从设备都支持。

我曾经在一个项目中,想用广播地址同时复位多个传感器。结果发现其中一个传感器根本不响应广播,导致系统状态不一致。从那以后,我都是逐个设备单独发复位命令,虽然多花点时间,但可靠得多。

3.4 保留地址

I2C规范保留了一些特殊地址,用于特定功能。这些地址不能被分配给普通设备。

地址(7位) 用途 说明
0x00 广播地址 所有从设备都应响应
0x01 起始字节 用于慢速设备检测起始条件
0x02 CBUS地址 保留给CBUS兼容设备
0x03 保留 未来扩展用
0x04 - 0x07 高速模式主设备码 用于高速模式(Hs-mode)
0x78 - 0x7B 10位地址前缀 用于10位地址模式
0x7C - 0x7F 保留 未来扩展用

你看,0x78到0x7B这4个地址,就是10位地址模式用的。所以即使你的设备是7位地址,也不能用这些地址,否则会和10位地址设备冲突。

避坑指南:选型时,一定要确认设备地址不在保留范围内。我见过有人选了地址0x78的传感器,结果怎么都通信不上。查了三天,最后发现是地址冲突。嗯,这种低级错误,犯一次就记住了。

3.5 地址冲突与解决

实际项目中,多个设备可能使用相同地址。比如两个型号一样的温度传感器,地址都是0x48。怎么办?

常见解决办法:

  • 地址引脚:很多I2C芯片有ADDR引脚,通过拉高或拉低来改变地址。比如一个传感器,ADDR接GND时地址0x48,接VCC时地址0x49。
  • I2C多路复用器:用PCA9548这类芯片,把I2C总线分成多路,每路挂一个同地址设备。
  • 片选使能:有些设备有EN引脚,通过GPIO控制,同一时间只让一个设备响应。

我个人比较喜欢用地址引脚的方式,简单可靠,不增加额外芯片。但要注意,有些芯片的地址引脚是内部上拉的,外部拉低时需要强下拉,否则地址可能不稳定。

3.6 地址寻址流程图

下面这张图,把主设备发起通信时的地址寻址流程画清楚了。

I2C地址寻址流程 I2C地址寻址流程 主设备发送起始条件 发送7位/10位地址 + R/W位 等待从设备ACK 收到ACK? (SDA被拉低) 是 开始数据传输 否 发送停止条件 说明: 主设备发送地址后,从设备必须在第9个SCL时钟周期将SDA拉低,表示ACK。如果SDA保持高电平,表示NACK。

流程其实不复杂。主设备发起始条件,然后发地址。从设备如果匹配,就回ACK。主设备收到ACK后,开始传数据。如果没收到ACK,主设备就发停止条件,结束通信。

嗯,这里要注意:如果总线上没有设备响应这个地址,SDA会一直保持高电平(因为上拉电阻)。主设备检测到NACK后,应该及时停止,不要一直重试,否则会浪费总线带宽。

3.7 实际调试中的地址问题

调试I2C设备时,地址问题是最常见的坑之一。我总结了几条经验:

  • 先确认地址:拿到新芯片,第一件事就是看数据手册的地址章节。注意7位地址和8位地址的写法差异。有些手册写的是7位地址,有些写的是8位地址(左移后的值)。
  • 用逻辑分析仪抓:不要靠猜。把SDA和SCL接到逻辑分析仪上,看主设备实际发送的地址字节。一眼就能看出地址对不对。
  • 注意地址引脚电平:如果芯片有ADDR引脚,用万用表量一下实际电平。我遇到过ADDR引脚虚焊,导致地址和预期不一样的情况。

小技巧:Linux下可以用i2cdetect命令扫描总线上的设备。它会尝试所有地址,看哪些设备有响应。比如 i2cdetect -y 1 会扫描I2C总线1上的所有设备。这个命令在调试时非常有用。

地址寻址是I2C通信的基础。搞懂了7位地址、10位地址、广播地址和保留地址,你就能理解主设备是怎么找到从设备的。下次遇到I2C通信问题,先查地址,往往能快速定位问题。