第三章:Zephyr设备树文件结构
设备树这玩意儿,刚接触Zephyr的时候确实容易懵。我刚开始也搞不清楚,为什么一个简单的LED闪烁要牵扯这么多文件?后来做项目做多了,才慢慢摸清门道。说白了,设备树就是一套描述硬件的“说明书”,告诉系统你的板子上有什么外设、接在哪个引脚上、怎么配置。
今天咱们就聊聊Zephyr设备树的文件结构。嗯,这部分很重要,你想想看,搞不清文件结构,后面写驱动、调外设都会很痛苦。
3.1 设备树文件的“四层结构”
Zephyr的设备树文件,我习惯把它分成四个层次。就像盖房子,从地基到装修,一层层往上搭。
| 层次 | 文件后缀 | 作用范围 | 谁在维护 |
|---|---|---|---|
| 芯片级 | .dtsi | 整个芯片系列 | 芯片厂商 |
| 默认配置 | .dtsi | 某系列开发板 | Zephyr社区 |
| 板级 | .dts | 具体某块板子 | 板卡厂商 |
| overlay | .overlay | 用户自定义 | 你 |
这四层文件通过 #include 机制层层叠加。最终编译时,它们会合并成一个完整的设备树。我在项目中遇到过好几次,明明改了overlay文件,但编译没生效——后来发现是文件路径写错了。
3.2 芯片级文件(.dtsi)—— 地基
芯片级文件,通常以芯片型号命名,比如 stm32f4.dtsi、nrf52840.dtsi。它定义了芯片内部的所有资源:CPU核心、中断控制器、GPIO控制器、SPI/I2C/UART外设等等。
举个例子,这是STM32F4系列芯片级文件的一部分:
/ {
soc {
spi1: spi@40013000 {
compatible = "st,stm32-spi";
reg = <0x40013000 0x400>;
interrupts = <35 5>;
status = "disabled";
};
};
};
注意看,这里 status = "disabled"。为什么默认是禁用的?因为芯片厂商不知道你会用哪个外设,干脆全部默认关闭,等你需要时再打开。我个人习惯是,在板级文件里按需使能,而不是一股脑全开——省电又省资源。
3.3 默认配置(.dtsi)—— 标准户型
默认配置文件,通常以开发板系列命名,比如 stm32f4_disco.dtsi。它定义了这个系列开发板的通用配置:板载LED、按键、外部晶振、调试接口等。
看这个例子:
/ {
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
label = "User LD1";
};
};
};
这里定义了板载LED,引脚是PC13,低电平有效。你想想看,如果每块板子都要重新写LED配置,那得多麻烦?默认配置文件就是帮你省掉这些重复劳动。
不过要注意,默认配置文件和芯片级文件都是 .dtsi 后缀。怎么区分?看路径:芯片级文件在 dts/arm/ 下,默认配置在 boards/ 目录里。
3.4 板级文件(.dts)—— 精装修
板级文件,以具体板卡型号命名,比如 stm32f4_disco.dts。它通过 #include 引入芯片级和默认配置,然后做三件事:
- 使能外设:把
status从disabled改成okay - 配置引脚:指定具体用哪个GPIO、哪个复用功能
- 调整参数:比如修改SPI速率、UART波特率
来看一个完整的板级文件:
#include <stm32f4.dtsi>
#include <stm32f4_disco.dtsi>
/ {
model = "STMicroelectronics STM32F4 Discovery";
compatible = "st,stm32f4_disco";
};
&spi1 {
status = "okay";
cs-gpios = <&gpiob 6 GPIO_ACTIVE_LOW>;
};
&usart1 {
status = "okay";
current-speed = <115200>;
};
注意这里的 &spi1 语法。这是设备树的“引用”写法,相当于说“我要修改之前定义的spi1节点”。我刚开始总写成 &spi1,少了个分号编译报错,找了半天才发现。
#include 芯片级文件,编译会报“未定义节点”的错误。我曾经在移植一个新板子时,漏掉了芯片级文件的引用,结果折腾了一下午才发现。
3.5 Overlay文件—— 软装改造
Overlay文件是Zephyr最灵活的设计之一。它允许你在不修改原始设备树文件的前提下,覆盖或添加配置。后缀是 .overlay。
什么时候用overlay?我总结了几种场景:
- 外接模块:比如给开发板外接一个OLED屏幕
- 引脚重映射:板子默认的I2C引脚被占用了,换一组
- 调试配置:临时打开某个外设的调试模式
举个例子,假设你想给STM32F4 Discovery外接一个I2C温湿度传感器:
/ {
i2c_sensor: sht30@44 {
compatible = "sensirion,sht3xd";
reg = <0x44>;
label = "SHT30";
};
};
&i2c1 {
status = "okay";
clock-frequency = <100000>;
};
把这个文件保存为 sht30.overlay,编译时加上 -DSHIELD=sht30 或者直接放在 app/boards/ 目录下,系统就会自动加载。
Overlay文件的优先级最高。如果板级文件里某个外设是 disabled,你在overlay里改成 okay,最终生效的是overlay的配置。嗯,这里要注意,overlay不是万能的,它不能删除节点,只能添加或修改。
3.6 文件加载顺序与编译机制
Zephyr在编译时,会按照特定顺序加载这些文件。我画了个简化的流程:
- 先加载芯片级
.dtsi(基础硬件定义) - 再加载默认配置
.dtsi(板级通用配置) - 然后加载板级
.dts(具体板卡配置) - 最后加载overlay文件(用户自定义)
每一步都是“增量修改”。如果同一个属性在多个文件中出现,后加载的会覆盖先加载的。这就是为什么overlay能“篡改”板级配置的原因。
我记得有一次调试一个SPI设备,怎么配置都不对。后来用 west build -t guiconfig 查看最终生成的设备树,才发现是overlay文件和板级文件里定义了同一个引脚,overlay覆盖了板级的配置。从那以后,我每次改设备树都会先检查最终生成的 build/zephyr/zephyr.dts 文件。
zephyr.dts 文件,这就是最终合并后的设备树。如果外设不工作,先看这个文件里的配置对不对。我90%的设备树问题,都是通过看这个文件找到原因的。
好了,设备树的文件结构就聊到这儿。下一章咱们会深入设备树的语法和属性,到时候你会看到更多实际案例。记住,搞懂这四层结构,后面写驱动、调外设就会顺手很多。