
做嵌入式开发这几年最让我头疼的问题之一就是选型。芯片厂商的 SDK 各有各的一套早期用 Nordic 的 nRF5 SDK 时代码风格和驱动框架都是私有的一套想移植到别的平台几乎等于重写后来接触 Zephyr才意识到“一个开源 RTOS 统一多个厂商生态”这件事确实不只是口号。更值得注意的是Nordic 是 Zephyr 项目里投入最深的芯片厂商之一。从早期的 nRF52832 支持到如今 nRF54 系列把 Zephyr 作为默认 RTOSNordic 几乎是把自己的软件战略押在了这个开源嵌入式平台上面。本文围绕 Nordic 与 Zephyr 的这十年合作路线展开既讲清楚概念和架构也带大家从零搭建一套基于 nRF Connect SDK 的开发环境并用一个实际可烧录的 BLE Beacon 示例说明完整流程。想入门 Zephyr、想把 Nordic 芯片用起来的开发者这篇教程可以作为一份较完整的参考。1. 嵌入式开源平台为什么走到“OS 之争”1.1 传统嵌入式开发的痛点十多年前做 MCU 开发基本是“一包例程走天下”。芯片原厂给你一个外设库你在这个库的基础上写逻辑。刚开始觉得没什么问题但一旦项目需要换芯片型号或者要做跨平台复用时痛点就会暴露出来驱动接口不统一。每个厂商的 GPIO、UART、SPI 接口命名和用法都不同。实时操作系统与 SDK 绑定太深。换 MCU 等于连 RTOS 一起换。网络协议栈、蓝牙协议栈往往由私有 SDK 提供无法灵活裁剪。社区生态分散问题排查只能靠厂商论坛和本地技术支持。这些痛点让“软件可移植性”成为嵌入式项目里非常重要但又很难实现的指标。尤其是 IoT 类产品芯片可能要同时支持 BLE、Wi-Fi、Thread、Matter 等协议如果每次适配一个新协议都要动底层研发效率会非常低。1.2 Nordic 的选择从私有 SDK 走向 ZephyrNordic 早期主推的是 nRF5 SDK配合 SoftDevice 蓝牙协议栈使用。这种方式在 nRF51、nRF52 时期很流行也积累了大量开发者。但它的局限同样明显协议栈是二进制封装调试手段受限应用代码与 Nordic 的库深度耦合多协议支持往往意味着要从底层做较多改动。所以 Nordic 在 Zephyr 项目刚起步的阶段就选择了加入并逐步将重心从 nRF5 SDK 转移到基于 Zephyr 的 nRF Connect SDK。这一决定让 Nordic 不只是做“芯片原厂 SDK”而是成为嵌入式开源平台生态的核心共建者之一。从结果来看这一选择的好处非常明显Zephyr 社区不断扩展对 Nordic 芯片的支持从 nRF52 到 nRF53、nRF54 系列板级支持包BSP和驱动都随主线同步更新。nRF Connect SDK 基于 Zephyr同时集成了 Nordic 专有的 BLE、Thread、Matter、Wi-Fi 等协议栈能力。开发者在 Zephyr 里开发的代码理论上可以迁移到其他支持 Zephyr 的 MCU 平台减少绑定风险。可以说Nordic 从“提供芯片 SDK”转向“运营嵌入式开源平台生态”是嵌入式行业里一次很有代表性的战略转变。1.3 Zephyr 解决了什么问题Zephyr 是一个开源实时操作系统但它的价值不止于提供任务调度。它更像是一套面向物联网和嵌入式场景的完整软件平台提供一个内核支持多线程、信号量、消息队列、内存管理等 RTOS 基础能力。提供统一的驱动框架让应用层尽量与具体芯片解耦。提供设备树devicetree机制来描述硬件把板级配置从代码中分离出来。提供 Kconfig 配置系统让开发者可以按需裁剪功能编译出尽量精简的固件。同时支持 BLE、Wi-Fi、Thread、Zigbee、Matter、TCP/IP 等网络协议栈。对开发者的直接好处是写应用时不需要太关心底层芯片寄存器更多精力放在业务逻辑和系统集成上。2. Zephyr 的核心概念与实时操作系统定位2.1 什么是 ZephyrZephyr 是一个由 Linux 基金会托管的开源项目定位是面向资源受限的嵌入式设备的实时操作系统。它不是 Linux 的精简版而是一套从零设计、针对 MCU 特点的 RTOS。Zephyr 的设计目标是模块化、跨平台和可裁剪。它的源码树包含了内核、驱动、协议栈、文件系统、电源管理等大量子系统但最终编译进固件的内容只占很小一部分。开发者通过 Kconfig 配置项决定底层包含哪些模块而不是把所有代码都拉进工程里。与很多商业 RTOS 不一样Zephyr 的许可证是 Apache 2.0商用友好不需要公开应用代码。这一点对很多做物联网产品的公司来说吸引力很大。2.2 核心特性概览下面是 Zephyr 中比较重要的能力维度能力维度说明内核支持协作式和抢占式线程、定时器、信号量、互斥量、消息队列、栈回溯设备驱动支持 GPIO、UART、SPI、I2C、PWM、ADC、DMA、USB、Flash 等网络支持 sockets API、TCP/IP、BLE、Thread、Zigbee、Matter、Wi-Fi配置系统Kconfig 配置裁剪构建时决定包含哪些功能硬件抽象devicetree 描述板级硬件应用代码通过 API 访问设备电源管理支持低功耗 tickless 模式、系统电源状态管理构建系统基于 CMake 和 west 工具支持多板级目标这些特性让 Zephyr 不只是“一个 RTOS 内核”而是一整套设备软件开发框架。2.3 Zephyr vs FreeRTOS2026 年嵌入式项目选型参考很多嵌入式开发者都会问Zephyr 和 FreeRTOS 到底选哪个这个问题没有绝对答案我从几个角度对比一下对比维度ZephyrFreeRTOS定位全功能嵌入式平台偏向 IoT 与复杂应用轻量实时内核偏向简单任务调度驱动与协议栈自带丰富驱动和网络协议栈内核只占很小一部分需要自己找生态配置方式Kconfig devicetree构建时裁剪头文件和配置宏相对传统跨厂商支持英飞凌、Nordic、NXP、ST、乐鑫等内核移植简单普遍支持学习曲线偏陡涉及构建系统和设备树较平缓容易上手适合场景多协议、复杂外设、需要长期维护的产品简单控制、资源极度受限、团队熟悉 FreeRTOS如果你的项目要接 BLE、Matter、Wi-Fi或者希望代码能在不同芯片厂商之间复用Zephyr 会更合适如果项目只需要简单调度、资源非常紧张FreeRTOS 依然有很大优势。2.4 Nordic 与 Zephyr 的关系Nordic 是 Zephyr 项目的长期贡献者和重要支持方。要理解这一层关系可以看几个事实Nordic 的官方主推 SDK —— nRF Connect SDK底层就是 Zephyr。Nordic 的芯片兼容性、BLE 协议栈、DFU 升级等能力都以 Zephyr 插件方式提供。Nordic 新系列芯片如 nRF54L 系列在设计之初就考虑了 Zephyr 支持。每次 Zephyr 版本发布Nordic 的 BSP 基本都会同步更新到主线。所以对 Nordic 开发者来说学习 Zephyr 就是在学习 Nordic 芯片的官方软件体系反过来Zephyr 也在不断吸收 Nordic 在低功耗蓝牙、多协议连接等方面的实践经验。3. 开发环境准备基于 nRF Connect SDK 的 Zephyr 环境搭建3.1 nRF Connect SDK 与 Zephyr 的关系nRF Connect SDK 是一个安装在 Zephyr 之上的 Nordic 软件开发套件。它包含Zephyr RTOS 的特定版本。Nordic 自研的 BLE 协议栈、SoftDevice Controller、Matter 组件等。丰富的板级支持包比如 nRF52840DK、nRF5340DK、nRF54L15DK 等开发板的设备树和默认配置。构建脚本、示例工程、单元测试框架等。也就是说安装 nRF Connect SDK 后你的代码既可以调用 Zephyr 的标准 API也可以使用 Nordic 特有的协议栈和硬件能力。3.2 环境版本说明Zephyr 版本迭代较快不同版本之间 Kconfig 配置项和 API 可能有变化。本文示例以常见环境为例操作系统Windows 10/11 或 Ubuntu 20.04/22.04。nRF Connect SDKv2.6 或更新版本示例基于 2.x 分支。Zephyr RTOS随 nRF Connect SDK 自动拉取。工具链Arm GNU Toolchain 12.3.rel1 或更新版本。构建工具CMake、Ninja、Python 3.8、west。如果你的环境版本与本文不一致重点看配置思路具体命令差别不会太大。3.3 安装必要工具这里以 Windows 环境为例。建议先安装并配置好下面几项Python 3.10 以上并确保python命令可以在命令行中执行。Git 客户端。VS Code并安装nRF Connect for VS Code扩展。J-Link 驱动用于开发板烧录与调试。接着安装 west 工具pip install west安装后确认版本west --version如果提示找不到 west检查 Python 的 Scripts 目录是否在 PATH 中。3.4 获取 nRF Connect SDK使用 west 可以很方便地拉取 nRF Connect SDK 以及对应的 Zephyr 源码。首先创建一个工作目录比如ncsmkdir ncs cd ncs west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.6.0 .这里的-m指定 manifest 仓库地址--mr指定修订版本。你完全可以根据项目需要改用其他版本。然后更新所有子仓库west update这一步会拉取 Zephyr、mcuboot、nrfx 等大量依赖仓库耗时取决于网络情况。更新完成后工作目录下会出现zephyr、nrf、bootloader、modules等文件夹。接着安装 Python 依赖pip install -r zephyr/scripts/requirements.txt pip install -r nrf/scripts/requirements.txt最后设置 Zephyr 环境变量。在终端里运行west zephyr-export如果你使用 VS Code 版 nRF Connect 扩展也可以直接在扩展界面里创建工程工具链路径和 SDK 路径都可以通过图形界面配置体验更友好。3.5 工具链安装与配置在 Windows 上推荐使用 nRF Connect for VS Code 扩展自带的工具链管理功能或者手动安装 Arm GNU Toolchain。安装完成后需要把工具链路径配置到环境变量中set GNUARMEMB_TOOLCHAIN_PATHC:\Program Files\Arm GNU Toolchain arm-none-eabi\12.3.rel1如果你使用 VS Code 扩展也可以直接在扩展设置里指定这样命令行构建和图形界面构建使用同一套工具链。工具链配置不当时最常见的报错是CMake Error: Could not find a supported GnuArmEmbedded toolchain.遇到这个错误时优先检查环境变量名是否拼写正确、路径是否包含空格导致解析异常。4. 理解 Zephyr 两大核心机制Kconfig 与设备树在编写任何 Zephyr 应用之前建议先花时间理解 Kconfig 和设备树。这两个机制是 Zephyr 区别于传统 MCU SDK 的关键也是初学者最容易困惑的地方。4.1 Kconfig配置驱动的构建方式Kconfig 是一种配置系统原本来源于 Linux 内核。在 Zephyr 中它用来决定“哪些模块编译进固件、哪些模块不编译”。每个 Zephyr 工程都有一个prj.conf文件里面定义应用需要的配置项。例如CONFIG_GPIOy CONFIG_BTy CONFIG_LOGy这里的y表示启用对应模块。当配置项被关闭或没有定义时相关代码就不会参与编译从而控制固件体积和裁剪功能。如果你需要修改配置但没有把握可以直接在构建目录里运行配置界面west build -t menuconfig这个命令会打开一个命令行菜单界面你可以在里面浏览和修改配置项。修改保存后需要重新构建才会生效。Kconfig 的优点是配置项以文本形式存在方便版本管理和自动化构建。缺点是需要花一点时间熟悉配置项的依赖关系比如某个协议栈可能依赖某个内核功能必须先同时启用。4.2 设备树硬件描述与板级适配设备树是一种用文本文件描述硬件信息的方式。在 Zephyr 中开发板对应的硬件信息由.dts文件和.dtsi文件描述。.dtsi通常用于描述芯片级或系列级共用的内容比如 CPU 核、中断控制器、外设基地址等。.dts则是具体板子级别的描述会包含某个型号的 Flash 大小、LED 引脚、UART 引脚等。对于应用工程一般不需要修改开发板原始的.dts文件而是通过app.overlay这样的 overlay 文件来覆盖或追加配置。比如把 LED 引脚从默认的 P0.13 改成 P0.25就可以在 overlay 里修改。举例下面的 overlay 文件打开了一个 UART1 实例并配置了对应的引脚uart1 { compatible nordic,nrf-uarte; current-speed 115200; pinctrl-0 uart1_default; pinctrl-names default; status okay; };应用代码可以通过设备树生成的宏或设备 API 来访问这些硬件实例。4.3 为什么这套机制适合 Nordic 多芯片平台Nordic 芯片型号很多从 nRF52832 到 nRF52840、nRF5340、nRF54L15虽然都是同一家厂商但引脚、外设数量和内存资源差异很大。如果每个芯片都单独维护一套应用代码工作量惊人。设备树把硬件描述与应用代码分离后同一个应用代码可以同时编译到不同开发板。只需要在构建时指定不同的board目标例如west build -b nrf52840dk_nrf52840 west build -b nrf54l15dk/nrf54l15/cpuapp这样同一份 main.c 就能适配不同开发板大大减少了多产品线维护成本。5. 完整实战在 nRF52840 DK 上运行一个 BLE Beacon下面我们通过一个实际项目来体验完整的 Zephyr 开发流程。目标是在 nRF52840 DK 开发板上实现一个简单的 BLE Beacon广播自定义数据并通过手机 BLE 扫描工具能看到设备。5.1 创建项目结构我们可以通过 west 快速创建示例工程也可以手动创建项目目录。推荐在 nRF Connect SDK 的nrf/samples里找相近示例然后复制到自己的项目目录。这里手动创建一个工程目录ble_beacon/ ├── CMakeLists.txt ├── prj.conf ├── app.overlay └── src/ └── main.cCMakeLists.txt内容如下cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(ble_beacon) target_sources(app PRIVATE src/main.c)这段 CMake 脚本的作用是引入 Zephyr 构建系统并把自己的源文件加入应用目标。5.2 编写 prj.conf编辑prj.confCONFIG_BTy CONFIG_BT_PERIPHERALy CONFIG_BT_DEVICE_NAMEnRF Beacon CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL3简单解释一下CONFIG_BTy启用 Zephyr 蓝牙协议栈。CONFIG_BT_PERIPHERALy启用外设角色让开发板可以被手机扫描到。CONFIG_BT_DEVICE_NAME设置设备广播名称。CONFIG_LOG启用日志方便调试。5.3 编写设备树 overlay在大多数 Nordic 开发板上BLE 功能不需要额外配置天线引脚所以app.overlay可以先保持为空文件或者在需要开启某个 LED 或串口时再添加内容。如果你希望在 Beacon 启动时点量某个 LED可以使用 Nordic DK 板上的 LED 引脚。以 nRF52840 DK 为例LED1 通常连接到 P0.13可以在 overlay 里添加/ { leds { compatible gpio-leds; led0: led_0 { gpios gpio0 13 GPIO_ACTIVE_LOW; label Green LED; }; }; };这里GPIO_ACTIVE_LOW表示低电平点亮这是因为开发板上的 LED 电路通常是低电平导通。5.4 编写 main.csrc/main.c的完整代码如下#include zephyr/kernel.h #include zephyr/ble/bluetooth.h #include zephyr/ble/uuid.h #include zephyr/logging/log.h LOG_MODULE_REGISTER(beacon, LOG_LEVEL_INF); static const struct bt_data ad[] { BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), BT_DATA_BYTES(BT_DATA_NAME_COMPLETE, nRF Beacon), }; static const struct bt_data sd[] { BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(0x1800)), }; static void bt_ready(void) { printk(Bluetooth initialized\n); } static void start_adv(void) { struct bt_le_adv_param *adv_param; adv_param BT_LE_ADV_PARAM_INIT(BT_LE_ADV_IND, BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME, BT_GAP_ADV_SLOW_INT_MIN, BT_GAP_ADV_SLOW_INT_MAX, NULL); int err bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); if (err) { printk(Advertising failed to start (err %d)\n, err); return; } printk(BLE Beacon advertising started\n); } int main(void) { int err; printk(Starting BLE Beacon Demo\n); err bt_enable(NULL); if (err) { printk(Bluetooth init failed (err %d)\n, err); return err; } bt_ready(); start_adv(); while (1) { k_sleep(K_SECONDS(1)); } return 0; }这段代码做的事情注册了一个日志模块。定义了广播数据和扫描响应数据。在main中调用bt_enable()初始化蓝牙协议栈。初始化完成后调用bt_le_adv_start()开始广播。主循环休眠等待中断或事件。这里要注意bt_enable(NULL)是异步初始化回调参数传NULL表示我们不等待初始化完成回调直接继续执行。如果初始化失败返回的非零错误码可以帮我们定位问题。5.5 构建与烧录在项目目录执行构建命令west build -b nrf52840dk_nrf52840如果代码和配置没有问题构建结束后会生成build/zephyr/zephyr.hex文件。连接开发板到电脑确认 J-Link 驱动正常识别后执行west flash烧录成功后开发板会自动复位并开始运行 Beacon 程序。5.6 运行验证用手机打开支持 BLE 扫描的 App比如 nRF Connect或者直接用手机蓝牙扫描可以看到一个名为nRF Beacon的广播设备。在串口终端中可以看到类似下面的日志输出Starting BLE Beacon Demo Bluetooth initialized BLE Beacon advertising started到这里一个最简单的 Zephyr Nordic BLE 应用就跑通了。6. 常见问题与排查思路在实际开发中环境搭建和构建阶段经常会遇到一些问题。下面是我认为出现频率较高的情况问题现象常见原因解决思路找不到 west 命令Python Scripts 目录未加入 PATH重新安装 west检查 Python 环境变量构建时提示找不到 Zephyr未执行west zephyr-export或环境变量未设置执行west zephyr-export确认ZEPHYR_BASE找不到 Arm 工具链GNUARMEMB_TOOLCHAIN_PATH 未配置通过 VS Code 扩展或环境变量指定工具链路径编译报错函数未声明使用的 API 在当前 Zephyr 版本中不存在查阅当前 SDK 版本 API 文档使用west对应版本的示例做对照烧录失败无法连接 J-Link驱动未安装、USB 线是充电线、板子未上电安装 J-Link 驱动换数据线确认 SWD 连接广播看不到设备蓝牙协议栈被配置为不可连接或广播参数错误检查prj.conf中CONFIG_BT_PERIPHERAL和广播参数Kconfig 配置修改不生效配置缓存未重建删除build目录重新构建或使用west build -t menuconfig检查实际生效配置在排查 Zephyr 构建问题时最直接的方法是删除 build 目录重新构建。因为 Zephyr 会在构建目录里缓存大量配置信息源代码修改后缓存不失效会导致“改了配置没效果”的错觉rm -rf build west build -b nrf52840dk_nrf528407. 工程实践建议7.1 固定 SDK 版本使用 manifest 管理Zephyr 迭代速度快API 变化频繁。不同 nRF Connect SDK 版本之间BLE API、设备树绑定、Kconfig 配置项都可能不同。建议在项目开始时固定 SDK 版本并保持west update生成的 manifest 文件可追溯。不要每次都用latest拉取最新代码避免构建突然失败。west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.6.0 .后续也可以使用west manifest --freeze生成锁定版本文件方便团队协同时保持依赖一致。7.2 用 west 管理多仓库工作区west 是 Zephyr 的官方多仓库管理工具。除了拉取源码它还支持添加本地扩展仓库。例如如果你的项目有自己封装的 BSP 或板级配置可以放在独立仓库中然后在 west 的 manifest 文件里加入这个仓库。这样团队成员west update后就能得到完全一致的工程环境比手动拷贝zephyr、nrf源码要可靠得多。7.3 重视 Kconfig 裁剪控制 Flash 和 RAM 占用Nordic 芯片虽然性能不错但 Flash 和 RAM 依然有限。开发阶段可以为了调试把所有模块都打开进入量产阶段一定要手动裁剪。建议优先检查以下方面关闭日志输出级别或者在产品发布版里关掉CONFIG_LOG。去掉不需要的蓝牙特性如CONFIG_BT_DEVICE_NAME_DYNAMIC如果不用就关闭。根据 BLE 角色选择对应配置不需要的中心设备功能不要开启。裁剪后重新编译观察固件体积变化。实测下来一个简单的 Beacon 固件裁剪后可以控制在几十 KB 以内。7.4 日志与调试信息管理Zephyr 的日志系统很强大但默认配置下日志输出可能占用较多资源。开发阶段建议使用CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL4发布阶段建议降低级别或关闭CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL1如果完全不输出日志也可以直接设置CONFIG_LOGn但这会影响某些依赖日志模块的驱动行为修改前要先确认自己用到的外设驱动是否依赖日志。7.5 低功耗设计要提前规划Nordic 芯片的低功耗能力经常是产品卖点但低功耗不只是芯片的问题还涉及 Zephyr 电源管理配置和应用代码写法。建议启动 Zephyr 的电源管理模块CONFIG_PMy。使用 tickless 内核避免空闲时频繁唤醒。外设使用完及时关闭比如 ADC、UART 等。设备树中正确配置 GPIO 唤醒功能和外部中断极性。低功耗调试是一个独立课题通常会结合实际电流波形分析。前期先把基础框架搭好不要在产品阶段才想起来调功耗。8. 总结与下一步学习路线从这篇教程可以梳理出一条比较清晰的学习路径理解 Zephyr 的定位和核心特性尤其是和 FreeRTOS 的差异。掌握 nRF Connect SDK 的目录结构和 west 工具的基本用法。学会阅读prj.conf和设备树文件理解 Kconfig 与设备树如何影响构建结果。能够独立创建 Zephyr 工程编写简单的 BLE 应用完成编译、烧录和调试。接下来可以继续深入学习的方向包括Nordic 的 DTS 绑定和 pinctrl 配置尝试自定义板级支持。BLE 的广播参数细节比如连接间隔、广播间隔、扫描响应包设计。Matter over Thread 开发理解 Zephyr 在多协议场景下的角色。使用 nRF Connect SDK 自带的测试框架为外设驱动和应用逻辑编写单元测试。结合实际的硬件功耗仪对 Zephyr 应用做低功耗调优。Zephyr 的门槛比 FreeRTOS 高一些但一旦理解 Kconfig 和设备树这套体系带来的跨平台能力会非常值得。如果在学习过程中遇到问题优先查看当前 SDK 版本自带的示例代码和docs.nordicsemi.com的官方文档再配合本文的排错思路基本可以解决大部分环境问题。如果这篇文章对你有帮助可以收藏备用后续我会继续更新 Zephyr 与 Nordic 相关的实战内容。