公司动态

Zephyr vs FreeRTOS:嵌入式RTOS选型与环境搭建实战指南

📅 2026/9/1 3:58:43
Zephyr vs FreeRTOS:嵌入式RTOS选型与环境搭建实战指南
嵌入式圈子里Zephyr 这个名字最近越来越频繁地出现在技术播客、社区帖和招聘要求里。如果你正在做物联网设备、可穿戴产品或者需要统一软件平台的嵌入式项目大概率已经在选型清单里看到过它。本期“涂鸦博物馆”播客把 Zephyr 作为嘉宾主题命名里还带上了“PT.1”——这本身就说明话题量级不小一个 RTOS 要讲清楚一次对话根本不够。而且从搜索热度来看“zephyr环境搭建”“workbench for zephyr kconfig”“zephyr vs freertos深度对比”都是开发者真正会去搜的关键词说明大家已经不再停留在‘听过名字’的阶段而是开始琢磨它到底能不能用在自家产品里。我先把判断放在开头Zephyr 不是一个比 FreeRTOS 更复杂的“RTOS 备选项”而是一个自带构建系统、设备树、驱动模型和协议栈的嵌入式操作系统平台。它的学习曲线比 FreeRTOS 陡但换来的回报是跨板卡复用、组件化配置和面向产品的软件架构能力。2026 年做嵌入式项目选型时如果你还在用“哪个内核占用 RAM 更小”这种维度去看它很容易错过真正重要的部分。今天这篇文章就顺着 Zephyr 最常被讨论的几条主线展开先讲它到底是什么再对比它和 FreeRTOS 的差异然后从零搭建环境、跑通第一个工程最后给出实际项目中真正用得上的配置建议和避坑清单。如果你刚接触 Zephyr或者正准备在一个新项目里评估它建议把这篇文章收藏起来边看边操作。1. Zephyr 到底是什么先放下“RTOS 对比”的思维定式不少开发者第一次了解 Zephyr 时会直接用 FreeRTOS 的思维去理解它抢占式调度、任务/消息队列/信号量、Tick 配置、堆栈大小……这些概念在 Zephyr 里确实都存在但它远远不止这些。Zephyr 由 Linux 基金会托管背后有 Nordic、NXP、ST、Intel 等多家芯片厂商和商业公司在持续投入。它提供的是一整套“面向产品开发”的软件平台而不是一个“跑在 MCU 上的调度内核”。从项目结构上就能看出区别。一个典型的 Zephyr 应用工程里有 CMakeLists.txt、prj.conf、src/main.c看起来和很多嵌入式项目类似但真正决定工程行为的是 Kconfig 配置和设备树Devicetree。Kconfig 负责“软件开关”比如要不要启用蓝牙、要不要启用日志、任务栈大小是多少设备树负责“硬件描述”比如这个板子上有几个 UART、SPI 接在哪个引脚、LED 挂在哪个 GPIO。应用层代码不再直接写死寄存器地址和引脚号而是通过编译期生成的设备树宏去访问硬件资源。这意味着同一个应用代码换一块开发板后只要设备树和 Kconfig 配置不同构建系统就能生成对应平台的镜像。在传统 RTOS 项目里换 MCU 往往意味着重写板级驱动、重新梳理中断映射和时钟配置而在 Zephyr 项目里大量的板级差异被设备树吸收掉了。这就是它被称为“平台”而不是“内核”的原因。另一个容易让人迷惑的地方是 west。west 是 Zephyr 的多仓库管理工具它不只是用来拉代码的还负责整个 workspace 的版本对齐。Zephyr 将内核、hal 库、第三方模块按 manifest 文件组织成多个仓库west init west update 之后整个工具链和模块版本才能保持一致。如果只 clone 一个 zephyr 仓库然后用 CMake 直接构建大概率会在编译时出现各种模块缺失或版本不匹配的问题。所以Zephyr 真正要解决的是嵌入式软件的可复用性和工程化问题。它把硬件描述、软件配置、构建脚本、驱动框架、子系统协议栈都纳入了一套统一体系。学习它不能只盯着调度器 API而是要理解 Kconfig、设备树、west 和构建系统这几根支柱。2. Zephyr 的核心概念Kconfig、设备树与 west 构建体系2.1 Kconfig软件功能开关Kconfig 的语法源自 Linux 内核Zephyr 把它改造为模块化的配置系统。简单理解它就是一组可以打开或关闭的编译期开关。比如你想让蓝牙协议栈参与编译就在 prj.conf 里写CONFIG_BTy想调整系统主线程栈大小CONFIG_MAIN_STACK_SIZE2048Zephyr 各子系统提供大量 Kconfig 选项这些选项决定“哪些代码被编译进来”。它的好处是最终镜像可以做到比较精简用不到的模块根本不会被编进去不会像某些 RTOS 一样把整套协议栈都驻留在内存里。2.2 设备树硬件描述与代码解耦设备树最早用于嵌入式 Linux用来描述 CPU、内存、外设、中断控制器Zephyr 借鉴了这套描述方式。在每个 board 目录下都有一个 .dts 文件描述这块板子上的硬件资源。比如 STM32 的某个开发板会定义usart1: serial40013800 { compatible st,stm32-usart; reg 0x40013800 0x400; interrupts 37 0; status disabled; };应用开发时你不需要手动去读寄存器手册来映射 UART 引脚。只要板级设备树里已经定义好 usart1 并设置了 status okay应用中就可以通过设备树宏直接拿到设备描述结构体。设备树的引入让“板级支持包”不再是散落在一堆 .h 和 .c 文件里的魔法数字而是一份结构清晰的硬件清单。2.3 west多仓库版本管理west 是 Zephyr 官方推荐的工具作用是管理多个 git 仓库。打开 west.yml 可以看到 Zephyr 工程依赖哪些仓库、各自在哪个 revision。换一个 Zephyr 版本或者把某个 hal 模块锁定到特定 commit都可以通过 manifest 文件统一管理。对团队协作来说这比每个人手动 clone 各自的分支要可靠得多。后续在环境搭建部分我会详细演示 west 的用法。这三者共同构成了 Zephyr 的构建哲学代码、配置、硬件描述分离。理解这一点之后再看任何 Zephyr demo你就不会只盯着 C 代码本身了。3. Zephyr vs FreeRTOS2026 年嵌入式项目选型怎么判断从搜索热度来看“zephyr vs freertos”是很多人在选型阶段最关心的问题。这里先说结论如果你的产品形态是单芯片、单功能的简单控制FreeRTOS 的轻量和低门槛仍然有优势如果你要做的是一个会持续迭代、可能跑 BLE/Wi-Fi、需要 OTA、需要支持多板卡的物联网产品Zephyr 的工程化优势会逐渐显现。下面从几个关键维度对比对比维度ZephyrFreeRTOS定位嵌入式操作系统平台实时内核 生态组件内核功能多线程、信号量、消息队列、内存管理、轮询等多线程、信号量、消息队列、内存管理内核本身很精简硬件描述设备树 板级目录通常由 MCU 厂商 SDK 提供配置方式Kconfig模块化编译头文件宏 厂商配置工具驱动模型统一驱动框架API 抽象完善依赖厂商 SDK风格不一无线协议栈内置 BLE、Wi-Fi、Thread、Zigbee 等子系统需要额外集成多板卡复用应用层代码与板级配置分离换平台时需要移植板级层学习门槛较高需要理解 west/设备树/Kconfig较低内核 API 简单直接社区与商业支持Linux 基金会 多家芯片厂商Amazon 生态 广泛 MCU 支持这个表并不是否定 FreeRTOS。相反对于很多简单产品FreeRTOS 仍然是性价比很高的选择。它的 API 直观资料多工程师招聘成本低而且在一些老牌 MCU SDK 里深度集成。但 2026 年选型时有一个趋势值得注意越来越多的 MCU 厂商开始把 Zephyr 作为官方支持的一级平台而不是第三方移植。这意味着你可以在 Zephyr 的板级目录里直接找到大量开发板的设备树和默认配置而不是自己从头适配。从架构层面看FreeRTOS 更像一个“内核库”你把它嵌进自己的工程里自己决定外设驱动、协议栈和构建方式怎么写而 Zephyr 更像一套“操作系统”它试图规定你如何构建、如何配置、如何描述硬件然后在这套规范之上提供完整的子系统。选择 Zephyr意味着你愿意接受它的工程框架换取长期可维护性。项目选型建议可以这样说如果团队已经有成熟的厂商 SDK 开发流程且产品短平快FreeRTOS 完全够用如果产品生命周期长、硬件方案可能频繁切换、需要无线协议栈和 OTA那 Zephyr 值得提前投入评估。播客里把它作为一期完整主题来讲也说明这个评估过程本身并不简单。4. 环境准备从零搭一个 Zephyr 开发环境Zephyr 的环境搭建第一次接触时会觉得繁琐但核心其实只有四步安装系统依赖、创建 Python 虚拟环境、用 west 拉取源码、安装 SDK 工具链。这里以 Linux 环境为例演示Windows 和 macOS 的步骤类似但依赖包安装方式不同。为了不踩版本坑建议先按照 Zephyr 官方文档确认当前版本要求本文重点演示通用思路。4.1 安装系统依赖在 Ubuntu/Debian 上需要先安装编译 Zephyr 依赖的基础工具sudo apt update sudo apt install 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 gcc-multilib g-multilib libsdl2-dev libmagic1这里几个工具的作用需要说明一下cmake 是构建系统ninja 是快速构建器device-tree-compiler 用于编译设备树python3 相关包是 Zephyr 构建脚本的运行时依赖。如果缺少这些包后面执行 west 命令时可能出现各种报错。4.2 创建 Python 虚拟环境并安装 westZephyr 的构建脚本依赖多个 Python 包为了避免污染系统 Python建议用虚拟环境隔离cd ~ python3 -m venv ~/zephyr-env source ~/zephyr-env/bin/activate pip install --upgrade pip pip install west激活虚拟环境后先确认 west 可用west --version如果出现版本号说明 west 安装成功。后面每次打开新终端时都要记得 source ~/zephyr-env/bin/activate或者把这一行写进 shell 配置。4.3 获取 Zephyr 源码并初始化 workspace先创建 workspace 目录再用 west init 初始化mkdir ~/zephyr-dev cd ~/zephyr-dev west init -m https://github.com/zephyrproject-rtos/zephyr.git zephyrproject cd zephyrproject west updatewest init 会拉取 manifest 文件west update 则按照 manifest 的锁定信息拉取所有依赖仓库包括 Zephyr 内核、HAL、第三方模块。这一步会根据网络情况耗时几分钟如果网络不稳定可以设置 git 的 http 缓存或错峰重试。4.4 安装 Zephyr SDK 与工具链Zephyr SDK 是一套预编译好的交叉编译工具链涵盖多个目标架构。下载方式建议访问 Zephyr SDK 的 GitHub Releases 页面选择与当前 Zephyr 版本匹配的 SDK 包。下载后解压并运行安装脚本cd ~/zephyr-dev wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/vX.Y.Z/zephyr-sdk-VERSION_linux-x86_64.tar.xz tar xf zephyr-sdk-*.tar.xz cd zephyr-sdk-* ./setup.sh -t all -h安装完成后设置两个关键环境变量export ZEPHYR_TOOLCHAIN_VARIANTzephyr export ZEPHYR_SDK_INSTALL_DIR$HOME/zephyr-sdkZEPHYR_TOOLCHAIN_VARIANT 表示使用 Zephyr 官方 SDK 作为交叉编译工具链ZEPHYR_SDK_INSTALL_DIR 指向 SDK 安装目录。如果没有设置这两个变量构建时通常会报“No toolchain found”之类的错误。4.5 安装 Python 依赖Zephyr 构建时还会用到一些 Python 包需要在虚拟环境里安装pip install -r ~/zephyr-dev/zephyrproject/zephyr/scripts/requirements.txt到这里环境就差不多准备好了。建议先跑一个 hello_world确认整条链路没问题再进行后面的开发。5. 第一个工程hello_world 跑通构建与运行环境搭好之后先用 Zephyr 自带的 hello_world 示例验证工具链。Zephyr 的 samples 目录包含大量官方示例hello_world 是最小可运行程序。5.1 查看示例代码cd ~/zephyr-dev/zephyrproject ls samples/hello_world这个目录下有 src/main.c、prj.conf、CMakeLists.txt 等文件。打开 src/main.c 可以看到核心逻辑非常简单#include zephyr/kernel.h void main(void) { printk(Hello World! %s\n, CONFIG_BOARD); }printk 是 Zephyr 的串口输出函数有点像嵌入式版的 printf。CONFIG_BOARD 是构建时由 Kconfig 生成的宏表示当前编译的板卡名称。5.2 构建 hello_world选择一块开发板作为目标先用 QEMU 模拟的 x86 板卡验证cd ~/zephyr-dev/zephyrproject west build -b qemu_x86 samples/hello_worldwest build 会自动创建 build 目录生成编译配置并启动 ninja 构建。如果环境配置正确最终会输出生成的固件路径比如 build/zephyr/zephyr.elf。如果想换一块真实开发板只需要更换 -b 参数比如west build -b nucleo_f103rb samples/hello_world5.3 用 QEMU 运行验证在验证逻辑时不一定要直接烧录真实板卡Zephyr 对很多开发板提供了 QEMU 支持。构建完之后直接运行west build -t run如果你之前构建的是 qemu_x86它会启动 QEMU 并在模拟串口上打印类似下面的内容*** Booting Zephyr OS build zephyr-v3.x *** Hello World! qemu_x86看到这行输出说明 Zephyr 的编译工具链、设备树、串口驱动、链接脚本都已经正常工作了。这一步成功后续所有开发都有了一个可依赖的基础。6. 从 hello_world 到 blinky设备树与 GPIO 驱动hello_world 只证明“能编译、能运行”但嵌入式开发更关心外设操作。下面用一个 blinky 例子讲解设备树和 GPIO API。许多初学者在这里会卡住因为 Zephyr 里获取 GPIO 引脚的方式和传统 SDK 差别很大。6.1 设备树在 Zephyr 里怎么工作Zephyr 在构建时会把板级设备树文件编译成二进制的 DTB并通过固定的头文件路径生成设备树宏。应用代码里可以用 DT_ALIAS、DT_NODELABEL 等宏直接引用设备树节点。比如常用的板载 LED 在设备树里通常有 led0 别名那么代码里可以这样获取引脚描述#define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led GPIO_DT_SPEC_GET(LED0_NODE, gpios);这里 gpio_dt_spec 结构体包含了引脚所属的 GPIO 控制器和设备树里定义的引脚号应用代码不再关心它是 PA5 还是 PB1只需要知道“led0 这个设备跑通了”。6.2 用 overlay 覆盖 LED 引脚不同板卡的 LED 可能接在不同的 GPIO 上。Zephyr 支持在应用目录下放一个 overlay 文件来覆盖板级设备树比如建立一个名为 board 的文件夹里面放一块开发板的 overlay。下面是一个在 STM32 板卡上把 PA5 配置为 LED 的例子。假设你的工程目录叫 blinky// blinky/boards/nucleo_f103rb.overlay / { leds { compatible gpio-leds; led0: led_0 { gpios gpioa 5 GPIO_ACTIVE_HIGH; label LD2; }; }; };这里的 compatible gpio-leds 是 Zephyr 定义的标准类型gpioa 表示 GPIOA 控制器的引用5 表示引脚号 5GPIO_ACTIVE_HIGH 表示高电平点亮。这个 overlay 只对 nucleo_f103rb 生效不会影响其他板卡。6.3 编写 blinky 应用代码在 blinky 工程目录下创建如下文件结构blinky/ ├── CMakeLists.txt ├── prj.conf ├── boards/ │ └── nucleo_f103rb.overlay └── src/ └── main.csrc/main.c 完整代码如下#include zephyr/kernel.h #include zephyr/drivers/gpio.h #define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led GPIO_DT_SPEC_GET(LED0_NODE, gpios); void main(void) { if (!device_is_ready(led.port)) { printk(Error: LED device %s is not ready\n, led.port-name); return; } gpio_pin_configure_dt(led, GPIO_OUTPUT_ACTIVE); while (1) { gpio_pin_toggle_dt(led); k_msleep(500); } }CMakeLists.txt 内容如下cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(blinky) target_sources(app PRIVATE src/main.c)prj.conf 内容CONFIG_GPIOy这段代码里的流程是先检查 GPIO 控制器是否 ready再把 LED 引脚配置为输出然后在死循环里每 500ms 翻转一次电平。构建命令仍然用 westwest build -b nucleo_f103rb blinky --pristine如果不想改设备树直接用板卡自带的 led0 别名很多开发板的设备树里已经定义好了 led0。这个工程更像一个 template让你理解从“硬件描述”到“应用代码”的完整链路。7. Kconfig 配置怎么改才会生效Zephyr 的 Kconfig 系统是很多初学者的困惑点明明在 prj.conf 里写了 CONFIG_XXXy构建后却没有效果。这通常是因为配置项的生效位置不对或者没有执行干净构建。7.1 配置优先级Zephyr 的最终配置由多个来源合并而成优先级从高到低大致是应用目录下的 prj.confboard 目录下的 _defconfigSoC 目录下的 defconfig架构目录下的 defconfigZephyr 内核中 Kconfig 默认值也就是说应用级 prj.conf 的优先级最高。修改 prj.conf 后构建系统会重新生成 .config 并检查依赖关系但有时候旧的编译缓存会影响新配置。7.2 修改配置后要记得 --pristine当你新增或删除了 CONFIG_XXX最好使用 pristine 构建west build --pristine -b qemu_x86 samples/hello_world--pristine 会清空旧的 build 目录并重新生成全部配置。Zephyr 构建系统在设计上比较保守配置变更不一定会触发全量重编这会导致“改了配置但没有效果”的错觉。规范做法是只要动了 prj.conf就加 --pristine。7.3 用 menuconfig 查看配置依赖如果不知道某个配置项叫什么名字或者想查看默认值可以用 Zephyr 的 menuconfigwest build -t menuconfig这会启动一个基于终端的功能配置界面。你可以搜索 CONFIG_BT、CONFIG_LOG、CONFIG_GPIO 等选项查看它的依赖、默认值和当前值。这个工具比直接翻 Kconfig 源码高效得多。从工程管理角度看Kconfig 的核心价值是“可追溯的配置”每个配置项都有默认值、依赖关系、帮助文档。应用只需要维护自己的 prj.conf板级差异交给 board defconfig这就是 Zephyr 能跨板卡复用软件的逻辑基础。8. 常见问题与排查思路Zephyr 环境搭建和实际开发中开发者遇到的大部分问题都集中在工具链、配置缓存、设备树引用这几个方向。下面整理一份可以直接对照排查的表格问题现象可能原因排查方式解决方案west 安装后 command not found虚拟环境未激活执行 which west 查看路径source 虚拟环境确认 pip 安装成功west update 很慢或失败网络不稳定仓库较多查看 git 下载地址换网络或错峰重试检查磁盘空间构建时报 Python module 缺失未安装 requirements.txt查看完整报错日志确认缺哪个模块进入虚拟环境执行 pip install -r requirements.txt构建报 No toolchain found未设置 ZEPHYR_TOOLCHAIN_VARIANT / ZEPHYR_SDK_INSTALL_DIRecho 查看环境变量正确设置两个环境变量后重新构建CMake 版本过低系统自带的 CMake 太旧执行 cmake --version 查看升级 CMake 到 Zephyr 要求版本修改 prj.conf 后配置不生效构建缓存未刷新检查 build/.config 中对应项用 west build --pristine 重建设备树引用报 node not foundalias 或节点名写错检查报错宏名和设备树文件修正 overlay 或改用 DT_NODELABEL编译通过但串口无输出串口配置错误或调试终端没有打开确认板子的默认 UART 是否设置 ok检查设备树或改用西桥的 debug UART烧录后程序跑飞时钟或电源配置不匹配查看 board 默认配置先跑官方 sample 验证板子本身是否正常排查 Zephyr 问题时最佳起点是看 build 目录下的 zephyr/.config 和 CMakeCache.txt。前者能确认 Kconfig 配置是否生效后者能定位工具链路径和编译参数。很多问题并不是代码本身有问题而是配置状态和预期不一致。另外提醒一下在烧录真实板卡之前建议先用官方的 blink_led 或 hello_world sample 验证一下 board 环境。如果官方示例都无法运行说明问题大概率在开发板、烧录器或串口终端配置上而不是你的应用代码问题。9. 工程落地时的最佳实践Zephyr 的上手门槛不只是语法更是工程组织方式。从实际项目角度看下面几点对团队协作和长期维护很关键。9.1 用 west manifest 锁定版本不要在多人协作时让每个人各自拉取 zephyr 仓库的分支一定要通过 west.yml 锁定版本和 commit。Zephyr 迭代很快不同版本之间的 API 会有变化。把 manifest 文件纳入 git 管理团队所有成员始终使用同一套依赖组合。升级时也通过 manifest 的 revision 变更来统一升级而不是在源码里打补丁。9.2 prj.conf 与 board 配置分离所有应用特有的配置比如日志级别、协议栈开关、线程栈大小放在应用目录的 prj.conf 中。不要修改 Zephyr 源码里的 board defconfig。如果某块板子有特殊需求可以在应用目录下按板卡放不同的 overlay 和 conf 文件。这样应用逻辑与硬件差异保持在清晰边界内后续从一块板子切到另一块板子时非常方便。9.3 设备树优先使用 alias 和 label在应用代码里优先用 DT_ALIAS 引用板级设备而不是直接使用具体的控制器标签。例如使用 DT_ALIAS(led0) 而不是 DT_NODELABEL(pa5)。这样在换板子时只需要在 overlay 中调整引脚映射代码不需要改动。设备树的意义就在于让硬件变更不扩散到业务代码里。9.4 编译时用 pristineCI 里完整跑一遍Zephyr 最稳妥的构建方式是带上 --pristine。虽然这会让增量编译优势减弱但能够避免配置缓存带来的隐蔽问题。在 CI 流水线中建议至少对目标板卡跑一次 clean build并生成编译警告日志。Zephyr 对编译告警比较敏感很多问题能在编译阶段提前暴露。9.5 日志与调试信息分级管理开发阶段可以在 prj.conf 里开启较多日志CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL4发布阶段再降低日志级别或者完全关闭减少串口输出对实时性的影响。使用 Zephyr 的 log 模块而不是裸 printk可以在运行时按模块查看日志便于定位问题。9.6 评估工具链时用官方 demo 建立基线接手一个新板卡时先跑一遍官方 sample 建立行为基线比如 hello_world、samples/drivers/led_ws2812、samples/net/wifi 等。如果官方 demo 能跑通后续应用问题才算真正属于应用层。这套方法可以减少一半以上的排查时间。10. 后续还能深入哪些方向“涂鸦博物馆”把这期做成 PT.1说明 Zephyr 的学习注定不是一次性的事。环境搭建和基础工程只是入场券真正有价值的方向在更上层BLE 和 Wi-Fi 协议栈的应用开发、USB 设备栈、TF-M 安全启动、OTA 升级、多核异构、Zephyr 的 Devicetree 高级用法以及基于 Zephyr 的 Product Lifecycle 管理。每一个方向都值得单独展开。如果这篇文章能帮你把 Zephyr 环境跑通并且建立一个“Kconfig 管软件、设备树管硬件、west 管版本”的心智模型那 PT.1 的核心目标就达到了。接下来不妨挑一块手头的开发板把 hello_world 换成 blinky再试着用 overlay 换一个 LED 引脚。亲手改一次设备树比读十篇介绍文章都有用。