返回首页

Zephyr 设备树学习笔记

概述

设备树(Device Tree)是描述硬件配置的树状数据结构。在 Zephyr 中,设备树用于定义板级硬件配置、外设参数和连接关系,与代码分离,使同一套代码可以在不同硬件上运行。

核心概念

1. 设备树层次结构

设备树源文件 (.dts)
    ↓ 编译
设备树 blob (.dtb) - 二进制格式
    ↓ Zephyr 处理
设备树头文件 (devicetree.h)
    ↓ C 预处理器
代码中的设备树 API

2. 关键术语

术语说明示例
Node设备树节点,代表一个设备或总线/soc, /soc/i2c@40003000
Property节点属性,描述设备特性compatible, reg, status
Label节点标签,便于引用i2c0: &i2c@40003000
Phandle节点引用标识符&i2c0, <&gpio0>
Compatible兼容性字符串"nordic,nrf-twi"

3. 设备树文件类型

文件类型用途位置
.dts设备树源文件boards/, samples/
.dtsi包含文件(通用定义)dts/, soc/
.overlay覆盖文件(修改配置)samples/, tests/
.yaml绑定定义(Schema)dts/bindings/

设备树源文件语法

1. 基本节点定义

/ {
    // 根节点
    model = "Nordic nRF54L15 DK";
    compatible = "nordic,nrf54l15-dk";

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        // I2C 外设
        i2c0: i2c@40003000 {
            compatible = "nordic,nrf-twi";
            reg = <0x40003000 0x1000>;
            interrupts = <3 1>;
            status = "disabled";
            sda-pin = <26>;
            scl-pin = <27>;
            clock-frequency = <I2C_BITRATE_FAST>;
        };
    };

    leds {
        compatible = "gpio-leds";
        led0: led_0 {
            gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
            label = "Green LED 0";
        };
    };
};

2. 节点属性类型

my_node {
    // 字符串
    string-prop = "hello world";
    
    // 整数
    int-prop = <42>;
    
    // 布尔(存在即为真)
    bool-prop;
    
    // 数组
    array-prop = <1 2 3 4>;
    
    // 字节数组
    byte-array = [01 02 03 04];
    
    // 字符串数组
    string-array = "first", "second", "third";
    
    // 引用(phandle)
    ref-prop = <&other_node>;
    
    // 引用数组
    ref-array = <&node1 &node2 &node3>;
};

3. 包含和覆盖

// 包含通用定义
#include <nordic/nrf54l15.dtsi>
#include <zephyr/dt-bindings/gpio/gpio.h>

/ {
    // 添加或修改节点
};

// 覆盖已有节点(&引用)
&i2c0 {
    status = "okay";
    
    sensor@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
        status = "okay";
    };
};

常用属性详解

1. compatible

定义设备的兼容性字符串,用于匹配驱动程序。

// 单一兼容
compatible = "nordic,nrf-twi";

// 多个兼容(优先匹配第一个)
compatible = "nordic,nrf-twim", "nordic,nrf-twi";

2. reg

定义寄存器地址和大小。

#address-cells = <1>;  // 地址用 1 个 cell(32位)
#size-cells = <1>;     // 大小用 1 个 cell

// reg = <address size>
i2c@40003000 {
    reg = <0x40003000 0x1000>;  // 地址 0x40003000,大小 4KB
};

3. status

控制设备启用状态。

status = "okay";      // 启用
status = "disabled";  // 禁用(默认)

4. interrupts

定义中断配置。

// 单一中断
interrupts = <IRQ_NUMBER IRQ_PRIORITY>;

// 多个中断
interrupts-extended = <&nvic 3 1>, <&nvic 4 2>;

5. pinctrl

引脚控制配置(现代 Zephyr 推荐)。

#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>

&i2c0 {
    pinctrl-0 = <&i2c0_default>;
    pinctrl-1 = <&i2c0_sleep>;
    pinctrl-names = "default", "sleep";
};

&pinctrl {
    i2c0_default: i2c0_default {
        group1 {
            psels = <NRF_PSEL(TWI_SDA, 0, 26)>,
                    <NRF_PSEL(TWI_SCL, 0, 27)>;
        };
    };
};

设备树绑定(Bindings)

1. YAML 绑定文件

定义设备属性的 Schema 验证。

# dts/bindings/i2c/nordic,nrf-twi.yaml
description: Nordic TWI (I2C) controller

compatible: "nordic,nrf-twi"

include: i2c-controller.yaml

properties:
  reg:
    required: true
  
  interrupts:
    required: true
  
  sda-pin:
    type: int
    required: false
    description: SDA pin number
  
  scl-pin:
    type: int
    required: false
    description: SCL pin number
  
  clock-frequency:
    type: int
    required: false
    default: 100000
    description: I2C clock frequency in Hz

2. 属性类型

properties:
  # 字符串
  string-prop:
    type: string
  
  # 整数
  int-prop:
    type: int
  
  # 布尔
  bool-prop:
    type: boolean
  
  # 数组
  array-prop:
    type: array
    # 元素类型: int, string, phandle
  
  # phandle
  ref-prop:
    type: phandle
  
  # phandle + spec
  gpio-prop:
    type: phandle-array
    specifier-space: gpio

在代码中使用设备树

1. 包含头文件

#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>

2. 获取设备实例

// 通过节点标签获取设备
const struct device *i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c0));

// 检查设备就绪
if (!device_is_ready(i2c_dev)) {
    printk("I2C device not ready\n");
    return -ENODEV;
}

3. 读取属性值

// 读取整数
uint32_t clock_freq = DT_PROP(DT_NODELABEL(i2c0), clock_frequency);

// 读取数组
uint32_t reg_addr = DT_REG_ADDR(DT_NODELABEL(i2c0));
uint32_t reg_size = DT_REG_SIZE(DT_NODELABEL(i2c0));

// 读取字符串
const char *label = DT_LABEL(DT_NODELABEL(led0));

// 检查布尔属性
#if DT_NODE_HAS_PROP(DT_NODELABEL(my_node), bool_prop)
    // bool_prop 存在
#endif

// 检查节点状态
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2c0), okay)
    // i2c0 已启用
#endif

4. GPIO 操作

#include <zephyr/drivers/gpio.h>

// 从设备树获取 GPIO 规格
#define LED0_NODE DT_NODELABEL(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

// 初始化
int init_led(void)
{
    if (!device_is_ready(led.port)) {
        return -ENODEV;
    }
    
    return gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
}

// 控制 LED
void toggle_led(void)
{
    gpio_pin_toggle_dt(&led);
}

5. I2C 设备操作

// 定义传感器节点
#define SENSOR_NODE DT_NODELABEL(bme280)

// 获取 I2C 总线
const struct device *i2c_dev = DEVICE_DT_GET(DT_BUS(SENSOR_NODE));

// 获取 I2C 地址
uint8_t i2c_addr = DT_REG_ADDR(SENSOR_NODE);

// 读取传感器数据
uint8_t read_sensor_reg(uint8_t reg)
{
    uint8_t val;
    i2c_reg_read_byte(i2c_dev, i2c_addr, reg, &val);
    return val;
}

6. 遍历设备

// 遍历所有 GPIO LED
#define LED_FOREACH(inst) \
    DT_FOREACH_CHILD(DT_PATH(leds), inst)

// 定义 LED 数组
#define LED_DEFINE(node_id) \
    GPIO_DT_SPEC_GET(node_id, gpios),

static const struct gpio_dt_spec leds[] = {
    LED_FOREACH(LED_DEFINE)
};

#define NUM_LEDS (sizeof(leds) / sizeof(leds[0]))

设备树覆盖(Overlay)

1. 应用场景

samples/my_sample/
├── src/
│   └── main.c
├── boards/
│   ├── nrf54l15dk_nrf54l15_cpuapp.overlay  # 特定板子覆盖
│   └── nrf52840dk_nrf52840.overlay
└── prj.conf

2. 覆盖文件示例

// boards/nrf54l15dk_nrf54l15_cpuapp.overlay

// 启用 I2C
&i2c0 {
    status = "okay";
    pinctrl-0 = <&i2c0_default>;
    
    bme280@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
        status = "okay";
    };
};

// 添加自定义节点
/ {
    my_custom_hw {
        compatible = "vnd,my-hw";
        status = "okay";
        custom-prop = <100>;
    };
};

3. 编译时应用覆盖

# 使用覆盖文件编译
west build -b nrf54l15dk/nrf54l15/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=custom.overlay

# 或使用 SHIELD
west build -b nrf54l15dk/nrf54l15/cpuapp -- -DSHIELD=my_shield

Nordic 特定配置

1. nRF52 系列

// Nordic nRF52840 DK
&uart0 {
    status = "okay";
    current-speed = <115200>;
    tx-pin = <6>;
    rx-pin = <8>;
    rts-pin = <5>;
    cts-pin = <7>;
};

&i2c0 {
    status = "okay";
    sda-pin = <26>;
    scl-pin = <27>;
};

&spi0 {
    status = "okay";
    sck-pin = <23>;
    mosi-pin = <24>;
    miso-pin = <25>;
};

2. nRF54 系列

// nRF54L15 - 使用 pinctrl
&uart20 {
    status = "okay";
    current-speed = <115200>;
    pinctrl-0 = <&uart20_default>;
    pinctrl-names = "default";
};

&pinctrl {
    uart20_default: uart20_default {
        group1 {
            psels = <NRF_PSEL(UART_TX, 1, 4)>,
                    <NRF_PSEL(UART_RX, 1, 5)>;
        };
    };
};

实际应用示例

传感器节点配置

// 在 board 的 .dts 文件中
&i2c0 {
    status = "okay";
    clock-frequency = <I2C_BITRATE_FAST>;
    
    bme280@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
        status = "okay";
    };
    
    lis2dh@19 {
        compatible = "st,lis2dh";
        reg = <0x19>;
        status = "okay";
        irq-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
    };
};
// 在代码中使用
#define BME280_NODE DT_NODELABEL(bme280)
#define LIS2DH_NODE DT_NODELABEL(lis2dh)

const struct device *bme280 = DEVICE_DT_GET(BME280_NODE);
const struct device *lis2dh = DEVICE_DT_GET(LIS2DH_NODE);

// 检查设备就绪
if (!device_is_ready(bme280) || !device_is_ready(lis2dh)) {
    printk("Sensors not ready\n");
    return -ENODEV;
}

自定义硬件配置

// 自定义硬件板子定义
/ {
    sensors {
        compatible = "vnd,sensor-array";
        
        temp_sensor: temp {
            compatible = "vnd,temp-sensor";
            io-channels = <&adc 0>;
            status = "okay";
        };
        
        light_sensor: light {
            compatible = "vnd,light-sensor";
            io-channels = <&adc 1>;
            status = "okay";
        };
    };
};

调试设备树

1. 查看生成的设备树

# 编译后查看生成的设备树
west build -t devicetree_target

# 生成的文件在 build/zephyr/zephyr.dts.pre

2. 设备树错误排查

# 启用详细输出
west build -v

# 检查预处理后的设备树
cat build/zephyr/zephyr.dts.pre

3. 常用宏

// 检查节点是否存在
#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_twim)
    // 存在 nordic,nrf-twim 节点且状态为 okay
#endif

// 获取节点数量
#define NUM_LEDS DT_NUM_INST_STATUS_OKAY(gpio_leds)

// 条件编译
#if DT_NODE_HAS_COMPAT(DT_NODELABEL(i2c0), nordic_nrf_twim)
    // 使用 TWIM 驱动
#else
    // 使用 TWI 驱动
#endif

总结

Zephyr 设备树是硬件配置的核心:

  1. 分离硬件与代码:同一代码适配不同板子
  2. 编译时确定:零运行时开销
  3. 类型安全:YAML 绑定验证
  4. 灵活覆盖:overlay 文件修改配置

学习重点:


*学习日期: 2026-03-20* *小白 🤖*