☰
Zephyr RTOS实战:从环境搭建到nRF52840点灯与传感器读取
2026/10/20 19:17:33 网站建设 项目流程

过去十年,嵌入式领域最热闹的赛道无疑是物联网和智能硬件。但如果你关注过 MCU 市场报告,会发现一个有趣的现象:在底层操作系统层面,真正实现“增速领跑全球”的并不是老牌商业 RTOS,也不是 Linux 的简化版,而是由 Linux 基金会托管的开源实时操作系统——Zephyr。

而在这条开源之路上,走得最坚决、投入最深的公司之一,就是 Nordic Semiconductor。从 nRF52 系列开始,Nordic 就把 Zephyr 当成 nRF Connect SDK 的根基,而不是把它当作一个“可选的实验性框架”。十年下来,这一策略正在收获回报:Zephyr 在 GitHub 上的提交数、贡献者数量、以及商业落地项目数都保持了高速增长,而 Nordic 的 nRF54 系列芯片也顺理成章地成为新一代低功耗蓝牙和 Matter 开发的首选平台。

这篇文章想解决三个问题:Zephyr 到底是什么级别的 RTOS,为什么它能成为物联网时代的“增量市场”选择;Nordic 为什么敢于把自家 SDK 押注在 Zephyr 上,这背后改变了嵌入式开发的哪些环节;以及如果你现在要在一个新项目里选型 RTOS,Zephyr 和 FreeRTOS 到底该怎么选,上手时需要跨过哪些真实的坑。

文章会以 Nordic nRF52840 DK 为硬件参照,带你从零搭建 Zephyr 开发环境,跑通一个最小工程,并完成一次简单的 GPIO 点灯和传感器数据读取。整个过程不依赖任何商业 IDE,全部使用命令行工具,方便你理解 Zephyr 的构建系统和设备树机制。

1. 为什么是 Zephyr:一个“系统级”的嵌入式开源平台

很多开发者对 RTOS 的印象,还停留在“一个内核 + 一堆信号量 + 一个调度器”。FreeRTOS 之所以普及,就是因为它足够轻、足够简单,跑在 STM32 和 ESP32 上毫无压力,网上资料也多。但如果你把视野放到“物联网设备的完整软件栈”上,FreeRTOS 提供的只是一个内核,而 Zephyr 提供的是一个平台。

这两者本质区别在哪?我用一个实际场景来解释。

假设你要做一个智能门锁,功能包括:蓝牙配网、指纹识别、低功耗待机、OTA 升级、本地日志、以及和手机 App 的安全通信。如果用 FreeRTOS,你需要自己拼装:选择一个 BLE 协议栈(比如 NimBLE)、一个加密库(mbedTLS)、一个 Bootloader、一个文件系统(LittleFS)、还要写设备树去管理引脚和外设配置。这些组件之间的 API 风格不一致,版本兼容性靠你自己维护,出了问题要分别去各自的社区查。

如果用 Zephyr,这些能力大多是内置的:BLE 协议栈是内置子系统,加密库、OTA(MCUboot)、文件系统(LittleFS / NVS)、设备树管理硬件、内核提供线程和 IPC,所有子系统共用一套 Kconfig 配置体系。你只需要在prj.conf里打开对应的开关,在设备树里声明硬件连接,然后写应用层的业务逻辑。

这种“系统级”的思路,很像从“裸机开发 + 自己拼库”切换到“Linux 开发 + 标准子系统”。对小型项目来说,Zephyr 反而显得重;但一旦项目复杂度上来,或者需要在多款芯片之间移植,Zephyr 的工程优势就非常明显。

更关键的是,Zephyr 不是 Nordic 一家的私有系统。它由 Linux 基金会管理,贡献者包括 Nordic、NXP、ST、Intel、ADI、Garmin 等数十家公司。这意味着你在 Nordic 芯片上写的驱动和应用逻辑,迁移到 NXP 或 ST 的芯片上时,API 基本不变,变的只是设备树里的引脚声明。对于做多平台产品线的团队,这是巨大的成本节省。

从增速来看,Zephyr 近年来的每一个 LTS 版本都会加入大量新 SoC 支持,社区规模持续扩大,尤其在 Matter、Thread、低功耗蓝牙、汽车电子和工业物联网这几个方向上,Zephyr 已经是事实上的标准选项之一。

这一节的小结论:Zephyr 不是“又一个 RTOS”,它更像“嵌入式领域的 Linux 用户态”。内核只是它最基础的部分,真正的价值在统一的子系统、统一的构建系统和跨厂商生态。

2. Zephyr 的核心设计理念:设备树 + Kconfig + 子系统

上手 Zephyr 之前,你必须理解它的三大支柱。不理解这三点,你会发现它的工程结构到处都是“魔法”,很难调通。

2.1 设备树:硬件描述不再藏在 C 代码里

传统嵌入式开发中,外设初始化通常是这样:

GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);

这种方式在芯片固定、板子固定时没什么问题。但如果同一颗芯片做了多种板型,LED 接到了不同的引脚,就得用宏定义切换,或者维护多份工程。

Zephyr 的做法是:把“这块板上有什么外设、每个外设连接在哪”全部写进设备树文件(.dts/.overlay)。应用代码只通过设备树节点获取设备实例,然后用统一的 API 操作设备。

比如在 Nordic nRF52840 DK 上,LED0 在设备树中定义为:

/ { leds { compatible = "gpio-leds"; led0: led_0 { gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; label = "Green LED 0"; }; }; };

应用代码里这样获取设备:

#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) { gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); gpio_pin_set_dt(&led, 1); }

注意看,应用代码里没有出现“13”这个引脚号。引脚定义在设备树里,应用代码通过DT_ALIAS(led0)拿设备。这样换板子时,只需要改.overlay文件,应用代码一行不用动。这正是 Zephyr 强调的“硬件描述与业务逻辑分离”。

2.2 Kconfig:模块像 Linux 内核一样可裁剪

Kconfig 是 Linux 内核同款的配置系统。Zephyr 用.conf文件(如prj.conf)来打开或关闭功能。

比如打开蓝牙和日志:

CONFIG_BT=y CONFIG_LOG=y CONFIG_BT_PERIPHERAL=y

每个功能模块都有对应的 Kconfig 选项。构建时,Kconfig 会根据配置生成最终的编译头文件,Zephyr 的源码会按这些宏条件编译。

这个设计带来的好处是:你可以按需裁剪,但不需要手动删除源码文件。关闭某个子系统后,构建系统会自动不编译对应的源文件,最终固件体积可以控制得很小。

2.3 子系统:RTOS 不再只是调度器

Zephyr 的子系统覆盖了你开发物联网产品需要的大部分模块:

  • 蓝牙:BT,支持 BLE、Mesh、Beacon、定向广播等
  • 传感器:SENSOR,统一接口读取温度、湿度、加速度计等
  • 显示:DISPLAY,支持 SSD1306、ST7789 等常见驱动
  • 存储:NVS、LittleFS、FCB
  • 网络:NET,支持 Wi-Fi、Ethernet、Thread、6LoWPAN
  • 安全:TLS、mbedTLS、Secure Boot
  • 电源管理:PM,支持 system off、deep sleep
  • OTA:MCUboot引导加载器

这意味着你不需要像使用 FreeRTOS 那样“搬运轮子”,而是直接在统一的框架里调用现成的子系统。学习成本在初期会高一些,但项目进入维护期后,收益非常明显。

3. 环境准备:在 Ubuntu 上搭建 Zephyr 开发环境

Zephyr 官方支持 Linux、macOS 和 Windows(WSL2 模式)。这里推荐Ubuntu 22.04 LTS或更高版本,命令行体验最顺畅,很多编译工具链的依赖问题也能少踩坑。

3.1 安装系统依赖

打开终端,先更新软件源并安装必要的工具:

sudo apt update sudo apt install --no-install-recommends 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-build:Zephyr 的构建系统基于 CMake,Ninja 负责并行编译。
  • dfu-util:Nordic 和很多开发板通过 USB DFU 烧录固件时要用。
  • device-tree-compiler:编译设备树源文件需要。
  • python3-pip:后续用west工具管理多仓库源码,它是 Python 写的。

3.2 安装 west 命令

west是 Zephyr 的元工具,它负责拉取 Zephyr 仓库、指定版本、更新依赖模块、构建、刷写等。

pip3 install --user west

安装完成后,确认版本:

west --version

如果提示“command not found”,可能是用户级 Python 包路径不在 PATH 里,执行:

export PATH=$PATH:~/.local/bin

3.3 获取 Zephyr 源码

Zephyr 的源码采用多仓库管理,主仓库是zephyr,还有hal_nordic、hal_stm32、mcuboot等依赖仓库。用west init会生成一个工作目录,然后west update拉取所有依赖。

mkdir zephyr-workspace && cd zephyr-workspace west init -m https://github.com/zephyrproject-rtos/zephyr.git --mr main cd zephyr west update

注意这一步会下载较多代码,耗时取决于网络。执行完后,在zephyr-workspace目录下会看到zephyr、bootloader、modules、tools等文件夹。

3.4 安装 Python 依赖

Zephyr 源码里带了一个requirements.txt,包含所有构建时用到的 Python 包:

pip3 install -r zephyr/scripts/requirements.txt

3.5 安装 ARM 交叉编译工具链

Nordic 芯片目前主流是 Cortex-M4 / Cortex-M33,需要 ARM 交叉编译工具链。

sudo apt install gcc-arm-none-eabi

验证版本:

arm-none-eabi-gcc --version

如果你的发行版自带的版本过旧(低于 10.2),建议从 ARM 官网下载最新的gcc-arm-none-eabi-*-x86_64-linux.tar.bz2,解压后把bin目录加入 PATH。

到这里,Zephyr 开发环境就准备好了。接下来用一个最小工程验证整个工具链是否正常。

4. 最小工程实践:Nordic nRF52840 DK 点灯

4.1 创建一个新工程

Zephyr 官方提供了很多示例。最简单的路径是直接使用samples/hello_world或samples/basic/blinky。我们先用blinky。

cd zephyr-workspace/zephyr west build -b nrf52840dk/nrf52840 samples/basic/blinky

如果一切正常,编译完成后会在build/zephyr目录下生成zephyr.hex和zephyr.bin。

4.2 烧录到开发板

用 USB 线连接 nRF52840 DK,确认系统识别出开发板:

lsusb

你应该能看到类似0x1915:0x521f的设备,就是 Nordic 的 USB 接口。然后执行:

west flash

west flash会调用nrfjprog或dfu-util自动烧录。

如果没有安装 nrfjprog,也可以直接用dfu-util:

dfu-util -a 0 -s 0x00000000:leave -D build/zephyr/zephyr.bin

烧录完成后,开发板上的 LED0 会开始闪烁。

4.3 修改为自己的点灯逻辑

接下来我们把默认点灯逻辑改一下,让 LED 亮 1 秒、灭 500 毫秒。

blinky示例的主文件路径是:

zephyr-workspace/zephyr/samples/basic/blinky/src/main.c

修改它:

#include <zephyr/kernel.h> #include <zephyr/drivers/gpio.h> #include <zephyr/sys/printk.h> #define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); int main(void) { if (!gpio_is_ready_dt(&led)) { printk("LED device not ready\n"); return 0; } gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); while (1) { gpio_pin_set_dt(&led, 1); k_msleep(1000); gpio_pin_set_dt(&led, 0); k_msleep(500); } return 0; }

重新编译烧录:

west build -b nrf52840dk/nrf52840 samples/basic/blinky --pristine west flash

4.4 串口日志输出

Zephyr 的 printk 输出会重定向到串口。用 minicom 或 putty 打开/dev/ttyACM0,波特率 115200,可以看到日志。

sudo apt install minicom sudo minicom -D /dev/ttyACM0 -b 115200

如果看不到输出,先检查开发板上的 VCOM 开关是否拨到默认开启位置,再确认CONFIG_SERIAL是否使能。blinky 默认有CONFIG_PRINTK=y,一般会正常输出。

5. 使用设备树 overlay 修改引脚

开发中经常遇到这种情况:LED 或传感器没有接在默认引脚上,需要改设备树。

假设你的 nRF52840 DK 外接了一个 LED,接在P1.02引脚。我们创建一个 overlay 文件来覆盖默认定义。

5.1 创建 overlay 文件

在samples/basic/blinky目录下新建led.overlay:

/ { leds { compatible = "gpio-leds"; led0: led_0 { gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; label = "External LED 0"; }; }; };

然后指定 overlay 文件编译:

west build -b nrf52840dk/nrf52840 samples/basic/blinky --pristine -d build_led -- -DOVERLAY_CONFIG=led.overlay

-d build_led指定新的构建目录,避免覆盖默认构建结果。

5.2 验证设备树编译结果

Zephyr 在构建时会生成一个可视化的设备树描述文件,路径是:

build_led/zephyr/zephyr.dts

打开这个文件,搜索leds,你会看到你的 overlay 内容已经被合并到最终的设备树中。如果 overlay 写错了,会在构建阶段直接报错,这是 Zephyr 对“硬件配置错误”最友好的防线。

这一节的小结论:设备树是 Zephyr 硬件适配的核心机制。新手最容易踩坑的地方是 GPIO 标志写错(GPIO_ACTIVE_HIGHvsGPIO_ACTIVE_LOW),以及DT_ALIAS拿不到节点时报编译错误。

6. 一个更完整的实例:读取温度传感器

点灯只是验证工具链。为了让读者更直观地理解 Zephyr 的传感器子系统,这里写一个读取 nRF52840 内置温度传感器的示例。

6.1 创建工程

cd zephyr-workspace/zephyr west build -b nrf52840dk/nrf52840 samples/sensor/temperature

这是 Zephyr 官方自带的温度传感器示例,支持读取板载温度传感器。源码路径:

samples/sensor/temperature/src/main.c

核心逻辑是:

#include <zephyr/kernel.h> #include <zephyr/device.h> #include <zephyr/drivers/sensor.h> #include <zephyr/sys/printk.h> int main(void) { const struct device *dev = DEVICE_DT_GET_ANY(nordic_nrf_temp); struct sensor_value temp; if (dev == NULL) { printk("No temperature device found\n"); return 0; } while (1) { sensor_sample_fetch(dev); sensor_channel_get(dev, SENSOR_CHAN_DIE_TEMP, &temp); printk("Temp: %d.%d C\n", temp.val1, temp.val2); k_sleep(K_SECONDS(1)); } }

6.2 编译与运行

west build -b nrf52840dk/nrf52840 samples/sensor/temperature --pristine west flash

连接串口,会看到类似下面的输出:

Temp: 27.4 C Temp: 27.5 C

这里的关键点是DEVICE_DT_GET_ANY(nordic_nrf_temp)。nordic_nrf_temp是设备树里温度传感器的 compatible 字符串,Zephyr 通过它自动匹配驱动。整个过程中,应用层代码完全不需要关心这个传感器挂在哪条 I2C 或 SPI 总线上——如果是外接传感器,你只需要在设备树里声明总线和地址。

7. Zephyr 与 FreeRTOS 的选型对比

很多开发者会问:既然 FreeRTOS 已经很成熟,为什么还要学 Zephyr?

我从业界的实际项目角度,给一个比较清晰的判断。

7.1 从项目复杂度看

维度FreeRTOSZephyr
内核体积极小,可裁剪到 4-8KB中等,配置裁剪得当可到 20-40KB
BLE/网络协议栈需自己集成 NimBLE / lwIP内置子系统,统一 API
设备驱动芯片厂商提供统一的设备驱动模型,跨厂商复用
多平台移植需自行适配 HAL设备树 + SoC 抽象,移植成本低
社区活跃度老牌,资料多增速快,厂商投入大
学习曲线平缓较陡,需要理解 Kconfig/设备树/构建系统

如果你的产品是单一型号、功能简单、团队规模小、生命周期短,FreeRTOS 的性价比更高。大部分工程师看几周文档就能上手。

但如果你的产品线有多款芯片、需要 BLE + OTA + 加密 + 文件系统、或者要对接 Matter/Thread 这类新型物联网协议,Zephyr 的集成优势会越来越明显。尤其是当项目进入维护期,需要升级协议栈或更换主控时,Zephyr 的系统级抽象能帮你省下大量时间。

7.2 从生态趋势看

当前智能家居领域最重要的标准之一 Matter,其 Thread 协议栈在 Zephyr 上是原生支持。很多智能门锁、传感器、灯具的参考设计都基于 Zephyr。Nordic 的 nRF54 系列、Silicon Labs 的 MG24、NXP 的 RW612 等新芯片,首发 SDK 都深度集成 Zephyr。这说明 Zephyr 已经不是“玩具系统”,而是厂商主推的规模化平台。

7.3 什么时候该避免用 Zephyr

也有几种情况不建议用 Zephyr:

  • 你只需要一个任务调度器,不需要任何协议栈。
  • 芯片资源极其紧张,Flash/RAM 只有几十 KB。
  • 团队完全没有 Linux 或构建系统的使用经验,且没有时间学习。
  • 项目要求纯裸机开发,不需要 RTOS。

选型没有绝对的对错,关键是看项目约束和时间成本。Zephyr 在 2025 年前后的生态位,更像“中大型物联网设备的标准选项”,而不是所有 MCU 项目的默认选项。

8. 常见问题与排查思路

我根据实际开发经验,整理了几个高频问题。

问题现象可能原因排查方式解决方案
west: command not foundwest 未安装或不在 PATH执行pip3 list | grep west将~/.local/bin加入 PATH
编译报错:fatal error: zephyr/kernel.h: No such file工程不在 Zephyr 工作空间内检查是否在zephyr-workspace目录下在 workspace 根目录执行west build
烧录失败:No device foundnrfjprog 未安装或驱动异常执行lsusb确认 USB 枚举安装 nrf-command-line-tools 或改用dfu-util
点灯无效但编译成功GPIO 有效电平配置反了查看zephyr.dts中 gpio 标志将GPIO_ACTIVE_HIGH改为GPIO_ACTIVE_LOW或相反
编译时提示 Python 依赖缺失requirements.txt未安装执行pip3 list检查重新安装pip3 install -r zephyr/scripts/requirements.txt
Flash 空间不足打开了过多子系统检查CONFIG_*裁剪情况关闭未使用的子系统,启用CONFIG_MINIMAL_LIBC等精简选项

另一个特别建议:不要直接使用默认配置跑大型示例。很多示例为了演示功能,打开了大量日志和调试选项,实际产品需要逐个检查prj.conf里的开关。

9. 最佳实践与工程建议

9.1 使用 west 管理多板型工程

在产品开发中,一套代码往往要支持多个板型。建议在工程根目录下创建boards/目录,按板型放.overlay和prj_xxx.conf文件,编译时指定:

west build -b nrf52840dk/nrf52840 -d build_v1 -- -DOVERLAY_CONFIG=boards/board_v1.overlay

9.2 注意日志级别的选择

开发阶段用CONFIG_LOG_DEFAULT_LEVEL=4(DEBUG)能看到大量信息,但在发布版本里一定要降到CONFIG_LOG_DEFAULT_LEVEL=2(WRN)或CONFIG_LOG_DEFAULT_LEVEL=1(ERR)。日志输出非常消耗 CPU 和功耗,在低功耗物联网产品中影响尤其明显。

9.3 养成查看构建摘要的习惯

每次west build成功后,终端会输出 Flash 和 RAM 使用情况。关注这两项:

Memory region Used Size Region Size %age Used FLASH: 123.4 KB 1 MB 12.0 % RAM: 45.6 KB 256 KB 17.8 %

如果 Flash 占用异常增加,优先检查是不是误开了CONFIG_BT_DEBUG_LOG或CONFIG_DEBUG_OPTIMIZATIONS。

9.4 设备树不是万能药,但它是硬件团队的沟通语言

很多硬件工程师和软件工程师之间的沟通,常常卡在“这个 LED 到底接在哪个引脚”。使用 Zephyr 后,软件工程里的设备树文件就是一份“可编译的硬件配置单”,硬件改了引脚,直接在 overlay 里改,然后提交到 Git。代码评审时可以直观地看到硬件变更,比口头沟通可靠得多。

9.5 版本锁定与可复现构建

Zephyr 迭代速度很快,为了工程可维护性,建议把 workspace 的 manifest 文件(west.yml)锁定到一个 LTS 版本或固定 release tag。不要长期使用 main 分支做产品开发,否则依赖模块的升级可能带来隐性编译错误。

锁定方式:

west init -m https://github.com/zephyrproject-rtos/zephyr.git --mr v3.7-lts west update

10. 总结与下一步学习方向

这篇文章从“Zephyr 为什么增速领跑”切入,介绍了 Nordic 在 Zephyr 生态上的布局,随后带你走通了 Zephyr 开发环境搭建、nRF52840 DK 点灯、设备树 overlay 修改、传感器数据读取,以及 Zephyr 与 FreeRTOS 的选型对比。读完你至少应该掌握了:Zephyr 和 FreeRTOS 的定位差异,设备树和 Kconfig 在工程中的实际作用,以及如何用命令行完成一次完整的 Zephyr 开发流程。

如果你想继续深入,建议按这个顺序学习:

  1. 拿到一块 nRF52840 DK 或 nRF54L15 DK,跑通官方 samples 中至少 5 个例程,包括blinky、hello_world、sensor/temperature、bluetooth/peripheral_hr、mcumgr/smp_svr。
  2. 认真读一遍samples/bluetooth/peripheral_hr的代码,理解 Zephyr 蓝牙服务和 GATT 数据回调的组织方式,这是后续开发 BLE 产品的基础。
  3. 学习 MCUboot 和 OTA 升级流程,Zephyr 的 OTA 是配套 MCUboot 的,理解双 bank 升级和交换机制,对量产设备至关重要。
  4. 尝试自己写一个简单的传感器驱动,用设备树暴露设备实例,再通过sensor_channel_get读取数据。这是理解 Zephyr 驱动模型最快的路径。

Zephyr 的入门曲线确实比 FreeRTOS 陡,但它带来的“硬件配置工程化、驱动跨平台复用、子系统开箱即用”这些能力,在 2025 年之后的物联网开发中会越来越值钱。建议收藏这篇文章,在搭建环境或选型对比时当成一份速查手册。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询