3. Zephyr工程结构与west构建系统
在 nRF Connect SDK(NCS)中,一个可编译、可烧录、可复现的固件工程并不是“一个 main.c 加一个 Makefile”,而是由 west manifest 锁定源码版本、CMake 组织构建目标、Kconfig 裁剪功能、设备树(DTS) 描述硬件、板级配置文件 提供默认参数这五套机制协同工作的结果。理解它们的职责边界与数据流向,是创建自定义应用工程的前提。
一、west manifest:工程版本与模块的“锁文件”
west 是 Zephyr 的元工具,它本身不编译代码,而是负责管理多个 Git 仓库(Zephyr、nrfxlib、mcuboot、hal_nordic 等)的版本与路径。这些信息集中在 west.yml 中,称为 manifest。NCS 安装后,工作区根目录下会有 .west/config 记录 manifest 路径,而真正的 west.yml 位于 nrf/west.yml,它通过 manifest.remotes 和 projects 列表声明每个模块的 URL、revision 与 path。
# 查看当前工作区与 manifest 状态
west topdir
west list
west manifest --path
# 更新所有模块到 manifest 指定版本
west update
# 冻结当前实际版本,生成可复现的 manifest
west manifest --freeze > west-frozen.yml
关键点在于:应用工程不应直接修改 nrf/west.yml。若需要引入额外模块(例如自定义驱动库),正确做法是在应用目录下创建 west.yml,使用 manifest.self.path 与 import 机制叠加到 NCS manifest 之上,再执行 west update。这样既保持 NCS 版本可控,又能扩展私有模块。
二、CMake 与 Kconfig 的协同机制
Zephyr 的构建系统以 CMake 为骨架,但配置项并不写在 CMakeLists.txt 里,而是由 Kconfig 生成 .config,再由 CMake 读取并转成编译宏与源文件列表。整个协同过程可以概括为:
- Kconfig 阶段:CMake 调用
kconfig.py,从Kconfig根文件递归解析各模块的Kconfig,结合prj.conf、板级_defconfig、SoC 默认值,生成build/zephyr/.config。 - CMake 阶段:读取
.config中的CONFIG_*,通过zephyr_library()、target_sources()决定编译哪些文件、链接哪些库。 - 生成阶段:输出
autoconf.h(供 C 代码使用CONFIG_XXX宏)与devicetree_generated.h(供 DTS 宏使用)。
因此,prj.conf 只负责“选功能”,CMakeLists.txt 只负责“加源文件与包含路径”,两者不要互相越界。例如启用日志系统:
# prj.conf
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_LOG_DEFAULT_LEVEL=3
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)
target_sources(app PRIVATE src/main.c)
三、板级设备树与配置文件组织
设备树描述“硬件长什么样”,Kconfig 描述“软件要什么功能”,二者通过 status = "okay" 与 CONFIG_* 配合。以 nRF52840 DK 为例,板级目录结构为:
zephyr/boards/arm/nrf52840dk_nrf52840/
├── board.cmake
├── nrf52840dk_nrf52840.dts
├── nrf52840dk_nrf52840_defconfig
├── Kconfig.defconfig
└── nrf52840dk_nrf52840.yaml
应用工程通过 boards/<board>.overlay 覆盖或追加节点,通过 boards/<board>.conf 追加 Kconfig。例如给自定义应用增加一个 UART1 节点并启用:
/* boards/nrf52840dk_nrf52840.overlay */
&uart1 {
status = "okay";
current-speed = <115200>;
};
/* boards/nrf52840dk_nrf52840.conf */
CONFIG_UART_1=y
构建时,west 会自动把 boards/ 下与目标板同名的 overlay 与 conf 合并进主配置,无需手动指定路径。若使用自定义板,则需在 boards/<arch>/<board>/ 下提供完整 dts、defconfig 与 yaml。
四、创建自定义应用工程的完整流程
下面以在 NCS 工作区中创建一个名为 my_sensor_app 的应用为例,目标板为 nrf52840dk_nrf52840,使用 UART1 输出传感器数据。
- 建立目录:在 NCS 工作区根目录下创建
my_sensor_app/,与zephyr/、nrf/平级。 - 编写 CMakeLists.txt:调用
find_package(Zephyr),声明project(my_sensor_app),用target_sources(app PRIVATE src/main.c)加入源文件。 - 编写 prj.conf:启用
CONFIG_GPIO、CONFIG_UART_1、CONFIG_LOG、CONFIG_SENSOR等。 - 编写 boards/nrf52840dk_nrf52840.overlay:将
uart1的status设为"okay",并配置引脚。 - 编写 src/main.c:使用
DEVICE_DT_GET(DT_NODELABEL(uart1))获取设备,调用uart_poll_out()输出。 - 构建与烧录:执行
west build -b nrf52840dk_nrf52840,再执行west flash。
# 在 NCS 工作区根目录执行
west build -b nrf52840dk_nrf52840 my_sensor_app
west flash
# 查看 Kconfig 实际生效值
west build -t menuconfig
# 查看设备树展开结果
west build -t devicetree
build/zephyr/.config中CONFIG_UART_1=y是否出现;build/zephyr/include/generated/devicetree_generated.h中是否存在DT_N_S_soc_peripheral_s_uart_1相关节点;- 串口终端是否按 115200 波特率输出日志,且无
<err> uart: Device not ready报错。
CONFIG_UART_1=y 写进 prj.conf 却忘记在 overlay 中把 uart1 的 status 改为 "okay",会导致设备树节点被禁用,运行时 DEVICE_DT_GET 返回未就绪设备。Kconfig 与 DTS 必须成对修改。
掌握上述五套机制的职责与数据流后,创建自定义应用工程就不再是“复制粘贴样例再改”,而是可以按需裁剪、按板适配、按版本复现的工程化过程。west 负责“拿到正确的源码”,Kconfig 负责“选对功能”,CMake 负责“组织编译”,DTS 与板级配置负责“匹配硬件”,四者各司其职,缺一不可。