Zephyr RTOS 学习系统:第一阶段 ------ 入门篇
版本:v1.0 | 目标读者:嵌入式零基础至初级开发者
预计学习周期:2~3 周(每天 1~2 小时)
完成本阶段后,你将能够独立搭建开发环境、理解 Zephyr 核心概念,并运行第一个多线程应用程序。
目录

1. 阶段目标
完成第一阶段后,你将具备以下 5 项核心能力:
| 序号 | 能力 | 具体描述 |
|---|---|---|
| 1 | 环境搭建能力 | 能在 Linux / Windows / macOS 上独立搭建完整的 Zephyr 开发环境,包括工具链、SDK、West 等 |
| 2 | 工程管理能力 | 熟练使用 West 工具进行代码下载、更新、编译、烧录和调试 |
| 3 | 概念理解能力 | 透彻理解 Zephyr 内核调度、设备树(Devicetree)和 Kconfig 三大核心机制 |
| 4 | 基础编程能力 | 能编写包含多线程、GPIO 控制、定时器的简单应用程序 |
| 5 | 问题排查能力 | 能独立解决环境搭建和入门阶段 80% 的常见问题 |
2. 学习内容总览
| 主题 | 核心内容 | 预计时间 | 难度 |
|---|---|---|---|
| 环境搭建 | 安装依赖、下载源码、配置工具链 | 3~5 小时 | ⭐⭐ |
| West 工具 | West 命令体系、工作区管理、项目结构 | 2~3 小时 | ⭐⭐ |
| 核心概念 | 内核调度、设备树、Kconfig 配置系统 | 5~7 小时 | ⭐⭐⭐ |
| 第一个程序 | Hello World、LED 闪烁、多线程入门 | 4~6 小时 | ⭐⭐ |
| 调试与排错 | 日志输出、GDB 调试、常见问题排查 | 2~3 小时 | ⭐⭐⭐ |
3. 开发环境搭建
3.1 为什么需要了解架构
Zephyr 的开发环境涉及多个组件协同工作:主机操作系统 → West 元工具 → Zephyr SDK → 目标硬件。理解这一架构,能帮助你快速定位"是哪一环出了问题"。

图片描述:该图展示了 Zephyr 开发环境的完整架构。左侧为主机系统(Linux / Windows / macOS),中间为 West 工作区和 Zephyr SDK(包含编译器、调试器、QEMU 模拟器等),右侧为目标硬件(开发板)。三者通过 West 命令进行协调,SDK 负责将源代码编译为固件并烧录到硬件上。
3.2 Linux 环境搭建(推荐)
生活化类比:Linux 是 Zephyr 的"原生家园",就像鱼在水中游泳一样自然。如果你有 Linux 环境,搭建过程最顺畅。
步骤一:安装系统依赖
bash
# Ubuntu/Debian
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
# Fedora
sudo dnf install git cmake ninja-build gperf ccache dfu-util \
dtc wget python3-devel python3-pip python3-setuptools python3-tkinter \
xz file make gcc SDL2-devel libmagic
步骤二:安装 Zephyr SDK
bash
# 下载 SDK(以 0.16.x 为例,请根据官方最新版本调整)
cd ~
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5/zephyr-sdk-0.16.5_linux-x86_64.tar.xz
# 解压到 /opt 目录
sudo tar xvf zephyr-sdk-0.16.5_linux-x86_64.tar.xz -C /opt
# 运行设置脚本
sudo /opt/zephyr-sdk-0.16.5/setup.sh
# 将当前用户加入 dialout 组(用于串口访问)
sudo usermod -a -G dialout $USER
步骤三:安装 West 并获取源码
bash
# 安装 West(Zephyr 的元工具)
pip3 install --user -U west
# 创建并进入工作目录
mkdir ~/zephyrproject && cd ~/zephyrproject
# 初始化并获取 Zephyr 源码(包括所有模块)
west init -m https://github.com/zephyrproject-rtos/zephyr.git --mr v3.7.0
west update
# 导出 Zephyr CMake 包
west zephyr-export
# 安装 Python 依赖
pip3 install --user -r ~/zephyrproject/zephyr/scripts/requirements.txt
步骤四:验证安装
bash
cd ~/zephyrproject/zephyr
west build -p auto -b qemu_x86 samples/hello_world
west build -t run
如果看到 "Hello World! qemu_x86" 的输出,恭喜你,环境搭建成功!
3.3 Windows 环境搭建
生活化类比:Windows 上开发嵌入式就像在城市里开车------路都是通的,但需要遵守更多的"交通规则"(环境变量、路径格式等)。
步骤一:安装必要软件
- Git for Windows:从 https://git-scm.com/download/win 下载安装
- Python 3.10+ :从 https://www.python.org/downloads/ 下载,安装时勾选 "Add Python to PATH"
- Visual Studio Build Tools:安装 C++ 构建工具(包含 CMake、Ninja)
步骤二:安装 West 和 SDK
powershell
# 以管理员身份打开 PowerShell
pip install --user -U west
# 创建工作目录
mkdir C:\zephyrproject
# 初始化(建议使用 cmd 或 Git Bash 执行 west init)
cd C:\zephyrproject
west init -m https://github.com/zephyrproject-rtos/zephyr.git --mr v3.7.0
west update
# 安装 Python 依赖
pip install --user -r C:\zephyrproject\zephyr\scripts\requirements.txt
# 下载 Windows 版 SDK 并解压到 C:\opt
# 然后运行 C:\opt\zephyr-sdk-0.16.5\setup.cmd
步骤三:配置环境变量
将以下路径添加到系统 PATH:
C:\Users\<你的用户名>\AppData\Roaming\Python\Python3x\Scripts- Zephyr SDK 的
bin目录
3.4 macOS 环境搭建
bash
# 安装 Homebrew(如未安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 安装依赖
brew install cmake ninja gperf ccache dfu-util qemu dtc wget python3
# 后续步骤与 Linux 相同
pip3 install --user -U west
# ... west init, west update 等
3.5 环境搭建速查卡
| 检查项 | Linux | Windows | macOS |
|---|---|---|---|
| Python 3.10+ | python3 --version |
python --version |
python3 --version |
| CMake 3.20+ | cmake --version |
cmake --version |
cmake --version |
| West 安装 | west --version |
west --version |
west --version |
| SDK 路径 | /opt/zephyr-sdk-* |
C:\opt\zephyr-sdk-* |
~/zephyr-sdk-* |
| 编译测试 | west build -b qemu_x86 |
west build -b qemu_x86 |
west build -b qemu_x86 |
4. West 工具链详解
4.1 West 是什么
生活化类比:West 就像是一位"项目管家"。想象你要装修一套房子:你需要协调设计师、水电工、木工、瓦工等多个工种。West 就是那位管家------它自己不贴瓷砖,但它知道什么时候该叫谁来干活。
West 是 Zephyr 的元工具(meta-tool),负责:
- 管理多仓库代码(Zephyr 主仓库 + HAL 库 + 中间件模块)
- 构建系统调用(自动传递正确的参数给 CMake 和 Ninja)
- 烧录与调试(集成 J-Link、OpenOCD、pyOCD 等)
- 项目脚手架(创建新应用、 boards 等)
4.2 West 工作区目录结构

图片描述 :该图展示了 West 工作区的标准目录结构。顶层为 zephyrproject/ 文件夹,其中包含 .west/config(West 配置文件)、zephyr/(主仓库,包含内核、驱动、示例)、modules/(外部模块,如 HAL、文件系统、网络协议栈等)。每个模块都是独立的 Git 仓库,由 West 统一管理版本。
zephyrproject/
├── .west/
│ └── config # West 配置文件(指定 manifest 位置)
├── zephyr/ # Zephyr 主仓库
│ ├── arch/ # 架构相关代码(ARM、x86、RISC-V 等)
│ ├── boards/ # 开发板定义
│ ├── drivers/ # 设备驱动
│ ├── dts/ # 共享设备树文件
│ ├── include/ # 公共头文件
│ ├── kernel/ # 内核核心代码
│ ├── samples/ # 示例程序
│ ├── scripts/ # 构建和辅助脚本
│ ├── subsys/ # 子系统(网络、蓝牙、存储等)
│ └── tests/ # 测试用例
├── modules/ # 外部模块(由 west.yml 定义)
│ ├── hal/ # 硬件抽象层(厂商 SDK)
│ ├── fs/ # 文件系统
│ ├── lib/ # 第三方库
│ └── net/ # 网络协议栈
└── bootloader/ # 引导加载程序(如 MCUboot)
4.3 West 核心命令速查
# ========== 代码管理 ==========
west init [-m <URL>] [--mr <版本>] # 初始化工作区
west update # 更新所有模块到 manifest 指定版本
west list # 列出所有模块及其状态
west manifest --resolve # 查看解析后的完整 manifest
# ========== 编译构建 ==========
west build [-p auto] -b <board> <source> # 编译项目
-p auto # 如有需要自动清除(pristine)
-b <board> # 指定目标开发板
-d <dir> # 指定构建目录(默认 build/)
west build -t menuconfig # 打开图形化配置界面
west build -t guiconfig # 打开 GUI 配置界面
west build -t clean # 清理构建目录
# ========== 烧录与调试 ==========
west flash # 将固件烧录到开发板
west flash --runner jlink # 使用 J-Link 烧录
west flash --runner openocd # 使用 OpenOCD 烧录
west debug # 启动 GDB 调试会话
west debugserver # 仅启动调试服务器
# ========== 辅助命令 ==========
west boards # 列出所有支持的开发板
west boards -n <name> # 搜索特定开发板
west help <command> # 查看命令帮助
4.4 West 配置详解
West 的配置文件位于 .west/config,典型内容如下:
ini
[manifest]
path = zephyr
file = west.yml # manifest 文件位置
[zephyr]
base = zephyr # Zephyr 主仓库相对路径
west.yml 定义了所有模块及其版本:
yaml
manifest:
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v3.7.0
path: zephyr
import: true # 导入 zephyr/west.yml 中的子模块
5. 核心概念理解
5.1 内核调度 ------ 乐团指挥模型
生活化类比:想象一个交响乐团。乐队有多个乐手(线程),但只有一个指挥(调度器)。指挥决定什么时候让哪个乐手演奏,确保整场演出和谐有序。在 Zephyr 中,内核调度器就是这个"指挥"。

图片描述:该图以交响乐团为类比展示 Zephyr 内核调度机制。顶部为"指挥"(调度器),负责根据优先级和状态决定哪个"乐手"(线程)获得 CPU 时间。图中展示了多个线程的状态转换:就绪态(等待演奏)、运行态(正在演奏)、挂起态(暂停休息)、阻塞态(等待某个信号)。箭头表示线程在不同状态之间的转换路径,体现了抢占式调度的核心理念。
Zephyr 线程的状态机:
| 状态 | 说明 | 生活类比 |
|---|---|---|
| 就绪(Ready) | 等待 CPU 时间 | 乐手拿着乐器,等待指挥点名 |
| 运行(Running) | 正在执行 | 乐手正在演奏 |
| 挂起(Suspended) | 被显式挂起 | 乐手被指挥示意暂停 |
| 阻塞(Blocked) | 等待某个事件/资源 | 乐手在等大提琴手准备好 |
| 终止(Terminated) | 线程结束 | 乐手演奏完毕,鞠躬退场 |
优先级系统:
Zephyr 线程优先级(数值越小,优先级越高)
├─ 协作式线程:-CONFIG_NUM_COOP_PRIORITIES ~ -1
│ └── 不会被抢占,直到主动放弃 CPU
├─ 抢占式线程:0 ~ CONFIG_NUM_PREEMPT_PRIORITIES - 1
│ └── 高优先级线程可立即抢占低优先级线程
└─ 空闲线程:最低优先级,仅在系统空闲时运行
关键概念:
- 时间片轮转(Time Slicing):同优先级线程轮流执行,防止"饿死"
- 协作式调度(Cooperative) :线程主动让出 CPU(
k_yield()),适合需要原子操作的场景 - 抢占式调度(Preemptive):高优先级线程立即打断低优先级线程,保证实时性
5.2 设备树(Devicetree)------ 硬件的"户口本"
生活化类比:设备树就像是每个硬件的"户口本"。户口本上记录了每个人的姓名、出生日期、住址、家庭成员关系。设备树则记录了板上有哪些设备、每个设备是什么类型、连接在哪个总线上、使用哪些资源(GPIO、中断、寄存器地址)。
为什么需要设备树?
在传统的嵌入式开发中,硬件信息直接硬编码在 C 代码里:
c
// 传统方式:硬编码
#define LED_PORT GPIOA
#define LED_PIN 13
#define LED_FLAGS GPIO_ACTIVE_LOW
这种方式的问题是:换一个开发板,就要改一堆代码。设备树将硬件描述 与驱动代码解耦:
dts
// 设备树方式:声明式描述
/ {
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpioa 13 GPIO_ACTIVE_LOW>;
label = "User LED";
};
};
};
设备树的三个层次:
| 层次 | 文件 | 作用 |
|---|---|---|
| SoC 级 | dts/arm/<vendor>/<chip>.dtsi |
描述芯片内置外设(UART、SPI、GPIO 等) |
| 板级 | boards/<arch>/<vendor>/<board>/<board>.dts |
描述开发板上的具体硬件连接 |
| 应用级 | <app>/boards/<board>.overlay |
应用特定的设备树覆盖(修改或新增节点) |
设备树覆盖(Overlay)示例:
dts
// app.overlay ------ 为应用添加自定义节点
/ {
my_sensor: my-sensor@0 {
compatible = "mycompany,sensor-v1";
reg = <0x0>;
status = "okay";
};
};
5.3 Kconfig ------ 系统的"配置菜单"
生活化类比:Kconfig 就像汽车选购时的"配置单"。你想要倒车影像吗?要天窗吗?真皮座椅还是织物座椅?每个选项都会影响最终车辆的配置和价格。Kconfig 让你通过勾选配置项,决定最终编译进内核的功能。
Kconfig 的层级结构:
Kconfig.zephyr(根配置)
├── Kconfig.kernel(内核选项)
│ ├── 调度器配置
│ ├── 线程栈大小
│ └── 定时器配置
├── drivers/Kconfig(驱动选项)
│ ├── GPIO 驱动
│ ├── UART 驱动
│ └── I2C/SPI 驱动
├── subsys/Kconfig(子系统选项)
│ ├── 网络栈
│ ├── 蓝牙栈
│ └── 文件系统
└── 应用级 prj.conf(应用专属配置)
常用配置项速查:
ini
# prj.conf ------ 应用配置文件
# ========== 内核配置 ==========
CONFIG_MAIN_STACK_SIZE=2048 # 主线程栈大小
CONFIG_HEAP_MEM_POOL_SIZE=4096 # 堆内存池大小
CONFIG_MULTITHREADING=y # 启用多线程
# ========== 驱动配置 ==========
CONFIG_GPIO=y # 启用 GPIO 驱动
CONFIG_SERIAL=y # 启用串口驱动
CONFIG_I2C=y # 启用 I2C 驱动
CONFIG_SPI=y # 启用 SPI 驱动
# ========== 调试配置 ==========
CONFIG_PRINTK=y # 启用 printk 输出
CONFIG_LOG=y # 启用日志子系统
CONFIG_ASSERT=y # 启用断言检查
CONFIG_DEBUG=y # 启用调试信息
# ========== 子系统配置 ==========
CONFIG_BT=y # 启用蓝牙
CONFIG_NETWORKING=y # 启用网络
CONFIG_FILE_SYSTEM=y # 启用文件系统
配置优先级(从高到低):
- 命令行传入:
-DCONFIG_XXX=y - 应用
prj.conf - Board 默认配置
boards/<board>_<defconfig> - 架构默认配置
arch/<arch>/Kconfig - Zephyr 根
Kconfig.zephyr
6. 第一个完整程序
6.1 Hello World ------ 嵌入式世界的"你好"
项目结构:
hello_world/
├── CMakeLists.txt
├── prj.conf
└── src/
└── main.c
CMakeLists.txt:
cmake
# 设置最小 CMake 版本
cmake_minimum_required(VERSION 3.20.0)
# 引入 Zephyr 构建系统
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
# 定义项目
project(hello_world)
# 添加源文件
target_sources(app PRIVATE src/main.c)
prj.conf:
ini
# 启用 printk 输出
CONFIG_PRINTK=y
# 启用标准输出到串口
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
src/main.c:
c
/*
* Hello World 示例
* 功能:在串口终端输出 "Hello World! <board_name>"
*/
#include <zephyr/kernel.h> /* 内核 API */
#include <zephyr/sys/printk.h> /* printk 输出 */
void main(void)
{
/* printk 是 Zephyr 的轻量级打印函数,类似标准 C 的 printf */
printk("Hello World! %s\n", CONFIG_BOARD);
/* 在 Zephyr 中,main() 函数返回后线程会退出 */
/* 这里我们让主线程休眠,保持系统运行 */
while (1) {
k_msleep(1000); /* 休眠 1000 毫秒 */
}
}
编译与运行:
bash
# 进入项目目录
cd ~/zephyrproject/hello_world
# 编译(自动指定 board)
west build -b qemu_x86
# 运行(使用 QEMU 模拟器)
west build -t run
# 输出:
# *** Booting Zephyr OS build v3.7.0 ***
# Hello World! qemu_x86
6.2 LED 闪烁 ------ 嵌入式"点灯仪式"
每个嵌入式开发者的必经之路:点亮一个 LED。让我们用一个完整的、带详细注释的程序来完成这个仪式。

图片描述:该图展示了开发板上 LED 的典型连接方式。图中包含一个 GPIO 引脚(通常标记为 LED0 或 P0.13),通过一个限流电阻(通常为 330Ω 或 1kΩ)连接到 LED 的正极(阳极),LED 的负极(阴极)连接到地(GND)。当 GPIO 输出高电平(或低电平,取决于 active-high 或 active-low 配置)时,LED 点亮。
项目结构:
blink_led/
├── CMakeLists.txt
├── prj.conf
└── src/
└── main.c
CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blink_led)
target_sources(app PRIVATE src/main.c)
prj.conf:
ini
# 启用 GPIO 驱动
CONFIG_GPIO=y
# 启用 printk 输出(用于调试)
CONFIG_PRINTK=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
src/main.c:
c
/*
* LED 闪烁示例
* 功能:控制开发板上的用户 LED 以 1Hz 频率闪烁
*
* 支持的开发板:nRF52840 DK、STM32 Nucleo、ESP32 等带有用户 LED 的板子
* 编译命令:west build -b <your_board>
*/
#include <zephyr/kernel.h> /* 内核 API:k_msleep 等 */
#include <zephyr/drivers/gpio.h> /* GPIO API */
#include <zephyr/sys/printk.h> /* 调试输出 */
/*
* 通过设备树别名获取 LED 设备节点。
* 大多数 Zephyr 支持的开发板都会定义 "led0" 别名指向用户 LED。
* 如果板子没有定义,需要检查 boards/<board>.dts 文件。
*/
#define LED0_NODE DT_ALIAS(led0)
/*
* 编译时检查:确保设备树中存在 led0 节点。
* 如果不存在,编译会在这里报错,给出清晰的提示。
*/
#if !DT_NODE_HAS_STATUS(LED0_NODE, okay)
#error "Unsupported board: led0 devicetree alias is not defined"
#endif
/*
* 定义 GPIO 规格(gpio_dt_spec)。
* 这是一个结构体,包含了操作 LED 所需的所有信息:
* - 设备指针(指向 GPIO 控制器)
* - 引脚号
* - 标志(active-high / active-low 等)
*/
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
void main(void)
{
int ret;
printk("=== LED Blink Demo ===\n");
printk("Board: %s\n", CONFIG_BOARD);
/*
* 步骤 1:检查 GPIO 设备是否就绪。
* 在 Zephyr 中,访问任何设备前都应该检查其是否已初始化完成。
* 这就像开车前检查钥匙是否插好。
*/
if (!gpio_is_ready_dt(&led)) {
printk("Error: LED device %s is not ready\n", led.port->name);
return; /* 设备未就绪,退出 */
}
/*
* 步骤 2:配置 GPIO 引脚为输出模式。
* GPIO_OUTPUT_INACTIVE 表示初始状态为"非激活"状态。
* 如果 LED 是 active-low(低电平点亮),则初始为灭;
* 如果 LED 是 active-high(高电平点亮),则初始为灭。
*/
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
printk("Error: failed to configure LED pin (err %d)\n", ret);
return;
}
printk("LED configured successfully. Starting blink loop...\n");
/*
* 步骤 3:进入主循环,让 LED 闪烁。
* 每次循环:翻转 LED 状态 → 等待 500ms → 重复
* 这样 LED 会以 1Hz(每秒一次)的频率闪烁。
*/
while (1) {
/*
* 翻转(toggle)LED 状态:
* 如果当前是灭的,就点亮;
* 如果当前是亮的,就熄灭。
*/
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
printk("Error: failed to toggle LED (err %d)\n", ret);
}
/*
* 让出 CPU 500 毫秒。
* 注意:这不是"忙等待",线程会进入睡眠状态,
* 调度器可以将 CPU 分配给其他就绪线程。
*/
k_msleep(500);
}
}
编译与烧录:
bash
# 以 nRF52840 DK 为例
west build -b nrf52840dk/nrf52840
west flash
# 或者使用 QEMU 模拟(没有真实 LED,但可验证编译)
west build -b qemu_cortex_m3
6.3 多线程 Hello ------ 两个线程的对话
c
/*
* 多线程示例:两个线程交替打印消息
* 展示了线程创建、优先级、休眠和协作的基本概念
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* 定义线程栈大小 */
#define STACK_SIZE 1024
/* 定义线程优先级(数值越小优先级越高) */
#define THREAD_A_PRIORITY 5
#define THREAD_B_PRIORITY 6
/* 定义线程休眠时间 */
#define SLEEP_TIME_MS 1000
/* 线程栈内存(K_THREAD_STACK_DEFINE 宏会自动分配和对齐) */
K_THREAD_STACK_DEFINE(thread_a_stack, STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_b_stack, STACK_SIZE);
/* 线程控制块 */
static struct k_thread thread_a_data;
static struct k_thread thread_b_data;
/*
* 线程 A 的入口函数
* 这个线程每秒钟打印一次消息
*/
void thread_a_entry(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
printk("[Thread A] Started!\n");
while (1) {
printk("[Thread A] Hello from high priority thread!\n");
k_msleep(SLEEP_TIME_MS);
}
}
/*
* 线程 B 的入口函数
* 这个线程也每秒钟打印一次消息,但优先级略低
*/
void thread_b_entry(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
printk("[Thread B] Started!\n");
while (1) {
printk("[Thread B] Hello from low priority thread!\n");
k_msleep(SLEEP_TIME_MS);
}
}
void main(void)
{
printk("=== Multi-threading Demo ===\n");
/*
* 创建线程 A(较高优先级)
* 参数说明:
* &thread_a_data ------ 线程控制块
* thread_a_stack ------ 线程栈
* K_THREAD_STACK_SIZEOF(thread_a_stack) ------ 栈大小
* thread_a_entry ------ 入口函数
* NULL, NULL, NULL ------ 传递给入口函数的参数
* THREAD_A_PRIORITY ------ 优先级
* 0 ------ 选项标志
* K_NO_WAIT ------ 启动延迟(立即启动)
*/
k_thread_create(&thread_a_data, thread_a_stack,
K_THREAD_STACK_SIZEOF(thread_a_stack),
thread_a_entry,
NULL, NULL, NULL,
THREAD_A_PRIORITY, 0, K_NO_WAIT);
/* 创建线程 B(较低优先级) */
k_thread_create(&thread_b_data, thread_b_stack,
K_THREAD_STACK_SIZEOF(thread_b_stack),
thread_b_entry,
NULL, NULL, NULL,
THREAD_B_PRIORITY, 0, K_NO_WAIT);
printk("Both threads created. Main thread going to sleep.\n");
/* 主线程进入休眠,让出 CPU 给子线程 */
while (1) {
k_msleep(10000); /* 每 10 秒打印一次主线程心跳 */
printk("[Main] Still alive...\n");
}
}
7. 必做实操任务
完成以下 3 个实操任务,是进入第二阶段的必要条件。
任务 1:环境搭建与 Hello World(必做)
目标:在真实环境(Linux / Windows / macOS)中搭建 Zephyr 开发环境,并运行 Hello World。
验收标准:
- 成功执行
west build -b qemu_x86 samples/hello_world - 在终端看到
"Hello World! qemu_x86"输出 - 理解
west build命令的每个参数含义 - 能在本地创建一个新的应用目录结构(CMakeLists.txt + prj.conf + src/main.c)
建议笔记:
- 记录你使用的操作系统版本、Python 版本、CMake 版本
- 截图保存成功的输出,作为里程碑纪念
任务 2:点亮你的第一个 LED(必做)
目标:在你的真实开发板上运行 LED 闪烁程序。
验收标准:
- 选定一块支持 Zephyr 的物理开发板(推荐 nRF52840 DK、STM32 Nucleo F429ZI 或 ESP32)
- 成功编译针对该开发板的 blink_led 程序
- 通过
west flash将固件烧录到开发板 - 观察到板载 LED 以 1Hz 频率稳定闪烁
- 修改代码,将闪烁频率改为 2Hz 和 0.5Hz,重新编译验证
进阶挑战:
- 尝试控制第二个 LED(如果开发板有多个用户 LED)
- 尝试使用
gpio_pin_set_dt()替代gpio_pin_toggle_dt(),精确控制亮灭时长
任务 3:多线程交通灯模拟(必做)
目标:创建一个模拟交通灯的程序,理解线程优先级和同步的基本概念。
需求描述:
- 创建 3 个线程:红灯线程、绿灯线程、黄灯线程
- 红灯亮 3 秒 → 绿灯亮 3 秒 → 黄灯亮 1 秒 → 循环
- 由于只有一个"灯"(串口输出),使用全局变量表示当前灯色
- 在主循环中打印当前灯色状态
代码框架:
c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
typedef enum {
LIGHT_RED,
LIGHT_GREEN,
LIGHT_YELLOW
} traffic_light_t;
static volatile traffic_light_t current_light = LIGHT_RED;
/* 红灯线程 */
void red_thread(void *p1, void *p2, void *p3)
{
while (1) {
if (current_light == LIGHT_RED) {
printk("[TRAFFIC] RED LIGHT - STOP! (3s)\n");
k_msleep(3000);
current_light = LIGHT_GREEN;
}
k_msleep(100);
}
}
/* 绿灯线程 */
void green_thread(void *p1, void *p2, void *p3)
{
while (1) {
if (current_light == LIGHT_GREEN) {
printk("[TRAFFIC] GREEN LIGHT - GO! (3s)\n");
k_msleep(3000);
current_light = LIGHT_YELLOW;
}
k_msleep(100);
}
}
/* 黄灯线程 */
void yellow_thread(void *p1, void *p2, void *p3)
{
while (1) {
if (current_light == LIGHT_YELLOW) {
printk("[TRAFFIC] YELLOW LIGHT - CAUTION! (1s)\n");
k_msleep(1000);
current_light = LIGHT_RED;
}
k_msleep(100);
}
}
void main(void)
{
printk("=== Traffic Light Simulation ===\n");
/* 创建三个线程... */
}
验收标准:
- 程序成功编译并在 QEMU 或真实开发板上运行
- 串口输出显示红→绿→黄→红的循环切换
- 每个灯色的持续时间符合预期(±100ms 误差内)
- 理解
volatile关键字在此场景中的作用 - 能解释为什么需要
k_msleep(100)在每个线程中
8. 避坑指南
以下是我(以及社区无数开发者)在入门阶段踩过的 4 个经典大坑。建议通读,有则改之,无则加勉。
坑 1:Python 依赖地狱
踩坑回忆录:
那是个月黑风高的夜晚,我满怀信心地执行
west build,结果迎面而来的是满屏的 Python 报错:ModuleNotFoundError: No module named 'elftools'。我疯狂pip install pyelftools,然后下一个错误是ModuleNotFoundError: No module named 'can'。装完 can,又来了dtc相关的错误......折腾到凌晨两点,才发现根本原因是------我忘了执行pip install -r scripts/requirements.txt!
根本原因:Zephyr 依赖大量 Python 包,手动逐个安装既痛苦又容易遗漏。
避坑方案:
bash
# 正确做法:一次性安装所有依赖
pip3 install --user -r $ZEPHYR_BASE/scripts/requirements.txt
# 如果之前手动装过一些包导致冲突,建议先清理
pip3 list | grep -i zephyr # 检查是否有残留
pip3 uninstall pyelftools intelhex # 如有问题先卸载
# 然后重新执行 requirements.txt 安装
检查命令:
bash
python3 $ZEPHYR_BASE/scripts/check_init.py
坑 2:West 工作区初始化失败
踩坑回忆录:
我第一次运行
west init时,网络不太稳定,结果命令执行到一半卡死了。我 Ctrl+C 中断后重新执行,West 却告诉我:"already initialized"。我删掉目录重新来,结果west update时各种 Git 子模块报错 "reference is not a tree"。最后发现,West 的.west隐藏目录和 manifest 文件的状态需要保持一致,粗暴删除目录并不能清理干净。
根本原因:West 依赖 Git 子模块和 manifest 文件,中断的初始化会留下不一致的状态。
避坑方案:
bash
# 如果初始化失败,彻底清理后重来
cd ~/zephyrproject
rm -rf .west/ modules/ zephyr/ bootloader/ # 删除所有 West 管理的目录
# 然后重新执行
west init -m https://github.com/zephyrproject-rtos/zephyr.git
west update
# 或者使用 --mr 指定稳定版本,避免 main 分支的不稳定
west init -m https://github.com/zephyrproject-rtos/zephyr.git --mr v3.7.0
# 如果更新卡在某个模块,可单独更新
west update -o "--depth 1" # 浅克隆,加快速度
坑 3:Board 名称写错导致编译失败
踩坑回忆录:
我有一块 nRF52840 DK,想当然地在命令行输入
west build -b nrf52840。编译报错说找不到这个 board。我又试了nrf52840dk、nrf52840_dk、nrf52dk_nrf52840......最后通过west boards | grep nrf52840才发现正确的名字是nrf52840dk/nrf52840(从 Zephyr v3.6 开始加入了 SoC 后缀)。
根本原因:Zephyr 的 board 命名规则在版本迭代中有变化,且部分 board 需要 SoC 限定符。
避坑方案:
bash
# 第一步:搜索确认正确的 board 名称
west boards | grep -i nrf52840
# 输出示例:
# nrf52840dk/nrf52840
# nrf52840dongle/nrf52840
# nrf52840dk/nrf52840/ns
# 第二步:查看 board 文档确认
cat $ZEPHYR_BASE/boards/nordic/nrf52840dk/doc/index.rst
# 第三步:编译时加上 -p auto(自动 pristine),避免旧构建产物干扰
west build -p auto -b nrf52840dk/nrf52840 samples/hello_world
常见 Board 名称速查:
| 开发板 | 正确名称(Zephyr 3.7+) |
|---|---|
| nRF52840 DK | nrf52840dk/nrf52840 |
| nRF5340 DK | nrf5340dk/nrf5340/cpuapp |
| STM32 Nucleo F429ZI | nucleo_f429zi |
| STM32 Nucleo L476RG | nucleo_l476rg |
| ESP32 DevKitC | esp32_devkitc_wroom/esp32/procpu |
| Raspberry Pi Pico | rpi_pico/rp2040 |
坑 4:设备树别名不存在
踩坑回忆录:
我直接把 LED 示例代码复制到一个自定义的板子上编译,结果编译器报错:
error: Unsupported board: led0 devicetree alias is not defined。我傻眼了------代码明明在官方板上能编译,为什么我的板子不行?折腾半天才意识到,每块板子的设备树定义不同,我的板子没有led0这个别名。
根本原因:设备树别名(alias)是板级定义,不同开发板的别名可能不同或不存在。
避坑方案:
bash
# 检查你的开发板有哪些别名
cat $ZEPHYR_BASE/boards/<vendor>/<board>/<board>.dts | grep -A 5 "aliases"
# 或者使用设备树工具查看
dtlib $ZEPHYR_BASE/boards/<vendor>/<board>/<board>.dts
# 代码中增加更友好的错误提示和回退逻辑
#if !DT_NODE_HAS_STATUS(DT_ALIAS(led0), okay)
# 如果没有 led0,尝试 led1、led2
#if !DT_NODE_HAS_STATUS(DT_ALIAS(led1), okay)
#error "No LED alias found on this board"
#endif
最佳实践:在写 GPIO 代码前,先查看目标板的设备树文件,确认别名名称和引脚定义。
9. 学习进度检查清单
以下 25 项检查清单 帮助你确认第一阶段的学习成果。建议打印出来,完成一项勾一项。
环境搭建(5 项)
- 1. 能够在主机上成功安装 Zephyr SDK
- 2. 能够使用
west init和west update初始化工作区 - 3. 能够运行
west build -b qemu_x86 samples/hello_world并成功编译 - 4. 理解
west build中-b、-p、-d参数的含义 - 5. 能够通过
west boards查找并确认目标开发板的正确名称
West 工具(4 项)
- 6. 理解 West 工作区的目录结构和
.west/config的作用 - 7. 能够使用
west list查看所有模块及其版本 - 8. 能够使用
west build -t menuconfig修改应用配置 - 9. 能够使用
west flash将固件烧录到真实开发板
核心概念(6 项)
- 10. 能用自己的话解释什么是设备树(Devicetree)以及它的三个层次
- 11. 能用自己的话解释 Kconfig 的作用和配置优先级
- 12. 理解 Zephyr 线程的 5 种状态及其转换条件
- 13. 理解协作式调度和抢占式调度的区别
- 14. 能在设备树中找到一个 GPIO 节点的定义并解读其属性
- 15. 能编写一个
prj.conf文件并解释每个配置项的作用
编程实践(6 项)
- 16. 能独立编写并运行 Hello World 程序
- 17. 能使用
gpio_dt_spec和 GPIO API 控制 LED - 18. 能创建至少两个线程并让它们同时运行
- 19. 能使用
k_msleep()实现精确的延时 - 20. 能使用
printk()输出调试信息并观察串口日志 - 21. 能够修改
CMakeLists.txt添加新的源文件到项目
调试排错(4 项)
- 22. 遇到编译错误时,能定位到是第一行错误还是最后一行错误
- 23. 知道如何使用
west build -t menuconfig检查配置是否生效 - 24. 知道如何检查设备树
.dts文件来确认硬件定义 - 25. 能独立解决本阶段"避坑指南"中提到的 4 个问题
10. 学习路径图

图片描述:该图展示了 Zephyr RTOS 的完整学习路径。第一阶段"入门筑基期"包含环境搭建、West 工具、核心概念理解、Hello World 和 LED 实验等基础内容。第二阶段"进阶实战期"涵盖内核机制、驱动开发、子系统使用等进阶主题。第三阶段"高级精通期"涉及网络协议栈、蓝牙协议栈、文件系统、安全启动等高级内容。图中还标注了各阶段之间的依赖关系和建议的过渡条件。
11. 学习建议
11.1 时间规划建议
| 周次 | 主题 | 建议时间 | 关键动作 |
|---|---|---|---|
| 第 1 周 | 环境搭建 + West 工具 | 5~7 小时 | 搭建环境、跑通 Hello World |
| 第 2 周 | 核心概念 + LED 实验 | 7~10 小时 | 理解设备树/Kconfig、点亮 LED |
| 第 3 周 | 多线程 + 综合练习 | 5~7 小时 | 完成交通灯模拟、检查清单 |
11.2 高效学习技巧
- 边学边做,不要只看不练:嵌入式是手艺活,光看教程不敲代码等于零。
- 善用 QEMU:没有开发板也能学 80% 的内容。QEMU 是免费的"虚拟开发板"。
- 阅读官方文档:https://docs.zephyrproject.org/ 是最权威、最及时的资料源。
- 加入社区:Zephyr Discord、中文嵌入式社区(如 RT-Thread 论坛的 Zephyr 板块)。
- 记笔记:建立自己的"踩坑备忘录",每个人的环境都不同,你自己的笔记最宝贵。
- 从例子出发 :
zephyr/samples/目录下有 100+ 官方示例,是最好的学习素材。
11.3 推荐学习资源
| 资源 | 类型 | 说明 |
|---|---|---|
| Zephyr 官方文档 | 文档 | 最权威,涵盖 API、开发指南、API 参考 |
zephyr/samples/ |
代码 | 官方示例,覆盖几乎所有子系统 |
zephyr/tests/ |
代码 | 测试用例,展示 API 的完整用法 |
| Zephyr GitHub Discussions | 社区 | 提问和搜索已有问题 |
| Devicetree 规范 | 规范 | https://devicetree-specification.readthedocs.io/ |
12. 进阶方向
完成第一阶段后,你有以下几个进阶方向可选:
| 方向 | 描述 | 推荐下一阶段 |
|---|---|---|
| 内核深入 | 深入理解调度器、同步原语(信号量、互斥量、消息队列)、中断管理 | 第二阶段:进阶实战期 |
| 驱动开发 | 学习如何编写设备驱动,理解设备模型和驱动 API | 第二阶段:进阶实战期 |
| 子系统应用 | 学习使用特定子系统(蓝牙、网络、文件系统、传感器) | 第三阶段:专项突破期 |
| 板级支持 | 为新的硬件平台添加 Zephyr 支持 | 第二阶段 + 厂商文档 |
| 贡献社区 | 修复 bug、改进文档、提交新驱动 | 全程 |
下一阶段预告:
在 第二阶段:进阶实战期 中,你将学习:
- 内核核心机制(线程状态机、同步原语、中断架构)
- Kconfig 和设备树的高级用法
- 驱动开发(GPIO、I2C、UART 驱动编写)
- 子系统使用(电源管理、日志系统、Shell)
- 更复杂的实战项目(温湿度传感器读取、低功耗设计等)
准备好了吗?当你完成本阶段 25 项检查清单后,就可以自信地进入第二阶段了!