3、I2C设备树配置实战:在Zephyr项目中对I2C控制器和从设备进行设备树节点配置

设备树(Device Tree)是 Zephyr OS 中描述硬件拓扑和资源配置的核心机制。对于 I2C 子系统,正确的设备树配置是驱动正常工作的前提。本节将带领你完成从 I2C 控制器使能到从设备节点绑定的完整配置流程,并解析常见配置陷阱。

核心原则:Zephyr 的设备树配置遵循“控制器 → 总线 → 从设备”的层级结构。必须先确保 I2C 控制器节点在 SoC 级设备树中被正确使能,然后才能在板级设备树中添加挂载在该总线上的从设备节点。

3.1 I2C 控制器节点配置

I2C 控制器节点通常定义在 SoC 的 .dtsi 文件中,板级 .dts 文件负责使能并调整参数。以下是一个典型的 I2C 控制器节点结构:

// soc.dtsi (SoC 级定义)
soc {
    i2c1: i2c@40005400 {
        compatible = "nordic,nrf-twim";
        reg = <0x40005400 0x400>;
        interrupts = <5 1>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
        clock-frequency = <100000>;
    };
};

在板级 .dts 文件中,通过 &i2c1 引用并覆盖属性:

// board.dts (板级使能)
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;  // 提升至 400kHz (Fast Mode)
    pinctrl-0 = <&i2c1_default>;
    pinctrl-names = "default";
};
关键参数说明:
  • compatible:匹配驱动绑定,必须与 I2C 控制器驱动名称一致。
  • clock-frequency:总线时钟频率,单位 Hz。标准模式 100000,快速模式 400000。
  • pinctrl-*:引脚控制配置,确保 SDA 和 SCL 引脚被正确复用。
  • status:"okay" 使能,"disabled" 禁用。
I2C 控制器设备树层级结构 soc.dtsi i2c1: i2c@40005400 compatible = "nordic,nrf-twim" status = "disabled" clock-frequency = <100000> board.dts &i2c1 { ... } status = "okay" clock-frequency = <400000> pinctrl-0 = <&i2c1_default> 引用 & 覆盖 编译后:i2c1 状态 = okay,频率 = 400kHz

3.2 I2C 从设备节点配置

从设备节点必须作为 I2C 控制器节点的子节点存在。每个从设备需要指定唯一的 reg 属性(即 7 位从机地址)和 compatible 字符串。

// board.dts — 在 i2c1 总线挂载两个从设备
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    // 温度传感器 TMP102
    tmp102: temperature-sensor@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;
        label = "TEMP_SENSOR_0";
    };

    // 加速度计 LIS2DH12
    lis2dh12: accelerometer@19 {
        compatible = "st,lis2dh12";
        reg = <0x19>;
        label = "ACCEL_0";
        irq-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
    };
};
地址冲突陷阱:同一 I2C 总线上不允许出现两个 reg 值相同的从设备节点。如果硬件上两个设备地址相同,必须通过硬件地址引脚(如 ADDR 引脚)修改其中一个的地址,或在设备树中只使能一个设备。

3.3 设备树绑定(YAML 绑定文件)

每个 compatible 字符串必须对应一个 YAML 绑定文件,位于 dts/bindings/ 目录下。绑定文件定义了该设备支持的属性和必需属性。

以 ti,tmp102 为例,其绑定文件 ti,tmp102.yaml 内容如下:

# dts/bindings/sensor/ti,tmp102.yaml
description: TI TMP102 Temperature Sensor

compatible: "ti,tmp102"

include: [i2c-device.yaml]

properties:
    label:
        type: string
        required: true
        description: Human-readable label
绑定字段 说明 示例值
compatible 与设备树节点中的 compatible 匹配 "ti,tmp102"
include 继承基础绑定(如 i2c-device.yaml) [i2c-device.yaml]
properties 定义设备特有属性及其类型 label: type: string
required 标记该属性是否为必需 true / false
从设备节点绑定流程 步骤 1 设备树节点 compatible = "ti,tmp102" 步骤 2 YAML 绑定文件 ti,tmp102.yaml 步骤 3 驱动匹配 DEVICE_DT_DEFINE 步骤 4 运行时设备 初始化完成 绑定匹配规则 1. 设备树节点 compatible → 查找同名 YAML 绑定文件 2. YAML 绑定文件 → 定义属性约束和继承关系 3. 驱动宏 DEVICE_DT_DEFINE → 实例化设备对象

3.4 常见配置错误与排查

设备树配置错误是 I2C 驱动无法初始化的首要原因。下表列出了最常见的错误类型及其排查方法:

错误现象 可能原因 排查方法
编译报错:undefined node 引用的节点别名不存在 检查 &i2c1 是否在 .dtsi 中正确定义
编译报错:duplicate unit-address 同一总线存在相同 reg 地址 核对所有从设备节点的 reg 值是否唯一
运行时:I2C transfer timeout 时钟频率过高或引脚配置错误 检查 clock-frequency 是否超出设备规格;验证 pinctrl 引脚复用
运行时:device not found compatible 字符串与绑定文件不匹配 运行 west build -t guiconfig 查看设备树生成状态
运行时:NACK on address 从设备地址错误或设备未上电 用逻辑分析仪抓取 SDA 波形,确认地址字节是否正确
调试利器:使用 west build -t devicetree 可以生成最终的设备树输出文件 build/zephyr/zephyr.dts,查看该文件可以确认所有节点是否按预期展开。

3.5 实战:配置一个 I2C 温度传感器

以下是一个完整的实战步骤,从零开始配置 TMP102 温度传感器:

  1. 确认硬件连接:TMP102 的 SDA 接 I2C1_SDA(如 P0.26),SCL 接 I2C1_SCL(如 P0.27),ADDR 引脚接地(地址 0x48)。
  2. 使能 I2C 控制器:在 board.dts 中添加 &i2c1 { status = "okay"; ... }。
  3. 添加从设备节点:在 &i2c1 内部添加 tmp102: temperature-sensor@48 { ... }。
  4. 验证绑定文件:确保 dts/bindings/sensor/ti,tmp102.yaml 存在且内容正确。
  5. 编译并检查:运行 west build -b your_board app,然后查看 build/zephyr/zephyr.dts 确认节点已展开。
  6. 编写驱动测试:使用 device_get_binding("TEMP_SENSOR_0") 获取设备句柄,调用 sensor_sample_fetch 读取数据。
// 应用层代码示例:读取 TMP102 温度
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>

void main(void)
{
    const struct device *dev = DEVICE_DT_GET_ONE(ti_tmp102);

    if (!device_is_ready(dev)) {
        printk("Device %s is not ready\n", dev->name);
        return;
    }

    while (1) {
        struct sensor_value temp;
        sensor_sample_fetch(dev);
        sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
        printk("Temperature: %d.%06d C\n", temp.val1, temp.val2);
        k_sleep(K_MSEC(1000));
    }
}
提示:如果 DEVICE_DT_GET_ONE 编译失败,请检查 ti_tmp102 的 compatible 字符串是否与绑定文件完全一致。Zephyr 的宏会将 compatible 中的逗号和斜杠转换为下划线。

通过以上步骤,你已经掌握了在 Zephyr 项目中配置 I2C 控制器和从设备节点的完整方法。正确的设备树配置是 I2C 通信稳定的基石,务必在硬件调试前仔细核对节点属性。