3. I2C寻址机制:7位寻址、10位寻址、广播地址、保留地址、设备地址分配
I2C的寻址机制,说白了就是主设备怎么找到从设备。这个问题我刚开始接触I2C时也犯过迷糊——总觉得地址就是随便给一个数就行。后来踩了坑才明白,这里面的门道不少。
咱们先看一张整体结构图,把寻址的脉络理清楚。
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 设备地址分配——实战中的选择
实际项目中,设备地址怎么分配?我总结了几条经验:
- 查手册:每个I2C器件都有固定的地址范围。比如AT24C02的地址是0x50~0x57,由A0/A1/A2引脚决定。
- 避免冲突:同一总线上不能有两个相同地址的设备。如果必须用,就加I2C多路复用器(如PCA9548)。
- 避开保留地址:0x00~0x07、0x78~0x7F这些地址别用。
- 留余量:我习惯在地址分配表里留几个空位,方便后期扩展。
地址分配表示例:
/* 系统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位。广播地址和保留地址要避开。地址分配表一定要文档化,不然半年后你自己都忘了哪个地址给了谁。