3. 从机地址的软件配置:设备树中 reg 属性的正确写法

设备树里配 I2C 从机地址,说白了就是写对 reg 属性。这行代码看着简单,但我见过太多人在这上面翻车。地址写错一位,整个驱动就认不到设备。

我个人习惯,拿到一款新 I2C 器件,第一件事就是翻数据手册,找到那个 7 位从机地址。嗯,这里有个坑——手册上给的地址,跟你在设备树里写的,往往不是同一个数。

3.1 7 位地址 vs 8 位地址:你写对了吗?

I2C 协议里,从机地址是 7 位的。但很多数据手册会把地址写成 8 位,左移了一位,最低位补了个 R/W 位。举个例子:

器件 手册给出的地址 实际 7 位地址 设备树 reg 值
LM75 温度传感器 0x90 (8 位) 0x48 0x48
MPU6050 陀螺仪 0xD0 (8 位) 0x68 0x68
AT24C02 EEPROM 0xA0 (8 位) 0x50 0x50

看清楚了吗?设备树里 reg 属性填的是 7 位地址,不是手册上那个带 R/W 位的 8 位值。我曾经带过一个新人,他照着手册抄了 0x90 进去,结果驱动死活 probe 不到设备。排查了半天,最后发现地址多了一位。

⚠️ 常见错误: 把 8 位地址直接写入 reg 属性。内核 I2C 子系统会自动左移一位去匹配硬件,你写 0x90,内核实际发的是 0x120,总线上的从机根本不会应答。

3.2 reg 属性的标准写法

设备树里 I2C 节点的 reg 属性,格式其实很固定:

/* 标准写法:reg = <地址>; */
&i2c1 {
    status = "okay";
    clock-frequency = <100000>;

    /* 温度传感器 LM75,7 位地址 0x48 */
    lm75: temperature-sensor@48 {
        compatible = "national,lm75";
        reg = <0x48>;
    };

    /* 加速度计 MPU6050,7 位地址 0x68 */
    mpu6050: accelerometer@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
    };
};

注意几个细节:

  • 节点名要带地址后缀:比如 temperature-sensor@48,这个 @48 必须跟 reg 里的值一致。内核会拿这个做校验,不一致会报 warning。
  • 地址用十六进制:别写十进制,看着别扭,也容易出错。
  • 一个节点只配一个地址:除非你的器件是多地址复合设备,否则别写多个值。

3.3 多地址设备怎么办?

有些 I2C 器件,比如某些音频编解码器或 FPGA 桥接芯片,会占用多个从机地址。这时候 reg 属性要写成数组:

/* 多地址设备示例 */
codec: audio-codec@1a {
    compatible = "some,vendor-codec";
    reg = <0x1a 0x1b 0x1c>;
    reg-names = "primary", "secondary", "debug";
};

reg-names 是个好习惯。我在移植一个 TI 的音频芯片时,它有三个地址空间:主控制、DSP 调试、GPIO 扩展。不加名字的话,驱动里只能靠索引去猜哪个地址是干嘛的,维护起来很痛苦。

3.4 地址冲突:一个容易被忽略的问题

同一个 I2C 总线上,不能有两个设备用同一个地址。听起来是废话,但实际项目中经常撞车。比如:

  • LM75 默认地址 0x48,跟某些电源管理芯片的地址冲突
  • 多个同型号传感器挂同一条总线,地址没做硬件跳线区分

解决办法:

  1. 硬件上改地址:很多芯片有 ADDR 引脚,拉高拉低可以切换地址范围。
  2. 软件上换总线:把冲突的设备挪到另一条 I2C 控制器上。
  3. 用 GPIO 模拟:实在不行,给其中一个设备单独用 GPIO 模拟 I2C。
💡 我的习惯: 画原理图阶段就跟硬件工程师确认好每个 I2C 设备的地址,列个表贴在工位上。等板子回来再改设备树,那叫一个折腾。

3.5 地址配置的完整流程

我画了张图,把从拿到芯片到设备树配好的流程串起来:

I2C 从机地址配置流程 1. 查阅数据手册 2. 确认是 7 位地址还是 8 位地址 是 8 位地址? 是 右移 1 位,去掉 R/W 位 否 直接使用 7 位地址 3. 写入设备树 reg 属性 4. 编译设备树,验证通信

3.6 验证地址配置是否正确

设备树写完了,怎么知道地址对不对?我最常用的方法:

  1. i2cdetect 扫描:板子起来后,跑 i2cdetect -y <bus-number>,看看对应地址上有没有设备应答。
  2. 看内核日志:dmesg | grep i2c,如果地址不匹配,内核会打印 "failed to probe" 之类的信息。
  3. 直接读写测试:用 i2cget 和 i2cset 工具,手动发一个读操作,看能不能拿到数据。
🔑 关键点: 设备树里的 reg 属性,填的是 7 位从机地址。手册上给的 8 位地址,记得右移一位再写进去。多地址设备用数组,配合 reg-names 让驱动更清晰。

地址配对了,驱动 probe 才能成功。这一步错了,后面所有调试都是白费功夫。我自己的经验是,每次拿到新板子,第一件事就是拿 i2cdetect 扫一遍,确认所有设备都在预期地址上。嗯,这个习惯帮我省了不少排查时间。