1. 设备树基础:从零认识这个嵌入式世界的“硬件说明书”
做嵌入式 Linux 开发这些年,我见过太多新手被设备树搞得一头雾水。说实话,我刚接触时也差不多——明明写驱动直接操作寄存器多痛快,怎么非要搞个 .dts 文件出来?
但干过几个项目后,我彻底服气了。设备树这东西,说白了就是一套描述硬件的“标准化语言”。你想想看,一个 SoC 上挂了多少外设?GPIO、I2C、SPI、UART、PWM……每个外设的地址、中断号、时钟频率都不一样。如果没有统一的管理方式,那代码得乱成什么样?
什么是设备树?
设备树(Device Tree)是一种描述硬件资源的数据结构。它用树形结构把 CPU、内存、总线、外设全部组织起来。内核启动时读取这个结构,就知道“哦,我这块板子上有个 UART3,地址在 0x1000A000,中断号是 42”。
它的核心文件是 .dts(Device Tree Source),编译后生成 .dtb(Device Tree Blob),内核直接解析 .dtb。
一句话总结:设备树 = 硬件的“身份证 + 户口本”,内核靠它认人。
为什么需要设备树?
在设备树出现之前,Linux 内核是怎么处理硬件的?
- 板级文件(Board File):每个板子一个 .c 文件,里面写死所有硬件信息。比如 arch/arm/mach-xxx/ 下面一堆 board-xxx.c。
- 问题来了:ARM 架构碎片化严重,厂商又多,每次换个板子就要改内核代码。Linux 主线内核维护者都快疯了——每天收到几百个板级补丁。
我记得有一次,帮客户移植内核到一块新板子。光改 board 文件就花了三天,还要重新编译整个内核。后来换成设备树,改个 .dts 文件,重新编译设备树就行,内核镜像都不用动。
| 对比项 | 板级文件时代 | 设备树时代 |
|---|---|---|
| 硬件描述位置 | 内核源码 .c 文件 | 独立 .dts 文件 |
| 更换板子 | 改内核、重编译 | 改 .dts、重编译 dtb |
| 内核维护成本 | 极高 | 低 |
| 厂商适配效率 | 慢 | 快 |
个人经验:现在做 BSP 移植,我习惯先看 .dts 文件。硬件怎么接的、中断用哪个、时钟配多少,一目了然。比翻原理图快多了。
设备树源文件语法
.dts 文件的语法其实不复杂。核心就几个概念:节点(node)、属性(property)、值(value)。
来看一个最简单的例子:
/dts-v1/;
/ {
compatible = "mycompany,myboard";
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x00>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB RAM */
};
uart0: serial@1000A000 {
compatible = "ns16550a";
reg = <0x1000A000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
};
这里我解释几个关键点:
- /dts-v1/:版本声明,必须写在第一行。
- / { }:根节点,整棵树从这里开始。
- compatible:最重要的属性之一。内核靠它匹配驱动。格式是“厂商,型号”。
- reg:地址信息。格式是 <地址 长度>。比如 uart0 的 reg = <0x1000A000 0x1000>,表示基地址 0x1000A000,地址范围 0x1000 字节。
- interrupts:中断描述。三个值分别是中断控制器编号、中断号、触发类型。
- uart0::标签(label),方便其他地方引用。比如 &uart0 就能引用这个节点。
避坑指南:我曾经在 reg 属性上栽过跟头。某个外设的地址范围写错了,结果驱动访问时直接挂死。后来养成习惯——每次写完 .dts 都拿原理图对一遍地址。
设备树编译与反编译
.dts 文件不能直接给内核用,得编译成二进制 .dtb。工具是 dtc(Device Tree Compiler)。
基本用法:
# 编译 .dts -> .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译 .dtb -> .dts
dtc -I dtb -O dts -o myboard.dts myboard.dtb
# 查看 dtb 内容(人类可读)
dtc -I dtb -O dts myboard.dtb | less
我个人的工作流是这样的:
- 从厂商拿到参考 .dts 文件
- 根据原理图修改节点(地址、中断、时钟)
- 编译成 .dtb
- 烧录到板子,启动内核
- 如果启动失败,反编译 .dtb 检查是否生效
小技巧:调试时可以用 dtc -I dtb -O dts /sys/firmware/devicetree/base 查看内核当前使用的设备树。这样能确认你烧录的 dtb 是不是对的。
下面这张图是我自己总结的设备树在嵌入式系统中的完整流转路径: