最近在好几个嵌入式交流群里都被问到同一个问题:Zephyr RTOS 到底怎么入门?说实话,这个问题在五年前还挺难回答,因为资料少、生态新;但现在答案已经很明确了——先在一台 Ubuntu 上把环境搭起来,编译第一个 Blinky 示例,再把固件烧到开发板上,看到板载 LED 闪烁的那一刻,整个 Zephyr 的开发模型你就懂了大半。
我见过不少人卡在第一步就放弃了,不是 Zephyr 难,而是它的工具链和传统单片机开发差别太大——它不是装个 Keil、点两下编译就完事,而是建立在 CMake、Ninja、west 这套现代构建体系之上。Zephyr 自己就是一套完整 RTOS,内核、驱动、网络协议栈、蓝牙栈全部内置,好用的同时学习曲线也确实比 FreeRTOS 陡一些。
这篇博文就从一台干净的 Ubuntu 开始,带你完整走一遍 Zephyr RTOS 的入门闭环:环境搭建、west 工具链准备、Blinky 示例的编译与代码拆解、开发板烧录与串口验证。文章里所有步骤都是我实际操作后整理的,中间穿插了大量踩坑记录和排查思路,适合刚接触 RTOS 的嵌入式新手,也适合从 FreeRTOS 或裸机开发转过来的老手快速摸清 Zephyr 的玩法。我把命令、代码、报错都放在对应位置,你照着抄就行。
1. 为什么选 Zephyr:RTOS 选型思路与 Ubuntu 平台考量
1.1 RTOS 赛道上,Zephyr 凭什么值得投入时间
先聊一个很多人纠结的问题:RTOS 那么多,FreeRTOS、RT-Thread、LiteOS 都有人用,为什么偏偏要学 Zephyr?我个人的答案是:如果你只想快速做一个简单的温湿度采集器,FreeRTOS 确实够用;但只要你做的产品稍微复杂一点——需要联网、需要低功耗管理、需要 OTA、需要多个外设驱动协同——Zephyr 的优势就会非常明显。
Zephyr 的项目定位不是“给你一个内核裸核”,而是“给你一个相对完整的物联网操作系统”。它从 Linux 内核借鉴了大量设计思想,比如 Kconfig 配置系统、设备树(Devicetree)、设备驱动模型。这种设计带来的直接好处是:你换一颗 SoC 或者换一块开发板,需要改的代码量比传统 RTOS 少一个数量级。驱动框架是统一抽象的,应用层写的 GPIO、UART、I2C 操作,底层换芯片的时候基本不用动。
从内核特性来看,Zephyr 支持多线程、信号量、消息队列、内存管理、轮询机制(polling API),而且早期版本做过一次很大的 API 重构,现在的接口风格比 FreeRTOS 现代得多。调度策略上,它是基于优先级的抢占式调度,同时支持时间片轮转,这两者的组合逻辑和 Linux 的 CFS 完全不同,面试和实际开发时都是高频考题。
还有一个不可忽视的点:生态。Zephyr 背后是 Linux 基金会,代码仓库活跃度、社区维护力度在开源 RTOS 里属于第一梯队。目前官方对 ARM Cortex-M、RISC-V、x86、Xtensa 等架构都有完善支持,这也意味着它的适用范围从 MCU 到 MPU 都能覆盖。你在招聘网站上搜“Zephyr 开发工程师”,北上广深这两年岗位量涨得很快,很多物联网、智能家居、可穿戴产品线都在往这个技术栈迁移。
1.2 Ubuntu 作为开发主机的三条理由
为什么这篇教程用 Ubuntu 而不是 Windows?不是 Windows 不能搞,而是在 Ubuntu 上搞 Zephyr 的体验顺畅得多。
第一,工具链源头更干净。Zephyr 的官方构建体系依赖 CMake、Ninja、gperf、DTC(设备树编译器)、Python 这一整套工具,它们在 Linux 的包管理器里基本都是现成的,apt install 一条命令装完。Windows 上也行,但要配 MSYS2、装各种 DLL 依赖,光环境问题就能劝退一批新手。
第二,命令行效率高。Zephyr 的日常工作流就是 west init、west build、west flash、west debug 这几条命令,配合 shell 的自动补全和脚本化,体验非常顺滑。你后面跑自动化编译、CI 流水线,服务器端几乎都是 Ubuntu,本地环境越接近服务器,越少踩“在我电脑上明明能跑”的坑。
第三,调试和串口工具链成熟。Linux 下的串口工具(picocom、minicom、screen)轻量稳定,CH340 这类常见的 USB 转串口芯片在内核里直接有驱动,插上就能识别。Windows 的串口驱动虽然也能装,但时不时会碰到驱动签名、端口号漂移之类的幺蛾子。
如果你手头没有闲置机器,用虚拟机装 Ubuntu 也可以,VMware 和 VirtualBox 都行,记得给虚拟机分配至少 2 核 CPU 和 4GB 内存,编译 Zephyr 时磁盘空间留 20GB 以上。我实测下来,原生 Ubuntu 编译速度比虚拟机快大约 30%~50%,如果不是有特殊原因,建议优先原生系统。WSL2 也能跑 Zephyr 编译,但 USB 透传给烧录工具偶尔会有兼容问题,入门阶段我不推荐用它做烧录环节。
2. Ubuntu 环境搭建:把 Zephyr 工具链一次装齐
2.1 基础依赖包清单与安装命令
Zephyr 官方 Getting Started 文档里有一份依赖清单,我结合自己的使用经验稍微调整了一下。打开终端,先执行下面这条命令:
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很多教程会漏掉libsdl2-dev和libmagic1,这两个是在 QEMU 仿真和某些构建检查阶段需要用到的,少了它们会在后面莫名其妙报错。device-tree-compiler提供dtc命令,Zephyr 编译过程要把设备树源文件编译成二进制,没有它连west build第一关都过不去。
gperf这个工具可能不少人不认识,它是生成哈希函数的工具,Zephyr 的配置文件解析要靠它。如果 apt 提示找不到某个包,八成是软件源没有更新,先执行sudo apt update再装。
另外我建议顺手把openocd和picocom也装一下,前者是后面烧录调试 STM32 等板子的后备方案,后者是串口查看日志的工具:
sudo apt install openocd picocom2.2 Python 环境与 west 工具安装
west 是 Zephyr 的元工具(meta-tool),它同时负责拉取和管理多个 git 仓库,也负责封装 CMake 构建。安装 west 只需要 pip 一条命令,但这里有个坑必须先说清楚。
新版 Ubuntu(23.10 之后,尤其是 24.04)对系统 Python 环境做了 PEP 668 限制,直接执行pip3 install west会报一个externally-managed-environment的错误,提示你“请用虚拟环境或 pipx 安装”。这不是你操作错了,是系统在阻止你污染全局 Python 环境。
我推荐的做法是用虚拟环境,把 west 和 Zephyr 相关的 Python 包隔离在一起,这样以后重装系统或者换机器,直接复制一份配置就行:
python3 -m venv ~/zephyr-venv source ~/zephyr-venv/bin/activate pip install --upgrade pip pip install west激活虚拟环境后,终端提示符前面会出现(zephyr-venv),这就是当前环境生效的标志。如果你不想每次开终端都手动 activate,可以把source ~/zephyr-venv/bin/activate追加到~/.bashrc末尾。
装完之后执行west --version,能输出版本号就算成功。如果没有输出,检查一下 PATH 路径——虚拟环境装的 west 在~/zephyr-venv/bin,确保这个目录在 PATH 里。我自己因为这一步折腾过不少时间,所以在这里多说一句:永远在激活了虚拟环境之后再执行 west 命令,不然会出现“前面编译好好的,第二天突然说 west 找不到了”的情况。
2.3 Zephyr SDK 安装与环境变量配置
Zephyr SDK 不是 Linux 上那个软件开发套件,它是 Zephyr 官方提供的交叉编译工具链集合,里面包含了针对不同架构的 GCC 编译器、binutils、newlib、QEMU 模拟器等。简单来说,你的 Ubuntu 上默认的 gcc 编译出来的是 x86 电脑能跑的程序,而 SDK 里的编译器生成的是 ARM Cortex-M 芯片能跑的机器码。
SDK 的下载地址在 Zephyr 官方文档的 Getting Started 页面有链接,目前稳定版本建议用 0.16.x 系列。下载和解压的命令大概是这个样子:
cd ~ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.8/zephyr-sdk-0.16.8_linux-x86_64.tar.xz tar xf zephyr-sdk-0.16.8_linux-x86_64.tar.xz cd zephyr-sdk-0.16.8 ./setup.sh执行setup.sh的时候,它会问你是否把 SDK 安装到~/.local/opt下,这里选默认即可。安装过程中还会提示安装一些额外的 host 工具,比如 QEMU、OpenOCD,建议全部确认,后面调试会用到。
SDK 装好后,还要配置几个环境变量,让 Zephyr 的构建系统能找到工具链。在你家目录下创建或编辑~/.zephyrrc文件,写入以下内容:
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr export ZEPHYR_SDK_INSTALL_DIR=~/zephyr-sdk-0.16.8ZEPHYR_SDK_INSTALL_DIR指向你刚才解压 SDK 的目录,如果 setup.sh 把它装到了~/.local/opt,这个路径就要改成对应的实际位置。我建议在安装完成后执行ls ~/.local/opt看一眼,确认 SDK 到底放在哪。
环境变量的作用可以在编译时验证:进入 Zephyr 源码目录后执行source zephyr-env.sh,这个脚本会自动读取~/.zephyrrc并加载所有需要的变量。后面我会在编译环节具体演示。
3. Blinky 示例详解与编译实战
3.1 拉取 Zephyr 源码:west init 与 west update
环境和 SDK 都就绪之后,下一步是把 Zephyr 源码拉到本地。这里要注意的是:Zephyr 不是一个单一仓库,而是由 zephyr 主仓库、hal(硬件抽象层)、第三方模块、工具等多个仓库组成的多仓库项目。如果单纯用git clone拉主仓库,编译时会因为缺少 hal 模块而报一堆错。所以必须用 west 来管理。
先创建一个项目工作区,然后初始化:
mkdir -p ~/zephyrproject cd ~/zephyrproject west init -m https://github.com/zephyrproject-rtos/zephyr --mr v3.7.0-m指定主仓库地址,--mr指定分支或 tag。不建议直接用默认的 main 分支,因为 main 分支经常处于开发状态,某些 API 正在变更,新手跟着最新代码学容易莫名其妙的碰壁。我一般在正式开发时锁定一个 release tag,比如 v3.7.0、v4.0.0 这些,等熟悉了再考虑升级。
初始化完成后,执行更新命令,这一步会把所有子模块拉下来:
west updatewest update的耗时取决于网络状况和仓库规模,通常在几分钟到十几分钟之间。拉取完成后,~/zephyrproject目录下会出现 zephyr、modules、tools 等目录。执行ls ~/zephyrproject检查一下,看到 zephyr 目录就说明主仓库已经就位。
这一步做完,我建议先验证一下环境是否联动正常。进入 zephyr 目录,执行source zephyr-env.sh,然后执行west topdir,如果它输出了~/zephyrproject,说明ZEPHYR_BASE等变量已经正确加载。west topdir是检查 west 工作区状态最直接的命令,比什么都好使。
3.2 代码级拆解:Blinky 为什么能点亮 LED
在编译之前,有必要先把 Blinky 示例的代码看明白。为什么非要挑这个示例?因为它足够短,却能覆盖 Zephyr 最核心的两个概念:设备树映射和 GPIO 驱动 API。
Blinky 的主程序路径在zephyr/samples/basic/blinky/src/main.c,完整代码如下:
#include <zephyr/kernel.h> #include <zephyr/drivers/gpio.h> #define SLEEP_TIME_MS 1000 #define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); int main(void) { int ret; if (!gpio_is_ready_dt(&led)) { return 0; } ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return 0; } while (1) { gpio_pin_toggle_dt(&led); k_msleep(SLEEP_TIME_MS); } return 0; }第一眼看上去会觉得奇怪:代码里为什么没有任何明确指定哪个引脚?这正是 Zephyr 和传统单片机开发的最大区别。DT_ALIAS(led0)的意思是从设备树中寻找别名为led0的节点,GPIO_DT_SPEC_GET(LED0_NODE, gpios)则是把这个节点的gpios属性解析成一个gpio_dt_spec结构体,里面包含了 GPIO 控制器指针、引脚号、极性标志等信息。
也就是说,具体用哪个引脚点亮 LED,不是写死在 C 代码里的,而是写在设备树里。每块开发板的设备树文件都会定义一颗 LED 并赋予led0这个别名。例如 STM32 的某个板子在设备树里可能写的是 PD12 引脚,Nordic 的板子可能写的是 P0.13,但应用层代码完全不用改。换一个板子重新编译,自动就适配过去了。
后面两行gpio_is_ready_dt用来检查这个 GPIO 控制器的驱动是否已经初始化完成,gpio_pin_configure_dt把引脚配置为输出模式并且默认点亮状态。GPIO_OUTPUT_ACTIVE里的 ACTIVE 指的是“激活电平”,对低有效 LED 来说就是拉低,对高有效 LED 就是拉高,Zephyr 通过设备树属性自动帮你处理了这个差异,你不需要关心硬件上到底是“低电平点亮”还是“高电平点亮”。
主循环里的gpio_pin_toggle_dt翻转电平,k_msleep(1000)延时 1 秒。k_msleep是 Zephyr 内核提供的休眠 API,它会让当前线程进入挂起状态,把 CPU 让给其他就绪线程——这就是 RTOS 和裸机delay()的本质区别,后者会空转浪费 CPU,前者是真正的让权。
3.3 编译实操与产物解读
代码看明白了,现在开始编译。先做一个最简单的 sanity check,编译 hello_world:
cd ~/zephyrproject west build -b qemu_x86 -p always zephyr/samples/hello_world-b指定目标板卡,-p always表示每次构建前先清除之前的构建产物,避免增量构建带来各种奇怪问题。这条命令会先跑 CMake 配置,再调用 Ninja 进行编译。第一次编译会比较慢,因为它要生成大量构建文件,后面再编译就快了。
如果能够编译成功,然后再编译真正的 Blinky:
west build -b nucleo_f103rb -p always zephyr/samples/basic/blinky这里把目标板卡换成了一块 STM32F103 的 Nucleo 板。你看,应用代码一模一样,只改了-b参数,这就是设备树抽象带来的复用好处的直观体验。
编译完成后,进入build目录看产物:
cd build ls zephyr/重点看三个文件。zephyr.elf是最完整的带调试信息的固件,包含符号表,GDB 调试时必须用它;zephyr.hex是 Intel HEX 格式的烧录文件,很多烧录工具认这个格式;zephyr.bin是纯二进制镜像,体积最小。如果想确认编译出来的代码大小,执行:
west build -t ram_report west build -t rom_report这两个命令会生成内存占用报告,显示每个模块占了多少 RAM 和 Flash。Zephyr 做裁剪优化的时候,这两个报告几乎是必看的。我用nucleo_f103rb编译 Blinky 时,整个镜像不到 20KB,跑在一颗只有 64KB Flash、20KB RAM 的单片机上毫无压力,这也是 Zephyr 能用在资源受限设备上的底气。
3.4 切换开发板与自定义配置
前面提到 Blinky 换板子只需要改-b参数,但有朋友会问:我怎么知道有哪些板子可选?执行下面这条命令就行:
west boards | grep -i stm32f103west 会把 Zephyr 源码树里所有支持的板卡都列出来,用 grep 筛选芯片关键字,很快就能找到自己手上的板型。比如搜一下nrf52,你会看到nrf52840dk_nrf52840等一系列目标名;搜esp32,会有对应的 espresso 系列目标名。
Zephyr 的另一个强大之处是 Kconfig 运行时可配置。编译中小企业时,内核的大小、功能的开关全部由配置项控制。在构建目录生成之后,执行:
west build -t menuconfig会弹出一个图形化配置界面(基于终端),你可以在这里打开或关闭某个功能模块、调整线程栈大小、修改内核 tick 频率等。修改后会写回当前构建目录的.config文件,然后执行:
west build增量重新编译即可。这种“改配置不影响应用代码”的开发模式,体验过之后就回不去了。
4. 开发板烧录实操:三种常见硬件的完整流程
4.1 west flash 的烧录机制与 runner 概念
编译只是第一步,真正的“能跑”是把固件烧进开发板。Zephyr 的烧录命令很统一,一条west flash搞定,但它背后到底做了什么,很多新手搞不明白,遇到报错也不知道怎么排查。
Zephyr 定义了所谓的“runner”概念,west flash 会根据你所构建的板子类型,自动选择一个合适的烧录器后端,也叫 runner。比如 STM32 的板子常用stlink或pyocd,Nordic 的板子用jlink,ESP32 用esptool,Raspberry Pi Pico 用openocd或picotool。每种 runner 本质上都是一组脚本,封装了对应的烧录工具和传输协议。
想知道当前板子默认用哪个 runner,执行:
west flash --dry-run--dry-run不会真正烧录,只打印出它即将执行的命令。比如对于nucleo_f103rb,你会看到类似openocd -f board/st_nucleo_f103rb.cfg的调用;对于nrf52840dk_nrf52840,你会看到调用 J-Link 相关的命令。
烧录的数据流是:zephyr.hex(编译产物)→ runner 调用的烧录工具 → 调试器硬件(ST-Link、J-Link、CMSIS-DAP)→ 目标芯片 Flash。所以烧录失败时,除了看 west 的报错,还要确认电脑和开发板之间的调试器连接是否正常。
4.2 STM32F103 实战:从 west flash 到 LED 点亮
STM32F103 是目前入门 Zephyr 的绝对主力,原因无他,便宜且资料多。以手头的nucleo_f103rb为例,先把开发板通过 USB 连到电脑上,然后确认调试器被识别:
lsusb如果看到 STMicroelectronics 相关的设备信息,说明 ST-Link 正常。有些第三方 ST-Link 或者山寨调试器会显示为 SEGGER 或其他厂商的名字,这也没关系,只要系统能识别到就行。
接着直接烧录:
cd ~/zephyrproject west build -b nucleo_f103rb zephyr/samples/basic/blinky west flashwest flash默认会用当前构建目录里的固件,它会在 build 目录自动寻找 zephyr.hex。如果一切正常,终端会打印出烧录进度,最后提示烧录完成。此时板子上的绿色或蓝色 LED 应该开始以 1 秒为周期闪烁。
如果你手上的不是官方 Nucleo 板,而是网上十几块钱的 STM32F103C8T6 蓝色开发板(大家常说的 Blue Pill),操作思路一样,只是板卡名可能不一样。先用west boards | grep f103看看有没有对应目标;如果没有,就需要找一个引脚兼容的官方板,或自己写一个简单的板级设备树文件。Zephyr 的社区里有很多针对 Blue Pill 的移植方案,照着板型定义改设备树,属于入门级的进阶操作,这里先不展开。
遇到 OpenOCD 找不到的情况,多半是系统里没装 openocd,执行sudo apt install openocd装上再试。如果 ST-Link 连接报错,检查接线和驱动:ST-Link 和板子之间要用排线连接 SWDIO、SWCLK、GND(如果是独立调试器),Nucleo 板则注意板上跳线和供电是否正常。
4.3 没有开发板也能跑:QEMU 模拟器体验 Blinky
在没有硬件的情况下,QEMU 模拟器是验证环境是否正确的救命稻草。我在装好新环境后,都会先用 QEMU 跑一遍 hello_world,确认工具链没装错,再碰真实硬件。
QEMU 的使用方式极其简单:
cd ~/zephyrproject west build -b qemu_x86 zephyr/samples/hello_world west build -t run-t run意味着执行构建系统里的 run 目标,也就是启动 QEMU。此时终端会弹出一个模拟的串口控制台,打印出Hello World! qemu_x86这句话。看到这行输出,说明你的 Zephyr 编译环境已经完全正常了,剩下的事就是买一块开发板把它变成物理世界的闪烁。
QEMU 也支持模拟 GPIO 和某些外设,但不是所有示例都能完美跑出视觉效果。我在实际使用中,hello_world 的输出演示效果最直观,也最适合给初学者验证环境。Blinky 这种依赖真实 GPIO 点灯的示例,建议还是拿到真实板子上跑。
需要注意,执行west build -t run会阻塞终端,QEMU 退出后才能输入下一条命令。退出 QEMU 的方法是按下Ctrl+A,然后松开再按X。
4.4 烧录后的串口日志与运行验证
固件烧进去能闪灯,并不意味着万事大吉。你要学会看日志,确认系统到底跑没跑起来。Zephyr 的日志输出走 UART,需要把开发板的串口接到电脑上。多数开发板自带 USB 转串口芯片,比如 ST-Link 的虚拟串口,或者板载 CH340,插上 USB 后系统会生成/dev/ttyUSB0或/dev/ttyACM0这样的设备节点。
先确认设备节点:
ls /dev/ttyUSB* ls /dev/ttyACM*然后启动串口工具:
picocom -b 115200 /dev/ttyUSB0-b 115200是波特率。如果你用的是 hello_world 示例,串口里会反复打印Hello World!;如果跑的是 Blinky,默认情况下可能没有串口输出,因为 Blinky 只操作 GPIO 不打印日志,这很正常。想看日志版的 LED 示例,可以编译带日志输出的 sample,或者在 Blinky 里自己加printk。
用完后,退出 picocom 的方法是Ctrl+A再Ctrl+X。这里有个常见坑:如果提示/dev/ttyUSB0: Permission denied,说明当前用户不在 dialout 组,执行sudo usermod -aG dialout $USER,然后注销重新登录,权限问题就解决了。
5. 常见问题与排坑实录
5.1 依赖缺失与 Python 版本引起的报错
这一节我把这几年被问得最多的问题集中列出来,每个都是真实踩过的坑。
第一种是编译时报 “CMake Error: The following variables are used in this project, but they are set to NOTFOUND”,这种通常是系统缺库或者工具。解决办法很简单,回到第 2 节把依赖清单对着装一遍,重点检查device-tree-compiler、gperf、libssl-dev这几项。有时候 apt 装完软件需要重新打开终端,因为 PATH 没刷新。
第二种是 Python 版本不满足要求。Zephyr 3.7 之后要求 Python 3.10 以上,如果你的 Ubuntu 版本旧(比如 20.04 自带 Python 3.8),按官方文档安装新版本 Python 或者升级系统。判断版本的方法:
python3 --version第三种是刚提到的 PEP 668 问题,pip 装 west 报 externally-managed-environment。处理方式就是用虚拟环境,用 pipx,或者加--break-system-packages强制安装(不推荐系统环境这么做)。我在 24.04 上踩过一次,之后所有嵌入式工具链都统一走虚拟环境,再也不为 Python 环境头疼。
5.2 west 命令找不到与虚拟环境问题
“刚装完还能用,重启终端后 west 找不到了”是出现频率很高的问题。原因几乎都是没有激活虚拟环境。我把source ~/zephyr-venv/bin/activate写进了.bashrc,这样每次开终端都会自动激活。
如果你用的是pip install --user west的方式安装,west 会被放在~/.local/bin,而这个目录默认可能不在 PATH 里。解决办法是在.bashrc中追加:
export PATH=$HOME/.local/bin:$PATH再用source ~/.bashrc刷新。判断方法:执行which west,看它指向的是虚拟环境路径还是系统目录,就知道有没有激活正确。
另一个容易漏掉的点:west 必须在它创建的工作区(~/zephyrproject)中或者其子目录中执行。你在家目录直接执行west build,它找不到工作区,会报 “fatal: not a west workspace” 之类的错。记住,west 命令前先cd ~/zephyrproject,或者你所在的项目目录。
5.3 编译阶段典型报错:工具链、设备树、内存不足
编译报错里最吓人的通常是红色一片,但拆开看其实就那么几类。
第一类:找不到工具链。报错信息类似 "Zephyr SDK not found" 或者 "unable to find a suitable toolchain"。这多半是ZEPHYR_SDK_INSTALL_DIR环境变量没设置或者指向错误。执行echo $ZEPHYR_SDK_INSTALL_DIR确认路径,如果为空,回到第 2.3 节把~/.zephyrrc里的 export 补上,然后重新source zephyr-env.sh。
第二类:设备树语法错误。Zephyr 的设备树用 DTS 格式描述,初学者改设备树时经常漏分号、写错属性名。报错信息里会指出具体文件和行号,照着改就行。我建议改设备树之前先备份,并且在终端里用west build编译验证,不要一次改一大堆,改一处编一次,很快就能锁定问题。
第三类:Flash 或 RAM 溢出。报错类似 “region 'FLASH' overflowed by X bytes”。这是板上资源不足,可以通过裁剪功能、使用更小的编译优化选项,或者换一颗资源更大的芯片解决。先执行west build -t rom_report和ram_report看到底谁吃了内存,再决定裁哪块。
5.4 烧录阶段失败排查
烧录失败比编译失败更让人崩溃,因为经常找不到错误入口。我按照从物理层到软件层的顺序排查。
先看物理连接。用lsusb确认调试器是否被识别,用dmesg | tail看内核日志。如果 USB 设备反复断开,大概率是供电不足,换一个 USB 直连口或者接外部电源。ST-Link 和板子的接线,SWDIO、SWCLK、GND 三根线必须牢靠,顺序不要接反。
再看烧录工具。对于 stlink runner,先确认 ST-Link 驱动正常:
st-info --descr如果这条命令报错,说明 ST-Link 相关的驱动或者二进制没装对。对于pyocdrunner,用pyocd list查看检测到的目标芯片。
有时候west flash报无法识别芯片,是因为目标板处于某种保护状态(读保护、写保护)。处理思路是先执行一次全擦除再烧录,ST-Link 一般支持st-flash erase命令,或者west flash时加上--erase参数(不同 runner 参数不一样,用west flash --help查看)。
5.5 串口调试连接不上的原因
串口问题也很典型:板子明明在跑,日志就是不出来。
先确认设备节点。很多 USB 转串口芯片是 CH340(很多低价开发板和 USB-TTL 模块都用它),在 Linux 下识别为/dev/ttyUSB0。如果插上没反应,检查内核模块:
lsmod | grep ch341 dmesg | grep -i ch34有些精简版 Linux 发行版默认没带这个驱动,执行sudo apt install linux-modules-extra-$(uname -r)补装。
然后检查权限。ls -l /dev/ttyUSB0输出如果是crw-rw---- root dialout,当前用户必须属于 dialout 组,用第 4.4 节的方法加入并重登。
最后检查波特率和串口工具。Zephyr 默认调试串口波特率一般是 115200,如果你的工具设置成 9600,收到的就是乱码。另外注意,烧录过程中使用的调试串口(ST-Link 虚拟串口)和 UART 日志串口可能不是同一个节点,比如/dev/ttyACM0是 CMSIS-DAP 调试口,/dev/ttyUSB0才是日志口。我用ls /dev/tty*把所有串口都列出来,逐个试,试出来是哪个就固定用哪个。
还有一个我在实际项目里踩过的坑:板子的高速 USB 与串口复用问题。某些开发板的 USB 口默认工作在烧录模式,串口通信需要先拔出再重新插,或者通过板上的跳线把 USB 切换到串口模式。这类问题没有通用解法,只能查对应板子的用户手册。
6. 进阶扩展与个人经验分享
Blinky 跑通之后,你的 Zephyr 入门任务就算完成了,但真正的开发之路才刚刚开始。我最后分享几个在实际项目中会立刻用到的方向和一个经验。
先说说扩展路径。跑完 Blinky,下一步我建议尝试samples/hello_world之外的通信类示例,比如samples/drivers/uart、samples/subsys/usb/console,把串口、USB、I2C 这几类最常见的通信方式跑通。之后如果你做物联网产品,再深入研究 Zephyr 的蓝牙协议栈(通过samples/bluetooth/peripheral_hr这类示例上手)和网络协议栈(通过samples/net/sockets上手)。Zephyr 的官方 sample 仓库就是一个宝库,每个示例都是围绕一个子系统的完整可运行工程,遇到什么需求先去里面搜一遍,比自己从零写框架高效太多。
再谈一个我个人的体会:Zephyr 的学习曲线不在于 C 语言写程序,而在于理解它的构建系统和配置体系。Kconfig 告诉你系统怎么裁剪,设备树告诉你硬件如何被抽象,west 告诉你多仓库项目怎么管理,这三者的组合是 Zephyr 的灵魂。很多从裸机开发转过来的朋友,最开始都受不了“配置比代码多”的开发模式,但一旦你理解了这种设计是为了在不同芯片、不同板卡之间最大化复用,就会觉得前期那点学习成本太值了。
最后再分享一个我的工作习惯:每次新建一个 Zephyr 项目,我都会把以下几个信息记录下来——使用的 Zephyr 版本 tag、CMake 版本、west 版本、SDK 版本、目标板卡名、裁剪了哪些 Kconfig 配置项。不要觉得这些是小事,C 项目编译不过时可以慢慢查,但嵌入式工程很容易出现“昨天还能编译,今天换了台机器就过不了”的情况,这时一份环境版本清单能帮你节省一整天时间。把环境记录做成 README,跟着项目一起走,是性价比最高的投资。
Zephyr 是一个迭代很快的活跃项目,我写这篇文章时用的版本是 3.7.0,也许你看到这篇文章时官方已经发布新版本。如果文档或命令有变化,以官网和west --help的输出为准。工具链思路不会变,跑通一个 Blinky,你就站在了嵌入式开发新时代的门口。