自制 STM32L496 板移植 Zephyr 要先搭好最小工程
这次 demo 的目标很小:在一块自制 STM32L496RGT6 最小系统板上,把 Zephyr 从环境安装、工程创建、设备树适配、编译、烧录、串口验证完整跑通。
完整仓库:
https://github.com/createskyblue/stm32l496-zephyr-rtos-skeleton
为什么要做这个 demo
STM32 裸机点灯只需要 GPIO 初始化和一个循环。Zephyr 的收益出现在工程结构上:硬件信息集中到 devicetree,功能裁剪集中到 Kconfig,应用逻辑拆成线程、消息队列、定时器和工作队列。
这个 demo 保留最小硬件目标,同时加入几个能直接观察的 RTOS 对象:
- LED 线程:500 ms 周期翻转 PC13。
- ADC 线程:周期采样 PA2 / ADC1_IN7。
- I2C 扫描:由
k_timer周期触发,再交给k_work执行。 - 消息队列:LED、ADC、I2C 把事件投递到
k_msgq。 - logger 线程:统一从消息队列取事件,通过 UART 打印。
运行时结构:
LED线程 ─┐
ADC线程 ─┼─> k_msgq ─> logger线程 ─> USART1
I2C工作 ─┘
↑
k_timer这个结构的好处很直接:新增传感器、通信任务或状态机时,可以新增线程或 work item,再把结果投递给统一事件通道,主入口保持简单。
第一次使用过程
Windows 原生环境先准备基础工具:
winget install Kitware.CMake Ninja-build.Ninja Python.Python.3.12 Git.Git oss-winget.dtc 7zip.7zipZephyr 工作用独立目录:
mkdir C:\zephyr
cd C:\zephyr
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install west第一次容易直接执行完整 west update。实际体验是下载量和等待时间都很大。这个 demo 只面向 STM32L496 最小 bring-up,最后采用了较小的依赖范围:Zephyr 主仓库、hal_stm32、CMSIS 相关模块,以及 Zephyr SDK 的 arm-zephyr-eabi 和 host tools。
安装完成后需要记住一点:west 在虚拟环境里。普通 PowerShell 直接输入 west 可能找不到命令,需要先激活 venv,或直接使用:
C:\zephyr\.venv\Scripts\west.exe硬件确认
自制板不能直接套官方 Nucleo 或 Discovery 的 board 文件烧录。先从网表确认最小连接:
| 功能 | 配置 |
|---|---|
| MCU | STM32L496RGT6 |
| LED | PC13,低电平点亮 |
| UART console | USART1,PA9/PA10,115200 8N1 |
| ADC | PA2 / ADC1_IN7 |
| I2C | PB8/PB9 |
| 下载 | ST-LINK,SWD |
| Flash | 1 MB |
| RAM | 320 KB total |
首次 board 配置使用 HSI + PLL,暂时不依赖外部 HSE/LSE。这样可以减少首次 bring-up 的排查变量。
设备树怎么写
自定义 board 放在应用仓库内部:
boards/arm/my_stm32l496_board/应用 CMakeLists.txt 设置:
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})这样仓库移动到其它目录后,自定义 board 仍然能被 Zephyr 找到。
设备树里描述 LED、ADC 和 I2C:
/ {
chosen {
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
leds {
compatible = "gpio-leds";
user_led: led_0 {
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
};
};
zephyr,user {
io-channels = <&adc1 7>;
};
aliases {
led0 = &user_led;
};
};
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
current-speed = <115200>;
status = "okay";
};
&adc1 {
pinctrl-0 = <&adc1_in7_pa2>;
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
status = "okay";
};
&i2c1 {
pinctrl-0 = <&i2c1_scl_pb8 &i2c1_sda_pb9>;
clock-frequency = ;
status = "okay";
}; prj.conf 打开最小驱动:
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_ADC=y
CONFIG_I2C=y
CONFIG_THREAD_NAME=y应用代码的最小形状
应用入口只负责初始化和启动模块:
int main(void)
{
app_led_init();
app_adc_init();
app_i2c_scan_init();
app_logger_start();
app_led_start();
app_adc_start();
app_i2c_scan_start();
return 0;
}模块之间不直接打印,统一投递事件:
struct app_event {
enum app_event_type type;
uint32_t source;
int32_t value0;
int32_t value1;
};
K_MSGQ_DEFINE(app_msgq, sizeof(struct app_event), 16, 4);
void app_event_publish(const struct app_event *event)
{
(void)k_msgq_put(&app_msgq, event, K_NO_WAIT);
}LED 线程只处理 LED:
static void led_thread(void *arg0, void *arg1, void *arg2)
{
uint32_t counter = 0;
while (1) {
gpio_pin_toggle_dt(&led);
const struct app_event event = {
.type = APP_EVENT_LED,
.value0 = counter++,
};
app_event_publish(&event);
k_msleep(500);
}
}logger 线程只处理输出:
static void logger_thread(void *arg0, void *arg1, void *arg2)
{
while (1) {
struct app_event event;
app_event_wait(&event);
if (event.type == APP_EVENT_LED) {
printk("[logger] blink counter = %u\r\n", event.value0);
}
}
}I2C 扫描由 timer 触发 work:
static void i2c_scan_timer_handler(struct k_timer *timer)
{
(void)k_work_submit(&i2c_scan_work);
}这几段代码能说明 Zephyr 的应用形态:外设驱动负责硬件操作,线程负责周期任务,消息队列负责模块解耦,work queue 负责延后执行较慢操作。
构建、烧录和串口验证
构建:
cd C:\zephyr
.\.venv\Scripts\Activate.ps1
$env:ZEPHYR_BASE = "C:\zephyr\zephyrproject\zephyr"
cd C:\zephyr\stm32l496-demo
west build -p always -b my_stm32l496_board .烧录:
west flash实测 ST-LINK 输出:
Device name : STM32L496xx/STM32L4A6xx
Flash size : 1 MBytes
Application is running串口输出在 ST-LINK Virtual COM Port 上验证:
[logger] blink counter = 237
[logger] ADC PA2/ADC1_IN7 raw = 202, 162 mV
[logger] I2C scan done, found 0 device(s)VS Code CMake Tools 踩坑
直接用 VS Code CMake Tools 配置 Zephyr 工程时,需要显式指定 board:
-DBOARD=my_stm32l496_board缺少该参数会报:
BOARD is not being definedCMake 还需要使用 Zephyr venv 里的 Python。否则 Zephyr 脚本可能找不到依赖:
ModuleNotFoundError: No module named 'jsonschema'可用配置:
{
"cmake.configureArgs": [
"-DBOARD=my_stm32l496_board",
"-DZEPHYR_TOOLCHAIN_VARIANT=zephyr",
"-DPython3_EXECUTABLE=C:/zephyr/.venv/Scripts/python.exe"
],
"cmake.buildDirectory": "${workspaceFolder}/build",
"cmake.generator": "Ninja",
"cmake.configureEnvironment": {
"ZEPHYR_BASE": "C:/zephyr/zephyrproject/zephyr"
}
}配置变更后执行:
CMake: Delete Cache and Reconfigure结论
- 自制 STM32 板先确认网表,再写自定义 board。
- devicetree 负责硬件描述,
prj.conf负责功能裁剪。 - 最小 RTOS demo 应包含线程、消息队列、定时器和工作队列。
- 首次 bring-up 按 build、flash、serial 的顺序排查。
- VS Code 需要显式配置 board 和 Zephyr Python。
完整示例仓库:
https://github.com/createskyblue/stm32l496-zephyr-rtos-skeleton
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。