你现在不应该“跑官方 sample”,而应该从空目录自己写一个最小 Blinky。这样才能真正理解 Zephyr 的工程结构、CMake、Kconfig、Devicetree 和 GPIO API。
我们就从:
~/workspace
开始,不复制 samples/basic/blinky。
第 1 步:创建空工程
先执行:
cd ~/workspace
mkdir -p blinky/src
cd blinky
touch CMakeLists.txt
touch prj.conf
touch src/main.c
现在:
tree
应该是:
blinky/
├── CMakeLists.txt
├── prj.conf
└── src
└── main.c
第 2 步:自己写 CMakeLists.txt
编辑:
nano CMakeLists.txt
写入:
cmake_minimum_required(VERSION 3.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
编辑:
nano src/main.c
先写:
#include <zephyr/kernel.h>
int main(void)
{
while (1) {
printk("Hello Zephyr!\n");
k_msleep(1000);
}
return 0;
}
注意:
现在还没有 GPIO。
我们先确认:
“我自己创建的 Zephyr 工程能不能编译、运行。”
这一步非常重要。
第 4 步:prj.conf
编辑:
nano prj.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 -b nucleo_f303re .
注意这里的:.
非常重要。
它表示:
当前目录就是我的 application。
而不是:
west build -b nucleo_f303re samples/basic/blinky
我们现在完全不使用官方 Blinky。
第 6 步:看看发生了什么
如果成功:
ls
你会看到:
CMakeLists.txt
prj.conf
src
build
进入:
cd build
看看:
ls
里面会出现大量 Zephyr 构建文件。
特别关注:
zephyr/
进入:
ls zephyr
你会看到类似:
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 -b 115200
应该看到:
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 = <&gpioa 5 GPIO_ACTIVE_HIGH>;
label = "User LD2";
};
以及:
aliases {
led0 = &green_led_2;
};
所以我们可以画出:
DT_ALIAS(led0)
│
▼
green_led_2
│
▼
gpioa
pin 5
│
▼
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/blinky
nano prj.conf
改成:
CONFIG_GPIO=y
CONFIG_PRINTK=y
为什么加:
CONFIG_GPIO=y
因为我们的程序马上要调用 GPIO API。
2. 修改 main.c
现在:
nano src/main.c
把原来的内容全部删除,写成:
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/devicetree.h>
#define LED_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(LED_NODE, gpios);
int main(void)
{
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
printk("Failed to configure LED\n");
return 0;
}
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(1000);
}
return 0;
}
现在先不要急着背这段代码。
我们逐行拆。
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 = <&gpioa 5 GPIO_ACTIVE_HIGH>;
因此 led 里面包含的信息大概就是:
GPIO Controller → GPIOA
Pin → 5
Active → HIGH
也就是:
PA5
5. 第三层:配置 GPIO
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
意思:
把这个 GPIO 配置成输出,并且初始状态为 inactive。
因为你的 Device Tree 是:
GPIO_ACTIVE_HIGH
所以:
INACTIVE → PA5 = 0 → LED OFF
6. 第四层:Toggle
真正让 LED 闪烁的是:
gpio_pin_toggle_dt(&led);
每执行一次:
OFF → ON
下一次:
ON → OFF
然后:
k_msleep(1000);
等待 1 秒。
所以:
gpio_pin_toggle_dt()
↓
ON
↓
1000 ms
↓
gpio_pin_toggle_dt()
↓
OFF
↓
1000 ms
↓
repeat
7. 编译
现在执行:
cd ~/workspace/blinky
west build -b nucleo_f303re .
如果之前已经 build 过,建议这一次:
west build -b nucleo_f303re . -p always
-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 + Pin 5
│
▼
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 真正值得学习的地方。

402

被折叠的 条评论
为什么被折叠?



