3. I2C地址机制:7位地址与10位地址、广播地址、保留地址、地址冲突检测
I2C地址机制,说白了就是总线上的“门牌号”系统。每个从设备都得有个唯一ID,主设备才能准确找到它。这个机制看似简单,但坑不少。我调试LCD驱动时,至少有一半的“不亮”问题,最后都追溯到地址配置错误上。
3.1 7位地址:最常用的门牌号
标准I2C设备绝大多数使用7位地址。地址范围从0x00到0x7F,共128个。但实际可用的没那么多——有16个保留地址不能用。
| 地址范围 | 用途 | 可用性 |
|---|---|---|
| 0x00 | 广播地址/通用呼叫 | 保留,不可分配给设备 |
| 0x01 - 0x03 | 保留 | 不可用 |
| 0x04 - 0x07 | 高速模式主码 | 保留 |
| 0x08 - 0x77 | 普通设备地址 | 可用(112个) |
| 0x78 - 0x7B | 10位地址标识 | 保留 |
| 0x7C - 0x7F | 保留 | 不可用 |
实际项目中,7位地址最常见的形式是0x27和0x3F——很多1602/2004 LCD模块用的就是这两个。我遇到过一块板子,焊了三片LCD模块,结果地址全是0x27,一上总线就打架。
3.2 10位地址:什么时候用?
7位地址只有112个可用,对于复杂系统来说不够用。10位地址扩展到了1024个地址。但说实话,我在LCD驱动项目里几乎没用过10位地址——大部分LCD控制器(如HD44780、SSD1306)都只支持7位。
10位地址的传输格式有点绕:
起始条件 + 11110xx(MSB) + R/W + 应答 + 低8位地址 + 应答 + 数据...
前5位固定是11110,后2位是10位地址的最高两位。接着再传剩下的8位。总共需要两个字节才能完成地址发送。
3.3 广播地址:一呼百应
地址0x00是广播地址,也叫通用呼叫地址。主设备发送0x00时,所有支持广播的从设备都会应答。
广播地址的典型用途:
- 全局复位:让所有I2C设备同时复位
- 固件升级:批量更新设备固件
- 系统同步:同步多个传感器的采样时序
但LCD驱动里很少用广播。我试过一次——想用广播同时清空4块LCD屏幕,结果只有2块响应了。后来查手册才发现,便宜的LCD模块根本不实现广播功能。
3.4 保留地址:别踩的雷区
I2C规范里有一堆保留地址。新手最容易踩的坑是:把设备地址设成了保留地址,然后死活通信不上。
常见的保留地址陷阱:
- 0x00:广播地址,别用
- 0x78-0x7B:10位地址前缀,别用
- 0x7C-0x7F:未来扩展保留,别用
- 0x04-0x07:高速模式主码,别用
我曾经帮一个同事排查问题,他用的LCD地址是0x78,结果怎么都读不到应答。我一看地址表就笑了——0x78是10位地址的标识区,普通7位设备根本不会应答。
3.5 地址冲突检测:实战中的硬功夫
地址冲突是I2C系统里最常见的故障之一。两个设备用同一个地址,总线就乱套了。
检测方法:
- 逐个扫描法:主设备依次发送每个地址,看谁应答
- 逻辑分析仪抓包:观察是否有两个设备同时拉低SDA
- 读ID寄存器:部分设备有唯一ID寄存器,可以区分
我写过一个简单的地址扫描函数:
void i2c_scan_addresses(void) {
uint8_t addr;
for (addr = 0x01; addr < 0x7F; addr++) {
// 跳过保留地址
if (addr == 0x00 || addr == 0x04 ||
addr == 0x78 || addr == 0x7C) continue;
i2c_start();
if (i2c_write(addr << 1) == ACK) {
printf("发现设备: 0x%02X\n", addr);
}
i2c_stop();
}
}
3.6 I2C地址机制知识图谱
下面这张图把地址机制的核心关系梳理清楚了:
3.7 实战建议
总结几条我踩坑换来的经验:
- 上电先扫描:写个地址扫描函数,跑一遍就知道总线上有哪些设备
- 别信丝印:LCD模块背面的地址标注可能是错的,以实测为准
- 保留地址别碰:0x00、0x78-0x7F这些地址,用了就是给自己挖坑
- 多设备冲突:同地址设备用多路复用器,或者改硬件地址引脚