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 知识结构图:地址不匹配的三大根因

下面这张图,把三个问题的关系梳理清楚了。你可以对照自己的调试场景,快速定位问题出在哪一环。

I2C 地址不匹配三大根因 I2C 地址不匹配三大根因 地址不匹配 移位问题 读写位混淆 地址掩码错误 7位地址 vs 8位寄存器 左移/右移不匹配 flags 漏设 I2C_M_RD 地址字节 R/W# 位错误 掩码寄存器未初始化 掩码位屏蔽了关键地址位 调试建议:先检查移位方向 → 再确认读写标志 → 最后核对掩码寄存器 逻辑分析仪抓地址字节,对照 datasheet 逐位验证

4.5 调试检查清单

遇到地址不匹配时,我一般按这个顺序排查:

  1. 确认地址值:从机 datasheet 里写的地址是 7 位还是 8 位?驱动里用的是哪个?
  2. 检查移位:控制器寄存器期望的是左移后的值,还是原始值?
  3. 验证读写位:用逻辑分析仪抓地址字节,看最低位是不是和操作方向一致。
  4. 核对掩码:读一下控制器的地址掩码寄存器,确认没有意外屏蔽关键位。
  5. 测试最小场景:只挂一个从机,掩码设成 0xFF,排除干扰。

最后提醒一句:别完全相信代码注释里的地址值。我见过好几次,注释写的是 7 位地址,但实际代码里用的是 8 位值。以 datasheet 和逻辑分析仪为准。