1. 设备树基础概念:什么是设备树、为什么需要设备树、设备树在嵌入式Linux中的角色
大家好,我是你们的嵌入式系统讲师。今天咱们来聊聊设备树——这个在嵌入式Linux开发中绕不开的话题。
说实话,我刚开始接触设备树的时候,也是一头雾水。那时候还在用老旧的板级文件,突然说要换成设备树,心里还挺抵触的。但用了一段时间后,我不得不承认——这玩意儿确实是个好东西。
什么是设备树?
设备树,英文叫Device Tree,简称DT。说白了,它就是一个描述硬件信息的文本文件。
你想想看,一个嵌入式板子上有CPU、内存、GPIO、I2C、SPI、UART……这么多硬件,操作系统怎么知道它们长什么样?怎么知道它们怎么用?
设备树就是干这个的。它用树形结构,把板子上的所有硬件信息都描述清楚。嗯,就像一棵倒着长的树——根节点是CPU,下面挂着内存控制器、总线、外设等等。
核心要点:设备树是一种数据结构,用于描述硬件资源,让操作系统无需修改内核代码就能支持不同的硬件平台。
我习惯把设备树比作一张硬件“身份证”。每个外设都有自己专属的节点,节点里写着它的地址、中断号、时钟频率等等。内核启动时,就读这张“身份证”,然后按图索骥去初始化硬件。
为什么需要设备树?
这个问题问得好。在设备树出现之前,嵌入式Linux是怎么处理硬件的?
答案是——板级文件(Board File)。
那时候,每出一个新板子,就得在内核源码里加一个arch/arm/mach-xxx/board-xxx.c文件。里面密密麻麻地写着各种硬件的初始化代码。我当年参与过一个项目,光板级文件就有十几个,每个都上千行。
这有什么问题呢?问题大了去了。
- 代码冗余:同样的硬件,换个板子就得重新写一遍初始化代码
- 维护困难:内核升级时,板级文件经常要跟着改,工作量巨大
- 碎片化严重:每个厂商都有自己的板级文件,Linux主线内核根本收不完
我记得有一次,客户要求把一块成熟的板子移植到新内核上。我花了整整两周,就为了把那个板级文件适配过去。改完以后,我就在想——这肯定不是长久之计。
设备树就是来解决这个痛点的。它把硬件描述和内核代码彻底分开了。
我的经验:设备树让硬件描述变成了“数据”而不是“代码”。数据可以独立于内核存在,换板子只需要换一个.dts文件,内核代码完全不用动。这在项目迭代中太香了。
设备树在嵌入式Linux中的角色
设备树在嵌入式Linux里到底扮演什么角色?我用一个图来说明。
从上图你能看到,设备树夹在硬件和内核之间,扮演着“翻译官”的角色。
具体来说,它的工作流程是这样的:
- 硬件厂商提供板子的设备树源文件(.dts)
- 编译工具把.dts编译成二进制文件(.dtb)
- Bootloader(比如U-Boot)把.dtb加载到内存
- 内核启动时解析.dtb,获取硬件信息
- 设备驱动通过设备树API读取硬件参数,完成初始化
注意:我曾经遇到过一个坑——设备树里中断号写错了,结果某个外设死活不工作。查了两天才发现是.dts里一个数字写反了。所以写设备树一定要仔细,一个字节都不能错。
设备树的核心文件类型
搞设备树开发,你得认识这几种文件:
| 文件类型 | 扩展名 | 说明 |
|---|---|---|
| 源文件 | .dts |
设备树源文件,描述具体板子的硬件 |
| 头文件 | .dtsi |
设备树包含文件,通常放SoC公共部分 |
| 二进制文件 | .dtb |
编译后的设备树,内核直接使用 |
| 编译脚本 | .dts → .dtb |
使用dtc工具编译 |
我个人习惯把.dtsi当作“公共库”。比如STM32MP1系列,所有板子都共用同一个stm32mp151.dtsi,里面定义了CPU、内存控制器、总线等。然后每个板子再写自己的.dts,只描述板级特有的外设。
小技巧:写设备树时,多用#include引入.dtsi文件。这样既能复用代码,又能保持清晰。我见过有人把所有东西都写在一个.dts里,结果文件上万行,维护起来想哭。
一个简单的设备树示例
光说不练假把式。咱们看一个最简单的设备树长什么样:
/dts-v1/;
/ {
model = "STM32MP157C-DK2";
compatible = "st,stm32mp157c-dk2", "st,stm32mp157";
memory@c0000000 {
device_type = "memory";
reg = <0xc0000000 0x20000000>;
};
chosen {
stdout-path = &uart4;
};
uart4: serial@40010000 {
compatible = "st,stm32-uart";
reg = <0x40010000 0x400>;
interrupts = <0 52 4>;
status = "okay";
};
};
这段代码描述了什么?
- 板子型号是STM32MP157C-DK2
- 内存从0xc0000000开始,大小512MB
- 控制台输出用UART4
- UART4的基地址是0x40010000,中断号是52
你看,是不是很直观?内核拿到这个文件,就知道怎么初始化硬件了。
设备树的优势总结
说了这么多,咱们总结一下设备树的几个核心优势:
- 解耦硬件与内核:换板子不用改内核代码
- 标准化描述:统一的语法,所有厂商都遵守
- 易于维护:硬件信息集中管理,一目了然
- 支持多板型:一个内核镜像可以适配多个板子
- 社区生态好:Linux主线内核大量使用设备树
嗯,这就是设备树的基础概念。说白了,它就是嵌入式Linux世界里,硬件和软件之间的一座桥梁。没有它,我们的开发工作会痛苦得多。
下一节,咱们会深入设备树的语法和节点结构。到时候我会拿一个真实的STM32MP1项目来拆解,看看设备树到底怎么写、怎么用。
一句话总结:设备树 = 硬件的“身份证” + 内核的“导航图”。搞懂它,嵌入式Linux开发就成功了一半。