在嵌入式 Linux 开发中,设备树是一个绕不开的核心概念。它的出现,从根本上改变了驱动开发的方式,让"一套内核,多套硬件"成为可能。
本篇博客只讲最基础的内容,包括:
- 什么是设备树?
- 为什么要有设备树?
- dts,dtsi,dtb 是什么?
- 设备树里最基本的节点、属性怎么写?
- 驱动和设备树是怎么对应起来的?

1.设备树基本概念
1.1什么是设备树?
在设备树出现之前,ARM 架构下的 Linux 开发曾面临一个严重的困境:硬件信息被硬编码在 C 源码中,每块电路板都需要定制独立的内核,导致内核镜像泛滥,维护成本极高,这与 Linux"一内核多平台"的目标严重冲突。
设备树的本质,就是一种 树状数据结构 ,用于描述硬件平台上那些 不能动态探测到的设备(如 CPU、内存、片内外设等)。它将硬件信息从内核源码中分离出来,保存在独立的配置文件中,内核启动时动态读取这些信息,自动加载适配驱动。
简单理解:设备树就是一份"硬件说明书",告诉 Linux 内核这块板子上有哪些设备,以及它们的地址、中断号等配置参数。
设备树可以先简单理解成一句话:
设备树就是用来描述硬件信息的数据结构。
Linux 内核启动时,需要知道板子上有哪些外设,比如:
- LED 接在哪个 GPIO
- 按键接在哪个 GPIO
- UART 的寄存器地址是多少
- I2C 控制器的中断号是多少
这些硬件信息不能写死在每个驱动里,否则同一个内核换一块板子就要改源码、重新编译,非常麻烦。
所以 Linux 引入了设备树,把这些和硬件板子相关的信息单独描述出来。
1.2设备树文件格式与编译工具
设备树涉及三种主要文件格式,理解它们的区别是入门的第一步:
| 文件类型 | 扩展名 | 说明 |
|---|---|---|
| DTS | .dts | 设备树源文件,人类可读的 ASCII 文本,一个 .dts 通常对应一个硬件平台 |
| DTSI | .dtsi | 设备树包含文件,描述 SoC 级别的公共信息(如 CPU 架构、外设寄存器地址等),可被多个 DTS 通过 #include 引用 |
| DTB | .dtb | 设备树二进制文件,由 DTS 编译生成,供 U-Boot 和 Linux 内核使用 |
从 DTS 到 DTB 的编译工具是 DTC(Device Tree Compiler)。典型用法如下:
bash
# 编译 DTS 为 DTB
dtc -I dts -O dtb -o output.dtb input.dts
# 反编译 DTB 为 DTS(调试常用)
dtc -I dtb -O dts -o output.dts input.dtb
DTC 工具源码位于内核的 scripts/dtc 目录,也可通过 apt install device-tree-compiler 单独安装;

2.设备树的基本语法
设备树文件由节点(Node)和属性(Property)两种基本元素构成。
2.1节点
节点的基本格式为:
bash
标签: 节点名@单元地址 {
属性...
子节点...
};
例如:
bash
i2c1: i2c@1c2b400 {
compatible = "vendor,i2c-controller";
reg = <0x1c2b400 0x400>;
/* 子节点和属性 */
};
- / 表示根节点
- 根节点下面可以挂很多子节点
bash
/ {
model = "my board";
compatible = "myvendor,myboard";
led0 {
compatible = "myvendor,myled";
};
key0 {
compatible = "myvendor,mykey";
};
};
这里 led0 和 key0 就是两个子节点。
2.2属性
属性以键值对的形式描述设备特性,常见的数据类型有:
(1)身份与状态类
这类属性决定了 "你是谁" 以及 "当前系统用不用你" ,是内核平台总线(Platform Bus)进行设备与驱动匹配的绝对核心。
| 属性名称 (Property) | 物理硬件含义 (硬件的世界) | 内核与驱动行为 (软件的世界) |
|---|---|---|
| compatible | 设备的"身份证号"。标明了具体的芯片制造商和器件的精确型号。 | 平台总线拿着这串字符与驱动代码里的**.of_match_table** 逐一比对。一旦字符串完全匹配,内核就会唤醒并执行该驱动的 probe函数。 |
| status | 电路的"物理开关"。描述该硬件模块在当前的电路板上是否实际存在(是否贴了芯片、是否飞了线)。 | okay 表示启用,内核会为其分配内存并尝试挂载驱动;disable 表示禁用,内核解析器会直接忽略该节点,节省系统资源。 |
(2)硬件资源类
这类属性描述了设备在物理世界中的 "坐标与连线" 。驱动程序被唤醒后,必须靠这些属性才能真正找到硬件并对其发号施令。
| 属性名称 (Property) | 物理硬件含义 (硬件的世界) | 内核与驱动行为 (软件的世界) |
|---|---|---|
| reg | 设备的"门牌号"。硬件模块在芯片内部总线上的绝对物理基地址,或者在外部通讯总线(如 I2C/SPI)上的从机地址。 | 驱动在 probe 中通过 platform_get_resource 获取该值,如果是内存地址,则进一步使用 ioremap 映射为可操作的虚拟地址指针。 |
| interrupt-parent | 中断的"直属上司"。该模块的物理中断报警线,具体接在了哪一个上级中断控制器(比如某组 GPIO)的身上。 | 告知内核中断的级联路由关系,让内核知道去哪个控制器那里为该设备申请虚拟中断号。 |
| interrupts | 中断的"触发条件"。具体接在第几号引脚?以及物理电平是如何跳变的(高/低电平,或上升/下降沿)。 | 内核解析此信息后,在内存中生成一个唯一的虚拟中断号 (VIRQ)。驱动程序随后使用 request_irq(virq, ...) 将中断处理函数注册到该号码上。 |
| clocks | 设备的"心脏起搏器"。硬件内部逻辑电路运行所依赖的物理振荡脉冲(频率源)。 | 驱动程序在读写任何 reg 寄存器之前,必须先调用 clk_enable() 开启这个时钟源。否则一旦访问未通时钟的寄存器,会导致总线死锁和内核崩溃。 |
示例:
bash
/* 必须包含头文件,才能使用硬件对应的文本宏(如 RK_PB5, GPIO_ACTIVE_LOW) */
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/pinctrl/rockchip.h>
/* 使用 & 符号引用在 .dtsi 中已经定义好的 i2c1 控制器节点 */
&i2c1 {
/* 【身份与状态类】把 I2C1 控制器总闸打开 */
status = "okay";
/* 在 I2C1 总线下定义触摸屏节点,@38 只是节点命名习惯,表示地址 */
touchscreen@38 {
/* ================= 身份与状态类 ================= */
/* 身份证号:内核拿着 "edt,edt-ft5206" 去驱动代码里找对应的 .c 文件 */
compatible = "edt,edt-ft5206";
/* 设备开关:表示板子上确实焊了这个触摸屏,请内核加载它 */
status = "okay";
/* ================= 硬件资源类 ================= */
/* 门牌号:I2C 通讯时的从机地址 (十六进制 0x38) */
reg = <0x38>;
/* 汇报线:这块屏幕的中断引脚,物理上连到了 gpio0 控制器上 */
interrupt-parent = <&gpio0>;
/* 触发源:具体连在 gpio0 的 PB5 引脚,且硬件设计为下降沿触发中断 */
interrupts = <RK_PB5 IRQ_TYPE_EDGE_FALLING>;
/* 物理连线:这块屏幕的复位引脚,物理上连到了 gpio0 的 PB6 引脚,低电平复位 */
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
/* (注:触摸屏自带内部振荡器,通常不需要系统给它分配 clocks 属性) */
};
};
3.中断实例分析
作为驱动工程师,我们撰写 ft5x06 触摸屏的中断的设备树时,要根据以下步骤撰写:
1.查看 ft5x06 原理图,找到中断对应的 gpio口

可以看到是GPIO3_A5;
2.查找gpio的设备树源码
了解gpio对下规则


对设备树代码的理解:
硬件连接过程:外设-gpio-gpio控制器-GIC控制器-cpu(内核);
所以,这个地方对gpio0的定义分为两部分,上半部分说明的是gpio作为普通外设,对上,对GIC的连接,下半部分是说明gpio作为gpio控制器或者中断控制器的作用,是对连接引脚的外设而言的,所以我们作为驱动开发者的话,我们要开发外设的驱动,肯定是要看下半部分的;
详细解释见:

3.找 ft5x06 对应的driver程序,确认name

最终得到的设备树代码如下:
bash
/dts-v1/
/include "/home/topeet/source/linux/rk3568_linux_5.10/kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi"
/{
model = "This is touchscreen dt"
ft5x06@38{
compatible = "edt,edt-ft5206" ; //name需要在ft5x06的驱动源码中看
interrupt-parent = <gpio0>;
interrupts = <13,1>; //等价于:interrupts=<RK PB5 IRQ_TYPE EDGE_RISING>; 用宏需要包含头文件
}
}