自制 STM32L496 板移植 Zephyr 要先搭好最小工程

这次 demo 的目标很小:在一块自制 STM32L496RGT6 最小系统板上,把 Zephyr 从环境安装、工程创建、设备树适配、编译、烧录、串口验证完整跑通。

完整仓库:

https://github.com/createskyblue/stm32l496-zephyr-rtos-skeleton

为什么要做这个 demo

STM32 裸机点灯只需要 GPIO 初始化和一个循环。Zephyr 的收益出现在工程结构上:硬件信息集中到 devicetree,功能裁剪集中到 Kconfig,应用逻辑拆成线程、消息队列、定时器和工作队列。

这个 demo 保留最小硬件目标,同时加入几个能直接观察的 RTOS 对象:

运行时结构:

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.7zip

Zephyr 工作用独立目录:

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 defined

CMake 还需要使用 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

结论

完整示例仓库:

https://github.com/createskyblue/stm32l496-zephyr-rtos-skeleton

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。