ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32上搭建Zephyr RTOS开发环境:从零开始点亮板载LED

STM32上搭建Zephyr RTOS开发环境:从零开始点亮板载LED 如果你玩过 STM32大概率是从 Keil 标准外设库或者 HAL 库起步的。点个灯、跑个串口、接个传感器这套流程驾轻就熟。但当你开始接触稍微复杂一点的项目比如要同时跑多个任务、要处理网络协议栈、要对接各种外部设备驱动裸机或者简单前后台轮询的写法会越来越吃力。我自己是在做一个需要 WiFi MQTT 多传感器数据采集的小项目时被中断优先级、内存管理、外设驱动这些琐碎细节反复折磨才下决心试试 Zephyr RTOS。这篇文章就是记录我从零开始给手头的 STM32 开发板搭好 Zephyr 环境、成功点亮板载 LED 的完整过程。Zephyr 是 Linux 基金会旗下的开源实时操作系统对 STM32 的支持非常完善。它最大的魅力在于驱动模型、调度器、网络协议栈、低功耗管理这些都是现成的你不用再从零造轮子。这篇“保姆级”博文会覆盖从硬件准备、主机环境配置、工具链安装、源码获取到编译烧录验证的每一个步骤还夹带了我踩过的坑和排查思路。不管你是刚从 Keil 转过来的老手还是第一次接触 RTOS 的初学者照着走一遍基本能让你手头的开发板跑起 Zephyr。1. 为什么是 Zephyr先搞懂它给 STM32 带来了什么1.1 从裸机到 RTOSZephyr 解决了什么很多人在 STM32 上的第一款 RTOS 会选 FreeRTOS因为资料多、上手快。但 Zephyr 的定位其实不太一样它更像一个“面向物联网的迷你 Linux”。它有统一的设备驱动模型、基于设备树Device Tree的硬件描述、模块化内核、丰富的网络协议支持还有一套非常强大的构建系统west CMake。直接用裸机开发遇到的最大问题不是“能不能跑”而是“跑了之后怎么维护”。比如你要在 STM32 上挂一个 OLED 屏同时用 DMA 接收串口数据还要定时采集传感器。裸机下你可能得自己维护一堆状态机处理各种优先级关系稍微一改逻辑就容易出 bug。Zephyr 里每个功能都可以拆成一个独立的线程通过信号量、消息队列来做线程间通信系统的整体结构清晰很多。再说外设驱动。裸机开发时每换一块屏幕、每换一个传感器型号都要去翻 datasheet、自己写初始化序列和读写时序。Zephyr 的驱动框架把这一层抽象掉了大多数常见外设GPIO、UART、I2C、SPI、PWM、ADC 等都有现成驱动你只需要在设备树里声明“我用的是哪个引脚、什么速率”驱动框架会自动完成剩下的工作。我第一次在 Zephyr 里操作 I2C 传感器时真的被这种“配置驱动”而不是“手写驱动”的开发方式惊艳到了。1.2 Zephyr 在 STM32 上的优势与适用场景STM32 家族庞大从 F0 到 H7内核从 Cortex-M0 到 Cortex-M7 都有。Zephyr 对 ST 系列的支持覆盖度很高官方支持的开发板列表里能看到很多常见的 NUCLEO、Disco 系列板卡。即使你的板子不在官方列表里只要芯片型号有支持也可以自己通过设备树文件“移植”过去并不复杂。Zephyr 特别适合需要网络功能的场景。它的原生网络协议栈支持 TCP/IP、UDP、CoAP、MQTT、TLS底层可以走以太网、WiFi通过外部模块、BLE 或者 6LoWPAN。这意味着你可以在 STM32 上直接跑 MQTT 客户端而不需要像裸机方案那样自己移植 lwIP、mbedTLS再把它们和业务代码粘在一起。我在后面那个项目里就是用 Zephyr 的 MQTT 库只写了不到两百行业务代码就把数据推送到了服务器。如果你有低功耗需求Zephyr 的电源管理框架也很有优势。它支持 tickless idle 模式能在没有任务运行时让 CPU 进入低功耗状态同时通过设备电源管理 API 去控制外设的休眠和唤醒。这在电池供电的物联网设备上非常关键。1.3 用 Zephyr 搭环境的整体思路Zephyr 的环境搭建有个特点它不像 Keil 那样装一个 IDE 就完事而是依赖命令行工具链。整个过程可以概括为“装 west、拿源码、装 SDK、编译烧录”四步。听起来简单但实际操作中容易卡在各种细节上west 是 Zephyr 的元工具负责管理多仓库代码、构建和刷写Zephyr 源码仓库本身很庞大而且通过 west 会拉取很多子模块Zephyr SDK 是一套预编译好的交叉编译工具链包含了针对各种架构的 GCC、binutils、调试器不用自己手动折腾 arm-none-eabi-gcc 版本。所以我强烈建议第一次搭建环境时严格按照官方推荐的流程走不要企图自己“精简步骤”。我在踩坑过程中发现很多编译错误都源于版本不匹配比如 Zephyr 主仓库版本太新但 SDK 版本太老或者 west 版本跟 manifest 文件里声明的依赖冲突。2. 环境搭建前的准备硬件、主机与软件选型2.1 开发板选型和硬件准备理论上只要是 Zephyr 官方支持列表里的 STM32 开发板都能按这篇文章的流程跑通。我用的是常见的 NUCLEO-F411RE板载 ST-LINK/V2 调试器通过 USB 连电脑就能同时供电和烧录。这种板子小巧、带板载调试器新手用起来最省心不需要额外买 J-Link 或者 ST-Link。如果你用的是 STM32F103C8T6 那种“蓝 pill”最小系统板也没有问题。它板载的 USB 口只是 USB-to-TTL 串口不能直接用来烧录调试你需要外接一个 ST-Link V2 或者其他 SWD 调试器。准备硬件的时候我建议先确认以下几点可以避免不少麻烦确认开发板上有板载调试器或者你手头有可用的 SWD/JTAG 调试器确认开发板供电正常。有些板子通过 USB 口供电时如果 USB 线质量差或者接触不良会出现调试器不稳定、烧录失败的问题确认板载 LED 接到了哪个 GPIO。Zephyr 的 blinky 示例会默认使用设备树里定义的“led0”节点如果你的板子设备树里没有定义需要自己改设备树或者选对 board target。2.2 宿主机操作系统的选择Zephyr 官方支持 Linux、macOS 和 Windows。但我个人强烈推荐在 Linux 环境下使用尤其是 Ubuntu 20.04 或 22.04 这些 LTS 版本。原因很简单Zephyr 的构建、烧录工具链在 Linux 下最“原生”很多自动化脚本和依赖管理工具都优先保证 Linux 环境的兼容性。Windows 下虽然可以做但往往要额外处理驱动、PATH 环境变量、串口权限等一堆琐碎问题。如果你和我一样主力电脑是 Windows但又不想装双系统或者换电脑有两个比较顺手的方案一是装虚拟机用 VirtualBox 或 VMware 跑一个 Ubuntu 22.04。整个 Zephyr 开发环境都放在虚拟机里好处是干净、不影响主系统缺点是 USB 设备直通到虚拟机偶尔会有小问题尤其是 ST-Link 这类调试器需要在虚拟机的 USB 设置里单独添加设备过滤规则。二是用 WSL2Windows Subsystem for Linux。Zephyr 官方对 WSL2 的支持比较友好USB 设备可以通过 usbipd-win 项目转发到 WSL2 里实现烧录。不过这个方案需要多配置一步 USB 串口转发如果没接触过 WSL2第一次配置容易卡住。我自己现在的习惯是日常在 Ubuntu 物理机或虚拟机上跑 ZephyrWindows 只用来开文档和查资料分工明确减少折腾。2.3 必备软件安装Python、CMake、Ninja 等Zephyr 的构建系统依赖 CMake、Ninja、Python3、pip、dtc设备树编译器等工具。在 Ubuntu 下这些大多可以通过 apt 直接安装。建议在开始之前先把系统软件源更新一遍避免装到一半因为老软件源导致依赖安装失败。sudo apt update sudo apt upgrade -y sudo apt install -y cmake ninja-build gcc g python3 python3-pip \ dfu-util device-tree-compiler git wget curl这里有几个小提醒CMake 版本不能太老。Zephyr 官方对 CMake 有最低版本要求通常是 3.20 以上如果 apt 源里默认的 CMake 版本偏低建议通过 pip 安装最新版本或者从 CMake 官网下载预编译包。Python 建议用 3.8 以上的版本。Zephyr 的脚本和 west 工具对 Python 版本有依赖老版本容易出现语法兼容问题。dfu-util 是 DFU 模式烧录工具如果你用支持 DFU 的板子比如某些 STM32 板卡可以通过 bootloader 进入 DFU这个工具很有用。对于 ST-Link 调试器来说虽然不一定直接用到但属于“装了不亏”的项。装完这些基础包之后最好再验证一下版本确认它们都可用。我遇到过一种情况系统里同时装了多个 CMake 版本终端里默认调用的结果跟预期不一致导致编译时出现奇怪的“找不到 CMake”错误。3. 一步步搭好 Zephyr 开发环境3.1 安装 west 与 Python 依赖west 是 Zephyr 的元工具负责仓库管理、构建和烧录。它本身是一个 Python 包通过 pip 安装。为了避免污染系统全局 Python 环境我强烈建议用 Python 虚拟环境venv来安装 west尤其是你平时还会在系统里装其他 Python 项目的时候。创建并激活虚拟环境python3 -m venv ~/zephyr-venv source ~/zephyr-venv/bin/activate pip install --upgrade pip pip install west激活虚拟环境后你的终端提示符前面会出现一个(zephyr-venv)前缀这说明你已经在虚拟环境里了。之后每次开新终端都要先执行source ~/zephyr-venv/bin/activate才能使用 west 命令否则系统会提示找不到命令。有两点要特别注意别在这时候急着装 Zephyr SDK。Zephyr SDK 和 west 是两套东西SDK 是交叉编译工具链west 负责拉取源码和构建。我一开始把这两个概念搞混折腾了很久。如果你之前安装过旧版 west建议pip uninstall west先卸载干净再装新版避免命令路径冲突。3.2 获取 Zephyr 源码并初始化 workspaceZephyr 的源码不是单个 Git 仓库而是由多个仓库组成的 manifest 项目。west 通过一个 manifest 文件来描述这些仓库的依赖关系west init会根据这个 manifest 把项目骨架搭好。在一个你希望存放代码的目录下执行cd ~ mkdir zephyr-project cd zephyr-project west init zephyr cd zephyr west updatewest init zephyr会在zephyr-project/zephyr目录下创建一个 Zephyr 的主仓库west update则会根据 manifest 把所有必要的仓库比如 modules、hal、third_party 等拉取到本地。这一步依赖网络而且仓库数据量不小可能需要几分钟到十几分钟取决于你的网络状况。我没有给这一步单独配加速方案因为 Zephyr 仓库本身托管在 GitHub 上如果你所在网络环境访问 GitHub 比较慢可以考虑用代理镜像但我不在这里展开这个话题。跑完west update后可以检查一下zephyr-project目录下面是否多了一些bootloader、modules、tools之类的文件夹这些就是 west 帮你拉下来的依赖仓库。3.3 安装 Zephyr SDK 交叉编译工具链Zephyr SDK 是预编译好的工具链集合官方发布了针对 x86、ARM、RISC-V 等架构的版本。对 STM32 来说我们主要用到的是 ARM 相关工具链。SDK 安装包很大建议直接去 Zephyr 官网或者 GitHub Releases 页面下载.run文件。我用的版本是 0.16.x与 Zephyr 3.x 系列主版本搭配没有问题。下载后执行chmod x zephyr-sdk-0.16.1_linux-x86_64.run ./zephyr-sdk-0.16.1_linux-x86_64.run -- -d ~/zephyr-sdk安装完成后SDK 会被解压到~/zephyr-sdk目录。你需要设置环境变量ZEPHYR_TOOLCHAIN_VARIANT和ZEPHYR_SDK_INSTALL_DIR或者简单地把 SDK 目录下的environment-setup脚本 source 进当前终端。我比较推荐后者source ~/zephyr-sdk/environment-setup把这个source命令加进~/.bashrc以后每次打开新终端就自动生效不用手动执行。这一点挺重要的因为 Zephyr 的构建系统在找不到 SDK 环境变量时会直接报错退出而且报错信息还比较隐晦。一个容易踩的坑是Zephyr SDK 自带了一个特定版本的 CMake 和 Python它可能会影响你系统中原本的默认版本。我建议不要在 PATH 里把 SDK 的 bin 目录放到最前面否则可能出现“新版系统 CMake 被 SDK 里的旧版覆盖”这种奇怪问题。3.4 编译第一个示例blinky环境搭好之后最激动的时刻来了编译第一个示例。Zephyr 官方示例里最“点灯”的就是zephyr/samples/basic/blinky。进入zephyr-project/zephyr目录执行cd ~/zephyr-project/zephyr west build -b nucleo_f411re samples/basic/blinky-b参数指定板卡 target也就是开发板型号。如果你的板子不在官方支持列表里可能会出现“找不到 board”的报错。这时候可以先执行west boards | grep 关键字查一下有没有相近型号或者去boards/arm目录下看看有哪些板卡定义。编译过程首次会比较慢因为要生成一堆构建文件、编译内核和外设驱动。这时候不要急着中断喝口水等一等。构建完成后终端末尾会出现类似Firmware files的输出告诉你生成的固件文件在哪。我当时的输出里提示固件位置在build/zephyr/zephyr.bin。这一步算是环境搭建成功与否的“体检”。如果编译过程没有报错说明 west、SDK、源码拉取都没有问题接下来就差烧录验证了。4. 烧录与验证让 LED 真正闪起来4.1 准备烧录工具ST-Link / OpenOCD / STM32CubeProgrammer在 Windows 上玩 STM32 的同学对 ST-Link 驱动和 STM32CubeProgrammer 应该很熟。Zephyr 里烧录则通常走 west flash它会自动调用底层烧录工具。当你指定-b nucleo_f411re这类板卡时Zephyr 构建系统会根据设备树的 flash 配置自动选择用 OpenOCD 或者 STM32CubeProgrammer 来烧录。Zephyr 对 OpenOCD 的支持比较老牌但 OpenOCD 对 ST-Link 的适配版本敏感。我建议在 Ubuntu 下先用 apt 安装 OpenOCD同时保证版本不要太旧sudo apt install openocd如果你更习惯 ST 官方工具可以去 ST 官网下载 STM32CubeProgrammer然后在 Linux 下配置好路径。Zephyr 的 west flash 会优先在系统 PATH 里查找STM32_Programmer_CLI所以安装后记得把它的路径加进环境变量。4.2 用 west flash 烧录固件把你手里的开发板用 USB 线连接到电脑。如果是 NUCLEO 板通常是板子上那个 Mini-USB 或 Micro-USB 口连接后电脑上应该会出现一个新的 USB 设备。在 Linux 下可以先执行lsusb看看是否识别到 ST-Link 设备。如果啥都没看到大概率是 USB 线质量不行只能供电不能传数据或者驱动没装好。确认硬件识别后直接执行west flashwest 会自动探测当前构建目录也就是之前 west build 生成的 build 目录找到对应的烧录配置。终端里会打印出调用的烧录命令和进度。烧录完成后如果一切顺利你会看到板载 LED 开始以默认频率闪烁。我当时遇到过一次烧录失败报错的是Error: open failed In procedure: transport select其实就是 OpenOCD 没有找到 ST-Link 调试器。检查之后发现是虚拟机里的 USB 设备没有挂载到 Ubuntu。如果你也遇到类似报错先别怀疑代码先看看 USB 设备是不是真的在虚拟机里可见。4.3 验证串口输出与系统运行blinky 示例本身没有串口输出但如果你的板子连接了 USB 转串口或者板载了 ST-Link 的虚拟串口你可以在烧录一个 hello_world 示例来看串口输出west build -b nucleo_f411re samples/hello_world west flash然后用串口调试工具比如 minicom、putty 或 screen打开对应的串口设备。Linux 下可以用screen /dev/ttyACM0 115200如果权限不足把当前用户加入 dialout 组sudo usermod -aG dialout $USER退出重新登录后按下开发板复位键串口终端里应该会打印出Hello World!以及uart:~$之类的 shell 提示符。Zephyr 默认还带了一个小 shell可以用来查看内核线程、设备列表等等还挺酷的。验证串口这个步骤在一次我接手一台没有显示器的 Linux 主机时成了排查环境问题的救命稻草——至少在头灯亮之前可以先确认系统有没有在跑。5. 常见问题排查与避坑记录5.1 STM32 开发板无法识别 USB 设备这个现象很常见插上开发板电脑没反应lsusb里也看不到任何新设备。优先排查 USB 线是不是“只有充电没有数据”的类型。嵌入式开发建议至少要备两三根支持数据传输的 USB 线。其次在虚拟机和 WSL2 环境下要检查 USB 设备是否已经通到宿主机系统。VirtualBox 需要在虚拟机的设置里添加 USB 设备过滤器WSL2 则需要用 usbipd 之类的工具绑定设备。这些细节很琐碎但不搞定它们后面的烧录和设备访问都无从谈起。还有一点开发板的 USB 口如果接了板载 ST-Link插上后系统应该能识别到一个 USB 转串口设备和 ST-Link 调试器两个设备。如果只有一个出现检查一下是不是板子上的跳线帽动了某些板卡的调试器和串口是可以通过跳线独立控制的。5.2 编译报错找不到 board、toolchain 相关问题排查编译时最常见的报错是error: invalid choice: nucleo_f411re说明在当前的 boards 目录里没有找到对应的板卡定义。可能原因一是板卡型号写错了去boards/arm目录下查实际的文件夹名字二是 Zephyr 源码拉取不完全执行west update后再试一次。另一种常见错误是Sdk not installed或Zephyr SDK can not be found。这是环境变量没设对。检查一下ZEPHYR_TOOLCHAIN_VARIANT和ZEPHYR_SDK_INSTALL_DIR是否正常或者有没有执行过 SDK 的environment-setup。一个非常容易忽略的点是你把 SDK 解压到了某个目录但这个路径下有空格或中文CMake 可能会解析失败。有时候编译报错的“真相”并不在最后几行。Zephyr 构建系统是 CMake 驱动的错误信息可能很长建议把整段日志滚动上去看寻找第一个Error开头的行而不是只看结尾的 summary。5.3 west update 网络超时与版本锁定问题west update拉取仓库时因为网络原因经常中断可以在命令前设置 git 的 http 和 https 代理环境变量来加速下载但这里不展开也可以通过多拉取几次的方式解决。还有一种更省事的做法直接用west update -o--depth1这样每个仓库都只拉取最近一次提交能显著减少下载量。不过这会取消本地仓库的完整历史如果之后要切换分支或者查历史就会比较麻烦。版本锁定的问题也很典型。Zephyr 主仓库会随着时间不断更新某些第三方仓库可能跟不上主仓库的变更导致west update之后出现编译错误。我自己在上半年就遇到过一次主仓库更新后某个传感器驱动模块的 API 变了但 west 拉下来的第三方模块没有同步。这种情况最稳妥的解决方式是用固定的 manifest 版本。你可以通过west init --mr指定某个 release 分支比如west init zephyr --mr v3.7.0这样你会得到一个相对稳定的源码树适合“先跑通环境再追新版本”的同学。5.4 烧录失败或连接不上调试器烧录失败一般分两类一类是 OpenOCD 报错另一类是 STM32CubeProgrammer 报错。OpenOCD 报错里比较常见的是无法连接调试器、SWD 通信超时。这时先检查调试器是否被其他程序占用比如你同时开着 STM32CubeIDE 的调试窗口它会占用 ST-Link导致 west flash 连不上。另一个容易踩的坑是开发板没有进入正确的 BOOT 模式。有些 STM32 板卡默认的 BOOT0 引脚电平会决定芯片是从 Flash 启动还是从系统 Bootloader 启动。如果你从 I2C 或者 UART 烧录时发现芯片总是烧完不执行检查一下 BOOT0 跳线。如果换了电脑或者第一次连接开发板Linux 下 ST-Link 需要 udev 规则。Zephyr SDK 自带的 OpenOCD 在安装时会顺带安装 udev 规则但如果你用的是系统 apt 装的 OpenOCD可能需要手动把 Zephyr SDK 下的openocd/contrib/60-openocd.rules复制到/etc/udev/rules.d/然后重新加载 udev。5.5 一些实用小技巧调试器和开发板之间的选择刚开始接触 Zephyr 的时候建议尽量选一块“官方支持度好、社区资料多”的板子比如 NUCLEO 系列。这些板子的设备树、板级定义文件、文档都非常完善照着跑不容易出幺蛾子。后期如果想把 Zephyr 移植到自己的自制 PCB 上可以直接从官方的板卡目录里拷贝一份配置文件修改设备树里的 flash 大小、引脚定义、时钟配置工作量会小很多。调试器的选择上ST-Link 对 STM32 肯定是首选。J-Link 性能更强、功能更多但对 Zephyr 的 OpenOCD 适配并不一定比 ST-Link 更省心。在 Zephyr 环境里ST-Link 和 OpenOCD 的组合是我实测最稳的。另外我建议准备一个 USB 转 TTL 模块哪怕板载了虚拟串口。在很多排查场景下——比如系统启动崩溃、看门狗复位、串口打印乱码——外接一个逻辑分析仪调试器或者独立串口能帮你定位到驱动开始初始化之前的问题。6. 跑通之后Zephyr 还能怎么玩6.1 设备树在 STM32 上的作用很多从 Keil 转过来的同学第一次看到 Zephyr 里的设备树文件.dts/.dtsi会觉得头大。实际上设备树的作用就是“用文本描述硬件”。你的 STM32 芯片有哪些 UART、哪些 SPI、哪些 GPIO每个外设挂在哪个地址、映射到哪个引脚都写在设备树文件里。对于 STM32 而言设备树文件通常分为三级SoC 级别的.dtsi描述芯片内部外设板卡级别的.dts描述具体板子上的引脚连接、外部设备以及 overlays用户自定义扩展。你在编译 Zephyr 时如果要用到某个外设只需要在 overlay 文件里把对应的节点状态从disabled改成okay并设置好引脚驱动框架会自动帮你初始化。这个设计对于产品做板级适配非常高效。我在一个项目中需要把按键从 PA0 换到 PB1改一行设备树重新编译就行完全不用动驱动代码。6.2 从 blinky 走向实际项目多线程、外设和网络跑通 blinky只能算环境没问题。真正用 Zephyr 做项目还需要掌握几个核心概念线程Thread和调度Zephyr 的调度器支持优先级抢占、时间片轮转。内核对象信号量、互斥量、消息队列、事件标志它们解决了线程间同步和通信。设备驱动模型通过device_get_binding()获取设备实例再用统一的 API 操作外设。实时性保障中断处理、线程优先级、内核 tick 的配置会影响系统的实时表现这部分需要慢慢调。我自己的体会是把 blinky 换成“UART 接收命令行 按键控制 LED 定时上报传感器数据”这样一个小项目你就基本掌握了 Zephyr 的核心用法。再往后如果想接入 WiFi 模块或者以太网Zephyr 有专门的网络子系统配合 DHCP、TCP/UDP、MQTT可以非常快地搭出一个物联网设备的雏形。6.3 少走弯路的两个学习建议最后分享两条切身体会的学习建议。第一遇到问题优先看 Zephyr 官方文档。虽然它有些页面写得不够“亲民”但对于环境搭建、设备树、驱动模型这些核心主题官方文档还是最准确、最及时的。网上博客和社区讨论可以作为补充但别盲目跟着抄尤其要注意文档版本和你的代码版本是否匹配。第二多花点时间读示例代码。Zephyr 的samples/目录里都是宝藏每个示例都对应一个具体的功能点比如 GPIO、UART、I2C、蓝牙、网络。你可以在这些示例的基础上改改看把几个示例拼到一起比单纯看 API 手册高效得多。我实际用下来Zephyr 学习曲线比裸机陡不少但只要把环境跑通、把第一个 LED 点起来接下来就顺畅许多了。对我个人来说Zephyr 最大的价值是让我在写 STM32 项目时能把更多精力放在业务逻辑上而不是反复折腾底层驱动细节。希望这篇文章能帮你少踩一些我踩过的坑顺利把你手上的 STM32 开发板也带进 Zephyr 的世界。
返回列表