news 2026/4/22 22:07:57

设备树语法详解:全面讲解DTS文件结构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设备树语法详解:全面讲解DTS文件结构

设备树不是魔法:从零读懂DTS文件的真正写法

你有没有遇到过这样的场景?

调试一块新板子,内核启动日志里反复报错:“No matching device found for 'my-sensor'”,翻遍驱动代码也没看出问题。最后发现,只是设备树里的compatible字符串拼错了——少了个逗号,或者厂商名写成了“rockchip”而不是“rockchip,”。

这正是设备树(Device Tree)最真实的一面:它不复杂,但极其讲究细节;它解放了内核代码,却把硬件描述的责任交给了开发者。而这份责任,藏在每一个节点、每一条属性、每一组地址单元之中。

今天,我们不讲概念堆砌,也不复制手册。我们要像拆电路板一样,一层层揭开.dts文件的真实结构,搞清楚——为什么这么写?不这么写会怎样?


一、设备树到底解决了什么问题?

在 ARM Linux 还没有统一标准的年代,每个开发板都要维护一套独立的 C 语言板级文件(BSP),里面塞满了类似这样的代码:

static struct platform_device uart0_device = { .name = "serial8250", .resource = { [0] = { .start = 0x3f8, .end = 0x3ff, .flags = IORESOURCE_MEM, }, [1] = { .start = 4, .end = 4, .flags = IORESOURCE_IRQ, }, } };

成百上千行这种代码,重复出现在不同板子上。改个引脚?重编内核。加个外设?还得进内核源码改。移植一次等于重做一遍。

于是社区决定:把硬件信息拎出来,用一种通用格式描述,让内核去读它,而不是硬编码进去。

这就是设备树的本质——一份给内核看的“硬件说明书”。


二、DTS 文件长什么样?从一个最小系统说起

来看一段真实的 DTS 片段,别急着看语法,先感受它的逻辑结构:

/dts-v1/; /include/ "skeleton.dtsi" / { model = "My Embedded Board"; compatible = "mycompany,myboard"; chosen { bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2"; }; memory@80000000 { device_type = "memory"; reg = <0x80000000 0x40000000>; /* 1GB */ }; soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges; uart0: serial@3f8 { compatible = "ns8250"; reg = <0x3f8 0x8>; interrupts = <4>; clock-frequency = <1843200>; status = "okay"; }; }; };

这段代码干了四件事:
- 声明这是一个 v1 格式的设备树;
- 包含了一个通用骨架文件;
- 描述了机器型号、内存位置和启动参数;
- 在 SoC 内部定义了一个串口控制器。

注意,这里没有任何函数调用或初始化逻辑。它是纯粹的数据描述。

那么,这些“数据”是如何变成内核能识别的设备的?

流程很清晰:

  1. 编写 DTS
  2. dtc 编译成 DTB(二进制 Blob)
  3. U-Boot 把 DTB 放到内存并传给内核
  4. 内核解析 DTB,创建platform_deviceamba_device
  5. 驱动通过of_match_table匹配设备,开始工作

整个过程就像:厨师(内核)拿到一张菜单(DTB),照着上面写的食材清单准备饭菜,不需要提前知道今天吃什么。


三、节点与属性:谁是主角?

节点(Node)——代表一个物理实体

格式为:

node-name@unit-address { // 属性和子节点 };

比如:

i2c@7e804000 { ... };

这里的i2c是类型名,7e804000是它的寄存器基地址。如果有多个 I2C 控制器,就靠这个地址区分。

⚠️ 小贴士:即使两个设备都是 I2C,只要地址不同,就是不同的节点。这是实现多实例的基础。

你可以给节点起个别名(label),方便后续引用:

i2c1: i2c@7e804000 { ... };

之后就可以用&i2c1来修改或添加内容,不用再写完整路径。


属性(Property)——描述节点特征

属性是键值对,比如:

compatible = "brcm,bcm2835-i2c"; reg = <0x7e804000 0x1000>; interrupts = <1>; status = "okay";

它们不是随便写的,而是有明确语义的“关键词”。下面我们挑几个最关键的深入聊聊。


四、compatible:驱动匹配的灵魂

当你注册一个平台驱动时,通常会写这样一段代码:

static const struct of_device_id my_driver_of_match[] = { { .compatible = "fsl,imx6ul-enet" }, { /* sentinel */ } };

内核启动时,会遍历所有设备树节点,查看哪个节点的compatible和你的驱动列表匹配。一旦命中,就会调用.probe()函数。

所以,compatible不是给人看的注释,是给内核做决策的依据

而且它支持回退机制:

compatible = "fsl,imx6ul-enet", "fsl,imx6q-enet";

意思是从左到右依次尝试匹配。如果imx6ul-enet驱动不存在,就试试imx6q-enet。这是一种兼容老驱动的设计技巧。

✅ 最佳实践:第一个字符串尽量具体(vendor,model),第二个可以更宽泛(vendor,family


五、reg与地址映射:怎么算出“占了多大地方”?

很多人第一次看到reg = <0x3f8 8>;都会疑惑:这两个数什么意思?

答案取决于它的父节点有没有定义#address-cells#size-cells

例如:

soc { #address-cells = <1>; #size-cells = <1>; serial@3f8 { reg = <0x3f8 0x8>; }; };

这意味着:
- 地址部分占 1 个 cell(32位)
- 大小部分也占 1 个 cell

所以<0x3f8 0x8>表示:从地址0x3f8开始,占用 8 字节。

但如果换成:

#address-cells = <2>; #size-cells = <1>; reg = <0x0 0x3f8 0x8>;

这就表示使用 64 位地址空间,前两个数字组成地址(高32 + 低32),第三个是长度。

💡 实际案例:某些 PCIe 控制器就需要双 cell 地址来支持大于 4GB 的映射空间。


六、中断系统是怎么连起来的?

中断是最容易出错的部分之一,因为它涉及两级结构:设备 → 中断控制器

举个例子:

gpio_keys { compatible = "gpio-keys"; interrupt-parent = <&gpio1>; interrupts = <21 IRQ_TYPE_EDGE_RISING>; };

这里的关键点在于:
-interrupt-parent指定了中断信号接到哪个控制器(&gpio1
-interrupts给出了具体的中断号和触发方式

而 GPIO 控制器本身必须声明自己是一个中断控制器:

gpio1: gpio@10000000 { compatible = "foo,gpio"; interrupt-controller; #interrupt-cells = <2>; };

其中#interrupt-cells = <2>表示每个中断需要两个参数(比如 pin 号 + 触发类型)。这决定了你在interrupts里要写几个值。

❗常见坑点:忘记设置interrupt-controller或者#interrupt-cells数量不对,会导致中断无法注册。


七、标签(Label)和引用:别再写冗长路径了!

想象你要配置一个 USB PHY,它属于某个 USB 控制器:

&usbdrd_dwc3 { dr_mode = "host"; status = "okay"; extcon = <&usbdrd_iddig>; };

这里的&usbdrd_iddig就是引用另一个带 label 的节点:

usbdrd_iddig: usb-id-dig { compatible = "linux,extcon-usb-gpio"; id-gpio = <&gpiob 12 GPIO_ACTIVE_HIGH>; };

如果没有 label,你就得写成:

extcon = "/soc/usb-dr-device/usb-id-dig";

不仅难读,还容易拼错。

✅ 强烈建议:所有会被引用的节点都加上 label!


八、Overlay:让设备树也能“热插拔”

你知道树莓派的 HAT 扩展板是怎么自动识别的吗?靠的就是设备树 Overlay

传统设备树是静态的,编译好就不能改。但 Overlay 允许你在运行时动态加载补丁,修改主设备树。

比如你想在运行中启用一个 I2C 上的 EEPROM:

// i2c-eeprom-overlay.dts /dts-v1/; /plugin/; / { fragment@0 { target = <&i2c1>; __overlay__ { status = "okay"; eeprom@50 { compatible = "atmel,24c02"; reg = <0x50>; }; }; }; };

编译后得到.dtbo文件,然后:

echo i2c-eeprom-overlay > /sys/kernel/config/device-tree/overlays/

内核就会把这个节点合并进主树,并尝试绑定驱动。

🎯 应用场景:USB 转 CAN 卡、FPGA 子卡、传感器扩展模块等即插即用需求。


九、实战经验:我在项目中踩过的坑

坑1:status = "disabled"写成了"disable"

结果设备根本没被扫描,日志里一句话都没有。因为内核只认"okay""disabled",其他都是无效值。

✅ 解决方案:永远用双引号包裹状态值,且只使用标准值。


坑2:.dtsi文件包含顺序错误

我曾经在一个项目中同时包含了imx6dl.dtsiimx6q.dtsi,结果 CPU 被识别成了 Quad-core,实际却是 DualLite。

✅ 正确做法:确保只有一个 SoC 级.dtsi被包含,板级.dts只继承一个基础文件。


坑3:Pinmux 配置没生效

明明写了 pad 设置,但 I2C 就是不通。后来才发现,pin control 节点没有被任何设备引用!

正确的做法是在设备节点中显式指定 pinctrl:

&i2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; };

否则,即使你定义了 pin group,也不会被应用。


十、设计建议:如何写出可维护的 DTS?

1. 分层管理.dts.dtsi

  • soc.dtsi:SoC 共享部分(CPU、内存、控制器框架)
  • board.dts:板级差异(外设、电源、GPIO分配)
  • module.dtso:模块化 overlay(可选功能)

这样升级 SoC 支持时,只需更新.dtsi,不影响板级配置。


2. 使用有意义的 label

不要写:

&LCD_CTRL { ... }

而应写:

&lcdif1 { lcd-display { ... }; }

越具体越好,避免缩写歧义。


3. 合理使用__forceW=1编译检查

在编译时加上:

make ARCH=arm dtbs W=1

可以暴露未声明的属性、拼写错误等问题。很多看似“运行正常”的 DTS,其实藏着潜在风险。


4. 文档化你的compatible字符串

如果你写了新的驱动,一定要在文档中说明支持哪些compatible值。最好提交到 Devicetree Binding 文档 。

否则别人根本不知道该怎么写设备树来匹配你的设备。


写在最后:设备树是桥梁,不是终点

掌握设备树的意义,不只是会写.dts文件。

它代表着一种思维方式的转变:硬件不再是代码的一部分,而是一种可配置的资源

未来,随着 RISC-V 生态的发展、Zephyr OS 对 DT 的全面采纳,以及 ACPI 在嵌入式领域的渗透,设备树的角色还会继续演化。

但对于现在的我们来说,理解它的语法细节、明白每一行背后的机制、避开那些隐蔽的坑——才是真正的基本功。

下次当你面对一片黑屏的日志时,不妨静下心来看看设备树:也许答案,早就写在那里了。

如果你在实际项目中遇到过离谱的设备树 bug,欢迎留言分享——我们一起排雷。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 16:23:28

关于页面有惊喜!Z-Image-Turbo项目版权全知道

关于页面有惊喜&#xff01;Z-Image-Turbo项目版权全知道 1. 项目背景与二次开发动因 1.1 技术演进中的功能缺口 Z-Image-Turbo 是阿里通义实验室推出的高效文生图模型&#xff0c;基于 DiffSynth Studio 框架实现低步数高质量图像生成&#xff08;支持1~40步内出图&#xf…

作者头像 李华
网站建设 2026/4/18 22:09:59

DeepSeek-R1支持哪些操作系统?跨平台部署实战手册

DeepSeek-R1支持哪些操作系统&#xff1f;跨平台部署实战手册 1. 引言 1.1 业务场景描述 随着大模型在本地化推理、隐私保护和边缘计算场景中的需求日益增长&#xff0c;如何在资源受限的设备上实现高效、安全的AI推理成为关键挑战。尤其在企业内部系统、离线办公环境或教育…

作者头像 李华
网站建设 2026/4/17 7:18:44

DeepSeek-R1技术分享:从大模型到轻量化的历程

DeepSeek-R1技术分享&#xff1a;从大模型到轻量化的历程 1. 引言&#xff1a;本地化大模型的现实需求 随着大语言模型在自然语言理解、代码生成和逻辑推理等任务上的持续突破&#xff0c;其应用范围迅速扩展至教育、金融、研发等多个领域。然而&#xff0c;主流大模型通常依…

作者头像 李华
网站建设 2026/4/18 5:39:21

VibeThinker-1.5B详细评测:数学推理能力有多强?

VibeThinker-1.5B详细评测&#xff1a;数学推理能力有多强&#xff1f; 在当前大模型参数规模不断膨胀的背景下&#xff0c;一个仅拥有1.5B参数的小型语言模型——VibeThinker-1.5B&#xff0c;凭借其在数学与编程任务中的出色表现引起了广泛关注。该模型由微博开源团队推出&a…

作者头像 李华
网站建设 2026/4/22 19:26:35

告别PS!用CV-UNet大模型镜像实现智能图片去背景

告别PS&#xff01;用CV-UNet大模型镜像实现智能图片去背景 1. 引言&#xff1a;AI抠图的工程化落地新选择 在图像处理领域&#xff0c;背景移除&#xff08;Image Matting&#xff09; 是一项高频且刚需的任务。传统方式依赖Photoshop等专业工具手动操作&#xff0c;耗时耗力…

作者头像 李华
网站建设 2026/4/18 14:54:04

Whisper语音识别开源方案:替代商业API的完整指南

Whisper语音识别开源方案&#xff1a;替代商业API的完整指南 1. 引言 1.1 业务场景描述 在当前全球化背景下&#xff0c;多语言语音转录需求日益增长。无论是跨国会议记录、在线教育内容生成&#xff0c;还是客服系统语音分析&#xff0c;企业与开发者都面临高昂的商业语音识…

作者头像 李华