3. 设备树(DTS)基础

设备树(Device Tree,DT)是 Zephyr RTOS 中描述硬件拓扑与资源配置的核心机制。它使用一种树形结构将 SoC 内部外设、内存映射、中断连接、引脚复用等硬件信息从驱动代码中剥离出来,实现“硬件描述”与“驱动逻辑”的解耦。本章将深入剖析 DTS 语法、节点与属性的组织方式,以及绑定(Binding)文件的结构与作用。

学习目标:掌握 DTS 文件的基本语法规则;能够独立阅读和编写设备树节点与属性;理解 Binding 文件如何为 DTS 节点提供类型与约束信息。

3.1 DTS 语法基础

DTS 文件采用一种类 C 语言的层次化语法,以分号 ; 结束语句,使用花括号 { } 表示层级。其基本单元是 节点(node) 和 属性(property)。

3.1.1 节点定义

每个节点代表一个硬件设备或总线。节点名称通常为 名称@地址 的形式,其中 @地址 部分用于区分同一名称下的不同实例。

/dts-v1/;

/ {
    model = "Zephyr Demo Board";
    compatible = "zephyr,demo-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        uart0: serial@40002000 {
            compatible = "ns16550";
            reg = <0x40002000 0x1000>;
            interrupts = <5 0>;
            status = "okay";
        };

        gpio0: gpio@40010000 {
            compatible = "nordic,nrf-gpio";
            reg = <0x40010000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
        };
    };
};
语法要点:
  • /dts-v1/;:文件版本声明,必须位于文件开头。
  • /:根节点,整个设备树的起点。
  • uart0: serial@40002000:uart0 是标签(label),用于在其它地方引用;serial 是节点名称;@40002000 是地址单元。
  • #address-cells 和 #size-cells:定义子节点中 reg 属性的地址和长度字段宽度。

3.1.2 属性类型与写法

属性是键值对,用于描述设备的配置参数。常见属性类型如下表所示:

属性类型 语法示例 说明
字符串 compatible = "ns16550"; 用于匹配驱动,通常为“厂商,设备型号”格式。
32位整数 interrupts = <5 0>; 尖括号内为一个或多个32位数值。
64位整数 reg = <0x40002000 0x1000>; 由两个32位整数拼接表示64位地址或长度。
布尔值 gpio-controller; 仅写属性名,不赋值,表示“真”。
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节序列。
字符串列表 compatible = "ti,cc2650", "ti,cc26xx"; 逗号分隔多个字符串,驱动按顺序匹配。

3.2 节点与属性的层级关系

设备树的树形结构通过父子节点嵌套实现。每个节点可以有子节点,形成从 SoC 总线到具体外设的完整路径。路径引用是 DTS 中定位节点的关键手段。

3.2.1 路径与标签引用

  • 绝对路径:从根节点开始,如 /soc/serial@40002000。
  • 标签引用:使用 &uart0 引用标签为 uart0 的节点,常用于在其它文件中追加或修改节点属性。
  • 别名(aliases):在根节点的 aliases 子节点中定义短名称,如 aliases { uart-0 = &uart0; };。
常见陷阱:标签引用 &uart0 必须在节点定义中先声明标签(如 uart0: serial@40002000),否则编译器会报“undefined label”错误。

3.2.2 节点追加与覆盖

在 Zephyr 中,板级 DTS 文件经常需要覆盖 SoC 级 DTS 中的节点属性。这通过 &标签 语法实现:

// 在板级 DTS 文件中
&uart0 {
    status = "okay";
    current-speed = <115200>;
};

&gpio0 {
    status = "okay";
};

上述代码将 uart0 的状态从 disabled 改为 okay,并添加了波特率属性。这种机制使得 SoC 级 DTS 保持通用,板级 DTS 只做差异化配置。

3.3 绑定(Binding)文件结构

Binding 文件是 YAML 格式的描述文件,用于为 DTS 节点提供类型检查、默认值、枚举约束等元信息。Zephyr 的构建系统在编译 DTS 时会读取 Binding 文件,生成结构化的 C 宏定义供驱动使用。

3.3.1 Binding 文件位置与命名

Binding 文件通常位于 dts/bindings/ 目录下,按厂商或设备类型组织子目录。文件名必须与 DTS 节点中的 compatible 属性值对应,例如:

  • dts/bindings/serial/ns16550.yaml 对应 compatible = "ns16550";
  • dts/bindings/gpio/nordic,nrf-gpio.yaml 对应 compatible = "nordic,nrf-gpio";

3.3.2 Binding 文件核心字段

一个典型的 Binding 文件包含以下部分:

# dts/bindings/serial/ns16550.yaml
description: NS16550 UART controller

compatible: "ns16550"

include: [uart-controller.yaml]

properties:
  reg:
    required: true
    type: array
    description: Memory-mapped base address and size

  interrupts:
    required: true
    type: array
    description: Interrupt number and flags

  current-speed:
    required: false
    type: int
    default: 115200
    description: Baud rate in Hz

  status:
    required: false
    type: string
    enum:
      - "okay"
      - "disabled"
    description: Device status
字段 说明
description 人类可读的设备描述。
compatible 与 DTS 节点中的 compatible 属性匹配。
include 继承其它 Binding 文件,复用公共属性定义。
properties 定义每个属性的类型、是否必须、默认值、枚举值等。
required true 表示该属性在 DTS 节点中必须存在。
type 属性数据类型,如 int、array、string、boolean。
enum 限定属性值只能从指定列表中选取。

3.3.3 Binding 继承机制

通过 include 字段,一个 Binding 文件可以继承另一个文件的所有属性定义。例如 ns16550.yaml 继承了 uart-controller.yaml,后者定义了所有 UART 控制器共有的属性(如 reg、interrupts、current-speed)。这避免了重复定义,保持了属性的一致性。

驱动开发流程:编写一个新外设驱动时,通常需要:
  1. 在 DTS 中定义节点,填写 compatible 和必要属性。
  2. 在 dts/bindings/ 下创建对应的 YAML Binding 文件。
  3. 在驱动 C 代码中使用 DT_* 宏(如 DT_REG_ADDR、DT_PROP)读取 DTS 属性。

3.4 DTS 编译与生成产物

Zephyr 构建系统在编译时会执行以下步骤:

  1. 将 .dts 文件通过 C 预处理器展开 #include 和宏定义,生成 .dts.pre 文件。
  2. 使用 dtc(Device Tree Compiler)将 .dts.pre 编译为 .dtb(二进制格式)。
  3. 解析 Binding 文件,生成 devicetree_generated.h 头文件,其中包含所有节点的宏定义。

驱动代码通过包含 <devicetree.h> 即可使用这些宏,实现编译时硬件配置注入。

3.5 设备树结构总览

下图展示了从 DTS 源文件到驱动代码的数据流与层级关系:

设备树(DTS)数据流与层级结构 DTS 源文件 soc.dtsi / board.dts Binding 文件 ns16550.yaml DTC 编译器 dtc + 脚本 devicetree_generated.h C 宏定义 .dtb 二进制 运行时设备树 驱动 C 代码 #include <devicetree.h> DT_* 宏展开 编译时替换为数值 硬件寄存器初始化 运行时生效 关键点 DTS 描述硬件 Binding 约束属性 驱动读取宏定义

3.6 实战:解析一个完整节点

以下是一个 I2C 控制器节点的完整示例,结合 DTS 语法与 Binding 文件进行解析:

// DTS 节点
i2c0: i2c@40003000 {
    compatible = "nordic,nrf-i2c";
    reg = <0x40003000 0x1000>;
    interrupts = <6 0>;
    status = "okay";
    clock-frequency = <100000>;
    #address-cells = <1>;
    #size-cells = <0>;

    sensor@48 {
        compatible = "ti,hdc1080";
        reg = <0x48>;
    };
};

对应的 Binding 文件 nordic,nrf-i2c.yaml 会定义:

  • reg 为必需数组,表示基地址和大小。
  • interrupts 为必需数组。
  • clock-frequency 为可选整数,默认值 100000。
  • #address-cells 和 #size-cells 用于子节点地址解析。

子节点 sensor@48 的 compatible = "ti,hdc1080" 会匹配另一个 Binding 文件 ti,hdc1080.yaml,该文件定义了温度/湿度传感器的特有属性。

调试技巧:使用 west build -t devicetree 可以查看编译后的 devicetree_generated.h 文件,确认宏定义是否正确生成。如果某个属性未定义,检查 Binding 文件中是否遗漏了 required: true 或属性名拼写错误。

3.7 本章小结

设备树是 Zephyr 驱动开发的基石。通过本章的学习,你应该能够:

  • 阅读并编写基本的 DTS 节点与属性。
  • 理解标签、路径、别名在节点引用中的作用。
  • 掌握 Binding 文件的结构,包括 compatible、properties、include 等核心字段。
  • 描述 DTS 从源文件到 C 宏定义的完整编译流程。