设备树(Device Tree)是嵌入式 Linux 开发中绕不开的一环。它用树形结构描述硬件平台上的设备信息,由节点(Node)和属性(Property)两种基本元素组成。设备树源文件以 .dts 为扩展名,通过 DTC(Device Tree Compiler)编译为 .dtb 二进制文件供内核使用。
本文梳理了设备树的核心语法、常用属性与节点写法,并结合 i.MX6ULL 平台风格给出 USART、I2C、SPI、CAN 等外设的完整配置示例,云栈社区希望这份指南能帮你少踩几个坑。
01 设备树的格式
设备树源文件以版本声明开头,根节点用 / 表示:
/dts-v1/;
/ {
/* 根节点内容 */
node1 {
/* 子节点 */
};
node2 {
/* 子节点 */
};
};
注意:设备树有且仅有一个根节点,所有硬件信息都挂载在根节点之下。
1.1 基本结构
节点的基本定义格式为:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
- node-name:节点名称,最长 31 个字符,应清晰描述设备功能
- @unit-address:单元地址,通常是设备的寄存器基地址或总线地址
- label:标签(可选),用于在其他地方引用该节点
- properties definitions:节点的属性定义
- child nodes:子节点定义
注意:节点内可包含属性定义和子节点,属性必须放在子节点之前。
一个串口节点的示例:
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart", "fsl,imx-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 4>;
status = "disabled";
};
其中 uart1 是标签,serial 是节点名,@02020000 是单元地址。
1.2 属性值的数据类型
设备树属性支持多种数据类型:
| 类型 |
语法 |
示例 |
| 32 位整数数组(cells) |
< > 包围,空格分隔 |
reg = <0x02020000 0x4000>; |
| 字符串 |
双引号包围 |
compatible = "fsl,imx-uart"; |
| 字符串列表 |
逗号分隔多个字符串 |
compatible = "v1", "v2", "v3"; |
| 二进制数据 |
[ ] 包围,十六进制字节 |
local-mac-address = [00 11 22 33 44 55]; |
| 空属性 |
仅有属性名,无值 |
status; |
| 混合类型 |
逗号分隔不同部分 |
example = <0xf00f0000 19>, "a string"; |
注意:
① 64 位值可用两个 32 位 cell 表示:clock-frequency = <0x00000001 0x00000000>;
② 可用 /bits/ 前缀指定 8、16、64 位元素:interrupts = /bits/ 8 <17 0xc>;
③ 属性值支持算术、位运算、逻辑运算和条件表达式。
1.3 注释风格
设备树沿用了 C 语言的注释风格:
- 单行注释:
// 注释内容
- 多行注释:
/* 注释内容 */
1.4 节点删除与属性删除
在 .dts 文件中可以删除先前在 .dtsi 中定义的节点或属性:
/delete-node/ &can1; // 删除节点
/delete-property/ ethernet0; // 删除属性
02 dts 文件如何包含 dtsi 文件
2.1 文件类型说明
| 文件类型 |
全称 |
作用 |
.dts |
Device Tree Source |
板级入口文件,每种主板一份 |
.dtsi |
Device Tree Source Include |
可复用的公共部分,类似 C 语言的 .h 文件 |
.dtb |
Device Tree Blob |
编译后的二进制文件,内核直接使用 |
2.2 包含语法
设备树使用 C 预处理器语法进行文件包含:
#include "rk3576.dtsi"
#include "rk3576-rk806.dtsi"
#include <dt-bindings/gpio/gpio.h>
关键点:
① 可使用 #include 引用 .dtsi 文件,也可以引用 .h 头文件
② 被包含的文件名应以 .dtsi 结尾
③ 包含文件可以递归包含其他文件
④ 所有 #include 的内容在编译时会被递归合并到最终的 .dtb 中
2.3 分层设计思想
设备树采用分层设计实现硬件描述的模块化和可重用:
- SoC 级(
.dtsi):描述芯片公共资源,如 CPU、中断控制器、片上外设控制器等
- 板级(
.dts):描述具体开发板的差异信息,如电源、外设、引脚复用等
以 Rockchip 平台为例的包含层次:
tspi-3m-rk3576.dts(主板入口)
├── #include "rk3576.dtsi"(SoC 公共资源)
└── #include "rk3576-rk806.dtsi"(板级电源配置)
03 嵌入式开发中常用到的属性
3.1 compatible — 兼容性属性
用于设备与驱动的匹配,内核通过匹配 compatible 值来识别设备并绑定对应驱动。推荐格式为 "manufacturer,model":
compatible = "fsl,imx6ul-uart", "fsl,imx-uart";
内核会按顺序匹配,直到找到匹配的驱动。
3.2 reg — 寄存器地址属性
描述设备的寄存器地址空间,格式为 <基地址 地址长度>:
reg = <0x02020000 0x4000>;
对于总线上的设备,reg 也可表示设备地址。
3.3 #address-cells 和 #size-cells — 地址/大小单元格数
定义子节点中 reg 属性的解析格式:
#address-cells = <1>; // reg 中地址占 1 个 cell
#size-cells = <1>; // reg 中大小占 1 个 cell
如果 #address-cells=<1>、#size-cells=<1>,则子节点 reg = <0x0 0x100> 表示地址 0x0、大小 0x100。
3.4 interrupts — 中断属性
描述设备的中断信息:
interrupts = <0 26 4>;
格式取决于中断控制器,通常包含中断号、触发类型等。
3.5 clocks — 时钟属性
描述设备使用的时钟:
clocks = <&clk1>, <&clk2>;
通过 & 引用其他节点(phandle)来指定时钟源。
3.6 status — 设备状态属性
描述设备的当前状态:
| 值 |
含义 |
"okay" |
设备正常工作,内核会初始化该设备 |
"disabled" |
设备禁用,内核不会初始化 |
3.7 phandle 与标签引用性
phandle 是节点的唯一标识符,用于节点间相互引用。更常用的方式是使用标签(label):
// 定义标签
uart1: serial@02020000 {
compatible = "fsl,imx-uart";
};
// 引用标签(编译时自动展开为 phandle)
some-device {
uart = <&uart1>;
};
&uart1 会被编译为 uart1 节点的 phandle 值。
3.8 其他常用属性
| 属性 |
用途 |
model |
设备型号描述,根节点用于传递板级型号 |
device_type |
设备类型,如 "cpu"、"memory" |
pinctrl-* |
引脚控制配置 |
dma-names / dmas |
DMA 通道配置 |
clock-frequency |
时钟频率 |
04 嵌入式开发中常用到的节点(node)
4.1 根节点 — /
所有设备树的顶层节点,有且仅有一个:
/ {
model = "My Board";
compatible = "myvendor,myboard";
#address-cells = <1>;
#size-cells = <1>;
};
4.2 /cpus — CPU 节点
描述系统的 CPU 信息,包含一个或多个 cpu 子节点:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0>;
clock-frequency = <996000000>;
};
};
4.3 /memory — 内存节点
描述系统的物理内存布局,所有设备树必须包含:
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB 内存
};
4.4 /chosen — 选择节点
用于传递内核启动参数等配置信息,不描述硬件:
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait";
stdout-path = &uart1;
};
4.5 /aliases — 别名节点
为设备节点定义别名,便于内核和用户空间引用:
aliases {
serial0 = &uart1;
serial1 = &uart2;
i2c0 = &i2c1;
spi0 = &ecspi1;
can0 = &flexcan1;
};
4.6 /soc — 片上系统节点
通常作为 SoC 所有片上外设的容器节点:
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
/* 各种外设控制器节点 */
uart1: serial@02020000 { ... };
i2c1: i2c@021a0000 { ... };
};
4.7 外设控制器节点
各类接口控制器节点,包括:
- UART/USART:
serial@ 或 uart@
- I2C:
i2c@
- SPI:
spi@
- CAN:
can@ 或 flexcan@
- GPIO:
gpio@
- Pinctrl:
pinctrl@
05 设备树示例(包含 USART、I2C、SPI、CAN)
以下是一个综合示例,展示了嵌入式开发板中常用的 USART、I2C、SPI、CAN 等接口的设备树配置(基于 i.MX6ULL 平台风格):
/dts-v1/;
#include "imx6ull.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
/ {
model = "My Embedded Board";
compatible = "myvendor,myboard", "fsl,imx6ull";
/* 内存配置 */
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB DDR */
};
/* 启动参数 */
chosen {
bootargs = "console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw";
stdout-path = &uart1;
};
/* 别名 */
aliases {
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
i2c0 = &i2c1;
i2c1 = &i2c2;
spi0 = &ecspi1;
can0 = &flexcan1;
};
};
&uart1 { /* USART1 — 调试串口 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
&uart2 { /* USART2 — RS232 通信 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2>;
fsl,uart-has-rtscts;
status = "okay";
};
&uart3 { /* USART3 — RS485 模式 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
fsl,uart-has-rtscts;
linux,rs485-enabled-at-boot-time;
status = "okay";
};
&i2c1 { /* I2C1 — 挂载传感器和 RTC */
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
/* I2C 温湿度传感器 */
sensor@44 {
compatible = "ti,hdc1080";
reg = <0x44>;
status = "okay";
};
/* I2C RTC 时钟芯片 */
rtc@68 {
compatible = "nxp,pcf8563";
reg = <0x68>;
status = "okay";
};
};
&i2c2 { /* I2C2 — 挂载 EEPROM */
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
status = "okay";
};
};
&ecspi1 { /* SPI1 — 挂载 ADC 芯片 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>;
status = "okay";
adc@0 {
compatible = "ti,ads7950";
reg = <0>; /* SPI 片选 0 */
spi-max-frequency = <1000000>;
status = "okay";
};
};
&ecspi2 { /* SPI2 — 挂载 Flash */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi2>;
cs-gpios = <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
flash@0 {
compatible = "winbond,w25q128";
reg = <0>;
spi-max-frequency = <50000000>;
status = "okay";
};
};
&flexcan1 { /* CAN1 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan1>;
status = "okay";
};
&flexcan2 { /* CAN2 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan2>;
status = "okay";
};
示例解析
文件头:/dts-v1/; 声明版本,通过 #include 引入 SoC 的公共 dtsi 文件和头文件。
根节点:定义板级 model 和 compatible,以及 memory、chosen、aliases 等标准节点。
USART 配置:&uart1 使用 & 引用 SoC dtsi 中定义的节点;pinctrl-* 指定引脚复用配置;status = "okay" 使能设备;RS485 相关属性用于 RS485 模式。
I2C 配置:clock-frequency 设置总线速率;I2C 子节点用 reg 指定从设备地址;每个从设备有自己的 compatible 和 status。
SPI 配置:cs-gpios 指定片选引脚;子节点用 reg 指定片选编号;spi-max-frequency 指定最大 SPI 时钟频率。
CAN 配置:使用 &flexcan1、&flexcan2 引用 CAN 控制器节点,配置 pinctrl 后使能。