2026 年再聊嵌入式实时操作系统,Zephyr 已经不算“新面孔”,但很多从 FreeRTOS 或裸机开发转过来的朋友,初次接触 west、Kconfig、设备树这套组合时,依然会有明显的门槛感。这篇文章是 Higgsfield 原创系列(2026)的特别篇,我把 Zephyr 的完整学习路径拆开来讲,从环境搭建、核心配置系统、选型对比,一直到一个可运行的多线程示例,尽量让零基础读者也能跟着把工程跑起来。
这篇文章适合以下读者:正在做嵌入式项目选型的技术负责人,从 FreeRTOS 迁移到 Zephyr 的应用开发者,以及刚接触 Zephyr 但对 west 和 Kconfig 一脸茫然的新手。读完你会理解 Zephyr 为什么这样设计、如何搭建一套可用的开发环境、如何在真实项目中配置并编译一个多线程应用,以及在 2026 年做嵌入式项目时,Zephyr 和 FreeRTOS 到底应该怎么选。
1. 背景:Zephyr 是什么?为什么 2026 年值得关注
1.1 Zephyr 的定位
Zephyr 是一个由 Linux Foundation 托管的开源嵌入式实时操作系统,采用 Apache 2.0 许可证。它一开始的目标就不是做一个“小而美”的 RTOS 内核,而是提供一套完整的物联网嵌入式软件平台,包括线程调度、内存管理、设备驱动模型、电源管理、蓝牙、Wi-Fi、传感器、显示、文件系统和网络协议栈等子系统。
用一句话概括:Zephyr 更像是“面向 MCU 的 Linux-like 系统”,它不是简单的任务调度器,而是一个模块化、可裁剪、跨架构的软件平台。它支持 ARM、RISC-V、x86、Xtensa、ARC 等多种 CPU 架构,这也是很多芯片厂商在 2026 年逐步把官方 SDK 转向 Zephyr 的原因之一。
1.2 Zephyr 解决什么问题
传统 RTOS 开发中,工程师通常需要自己适配外设驱动,项目换一颗 MCU 后,驱动层可能要重写。Zephyr 通过统一的设备驱动模型和 Devicetree 硬件描述机制,把“板级硬件差异”和“应用逻辑”尽可能分离。应用代码不必关心具体寄存器地址和管脚号,而是通过设备树节点和 API 操作外设。
Zephyr 还解决了“多仓库多组件”的版本管理问题。Zephyr 内核、硬件抽象层、第三方库、应用代码通过 west 工具统一管理,manifest 文件锁定版本,这在大型团队和产品化项目中非常实用。
1.3 常见应用场景
Zephyr 的典型应用场景包括智能穿戴设备、工业控制器、智能家居网关、BMS 电池管理、医疗设备、跟踪器、传感器采集节点等。它尤其适合需要蓝牙连接、低功耗管理、网络通信,并且希望在不同芯片平台之间复用的产品。
2. Zephyr 环境搭建:从零开始
Zephyr 环境搭建是新手的第一道坎。它不像 Keil 或 STM32CubeIDE 那样安装一个软件就能开发,而是需要组合 Python、CMake、Ninja、west、交叉编译工具链等多个组件。下面以 Ubuntu 22.04 / 24.04 环境为例,演示一套完整搭建流程。
2.1 安装基础依赖
先安装系统级依赖工具。Zephyr 构建依赖 CMake 和 Ninja,设备树编译依赖 device-tree-compiler,代码格式化依赖 gperf,下载烧录依赖 dfu-util,另外还需要 Python 工具链。
sudo apt update sudo apt install -y git cmake ninja-build gperf \ ccache dfu-util device-tree-compiler wget \ python3-dev python3-pip python3-setuptools \ python3-tk python3-wheel xz-utils file make gcc这里有几个容易遗漏的包:device-tree-compiler在编译设备树时必需,gperf用于生成哈希表代码,dfu-util用于 Nordic 等芯片的 DFU 烧录。如果编译时出现“dtc not found”或“gperf not found”,多半是这些包没装。
2.2 安装 west 与获取源码
west 是 Zephyr 的官方多仓库管理工具,使用 Python 编写。它会根据 manifest 文件拉取 Zephyr 内核、hal、第三方模块等指定版本的源码。
pip3 install west west --version如果west命令找不到,通常是因为用户级 Python 包目录未加入 PATH,可以执行:
export PATH=$HOME/.local/bin:$PATH然后创建工程目录并初始化:
mkdir zephyrproject cd zephyrproject west init west updatewest init默认使用官方 manifest 仓库。执行后目录中会出现.west目录和zephyr目录,west update会继续拉取 west.yml 中声明的所有模块,这一步骤取决于网络环境,耗时可能较长。
2.3 安装 Python 依赖
进入 Zephyr 仓库目录,安装 Zephyr 构建脚本所需的 Python 依赖:
cd zephyrproject pip3 install -r zephyr/scripts/requirements.txt这一步会让 west、pyelftools、pyserial 等工具版本统一。Zephyr 对 Python 版本有一定要求,一般 Python 3.8 以上都可以正常工作。
2.4 安装交叉编译工具链
Zephyr 官方强烈推荐使用 Zephyr SDK,它包含针对多种架构的 GCC 交叉编译器、binutils、newlib、QEMU 模拟器等工具。下载地址在 Zephyr 官网的 Download 页面,需要选择与当前版本匹配的 SDK 包。解压后建议放在固定路径,例如/opt下:
tar xvf zephyr-sdk-<版本号>.tar.xz mv zephyr-sdk-<版本号> /opt/然后设置环境变量:
export ZEPHYR_SDK_INSTALL_DIR=/opt/zephyr-sdk-<版本号>也可以把这一行写入~/.bashrc或~/.zshrc,避免每次打开终端重新配置。如果不想下载完整 SDK,某些编译目标也可以使用系统自带工具链,但 Zephyr SDK 能保证版本一致,减少兼容问题。
2.5 验证环境是否可用
用 qemu_x86 目标编译官方 hello_world 示例验证环境:
cd zephyr west build -b qemu_x86 samples/hello_world west build -t run如果能看到Hello World! qemu_x86输出,说明 west、CMake、Ninja、工具链全部正常。这样一套 Zephyr 环境搭建就完成了。
3. 核心设计:Kconfig、Devicetree 与 west 构建体系
Zephyr 和传统 RTOS 最大的区别在于,它引入了 Linux 生态里常用的 Kconfig 和 Devicetree 两套机制。理解这两套机制,基本上就理解了 Zephyr 的开发模型。
3.1 Kconfig 配置系统
Kconfig 是一种层级式配置系统,Zephyr 用它管理内核特性、驱动开关和子系统配置。每个功能模块都会声明自己依赖哪些其他配置、默认值是什么、是否可被应用覆盖。
应用层的配置入口是prj.conf,里面每行一个配置项,例如:
CONFIG_LOG=y CONFIG_PRINTK=y CONFIG_MAIN_STACK_SIZE=4096CONFIG_LOG=y表示开启日志子系统,CONFIG_MAIN_STACK_SIZE=4096表示主线程栈大小。不同板卡还有自己的默认配置,通常存放在boards/<架构>/<board>/<board>_defconfig文件中。最终生效的配置是所有配置层级合并后的结果,优先级从高到低大致是:应用prj.conf-> 板级 defconfig -> 架构默认 Kconfig。
初学者经常遇到“我明明在 prj.conf 写了 CONFIG_XXX=y,但编译后没有生效”的情况。原因往往是该配置依赖的其他配置没有打开,或者配置符号名拼写错误。这时可以在构建目录中运行:
west build -t menuconfig会打开一个字符界面的图形化配置工具,可以查看所有 Kconfig 符号的依赖关系和当前值。很多 IDE 或第三方插件也提供 Kconfig 的“工作台”(Workbench)可视化界面,本质上就是把 Config Symbol 的修改写回配置文件,理解这一点之后,无论用什么工具都不会迷路。
3.2 Devicetree 设备树
设备树是一种描述硬件资源的机制。Zephyr 用它描述板载外设、GPIO 管脚、I2C/SPI 总线、中断号等硬件属性。设备树源文件后缀是.dts,公共片段是.dtsi,应用层可以通过.overlay文件对设备树进行追加或修改,而不需要改动板级文件。
举个例子,要描述一个 GPIO LED:
/ { aliases { led0 = &led0; }; };这段 overlay 为板子增加了一个名为led0的 alias。应用代码通过DT_ALIAS(led0, gpios)获取 LED 的 GPIO 控制器和管脚号,实现“硬件描述分离”。
设备树虽然让新人觉得繁琐,但它带来的好处非常明显:应用代码不关心具体芯片引脚,只关心逻辑设备;换板子时只需要更换 board 目录,应用层代码几乎不需要改动。
3.3 west 与 CMake 构建流程
Zephyr 的构建过程可以分成三层:
- west 负责多仓库管理,解析 manifest 文件,拉取指定版本的依赖模块。
- CMake 负责生成构建脚本,处理工具链选择、配置文件合并、设备树编译。
- Ninja 负责实际编译链接,生成最终的固件文件。
一个 Zephyr 应用目录通常包含:
app/ ├── CMakeLists.txt ├── prj.conf ├── boards/ │ └── <board>.overlay └── src/ └── main.cCMakeLists.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)find_package(Zephyr)会加载 Zephyr 的构建系统,target_sources指定应用源文件。编译时 west 将 CMake、Ninja、Kconfig、设备树等步骤串联起来:
west build -b <board> app生成的固件在build/zephyr/zephyr.bin或zephyr.hex中,烧录命令是:
west flash3.4 线程与内核对象
Zephyr 的线程模型与传统 RTOS 类似,支持优先级抢占式调度。线程通过K_THREAD_DEFINE静态定义,也可以使用k_thread_create动态创建。优先级数值越小,优先级越高。线程之间通过内核对象通信,包括信号量、互斥量、消息队列、管道、事件标志等。
内核对象是 Zephyr 非常有特色的设计。比如信号量可以解决“中断到线程”的同步,互斥量解决资源共享,消息队列解决数据传递。在工程实践中,合理使用内核对象比到处使用全局变量要安全得多。
4. 2026 年嵌入式项目选型:Zephyr vs FreeRTOS 深度对比
Zephyr 环境搭建和基础概念讲完后,很多开发者最关心的问题就是:项目选型到底选 Zephyr 还是 FreeRTOS?下面从多个维度做一个深度对比。
| 对比维度 | Zephyr | FreeRTOS |
|---|---|---|
| 许可证 | Apache-2.0 | MIT(核心) |
| 托管方 | Linux Foundation | AWS 维护 |
| 内核体积 | 可裁剪,但完整系统偏大 | 极致轻量,适合资源受限芯片 |
| 硬件抽象 | 设备树 + 统一设备驱动模型 | 较弱,依赖厂商 SDK 提供驱动 |
| 配置方式 | Kconfig + 设备树,编译期配置强 | 头文件和宏配置为主 |
| 网络/蓝牙 | 原生支持大量协议栈和子系统 | 需要额外第三方组件 |
| 多架构支持 | ARM / RISC-V / x86 / Xtensa 等 | 主要面向 MCU,架构支持广泛 |
| 工具链 | CMake + Ninja + west,现代但门槛高 | 支持 IDE 和任意编译链,上手快 |
| 生态 | 芯片厂商适配度持续提高 | 生态成熟,历史代码资源多 |
| 学习曲线 | 较陡 | 平缓 |
| 适用产品 | 物联网、穿戴、工业、需要复杂连接的场景 | 简单控制、资源极受限产品 |
4.1 什么情况优先选 Zephyr
如果你的产品需要蓝牙、Wi-Fi、低功耗连接、传感器管理、OTA 升级、多协议支持,Zephyr 的子系统可以直接使用。Zephyr 在网络和蓝牙方面的积累明显强于 FreeRTOS 内核本身。团队如果重视代码在不同芯片平台之间的复用性,Zephyr 的统一设备驱动模型能显著减少移植成本。
2026 年很多芯片厂商已经在官方 SDK 中提供 Zephyr 支持,Nordic、ST、NXP、乐鑫等都有对应移植。如果产品线覆盖多种供应商芯片,工程师可以用同一套应用代码跑在不同硬件上,这是 Zephyr 最大的长期价值。
4.2 什么情况继续用 FreeRTOS
如果产品只需要简单的任务调度、队列、信号量,并且 Flash/RAM 非常紧张,FreeRTOS 依然是优秀选择。FreeRTOS 的文档和 Demo 非常多,团队招人成本低,遇到问题时更容易找到参考代码。如果公司已经有大量基于 FreeRTOS 的存量代码和厂商 SDK 资产,迁移到 Zephyr 的成本可能高于收益。
4.3 选型建议
选型不是看谁更“先进”,而是看团队和产品需求。2026 年的趋势是:新项目、复杂连接设备、长期维护产品,Zephyr 的优势越来越明显;存量项目、极简产品、快速迭代验证,FreeRTOS 依然很稳。也可以采用混合策略,比如先在小核芯片上用 FreeRTOS,在主控 SoC 上用 Zephyr,两类系统通过通信协议协同工作。
5. 实战:用 Zephyr 实现多线程 GPIO 控制
前面理论讲得再多,不如一个能跑起来的示例。下面我们用 Zephyr 创建一个应用,包含两个线程:一个线程控制板载 LED 闪烁,另一个线程周期性打印计数日志。这个示例覆盖了 Kconfig、设备树 overlay、线程创建、GPIO API 和日志模块,是 Zephyr 工程的最小完整闭环。
5.1 创建项目结构
假设应用放在zephyrproject目录下,应用名为app:
zephyrproject/ └── app/ ├── CMakeLists.txt ├── prj.conf ├── boards/ │ └── nrf52840dk_nrf52840.overlay └── src/ └── main.c本文以 nRF52840 DK 为例。如果你使用其他板子,请把boards目录下的文件名和west build的-b参数替换成实际板子名称。
5.2 编写 prj.conf
CONFIG_LOG=y CONFIG_PRINTK=yCONFIG_LOG=y打开日志子系统,CONFIG_PRINTK=y确保printk输出可用。日志模块是 Zephyr 推荐的输出方式,支持分级打印和后台过滤,比直接printk更适合工程开发。
5.3 编写设备树 overlay
/ { aliases { led0 = &led0; }; };这段 overlay 的作用是给应用层一个稳定的逻辑名称led0。很多官方板子已经定义了led0alias,如果你的板子已有,可以省略这个文件。如果编译时报“led0 not found”,就需要根据板子的实际设备树节点补充 alias。
5.4 编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(my_zephyr_app) target_sources(app PRIVATE src/main.c)注意find_package(Zephyr)必须写在project()之前,Zephyr 构建系统会在此时加载核心定义。
5.5 编写 main.c
#include <zephyr/kernel.h> #include <zephyr/device.h> #include <zephyr/drivers/gpio.h> #include <zephyr/logging/log.h> #include <zephyr/dt-bindings/gpio/gpio.h> LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); #define LED_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE, gpios); static uint32_t counter = 0; static void led_thread_entry(void *p1, void *p2, void *p3) { if (!gpio_is_ready_dt(&led)) { LOG_ERR("LED GPIO not ready"); return; } gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); while (1) { gpio_pin_toggle_dt(&led); k_sleep(K_MSEC(500)); } } static void log_thread_entry(void *p1, void *p2, void *p3) { while (1) { LOG_INF("uptime counter = %u", counter++); k_sleep(K_SECONDS(1)); } } K_THREAD_DEFINE(led_tid, 1024, led_thread_entry, NULL, NULL, NULL, 7, 0, 0); K_THREAD_DEFINE(log_tid, 1024, log_thread_entry, NULL, NULL, NULL, 6, 0, 0); int main(void) { LOG_INF("Zephyr multi-thread demo started"); return 0; }代码做了四件事:
- 通过
DT_ALIAS(led0)获取 LED 设备节点,再用GPIO_DT_SPEC_GET生成struct gpio_dt_spec,它封装了 GPIO 控制器和设备管脚号。 led_thread_entry线程每 500ms 翻转一次 LED,表示系统调度正常。log_thread_entry线程每秒钟打印一次递增计数,方便通过串口观察。K_THREAD_DEFINE静态创建两个线程,优先级分别设置为 7 和 6,数值越小优先级越高,所以日志线程会优先于 LED 线程执行。
k_sleep是 Zephyr 推荐的线程延时方式,它会主动让出 CPU,而不是忙等。K_MSEC(500)是毫秒转 tick 的宏,如果内核配置了 tickless 模式,睡眠功耗会进一步降低。
5.6 编译与烧录
在zephyrproject目录下执行:
west build -b nrf52840dk_nrf52840 app west flash如果已经在app目录内,可以简化为:
west build -b nrf52840dk_nrf52840 . west flashwest flash会调用对应的烧录工具。对于支持 DAPLink 或 J-Link 的板子,插上 USB 后通常可以直接烧录。如果没有硬件,也可以用 QEMU 目标验证:
west build -b qemu_x86 app west build -t run不过qemu_x86没有板载 LED,需要根据模拟环境调整代码中的设备节点,这里不再展开。
5.7 预期结果
烧录成功后,串口终端会输出类似内容:
*** Booting Zephyr OS build zephyr-v3.x *** [00:00:00.000,000] <inf> app: Zephyr multi-thread demo started [00:00:01.000,000] <inf> app: uptime counter = 0 [00:00:02.000,000] <inf> app: uptime counter = 1 [00:00:03.000,000] <inf> app: uptime counter = 2同时板载 LED 以 500ms 周期闪烁。看到这样的输出,说明 Zephyr 环境、设备树、Kconfig、日志系统、线程调度全部正常工作。
6. 常见问题与排查思路
Zephyr 工程报错往往让新手无从下手,下面整理几个高频问题,每个问题都给出排查方向。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
west: command not found | Python 用户级 bin 目录未加入 PATH | 执行export PATH=$HOME/.local/bin:$PATH |
编译报错'CONFIG_XXX' undeclared | Kconfig 符号名写错或依赖未开启 | 用west build -t menuconfig检查符号 |
设备树报错led0 not found | 板级设备树没有该 alias | 编写 overlay 补充 alias,改为实际 node label |
ZEPHYR_BASE未设置 | 环境变量缺失 | 在 zephyr 目录执行source zephyr-env.sh |
烧录失败permission denied | 调试器 USB 权限不足 | 配置 udev 规则,或把用户加入 dialout 组 |
| 线程没有执行 | 栈溢出或优先级配置不当 | 增大 K_THREAD_DEFINE 栈大小,检查优先级 |
6.1 west 命令找不到
这个问题的根本原因是 Python 安装的脚本目录没有加入 PATH。优先用python3 -m pip show west查看安装位置,再手动导出 PATH。
6.2 Kconfig 配置未生效
Zephyr 的配置合并逻辑比较强,prj.conf只是最上层配置。如果某个配置依赖其他配置,需要在 menuconfig 中查看该符号变为灰色的原因,通常是依赖条件不满足或选择了互斥选项。
6.3 设备树 alias 缺失
设备树报错时,先看build/zephyr/zephyr.dts文件,这是最终生成的设备树视图,可以确认板级定义是否完整。大多数官方开发板的 LED 节点 label 是led0,但也可能是green_led、blue_led等,需要根据实际板卡调整 overlay。
6.4 线程栈溢出
Zephyr 提供了栈溢出检测机制,打开CONFIG_THREAD_STACK_INFO或CONFIG_DEBUG_THREAD_INFO可以帮助定位问题。如果线程内使用了较大局部数组,需要同步调大K_THREAD_DEFINE中的栈大小。
7. 最佳实践与工程建议
Zephyr 项目上了规模之后,工程规范比代码技巧更重要。下面几条建议来自实际项目中的高频踩坑点。
7.1 使用 west manifest 固定版本
团队多人开发时,统一 Zephyr 版本非常关键。建议在west.yml中显式锁定 Zephyr 内核和所有模块的 revision,成员执行west update后能获得完全一致的代码状态,避免“我这边能编译你那边报错”的版本漂移问题。
7.2 分层管理配置文件
不要把全部配置堆在prj.conf里。建议遵循以下分层:
prj.conf:应用通用配置。boards/<board>.conf:针对某块板子的特殊配置。<board>.overlay:硬件属性差异化配置。- 条件编译场景使用
prj_<board>.conf,Zephyr 会按优先级加载。
这样换方案、换板子时,差异点一目了然。
7.3 设备树优先,避免硬编码管脚
应用代码里尽量不要写死 GPIO 控制器和管脚号。用设备树属性描述管脚用途,代码通过DT_ALIAS、GPIO_DT_SPEC_GET等 API 获取。这样当硬件改板、管脚重映射时,只需要改 overlay,不用动 C 代码。
7.4 日志系统代替 printk
printk简单直接,但不适合生产环境。建议使用 Zephyr 日志模块,通过LOG_MODULE_REGISTER注册模块,按 DEBUG / INFO / WARN / ERR 分级输出。日志模块支持运行时过滤,还能减少对实时性的影响。
7.5 线程设计要规划优先级
Zephyr 是抢占式调度系统,优先级规划需要全局统一。建议将周期短、实时性高的任务放到高优先级,耗时操作放到低优先级线程中,并通过信号量或消息队列异步处理。避免在中断处理函数里做耗时操作,中断只负责标记事件并触发线程处理。
7.6 注意内存和栈资源
MCU 的 RAM 有限,线程栈不是越大越好。先给合理初值,通过栈溢出检测和内存统计工具定位峰值占用。Zephyr 提供了CONFIG_THREAD_ANALYZER等工具,可以帮助统计各线程栈使用率。上线前记得关闭调试配置,释放 Flash 和 RAM。
7.7 注意日志外设和调试权限
涉及安全、权限和产品配置时,遵循最小权限原则。烧录工具和调试器配置需要确认开发板权限,生产环境不要随意开启可能泄露信息的调试接口。
8. 下一步学习路线
看完这篇文章,你已经掌握了 Zephyr 环境搭建、核心配置体系、选型对比和一个多线程示例。下一步可以按以下顺序深入:
- 阅读 Zephyr 官方文档中关于 Kconfig 和 Devicetree 的详细说明,理解更多配置符号和设备树 API。
- 尝试官方 samples 中的
samples/hello_world、samples/philosophers、samples/boards,观察不同示例的配置方式。 - 如果项目涉及蓝牙,学习
samples/bluetooth下的 peripheral 和 central 示例,了解 Zephyr 蓝牙协议栈的层次。 - 尝试把现有 FreeRTOS 项目中的任务和队列,用 Zephyr 线程和内核对象重写,对比两种设计的差异。
- 研究 Zephyr 的电源管理和节能模式,这是低功耗产品的关键能力。
如果这篇特别篇对你有帮助,建议直接打开官方文档,挑一个 sample 编译烧录,再回来对照本文的配置逻辑,理解会快很多。Zephyr 的路很长,但值得走。