4. 地址不匹配的常见原因:移位问题、读写位混淆、地址掩码
I2C 地址不匹配,是驱动移植中最容易让人抓狂的问题之一。信号都出来了,波形也看到了,Slave 就是不 ACK。我早期调试时,经常对着逻辑分析仪发愣——明明地址写对了啊?
后来踩的坑多了,总结下来,地址不匹配的根因就三个:移位问题、读写位混淆、地址掩码配置错误。咱们一个一个说。
4.1 移位问题:7 位地址怎么变成了 8 位?
I2C 协议里,从机地址是 7 位的。但传输时,这 7 位地址被左移了 1 位,空出来的最低位用来放读写标志(R/W#)。
举个例子:你的从机地址是 0x48(7 位二进制 1001000)。在总线上实际发送的字节是 0x90(写操作)或 0x91(读操作)。
很多驱动框架,比如 Linux 的 i2c_client,存的地址就是 7 位原始值。但有些底层控制器或老旧驱动,会直接拿 8 位地址去配寄存器。
关键点:驱动里配地址寄存器时,一定要搞清楚硬件期望的是 7 位地址还是 8 位地址(左移后的)。
我遇到过这么个事:某款 SoC 的 I2C 控制器,地址寄存器说明文档写的是「7-bit slave address」。我按 0x48 写进去,死活不通。后来读了一遍 datasheet 的脚注,才发现它内部自动左移了,寄存器实际要的是 7 位原始值。嗯,文档没写清楚,坑了我半天。
| 场景 | 7 位地址 | 总线发送字节(写) | 寄存器配置值 |
|---|---|---|---|
| 原始地址 | 0x48 | 0x90 | 0x48(部分控制器) |
| 左移后 | — | — | 0x90(部分控制器) |
我的习惯:写驱动前,先看控制器手册里地址寄存器的位定义。如果寄存器描述是「ADDR[6:0]」,那就是 7 位原始值;如果是「ADDR[7:1]」,那就是左移后的 8 位值。
4.2 读写位混淆:Slave 为什么 NACK?
读写位混淆,说白了就是驱动在发送地址时,把 R/W# 位搞反了。
I2C 协议规定:地址字节的最低位为 0 表示写,为 1 表示读。如果你要读从机数据,但发送的地址字节最低位是 0,从机就会认为你要写——如果它不支持写操作,直接 NACK。
我曾经调试一颗温度传感器,读寄存器时总是返回 0xFF。逻辑分析仪一抓,发现驱动发的是 0x90(写地址),但后面跟着的却是读操作的时序。从机懵了,我也懵了。
后来定位到是 i2c_transfer 里 msg 的 flags 没设对。Linux 内核的 i2c_msg 结构体里,flags 的 I2C_M_RD 位控制读写方向。如果漏设了这个标志,驱动默认走写路径。
struct i2c_msg {
__u16 addr; // 7 位地址,不要左移
__u16 flags;
#define I2C_M_RD 0x0001 // 读标志
__u16 len;
__u8 *buf;
};
// 正确用法:读操作
struct i2c_msg msg;
msg.addr = 0x48;
msg.flags = I2C_M_RD; // 告诉控制器这是读
msg.len = 2;
msg.buf = rxbuf;
注意:有些控制器硬件会自动根据 R/W# 位切换数据方向。如果你在 flags 里设了读,但地址字节最低位是 0,硬件行为可能不可预期。我建议:地址字段只放 7 位原始值,读写方向交给 flags 或控制器寄存器控制。
4.3 地址掩码:为什么多个从机响应了?
地址掩码,是 I2C 控制器用来做地址过滤的。它决定哪些地址位必须精确匹配,哪些位可以忽略。
举个例子:你的从机地址是 0x48(二进制 1001000)。如果地址掩码设为 0xFE(二进制 1111110),最低位被屏蔽了。那么 0x48 和 0x49 都会匹配。这在某些场景下有用,比如你想用一个驱动处理两个地址的从机。
但大多数时候,掩码配置错误会导致地址冲突。我见过一个案例:某工程师移植音频编解码器驱动,总线上有两个地址不同的从机,结果每次发地址,两个从机都 ACK。查了半天,发现掩码寄存器默认值是 0x00(全部忽略),等于没做地址过滤。
| 掩码值 | 匹配规则 | 典型用途 |
|---|---|---|
| 0xFF | 全部位精确匹配 | 标准单从机 |
| 0xFE | 忽略最低位 | 兼容两个地址的从机 |
| 0x00 | 全部忽略(不推荐) | 极少用,容易冲突 |
我曾经踩过的坑:某款 MCU 的 I2C 控制器,复位后地址掩码寄存器是 0x00。我忘了初始化它,结果总线上所有从机都响应了同一个地址。调试时逻辑分析仪显示一堆 ACK,场面一度很混乱。
4.4 知识结构图:地址不匹配的三大根因
下面这张图,把三个问题的关系梳理清楚了。你可以对照自己的调试场景,快速定位问题出在哪一环。
4.5 调试检查清单
遇到地址不匹配时,我一般按这个顺序排查:
- 确认地址值:从机 datasheet 里写的地址是 7 位还是 8 位?驱动里用的是哪个?
- 检查移位:控制器寄存器期望的是左移后的值,还是原始值?
- 验证读写位:用逻辑分析仪抓地址字节,看最低位是不是和操作方向一致。
- 核对掩码:读一下控制器的地址掩码寄存器,确认没有意外屏蔽关键位。
- 测试最小场景:只挂一个从机,掩码设成
0xFF,排除干扰。
最后提醒一句:别完全相信代码注释里的地址值。我见过好几次,注释写的是 7 位地址,但实际代码里用的是 8 位值。以 datasheet 和逻辑分析仪为准。