3. 设备树基础语法:节点、属性、标签、包含文件(#include)、节点引用与覆盖
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的设备、总线、中断、时钟、GPIO 等信息以节点和属性的形式组织起来。对于 SPI 设备配置而言,掌握设备树的基础语法是编写正确设备树文件的前提。本章将系统讲解设备树的五大核心语法要素:节点、属性、标签、包含文件以及节点引用与覆盖。
学习提示:设备树语法与 C 语言结构体类似,但更强调层次化和可复用性。建议读者在阅读本章时,对照实际开发板(如 i.MX6ULL、STM32MP1)的 .dts 文件进行验证。
3.1 节点(Node):设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过路径(path)唯一标识,路径从根节点(/)开始,类似文件系统的目录结构。
3.1.1 节点定义语法
// 节点定义格式
[label:] node-name[@unit-address] {
[property-definitions]
[child-node-definitions]
};
- node-name:节点名称,描述设备类型,如
spi、gpio、serial。 - @unit-address:可选,通常为设备在总线上的地址(如 SPI 片选号、寄存器基地址)。
- label:可选,节点标签,用于在其他位置引用该节点。
- 花括号 {} 和分号 ;:节点体必须用花括号包裹,并以分号结束。
3.1.2 根节点与子节点
每个设备树文件有且只有一个根节点(/),所有其他节点都是根节点的后代。SPI 控制器节点通常挂接在 SoC 内部总线节点(如 soc、ahb、apb)下,而 SPI 外设节点则作为 SPI 控制器的子节点。
/ {
model = "Embedded SPI Demo Board";
compatible = "vendor,spi-board";
soc {
spi@ff010000 {
compatible = "vendor,spi-controller";
reg = <0xff010000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
// SPI 外设子节点
spi-device@0 {
compatible = "vendor,spi-sensor";
reg = <0>; // 片选号 0
spi-max-frequency = <10000000>;
};
};
};
};
关键点:SPI 外设节点必须作为 SPI 控制器节点的子节点,且通过
reg 属性指定片选号(CS)。控制器通过 #address-cells 和 #size-cells 定义子节点地址的编码格式。
3.2 属性(Property):描述节点的特征
属性是附着在节点上的键值对,用于描述设备的硬件特性、配置参数和连接关系。属性值可以是字符串、整数、字节数组、字符串列表或二进制数据。
3.2.1 常用属性类型
| 属性类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | property = "string"; |
compatible = "vendor,spi-device"; |
设备兼容性标识,驱动匹配的关键 |
| 32 位整数 | property = <value>; |
spi-max-frequency = <5000000>; |
SPI 最大时钟频率,单位 Hz |
| 64 位整数 | property = <0x00000000 0xvalue>; |
reg = <0x00000000 0xff010000>; |
地址范围,高 32 位 + 低 32 位 |
| 字符串列表 | property = "str1", "str2"; |
compatible = "vendor,spi-a", "vendor,spi-b"; |
多个兼容驱动,按优先级排列 |
| 字节数组 | property = [hex bytes]; |
local-mac-address = [00 11 22 33 44 55]; |
MAC 地址、UUID 等二进制数据 |
| 布尔值 | property;(无值) |
spi-cpha; spi-cpol; |
存在即表示 true,用于使能特性 |
3.2.2 SPI 设备关键属性
在配置 SPI 外设节点时,以下属性是驱动正确工作的基础:
- compatible:驱动匹配字符串,格式为
"厂商,设备型号",如"maxim,ds1307"。 - reg:SPI 片选号(CS),对于 SPI 总线,
#address-cells = <1>时,reg = <0>表示 CS0。 - spi-max-frequency:设备支持的最大 SPI 时钟频率,单位 Hz。
- spi-cpha / spi-cpol:布尔属性,配置 SPI 模式(CPHA/CPOL)。
- spi-cs-high:布尔属性,片选信号高电平有效(默认低电平)。
- interrupts:中断号,需配合中断控制器使用。
// 完整的 SPI 外设节点属性示例
&spi1 {
status = "okay";
temperature-sensor@0 {
compatible = "ti,lm70";
reg = <0>; // 片选 CS0
spi-max-frequency = <10000000>; // 10 MHz
spi-cpha; // CPHA = 1
spi-cpol; // CPOL = 1
// 等效于 SPI 模式 3
interrupts = <5 2>; // 中断号 5,触发类型 2(下降沿)
};
};
常见错误:忘记设置
spi-max-frequency 或设置值超过控制器/设备上限,会导致 SPI 通信时序异常。建议根据设备数据手册设置安全值,通常从较低频率开始调试。
3.3 标签(Label):节点的别名
标签是节点的别名,用于在设备树的其他位置快速引用该节点。标签定义在节点名称之前,使用冒号分隔。标签的主要用途包括:
- 简化引用:避免书写冗长的节点路径。
- 节点覆盖:通过标签引用节点并修改其属性。
- 驱动绑定:驱动代码中通过标签查找节点。
// 定义标签
spi1: spi@ff010000 {
compatible = "vendor,spi-controller";
reg = <0xff010000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
};
// 在其他位置通过 &spi1 引用
&spi1 {
status = "okay";
pinctrl-0 = <&pinctrl_spi1>;
};
标签的命名规范:
- 使用小写字母、数字和下划线。
- 避免使用关键字(如
compatible、reg)。 - 建议使用设备功能缩写,如
spi1、i2c2、uart3。
3.4 包含文件(#include):复用与模块化
设备树支持 C 语言预处理器指令 #include,用于包含头文件(.h)或其他设备树源文件(.dtsi)。这是实现设备树模块化和复用性的核心机制。
3.4.1 包含文件的作用
| 文件类型 | 扩展名 | 用途 | 示例 |
|---|---|---|---|
| 设备树头文件 | .h | 定义宏、常量、GPIO 编号、时钟 ID | #include "imx6ull-pinfunc.h" |
| 设备树包含文件 | .dtsi | SoC 公共设备树,包含所有外设控制器节点 | #include "imx6ull.dtsi" |
| 板级设备树 | .dts | 具体板卡的设备树,包含 .dtsi 并覆盖配置 | #include "my-board.dts" |
3.4.2 包含文件实战
// my-board.dts
#include "soc-common.dtsi" // 包含 SoC 公共定义
#include "pinfuncs.h" // 包含引脚复用宏
/ {
model = "My SPI Board";
compatible = "vendor,my-board";
};
// 引用并覆盖 soc-common.dtsi 中定义的 spi1 节点
&spi1 {
status = "okay";
pinctrl-0 = <&pinctrl_spi1>;
adc@0 {
compatible = "ti,ads1256";
reg = <0>;
spi-max-frequency = <19200000>;
// 使用头文件中定义的宏
clocks = <&clks IMX6UL_CLK_SPI1>;
};
};
最佳实践:将 SoC 公共定义放在 .dtsi 文件中,板级差异放在 .dts 文件中。头文件(.h)用于定义宏常量,避免在设备树中硬编码魔法数字。
3.5 节点引用与覆盖(Reference & Overlay)
节点引用与覆盖是设备树实现“增量修改”的核心机制。通过标签引用一个已存在的节点,可以在不重新定义整个节点的情况下,添加或修改其属性和子节点。
3.5.1 引用语法
// 引用格式:<label> { ... };
&spi1 {
status = "okay";
// 添加新属性或覆盖已有属性
pinctrl-0 = <&pinctrl_spi1>;
};
3.5.2 覆盖规则
| 操作 | 语法 | 效果 |
|---|---|---|
| 添加属性 | &node { new-prop = <value>; }; |
节点新增属性,不影响已有属性 |
| 覆盖属性 | &node { existing-prop = <new-value>; }; |
用新值替换已有属性的值 |
| 添加子节点 | &node { child@addr { ... }; }; |
在节点下新增子节点 |
| 删除属性 | &node { /delete-property/ prop-name; }; |
删除指定属性(DTC 支持) |
| 删除子节点 | &node { /delete-node/ child@addr; }; |
删除指定子节点(DTC 支持) |
3.5.3 覆盖实战:SPI 设备配置
// soc-common.dtsi 中定义 SPI 控制器骨架
spi1: spi@ff010000 {
compatible = "vendor,spi-controller";
reg = <0xff010000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled"; // 默认关闭
};
// my-board.dts 中覆盖配置
#include "soc-common.dtsi"
&spi1 {
status = "okay"; // 覆盖为启用
pinctrl-0 = <&pinctrl_spi1>; // 添加引脚配置
cs-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; // 添加片选 GPIO
// 添加 SPI 外设子节点
spi-nor@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
m25p,fast-read;
};
spi-touch@1 {
compatible = "vendor,touch-controller";
reg = <1>;
spi-max-frequency = <10000000>;
interrupt-parent = <&gpio2>;
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
};
};
覆盖机制的优势:SoC 厂商提供 .dtsi 文件定义所有外设的默认配置,板卡厂商只需通过引用覆盖修改少量属性(如 status、pinctrl、SPI 外设),无需复制整个节点定义,大大减少了代码冗余和出错概率。
3.6 综合示例:完整的 SPI 设备树配置
以下是一个完整的板级设备树文件,综合运用了本章所学的所有语法要素:
// my-spi-board.dts
#include "soc-imx6ull.dtsi" // SoC 公共定义
#include "imx6ull-pinfunc.h" // 引脚复用宏
/ {
model = "My SPI Sensor Board";
compatible = "vendor,my-spi-board";
chosen {
stdout-path = &uart1;
};
// 板级 GPIO 按键
gpio-keys {
compatible = "gpio-keys";
button@0 {
label = "USER-BUTTON";
gpios = &gpio5 1 GPIO_ACTIVE_LOW;
linux,code = <KEY_ENTER>;
};
};
};
// 引用并覆盖 SPI 控制器
&spi1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi1>;
cs-gpios = <&gpio4 11 GPIO_ACTIVE_LOW>;
// SPI 温度传感器
temp-sensor@0 {
compatible = "maxim,ds18b20-spi";
reg = <0>;
spi-max-frequency = <4000000>;
spi-cpha;
spi-cpol;
resolution = <12>;
};
// SPI ADC
adc@1 {
compatible = "ti,ads1256";
reg = <1>;
spi-max-frequency = <19200000>;
interrupts = <6 IRQ_TYPE_EDGE_RISING>;
vref-supply = <®_vref>;
};
};
// 引脚复用配置
&iomuxc {
pinctrl_spi1: spi1grp {
fsl,pins = <
MX6UL_PAD_CSI_DATA00__GPIO4_IO11 0x10b0 // CS
MX6UL_PAD_CSI_DATA01__ECSPI1_MISO 0x10b0 // MISO
MX6UL_PAD_CSI_DATA02__ECSPI1_MOSI 0x10b0 // MOSI
MX6UL_PAD_CSI_DATA03__ECSPI1_SCLK 0x10b0 // SCLK
>;
};
};
// 电源节点
®_vref {
regulator-name = "vref-3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};