4、Zephyr 构建系统详解:CMake 与 Kconfig 的协作机制、prj.conf 配置、设备树(Devicetree)基础
Zephyr RTOS 的构建系统并非简单的“编译脚本集合”,而是一套由 CMake 驱动、Kconfig 配置、Devicetree 描述硬件、三者深度耦合的元构建框架。理解这三者的协作关系,是进行板级移植、驱动开发、内存优化和功能裁剪的前提。本章将拆解 Zephyr 构建系统的核心链路,从配置源头到编译产物,帮助您建立完整的构建心智模型。
4.1 核心原理:CMake 与 Kconfig 的分工与协作
Zephyr 的构建系统以 CMake 为“总调度”,负责定义编译目标、源文件、链接脚本、工具链选择;而 Kconfig 则负责“功能开关”和“参数配置”。两者通过 Kconfig.zephyr 文件树和 kconfig.cmake 模块实现数据交换。CMake 在配置阶段会调用 kconfiglib 解析所有 Kconfig 文件,结合 prj.conf 和板级默认配置,生成最终的 .config 文件。随后,CMake 将 .config 中的符号转换为编译宏(-DCONFIG_XXX),并传递给编译器。
这种设计带来的核心优势是:配置与代码分离。开发者无需修改 C 源码即可裁剪内核、启用驱动、调整堆栈大小。同时,Kconfig 的依赖关系(depends on、select)保证了配置的合法性,避免出现“启用驱动但未启用底层总线”的冲突。
| 组件 | 职责 | 输入 | 输出 |
|---|---|---|---|
| CMake | 构建编排、目标定义、工具链 | CMakeLists.txt、*.cmake | Makefile / Ninja 规则 |
| Kconfig | 功能配置、参数定义、依赖管理 | Kconfig*、prj.conf、*_defconfig | .config、autoconf.h |
| Devicetree | 硬件描述、地址映射、中断号 | .dts / .dtsi / .overlay | generated_dts_board.h |
CONFIG_XXX 宏在编译时通过 -D 选项注入,而设备树生成的宏以 DT_ 前缀开头(如 DT_NODELABEL_UART0)。两者命名空间不同,但共同作用于驱动代码。4.2 操作流程:prj.conf 配置与 menuconfig 可视化
最常用的配置入口是项目根目录下的 prj.conf 文件。其语法为 CONFIG_符号=值,例如 CONFIG_UART_CONSOLE=y。构建时,Zephyr 会先读取板级默认配置(boards/<arch>/<board>/<board>_defconfig),再用 prj.conf 覆盖同名符号。若需临时修改配置,可运行 west build -t menuconfig 打开图形界面,保存后会自动写回 prj.conf 或生成 merged.conf。
# prj.conf 示例:启用 GPIO 和 I2C 驱动
CONFIG_GPIO=y
CONFIG_I2C=y
# 设置系统主频为 64MHz
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=64000000
# 减小主栈大小以节省 RAM
CONFIG_MAIN_STACK_SIZE=2048
对于多配置场景(如测试与生产),可使用 prj_<board>.conf 或通过 west build -- -DCONF_FILE=prj2.conf 指定额外配置文件。注意:prj.conf 中的符号必须存在于 Kconfig 树中,否则构建会报“undefined symbol”错误。
build/zephyr/.config 文件,该文件是生成产物,每次构建都会被覆盖。如需持久化修改,请编辑 prj.conf 或板级 _defconfig。4.3 设备树基础:从 .dts 到 generated_dts_board.h
设备树(Devicetree)用于描述硬件拓扑,包括 CPU、内存、外设、中断控制器、GPIO 引脚等。Zephyr 在构建时,会将板级 .dts 文件与 SoC 级 .dtsi 文件合并,并应用 *.overlay 补丁(用于应用级硬件修改),最终通过 dtc 编译生成二进制 DTB,同时生成 C 头文件 generated_dts_board.h。驱动代码通过 DT_NODELABEL()、DT_PROP() 等宏访问设备树信息。
// 设备树节点示例(nRF52840 DK 的 UART0)
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = &uart0_default;
pinctrl-names = "default";
};
在 C 代码中,可通过 DEVICE_DT_GET(DT_NODELABEL(uart0)) 获取设备实例。设备树的关键优势在于:硬件描述与驱动逻辑解耦。更换板卡时,只需更换设备树文件,无需修改驱动源码。
| 设备树文件类型 | 作用域 | 典型路径 |
|---|---|---|
| .dtsi | SoC 级(芯片内部外设) | dts/arm/nxp/nrf52840.dtsi |
| .dts | 板级(具体开发板) | boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts |
| .overlay | 应用级(用户自定义) | app/boards/nrf52840dk_nrf52840.overlay |
4.4 案例拆解:为自定义板卡添加 LED 驱动
假设我们有一块基于 STM32F103 的自制板卡,板载 LED 连接在 PB0 引脚(低电平点亮)。目标是通过设备树和 Kconfig 配置,使 gpio-leds 驱动正常工作。
第一步:创建设备树 overlay 文件(boards/stm32f103_mini.overlay):
/ {
leds {
compatible = "gpio-leds";
user_led: led_0 {
gpios = &gpiob, 0, GPIO_ACTIVE_LOW;
label = "User LED";
};
};
};
&gpiob {
status = "okay";
};
第二步:在 prj.conf 中启用 GPIO 和 LED 驱动:
CONFIG_GPIO=y
CONFIG_LED=y
第三步:构建并验证。运行 west build -b stm32f103_mini 后,检查 build/zephyr/include/generated/generated_dts_board.h,应能看到 DT_NODELABEL_LED_0 相关宏。应用代码中调用 led_on(0) 即可点亮 LED。
排障复盘:若 LED 不亮,首先检查 gpios = &gpiob, 0, GPIO_ACTIVE_LOW 中的引脚号和极性是否正确;其次确认 gpiob 节点在 SoC 级 .dtsi 中已定义且时钟已使能;最后用 west build -t menuconfig 确认 CONFIG_GPIO_STM32 已自动被选中(因为 LED 驱动依赖它)。
west build -- -DEXTRA_DTC_OVERLAY_FILE=my.overlay 可临时指定 overlay 文件,适合快速验证硬件修改,无需改动项目文件。4.5 常见误区与总结复盘
- 误区一:混淆 Kconfig 与设备树。Kconfig 管“软件功能”,设备树管“硬件描述”。例如,启用 SPI 驱动用
CONFIG_SPI=y,但 SPI 外设的基地址、中断号必须在设备树中定义。 - 误区二:修改设备树后未重新构建。设备树变更会触发 CMake 重新生成头文件,但若使用
ninja增量编译,偶尔会出现缓存未失效的情况。此时执行west build -t clean后重建。 - 误区三:忽略 Kconfig 依赖链。例如,启用
CONFIG_I2C=y后,还需确保CONFIG_I2C_STM32=y(具体 SoC 驱动)被选中。通常可通过depends on自动处理,但手动配置时需留意。 - 误区四:在 prj.conf 中设置设备树属性。设备树属性(如
current-speed)只能在.dts/.overlay中修改,Kconfig 无法覆盖。
总结:Zephyr 构建系统的核心心智模型是“CMake 编排 + Kconfig 裁剪 + Devicetree 描述”。掌握三者的数据流向(prj.conf → .config → autoconf.h;.dts → generated_dts_board.h),即可高效进行板级适配和功能定制。建议读者在开发新板卡时,先复制官方相近板卡的 .dts 和 _defconfig 作为起点,再逐步修改,这是最稳妥的路径。