文章目录
-
- 每日一句正能量
- 摘要
- 一、引言:为什么Zephyr需要设备树?
- [二、设备树(Device Tree)详解](#二、设备树(Device Tree)详解)
-
- [2.1 设备树的基本概念](#2.1 设备树的基本概念)
- [2.2 设备树文件类型与层级](#2.2 设备树文件类型与层级)
- [2.3 设备树编译流程](#2.3 设备树编译流程)
- [2.4 设备树Overlay机制](#2.4 设备树Overlay机制)
- 三、Kconfig配置系统详解
-
- [3.1 Kconfig的核心作用](#3.1 Kconfig的核心作用)
- [3.2 Kconfig语法详解](#3.2 Kconfig语法详解)
- [3.3 配置优先级](#3.3 配置优先级)
- [3.4 配置片段(Overlay Config)](#3.4 配置片段(Overlay Config))
- 四、DTS与Kconfig的协同工作
-
- [4.1 两者的关系与分工](#4.1 两者的关系与分工)
- [4.2 驱动中的典型用法](#4.2 驱动中的典型用法)
- [4.3 应用代码的硬件无关编程](#4.3 应用代码的硬件无关编程)
- 五、设备树节点结构深度解析
-
- [5.1 常用DT宏API速查](#5.1 常用DT宏API速查)
- 六、完整工程实践:从零配置一个Zephyr项目
-
- [6.1 项目目录结构](#6.1 项目目录结构)
- [6.2 CMakeLists.txt](#6.2 CMakeLists.txt)
- [6.3 设备树覆盖(app.overlay)](#6.3 设备树覆盖(app.overlay))
- [6.4 配置文件(prj.conf)](#6.4 配置文件(prj.conf))
- [6.5 应用代码(src/main.c)](#6.5 应用代码(src/main.c))
- [6.6 构建与烧录](#6.6 构建与烧录)
- 七、常见问题与调试技巧
-
- [7.1 设备树调试](#7.1 设备树调试)
- [7.2 Kconfig调试](#7.2 Kconfig调试)
- [7.3 常见错误排查](#7.3 常见错误排查)
- 八、最佳实践总结
-
- [8.1 设备树最佳实践](#8.1 设备树最佳实践)
- [8.2 Kconfig最佳实践](#8.2 Kconfig最佳实践)
- [8.3 协同设计原则](#8.3 协同设计原则)
- 九、总结

每日一句正能量
世间所有的善意都不是单向的消耗,而是一场双向的成全。
当你给出善意,你也在塑造一个更柔和、更值得信任的环境。善意不是牺牲,它会回流------有时直接,有时间接,但从不浪费。
摘要
摘要:Zephyr RTOS作为Linux基金会主导的开源实时操作系统,其独特的设备树(Device Tree)与Kconfig配置体系是区别于其他RTOS的核心设计。本文深入解析Zephyr的设备树编译流程、Kconfig依赖解析机制,以及两者如何协同工作实现硬件抽象与软件配置的分离,帮助开发者快速掌握Zephyr的配置精髓。
一、引言:为什么Zephyr需要设备树?
在传统的嵌入式开发中,硬件信息通常直接硬编码在C代码中------GPIO引脚号、寄存器地址、中断号等散落在各个驱动文件的宏定义里。当硬件变更时,开发者需要逐行修改代码,极易出错且难以维护。
Zephyr借鉴了Linux内核的设备树思想,将硬件描述从代码中彻底分离:
- 设备树(DTS):用声明式语法描述"板子上有什么硬件"
- Kconfig:用配置选项控制"编译哪些软件功能"
- 应用代码:通过统一宏API访问硬件,完全不关心具体引脚号

上图展示了Zephyr设备树与Kconfig配置体系的整体架构。设备树负责描述硬件拓扑(有什么),Kconfig负责控制软件功能(用什么),两者在构建时生成C头文件,供应用代码统一访问。
二、设备树(Device Tree)详解
2.1 设备树的基本概念
设备树是一种树形数据结构,用.dts(Device Tree Source)文件描述硬件。其核心组成包括:
| 概念 | 说明 | 示例 |
|---|---|---|
| 节点(Node) | 表示一个硬件设备 | gpio0: gpio@50000000 { } |
| 属性(Property) | 描述设备特性 | reg = <0x50000000 0x1000>; |
| 标签(Label) | 节点的引用标识 | gpio0: |
| 兼容字符串 | 驱动匹配关键字 | compatible = "nordic,nrf-gpio"; |
| phandle | 节点引用句柄 | &gpio0 |
| 别名(Alias) | 快捷访问名 | led0 = &led_0; |
上图清晰展示了设备树overlay文件的结构:通过compatible指定绑定类型,gpios属性引用GPIO控制器,aliases定义快捷别名,最终应用代码通过DT_ALIAS()宏实现硬件无关编程。
2.2 设备树文件类型与层级
Zephyr的设备树采用分层设计,包含四种文件类型:
1. SoC级 .dtsi 文件
dts
/* nordic/nrf52840_qiaa.dtsi - SoC通用定义 */
/ {
soc {
gpio0: gpio@50000000 {
compatible = "nordic,nrf-gpio";
reg = <0x50000000 0x1000>;
interrupts = <6 1>;
status = "okay";
#gpio-cells = <2>;
};
uart0: uart@40002000 {
compatible = "nordic,nrf-uarte";
reg = <0x40002000 0x1000>;
interrupts = <2 1>;
status = "disabled"; /* 默认禁用 */
};
};
};
2. 板级 .dts 文件
dts
/* nrf52840dk_nrf52840.dts - 板级配置 */
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
/ {
model = "Nordic nRF52840 DK";
compatible = "nordic,nrf52840-dk";
/* 启用板载LED */
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
label = "Green LED 0";
};
};
/* 启用UART0 */
&uart0 {
status = "okay";
current-speed = <115200>;
};
};
3. 应用级 .overlay 文件
dts
/* app.overlay - 应用级硬件覆盖 */
/ {
/* 添加自定义传感器 */
i2c0 {
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
label = "BME280";
};
};
/* 修改LED引脚 */
&led0 {
gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
};
};
4. 绑定文件 .yaml
yaml
# dts/bindings/sensor/bosch,bme280.yaml
description: Bosch BME280 environmental sensor
compatible: "bosch,bme280"
include: i2c-device.yaml
properties:
reg:
required: true
type: array
label:
type: string
required: false
2.3 设备树编译流程

设备树的编译流程如下:
- 预处理阶段 :C预处理器展开
#include,合并所有.dtsi文件 - dtc编译 :设备树编译器检查语法,生成二进制
.dtb - Python脚本处理 :
gen_defines.py解析绑定文件,生成devicetree.h - 生成C宏 :每个节点属性转换为
DT_前缀的宏定义
2.4 设备树Overlay机制
Overlay是Zephyr设备树最强大的特性之一,允许应用在不修改板级设备树的情况下覆盖硬件配置:

Overlay支持的操作:
| 操作 | 语法 | 用途 |
|---|---|---|
| 修改属性 | &label { prop = <value>; }; |
更改已有设备配置 |
| 删除节点 | /delete-node/ &label; |
移除不需要的设备 |
| 删除属性 | /delete-property/ prop; |
移除特定属性 |
| 新增节点 | 直接定义新节点 | 添加应用专属外设 |
三、Kconfig配置系统详解
3.1 Kconfig的核心作用
如果说设备树回答"板子上有什么硬件",那么Kconfig回答"软件要编译哪些功能"。Kconfig是Linux内核的配置系统,Zephyr完整继承了这套机制。

Kconfig的工作流程:
- 定义阶段 :各级
Kconfig文件定义配置选项 - 解析阶段 :
Kconfig解析器处理依赖关系 - 配置阶段 :
prj.conf等文件设置具体值 - 生成阶段 :输出
.config和autoconf.h
3.2 Kconfig语法详解
基础配置选项:
kconfig
# drivers/sensor/bme280/Kconfig - 驱动级配置
menuconfig BME280
bool "BME280 sensor"
default y
depends on I2C && SENSOR
help
Enable driver for Bosch BME280 temperature,
humidity and pressure sensor.
if BME280
config BME280_MODE_FORCED
bool "Forced mode"
default y
help
Use forced sampling mode instead of normal mode.
Lower power consumption but requires explicit trigger.
config BME280_OVERSAMPLING_TEMP
int "Temperature oversampling"
default 1
range 0 16
help
Temperature measurement oversampling factor.
0 = skipped, 1 = x1, 2 = x2, 4 = x4, 8 = x8, 16 = x16
choice BME280_IIR_FILTER
prompt "IIR filter coefficient"
default BME280_IIR_FILTER_4
help
Infinite Impulse Response filter coefficient.
config BME280_IIR_FILTER_OFF
bool "Off"
config BME280_IIR_FILTER_2
bool "2"
config BME280_IIR_FILTER_4
bool "4"
endchoice
endif # BME280
关键语法元素:
| 关键字 | 作用 |
|---|---|
menuconfig |
带菜单的配置项 |
config |
基础配置选项 |
bool/int/hex/string |
数据类型 |
default |
默认值 |
depends on |
依赖条件 |
select |
自动选中 |
range |
数值范围 |
choice/endchoice |
单选组 |
if/endif |
条件块 |
3.3 配置优先级
Zephyr的Kconfig配置遵循严格的优先级(从高到低):
text
1. prj.conf (应用配置 - 最高优先级)
2. boards/<board>.conf (板级配置)
3. boards/<board>_<revision>.conf (板级修订配置)
4. <board>_defconfig (板级默认配置)
5. <soc>_defconfig (SoC默认配置)
6. Kconfig默认值 (最低优先级)
prj.conf示例:
conf
# prj.conf - 应用级配置
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_BME280=y
CONFIG_BME280_MODE_FORCED=y
CONFIG_BME280_OVERSAMPLING_TEMP=4
# 调试配置
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_SENSOR_LOG_LEVEL_DBG=y
3.4 配置片段(Overlay Config)
Zephyr支持.conf片段文件,用于模块化配置:
conf
# debug.conf - 调试配置片段
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_THREAD_MONITOR=y
CONFIG_THREAD_NAME=y
# sensor.conf - 传感器配置片段
CONFIG_SENSOR=y
CONFIG_BME280=y
CONFIG_BME280_TRIGGER=y
使用方式:
bash
# 构建时指定多个配置文件
west build -b nrf52840dk/nrf52840 -- -DCONF_FILE="prj.conf;debug.conf;sensor.conf"
四、DTS与Kconfig的协同工作
4.1 两者的关系与分工

上图以BME280 I2C传感器为例,展示了DTS与Kconfig的协同关系:
- DTS 描述硬件存在:
bme280@76节点定义了传感器的I2C地址和兼容性 - Kconfig 控制软件编译:
CONFIG_BME280=y决定是否编译驱动代码 - 驱动层通过条件编译和DTS宏实现硬件适配
- 应用层使用统一API,完全不关心底层细节
4.2 驱动中的典型用法
c
/* drivers/sensor/bosch/bme280/bme280.c */
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
/* 检查设备树中是否存在BME280节点 */
#if DT_HAS_COMPAT_STATUS_OKAY(bosch_bme280)
/* 为每个BME280实例生成配置结构 */
#define BME280_DEFINE(inst) \
static const struct bme280_config bme280_config_##inst = { \
/* 从设备树获取I2C总线配置 */ \
.bus = I2C_DT_SPEC_INST_GET(inst), \
/* 从Kconfig获取采样模式 */ \
#if CONFIG_BME280_MODE_FORCED \
.mode = BME280_MODE_FORCED, \
#else \
.mode = BME280_MODE_NORMAL, \
#endif \
}; \
\
/* 注册设备实例 */ \
DEVICE_DT_INST_DEFINE(inst, \
bme280_init, \
NULL, \
&bme280_data_##inst, \
&bme280_config_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&bme280_api);
/* 为所有状态为"okay"的BME280实例生成代码 */
DT_INST_FOREACH_STATUS_OKAY(BME280_DEFINE)
#endif /* DT_HAS_COMPAT_STATUS_OKAY(bosch_bme280) */
4.3 应用代码的硬件无关编程
c
/* src/main.c - 应用层代码 */
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
/* 通过别名获取LED设备 - 完全不关心具体GPIO引脚 */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* 通过标签获取传感器设备 */
#define BME280_NODE DT_NODELABEL(bme280)
static const struct device *bme280_dev = DEVICE_DT_GET(BME280_NODE);
int main(void)
{
int ret;
/* 初始化LED */
if (!gpio_is_ready_dt(&led)) {
printk("LED device not ready\\n");
return -1;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return ret;
}
/* 初始化传感器 */
if (!device_is_ready(bme280_dev)) {
printk("BME280 device not ready\\n");
return -1;
}
while (1) {
struct sensor_value temp, press, humidity;
/* 读取传感器数据 */
sensor_sample_fetch(bme280_dev);
sensor_channel_get(bme280_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(bme280_dev, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(bme280_dev, SENSOR_CHAN_HUMIDITY, &humidity);
printk("Temp: %d.%06d C, Press: %d.%06d hPa, Hum: %d.%06d %%\\n",
temp.val1, temp.val2, press.val1, press.val2,
humidity.val1, humidity.val2);
/* LED闪烁 */
gpio_pin_toggle_dt(&led);
k_sleep(K_MSEC(1000));
}
return 0;
}
五、设备树节点结构深度解析

上图详细展示了LED设备在设备树中的完整节点结构,以及生成的C宏。关键理解:
- 根节点
/:设备树的起点,所有节点都在其下 - soc节点:包含所有SoC内置外设
- gpio1控制器 :带标签
gpio1:,可被其他节点引用 - leds父节点 :
compatible = "gpio-leds"指定使用LED子系统驱动 - led0子节点 :具体的LED设备,通过
gpios属性引用GPIO控制器 - aliases :定义
led0 = &led0快捷别名,应用代码使用DT_ALIAS(led0)
5.1 常用DT宏API速查
c
/* 节点引用宏 */
DT_NODELABEL(label) /* 通过标签引用 */
DT_ALIAS(alias) /* 通过别名引用 */
DT_PATH(path...) /* 通过路径引用 */
DT_INST(inst, compat) /* 通过实例号引用 */
/* 属性读取宏 */
DT_PROP(node_id, prop) /* 读取普通属性 */
DT_PROP_OR(node_id, prop, default) /* 读取属性,不存在返回默认值 */
DT_REG_ADDR(node_id) /* 读取reg地址 */
DT_REG_SIZE(node_id) /* 读取reg大小 */
DT_PHANDLE(node_id, prop) /* 读取phandle */
DT_PHA(node_id, prop, idx, cell) /* 读取phandle数组中的cell */
/* GPIO专用宏 */
GPIO_DT_SPEC_GET(node_id, prop) /* 获取GPIO配置规范 */
GPIO_DT_SPEC_INST_GET(inst, prop) /* 通过实例获取GPIO配置 */
gpio_pin_configure_dt(&spec, flags) /* 使用规范配置GPIO */
/* 设备获取宏 */
DEVICE_DT_GET(node_id) /* 获取设备指针 */
DEVICE_DT_INST_GET(inst) /* 通过实例获取设备 */
device_is_ready(dev) /* 检查设备是否就绪 */
六、完整工程实践:从零配置一个Zephyr项目

6.1 项目目录结构
text
my_zephyr_app/
├── CMakeLists.txt # CMake构建配置
├── prj.conf # 应用级Kconfig配置
├── app.overlay # 应用级设备树覆盖
├── boards/
│ ├── nrf52840dk_nrf52840.overlay # 板级覆盖(可选)
│ └── nrf52840dk_nrf52840.conf # 板级配置(可选)
└── src/
└── main.c # 应用代码
6.2 CMakeLists.txt
cmake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
# 查找Zephyr包
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
# 定义项目
project(my_zephyr_app)
# 添加源文件
target_sources(app PRIVATE src/main.c)
6.3 设备树覆盖(app.overlay)
dts
/* app.overlay - 添加BME280传感器和自定义LED */
/ {
/* 别名定义 */
aliases {
mysensor = &bme280;
myled = &custom_led;
};
/* 自定义LED节点 */
leds {
custom_led: led_custom {
gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
label = "My Custom LED";
};
};
};
/* 在I2C0总线上添加BME280 */
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
label = "BME280";
};
};
6.4 配置文件(prj.conf)
conf
# prj.conf - 启用所需功能
# GPIO
CONFIG_GPIO=y
# I2C
CONFIG_I2C=y
# 传感器子系统
CONFIG_SENSOR=y
# BME280驱动
CONFIG_BME280=y
CONFIG_BME280_MODE_FORCED=y
# 日志
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
# 线程监控
CONFIG_THREAD_MONITOR=y
CONFIG_THREAD_NAME=y
6.5 应用代码(src/main.c)
c
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
/* 通过别名获取设备 */
#define SENSOR_NODE DT_ALIAS(mysensor)
#define LED_NODE DT_ALIAS(myled)
static const struct device *sensor_dev = DEVICE_DT_GET(SENSOR_NODE);
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE, gpios);
int main(void)
{
int ret;
LOG_INF("Zephyr DTS + Kconfig Demo Starting...");
/* 检查设备就绪 */
if (!device_is_ready(sensor_dev)) {
LOG_ERR("Sensor device not ready");
return -ENODEV;
}
if (!gpio_is_ready_dt(&led)) {
LOG_ERR("LED device not ready");
return -ENODEV;
}
/* 配置LED */
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("LED config failed: %d", ret);
return ret;
}
LOG_INF("Devices initialized successfully");
while (1) {
struct sensor_value temp, press, humidity;
/* 获取传感器数据 */
ret = sensor_sample_fetch(sensor_dev);
if (ret < 0) {
LOG_ERR("Sensor fetch failed: %d", ret);
} else {
sensor_channel_get(sensor_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(sensor_dev, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(sensor_dev, SENSOR_CHAN_HUMIDITY, &humidity);
LOG_INF("T: %d.%02d C, P: %d.%02d hPa, H: %d.%02d %%",
temp.val1, temp.val2/10000,
press.val1, press.val2/10000,
humidity.val1, humidity.val2/10000);
}
/* LED闪烁 */
gpio_pin_toggle_dt(&led);
k_sleep(K_MSEC(2000));
}
return 0;
}
6.6 构建与烧录
bash
# 构建项目
west build -b nrf52840dk/nrf52840
# 查看生成的设备树
cat build/zephyr/zephyr.dts
# 查看生成的头文件
cat build/zephyr/include/generated/devicetree.h
cat build/zephyr/include/generated/autoconf.h
# 烧录到开发板
west flash
# 查看日志
west espressif monitor # 或使用J-Link RTT
七、常见问题与调试技巧
7.1 设备树调试
bash
# 查看合并后的完整设备树
west build -t zephyr.dts
# 查看设备树编译输出
cat build/zephyr/zephyr.dts.pre # 预处理后的设备树
# 使用dtc反编译查看
dtc -I dtb -O dts build/zephyr/zephyr.dtb -o zephyr_decompiled.dts
7.2 Kconfig调试
bash
# 查看最终配置
cat build/zephyr/.config
# 查看配置冲突
west build -t menuconfig # 图形化配置界面
# 查看某个配置的依赖关系
west build -t guiconfig # GUI配置界面(更直观)
7.3 常见错误排查
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
DT_NODELABEL undefined |
节点不存在或状态非okay | 检查设备树节点和status属性 |
Kconfig warning: undefined symbol |
配置选项拼写错误 | 检查prj.conf中的配置名 |
devicetree error: unknown property |
属性未在binding中定义 | 检查或创建对应的.yaml绑定文件 |
Multiple drivers match |
compatible匹配多个驱动 | 检查compatible字符串的唯一性 |
GPIO port not ready |
GPIO控制器未启用 | 在设备树中设置status = "okay" |
八、最佳实践总结
8.1 设备树最佳实践
- 优先使用别名 :在应用代码中使用
DT_ALIAS()而非硬编码路径 - 合理使用Overlay :应用级修改放在
app.overlay,板级修改放在boards/ - 完善Binding文件 :为自定义设备编写完整的
.yaml绑定,包含所有属性定义 - 保持节点状态一致 :不使用的设备设置为
status = "disabled"而非删除
8.2 Kconfig最佳实践
- 模块化配置 :使用
.conf片段文件分离不同功能的配置 - 合理设置默认值 :驱动级配置使用合理的
default,减少用户配置负担 - 明确依赖关系 :使用
depends on而非select,避免隐式依赖 - 添加Help文档 :每个配置项都添加清晰的
help说明
8.3 协同设计原则
| 场景 | DTS负责 | Kconfig负责 |
|---|---|---|
| I2C地址 | reg = <0x76> |
无需配置 |
| 采样频率 | 设备树属性 | CONFIG_BME280_OVERSAMPLING |
| 功能开关 | status = "okay" |
CONFIG_BME280=y |
| 调试日志 | 无需配置 | CONFIG_BME280_LOG_LEVEL_DBG |
| 引脚分配 | gpios = <&gpio0 13 ...> |
无需配置 |
九、总结
Zephyr的设备树与Kconfig配置体系是其架构设计的精髓所在:
- **设备树(DTS)**实现了硬件描述的声明式管理,将"有什么硬件"与代码分离
- Kconfig实现了软件功能的模块化配置,精确控制"编译什么代码"
- 两者协同通过编译时宏生成,在零运行时开销的前提下实现完美的硬件抽象
掌握这套体系后,开发者可以:
- 同一套应用代码无缝迁移到不同硬件平台
- 通过简单的Overlay文件适配定制硬件
- 通过Kconfig精确裁剪系统功能,优化资源占用
Zephyr的配置体系虽然学习曲线较陡,但一旦掌握,将极大提升嵌入式开发的效率和代码的可维护性。
转载自:https://blog.csdn.net/u014727709/article/details/162495866
欢迎 👍点赞✍评论⭐收藏,欢迎指正