你现在不应该“跑官方 sample”,而应该从空目录自己写一个最小 Blinky。这样才能真正理解 Zephyr 的工程结构、CMake、Kconfig、Devicetree 和 GPIO API。
我们就从:
~/workspace开始,不复制 samples/basic/blinky。
第 1 步:创建空工程
先执行:
cd~/workspacemkdir-pblinky/srccdblinkytouchCMakeLists.txttouchprj.conftouchsrc/main.c现在:
tree应该是:
blinky/ ├── CMakeLists.txt ├── prj.conf └── src └── main.c第 2 步:自己写 CMakeLists.txt
编辑:
nanoCMakeLists.txt写入:
cmake_minimum_required(VERSION3.20.0)find_package(Zephyr REQUIRED HINTS$ENV{ZEPHYR_BASE})project(blinky)target_sources(app PRIVATE src/main.c)这里先不要急着背。
我们现在只理解:
find_package(Zephyr REQUIRED HINTS$ENV{ZEPHYR_BASE})意思是:
找到 Zephyr 构建系统。
而:
project(blinky)表示:
我的应用程序叫 blinky。
最后:
target_sources(app PRIVATE src/main.c)表示:
把 src/main.c 加入这个应用程序。
第 3 步:先写最简单的 main.c
编辑:
nanosrc/main.c先写:
#include<zephyr/kernel.h>intmain(void){while(1){printk("Hello Zephyr!\n");k_msleep(1000);}return0;}注意:
现在还没有 GPIO。
我们先确认:
“我自己创建的 Zephyr 工程能不能编译、运行。”
这一步非常重要。
第 4 步:prj.conf
编辑:
nanoprj.conf写:
CONFIG_PRINTK=y这里第一次接触 Kconfig。
它的作用是:
prj.conf ↓ Kconfig ↓ Zephyr configuration ↓ 编译系统CONFIG_PRINTK=y 表示:
我要启用 Zephyr 的 printk 功能。
第 5 步:第一次编译
你之前已经配置好了 Zephyr 环境,所以先确认:
echo$ZEPHYR_BASE应该类似:
/home/cxicc/zephyrproject/zephyr然后进入工程:
cd~/workspace/blinky执行:
west build-bnucleo_f303re.注意这里的:.
非常重要。
它表示:
当前目录就是我的 application。
而不是:
west build -b nucleo_f303re samples/basic/blinky
我们现在完全不使用官方 Blinky。
第 6 步:看看发生了什么
如果成功:
ls你会看到:
CMakeLists.txt prj.conf src build进入:
cdbuild看看:
ls里面会出现大量 Zephyr 构建文件。
特别关注:
zephyr/进入:
lszephyr你会看到类似:
zephyr.elf zephyr.bin zephyr.hex这时候你应该理解:
~/workspace/blinky │ │ west build ▼ ~/workspace/blinky/build │ ▼ zephyr.elf zephyr.bin zephyr.hex第 7 步:烧录
回到工程目录:
cd~/workspace/blinky然后:
west flash
如果烧录成功,再打开串口:
minicom-D/dev/ttyACM0-b115200应该看到:
Hello Zephyr!Hello Zephyr!Hello Zephyr!Hello Zephyr!每秒一次。
To exit or abort minicom normally, press Ctrl+A, then press X, and
hit Enter to confirm
到这里先不要写 Blinky
这一步看起来很简单,但是实际上我们已经自己完成了:
空目录 ↓ CMakeLists.txt ↓ prj.conf ↓ main.c ↓ west build ↓ Zephyr ↓ zephyr.elf ↓ west flash ↓ STM32这才是你应该掌握的第一层。
第 8 步:下一步才加入 GPIO
等上面的 Hello Zephyr! 成功以后,我们把:
printk("Hello Zephyr!\n");换成:
GPIO ↓ LED ↓ Toggle但是这里会遇到一个非常关键的问题:
我们的 main.c 怎么知道 Nucleo-F303RE 的 LED 在哪个 GPIO?
这就会自然进入:
Device Tree然后我们自己写:
app.overlay
最终自己的工程会变成:
~/workspace/blinky/ ├── CMakeLists.txt ├── prj.conf ├── app.overlay └── src/ └── main.c然后实现:
const struct gpio_dt_spec led=GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);再:
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);最后:
gpio_pin_toggle_dt(&led);这样你就会真正理解:
app.overlay ↓ Device Tree ↓ DT_ALIAS(led0)↓ GPIO specification ↓ GPIO API ↓ STM32 GPIO Driver ↓ GPIO hardware ↓ LED这才是这一课真正要学的东西。
在我们不要急着写代码,先把 Device Tree 这条关系搞明白。
你找到:
led0=&green_led_2;这句话的意思是:
led0 │ └──>green_led_2也就是说,应用程序以后写:
DT_ALIAS(led0)实际上就是在寻找:
green_led_2现在我们已经把 Nucleo-F303RE 的 LED 完整链路找出来了。
你得到的核心信息是:
green_led_2: led_2{gpios=<&gpioa5GPIO_ACTIVE_HIGH>;label="User LD2";};以及:
aliases{led0=&green_led_2;};所以我们可以画出:
DT_ALIAS(led0)│ ▼ green_led_2 │ ▼ gpioa pin5│ ▼ PA5 │ ▼ User LD2而且:
GPIO_ACTIVE_HIGH意味着:
PA5=1→ LED ON PA5=0→ LED OFF第9步: 现在开始自己写 Blinky
注意:我们暂时不需要 app.overlay。
这是一个很重要的学习点。
因为你的 Nucleo-F303RE 已经在 board Device Tree 中定义好了 led0:
led0=&green_led_2;所以我们的应用可以直接使用它。
1. 修改 prj.conf
现在:
cd~/workspace/blinkynanoprj.conf改成:
CONFIG_GPIO=yCONFIG_PRINTK=y为什么加:
CONFIG_GPIO=y因为我们的程序马上要调用 GPIO API。
2. 修改 main.c
现在:
nanosrc/main.c把原来的内容全部删除,写成:
#include<zephyr/kernel.h>#include<zephyr/device.h>#include<zephyr/drivers/gpio.h>#include<zephyr/devicetree.h>#defineLED_NODEDT_ALIAS(led0)staticconststructgpio_dt_specled=GPIO_DT_SPEC_GET(LED_NODE,gpios);intmain(void){intret;ret=gpio_pin_configure_dt(&led,GPIO_OUTPUT_INACTIVE);if(ret<0){printk("Failed to configure LED\n");return0;}while(1){gpio_pin_toggle_dt(&led);k_msleep(1000);}return0;}现在先不要急着背这段代码。
我们逐行拆。
3. 第一层:找到 LED
这一行:
#define LED_NODE DT_ALIAS(led0)对应你刚才找到的:
aliases{led0=&green_led_2;};所以:
DT_ALIAS(led0)最终找到:
green_led_2
4. 第二层:取得 GPIO 信息
这一段:
static const struct gpio_dt_spec led=GPIO_DT_SPEC_GET(LED_NODE, gpios);它读取:
gpios=<&gpioa5GPIO_ACTIVE_HIGH>;因此 led 里面包含的信息大概就是:
GPIO Controller → GPIOA Pin →5Active → HIGH也就是:
PA55. 第三层:配置 GPIO
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);意思:
把这个 GPIO 配置成输出,并且初始状态为 inactive。
因为你的 Device Tree 是:
GPIO_ACTIVE_HIGH所以:
INACTIVE → PA5=0→ LED OFF6. 第四层:Toggle
真正让 LED 闪烁的是:
gpio_pin_toggle_dt(&led);每执行一次:
OFF → ON下一次:
ON → OFF然后:
k_msleep(1000);等待 1 秒。
所以:
gpio_pin_toggle_dt()↓ ON ↓1000ms ↓ gpio_pin_toggle_dt()↓ OFF ↓1000ms ↓ repeat7. 编译
现在执行:
cd~/workspace/blinky west build-bnucleo_f303re.如果之前已经 build 过,建议这一次:
west build-bnucleo_f303re.-palways-p always 表示重新生成构建配置,避免我们修改 prj.conf 后受到旧配置影响。
8. 烧录
编译成功以后:
west flash然后观察 Nucleo-F303RE 上的 LD2。
应该:
亮 ↓1秒 ↓ 灭 ↓1秒 ↓ 亮 ↓...- 这一阶段你真正应该理解的东西
现在我们已经从:
Hello Zephyr走到了:
main.c │ │ DT_ALIAS(led0)▼ Device Tree │ │ led0=&green_led_2 ▼ green_led_2 │ │ gpios ▼ GPIOA + Pin5│ ▼ GPIO Driver │ ▼ STM32F303RE │ ▼ LD2这就是为什么我建议你不要直接跑官方 samples/basic/blinky。
官方 sample 跑起来很容易,但你很容易只学会:
>**摘要**:本文通过从零构建 Zephyr Blinky 项目的完整过程,系统讲解了 Zephyr RTOS 的核心构建机制。不同于直接运行官方示例,我们从空目录开始,逐步创建 CMakeLists.txt、prj.conf、main.c 等文件,深入理解 Zephyr 工程结构、CMake 配置、Kconfig 系统、Device Tree 设备树和 GPIO API 的完整链路。重点包括:1)如何通过`find_package(Zephyr)`集成 Zephyr 构建系统;2)Kconfig 配置项(如 CONFIG_PRINTK、CONFIG_GPIO)的作用;3)Device Tree 中 LED 别名(led0=&green_led_2)到具体 GPIO(PA5)的映射关系;4)使用`DT_ALIAS(led0)`和`GPIO_DT_SPEC_GET`获取设备树信息;5)完整的 GPIO 配置与闪烁实现。最终实现从空目录到可烧录运行的 LED 闪烁程序,掌握 Zephyr 应用开发的底层原理。 west build west flash现在你是在自己构建:
CMake + Kconfig + Device Tree + GPIO API + Application这才是 Zephyr 真正值得学习的地方。