3. 设备树(DTS)基础
设备树(Device Tree,DT)是 Zephyr RTOS 中描述硬件拓扑与资源配置的核心机制。它使用一种树形结构将 SoC 内部外设、内存映射、中断连接、引脚复用等硬件信息从驱动代码中剥离出来,实现“硬件描述”与“驱动逻辑”的解耦。本章将深入剖析 DTS 语法、节点与属性的组织方式,以及绑定(Binding)文件的结构与作用。
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)。这避免了重复定义,保持了属性的一致性。
- 在 DTS 中定义节点,填写
compatible和必要属性。 - 在
dts/bindings/下创建对应的 YAML Binding 文件。 - 在驱动 C 代码中使用
DT_*宏(如DT_REG_ADDR、DT_PROP)读取 DTS 属性。
3.4 DTS 编译与生成产物
Zephyr 构建系统在编译时会执行以下步骤:
- 将
.dts文件通过 C 预处理器展开#include和宏定义,生成.dts.pre文件。 - 使用
dtc(Device Tree Compiler)将.dts.pre编译为.dtb(二进制格式)。 - 解析 Binding 文件,生成
devicetree_generated.h头文件,其中包含所有节点的宏定义。
驱动代码通过包含 <devicetree.h> 即可使用这些宏,实现编译时硬件配置注入。
3.5 设备树结构总览
下图展示了从 DTS 源文件到驱动代码的数据流与层级关系:
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 宏定义的完整编译流程。