轻松学习Zephyr: 04-从零构建Blinky

📅 2026/8/13 2:54:07
轻松学习Zephyr: 04-从零构建Blinky
你现在不应该“跑官方 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先写#includezephyr/kernel.hintmain(void){while(1){printk(Hello Zephyr!\n);k_msleep(1000);}return0;}注意现在还没有 GPIO。我们先确认“我自己创建的 Zephyr 工程能不能编译、运行。”这一步非常重要。第 4 步prj.conf编辑nanoprj.conf写CONFIG_PRINTKy这里第一次接触 Kconfig。它的作用是prj.conf ↓ Kconfig ↓ Zephyr configuration ↓ 编译系统CONFIG_PRINTKy 表示我要启用 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 CtrlA, then press X, andhit 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 ledGPIO_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 这条关系搞明白。你找到led0green_led_2;这句话的意思是led0 │ └──green_led_2也就是说应用程序以后写DT_ALIAS(led0)实际上就是在寻找green_led_2现在我们已经把 Nucleo-F303RE 的 LED 完整链路找出来了。你得到的核心信息是green_led_2: led_2{gpiosgpioa5GPIO_ACTIVE_HIGH;labelUser LD2;};以及aliases{led0green_led_2;};所以我们可以画出DT_ALIAS(led0)│ ▼ green_led_2 │ ▼ gpioa pin5│ ▼ PA5 │ ▼ User LD2而且GPIO_ACTIVE_HIGH意味着PA51→ LED ON PA50→ LED OFF第9步 现在开始自己写 Blinky注意我们暂时不需要 app.overlay。这是一个很重要的学习点。因为你的 Nucleo-F303RE 已经在 board Device Tree 中定义好了 led0led0green_led_2;所以我们的应用可以直接使用它。1. 修改 prj.conf现在cd~/workspace/blinkynanoprj.conf改成CONFIG_GPIOyCONFIG_PRINTKy为什么加CONFIG_GPIOy因为我们的程序马上要调用 GPIO API。2. 修改 main.c现在nanosrc/main.c把原来的内容全部删除写成#includezephyr/kernel.h#includezephyr/device.h#includezephyr/drivers/gpio.h#includezephyr/devicetree.h#defineLED_NODEDT_ALIAS(led0)staticconststructgpio_dt_specledGPIO_DT_SPEC_GET(LED_NODE,gpios);intmain(void){intret;retgpio_pin_configure_dt(led,GPIO_OUTPUT_INACTIVE);if(ret0){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{led0green_led_2;};所以DT_ALIAS(led0)最终找到green_led_24. 第二层取得 GPIO 信息这一段static const struct gpio_dt_spec ledGPIO_DT_SPEC_GET(LED_NODE, gpios);它读取gpiosgpioa5GPIO_ACTIVE_HIGH;因此 led 里面包含的信息大概就是GPIO Controller → GPIOA Pin →5Active → HIGH也就是PA55. 第三层配置 GPIOgpio_pin_configure_dt(led, GPIO_OUTPUT_INACTIVE);意思把这个 GPIO 配置成输出并且初始状态为 inactive。因为你的 Device Tree 是GPIO_ACTIVE_HIGH所以INACTIVE → PA50→ 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 │ │ led0green_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 构建系统2Kconfig 配置项如 CONFIG_PRINTK、CONFIG_GPIO的作用3Device Tree 中 LED 别名led0green_led_2到具体 GPIOPA5的映射关系4使用DT_ALIAS(led0)和GPIO_DT_SPEC_GET获取设备树信息5完整的 GPIO 配置与闪烁实现。最终实现从空目录到可烧录运行的 LED 闪烁程序掌握 Zephyr 应用开发的底层原理。 west build west flash现在你是在自己构建CMake Kconfig Device Tree GPIO API Application这才是 Zephyr 真正值得学习的地方。