3、第一个 Zephyr 项目:使用 west 创建项目、构建、烧录、运行 hello_world,理解项目结构与构建系统

Zephyr RTOS 的工程实践与传统的嵌入式 IDE 开发模式有显著差异。它采用了一套基于 west 工具和 CMake 的模块化构建体系。对于初学者而言,理解这套体系的核心不在于记住每一个 CMake 变量,而在于建立“源码树(Workspace)”与“应用(Application)”分离的认知模型。本章将带领你从零开始,使用 west 命令行工具完成 hello_world 示例的创建、编译、烧录与运行,并深入剖析 Zephyr 项目的目录结构、west 工作流以及 Kconfig 与 Devicetree 在构建中的角色。下图展示了 Zephyr 项目从源码到目标板运行的完整工程链路。

Zephyr 工程链路:west 工作流与构建系统 Zephyr Workspace zephyr/ (内核+HAL) modules/ (第三方库) app/ (用户应用) west init/update 拉取 manifest 仓库 同步所有模块版本 生成 west 工作区 west build CMake 配置 Kconfig 符号解析 Devicetree 生成 构建产物 zephyr.elf zephyr.bin/hex map 文件 west flash 调用调试器/烧录器 west build -t run 串口终端输出 关键配置文件: west.yml (manifest) → 定义模块版本与路径 CMakeLists.txt → 定义应用构建规则 prj.conf → 配置 Kconfig 内核选项 board.dts → 描述硬件 Devicetree

上图清晰地展示了从 west init 到最终运行的完整链路。接下来,我们将分步骤拆解这一流程,并深入理解每个环节背后的设计逻辑。

3.1 核心原理:west 与 CMake 的分工协作

Zephyr 的构建系统并非从零发明,而是建立在 CMake 之上,并增加了 west 作为高层管理工具。理解两者的分工是掌握 Zephyr 开发的第一步。

  • west(Zephyr's meta-tool):负责项目级管理。它处理多仓库的拉取(west init / west update)、构建命令的封装(west build)、烧录与调试的调度(west flash / west debug)。west 本身不编译代码,它只是“指挥家”。
  • CMake:负责构建级控制。它读取 CMakeLists.txt 文件,结合 Zephyr 提供的 zephyr.cmake 模块,生成 Makefile 或 Ninja 构建脚本。CMake 处理源文件依赖、编译选项、链接脚本等底层细节。
  • Ninja/Make:负责实际编译执行。Ninja 是默认的生成器,它以并行方式快速编译 C/ASM 源文件,最终链接生成 ELF 文件。

这种分层设计带来了显著优势:开发者无需关心底层编译命令,只需通过 west build 统一入口,即可完成从配置到产物的全流程。同时,CMake 的跨平台特性使得 Zephyr 可以轻松支持 Linux、Windows、macOS 主机环境。

3.2 操作流程:从零创建并运行 hello_world

下面以 nRF52840DK 开发板为例(其他板子只需更换 BOARD 变量),演示完整的操作流程。请确保已安装 Zephyr SDK 3.2 以上版本,并配置好 ZEPHYR_TOOLCHAIN_VARIANT 环境变量。

步骤 1:初始化工作区

# 创建并进入工作目录
mkdir zephyr-project && cd zephyr-project

# 初始化 west 工作区(拉取 manifest 仓库)
west init -m https://github.com/zephyrproject-rtos/zephyr --mr v3.5.0

# 拉取所有模块(zephyr、hal_nordic、mcuboot 等)
west update
经验提示:如果网络不稳定,可以设置 west config 使用镜像源。例如:west config manifest.url https://ghproxy.com/https://github.com/zephyrproject-rtos/zephyr。另外,--mr 参数指定了 manifest 仓库的分支或标签,建议固定使用发布版本(如 v3.5.0)而非 main 分支,以保证模块兼容性。

步骤 2:创建应用目录

Zephyr 应用可以放在工作区内的任意位置,但推荐放在 app 子目录下,与 zephyr 主仓库平级。手动创建以下文件:

app/
├── CMakeLists.txt
├── prj.conf
└── src/
    └── main.c

CMakeLists.txt 内容如下:

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)

target_sources(app PRIVATE src/main.c)

prj.conf 内容(初始为空即可,hello_world 不需要额外配置):

# 空文件,或添加 CONFIG_PRINTK=y(默认已开启)

src/main.c 内容:

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

void main(void)
{
    printk("Hello World! %s\n", CONFIG_BOARD);
    while (1) {
        k_sleep(K_MSEC(1000));
    }
}

步骤 3:构建项目

# 进入应用目录
cd app

# 构建(指定目标板)
west build -b nrf52840dk_nrf52840

# 查看构建产物
ls build/zephyr/zephyr.elf build/zephyr/zephyr.bin

构建过程会输出大量日志,最后出现 Generating files done 和 Build complete 表示成功。如果出现错误,请检查 ZEPHYR_BASE 环境变量是否指向 zephyr 仓库路径。

步骤 4:烧录与运行

# 连接开发板(通过 USB 或 J-Link),烧录固件
west flash

# 查看串口输出(默认 115200 波特率)
west build -t run

west flash 会自动调用 J-Link 或 OpenOCD 烧录器。对于 nRF52840DK,默认使用 J-Link OB。烧录完成后,开发板自动复位运行,串口终端应输出:

*** Booting Zephyr OS build v3.5.0 ***
Hello World! nrf52840dk_nrf52840
风险提示:如果 west flash 报错 Error: J-Link not found,请检查 USB 连接和驱动。在 Linux 下可能需要添加 udev 规则。另外,west build -t run 需要配置串口终端(如 minicom 或 screen),否则输出无法显示。

3.3 案例拆解:构建系统如何“看到”你的硬件

很多初学者会困惑:为什么 west build -b nrf52840dk_nrf52840 就能自动识别板载 LED、UART 等外设?这背后是 Devicetree 与 Kconfig 的协同工作。我们以 hello_world 为例,拆解构建系统如何将硬件描述转化为可执行代码。

构建阶段 输入文件 输出产物 作用
1. Devicetree 解析 boards/arm/nrf52840dk_nrf52840.dts build/zephyr/zephyr.dts 生成设备树二进制(DTB)和头文件 devicetree_generated.h
2. Kconfig 配置 prj.conf + Kconfig.* 文件 build/zephyr/.config 生成 autoconf.h,定义内核编译宏
3. CMake 生成 CMakeLists.txt + zephyr.cmake build/build.ninja 生成编译规则,确定源文件列表和编译选项
4. 编译链接 所有 .c/.h 文件 zephyr.elf / zephyr.bin 生成最终固件,包含链接脚本和符号表

在 main.c 中,我们使用了 CONFIG_BOARD 宏,它来自 Kconfig 生成的 autoconf.h。而 printk 函数则通过 Devicetree 找到 UART 外设的基地址,并初始化驱动。整个过程中,开发者无需手动编写寄存器配置,只需关注应用逻辑。

深入理解:Zephyr 的 Devicetree 采用“覆盖(overlay)”机制。如果板级 dts 文件不满足需求,可以在应用目录下创建 app.overlay 文件,通过 west build -d build -b nrf52840dk_nrf52840 -- -DDTC_OVERLAY_FILE=app.overlay 指定。这种设计使得同一份应用代码可以适配不同硬件,只需更换 overlay 文件即可。

3.4 常见误区与总结复盘

在初学者实践中,以下问题出现频率最高,需要特别警惕:

  • 误区一:直接修改 zephyr 仓库内的文件。 Zephyr 源码树是只读的,任何修改都会在 west update 时被覆盖。正确的做法是在应用目录中通过 Kconfig 或 Devicetree overlay 进行配置。
  • 误区二:混淆 west build 与 cmake。 不要手动运行 cmake 命令,除非你完全理解 Zephyr 的 CMake 模块。west 会自动设置 ZEPHYR_BASE、BOARD 等变量,手动操作极易出错。
  • 误区三:忽略 prj.conf 的作用。 很多外设驱动默认未启用,需要在 prj.conf 中添加 CONFIG_XXX=y。例如使用 SPI 需要 CONFIG_SPI=y,使用 GPIO 需要 CONFIG_GPIO=y。
  • 误区四:烧录后无输出就认为板子坏了。 首先检查串口终端波特率是否为 115200,其次确认 west flash 是否成功(观察 J-Link 日志),最后检查 prj.conf 是否启用了 CONFIG_PRINTK(默认开启)。

复盘总结:本章的核心目标是建立“west 管理项目,CMake 管理构建,Devicetree/Kconfig 描述硬件”的三层认知。通过 hello_world 的实践,你应该掌握以下技能:

  1. 使用 west init 和 west update 搭建开发环境。
  2. 编写最小化的应用目录结构(CMakeLists.txt + prj.conf + main.c)。
  3. 使用 west build -b <board> 构建固件,并定位构建产物。
  4. 使用 west flash 烧录,并通过串口验证运行结果。
  5. 理解构建过程中 Devicetree 和 Kconfig 的输入输出关系。