跳到主要内容

STM32 代码生成

代码生成器(pip 包 libxr)的 libxr stm32 setup 命令把 STM32CubeMX 工程接入 LibXR:解析 .ioc 文件,生成 C++ 初始化代码,并把 LibXR 接入 CMake 构建。


快速使用​

在 STM32CubeMX 工程根目录下执行:

libxr stm32 setup -d .

在 VS Code 中使用 XRobot.xrobot 插件时,也可以在插件提供的 GUI 页面中修改配置,修改后插件自动执行生成命令。

该命令依次完成以下步骤:

  1. 加入 LibXR 子模块 Middlewares/Third_Party/LibXR 并写入 .gitmodules(工程还不是 Git 仓库时先执行 git init);已有的检出保持不变,--commit 切换到指定提交;
  2. 创建或补全 .gitignore 和 .gitattributes;
  3. 查找 .ioc 文件并解析为 .config.yaml;
  4. 在 User/ 下生成 app_main.cpp、app_main.h、flash_map.hpp 和 libxr_config.yaml;
  5. 生成 cmake/LibXR.CMake,并在 CMakeLists.txt 中 include 它。

目录中只有 .ioc 文件、还没有 STM32CubeMX 生成的代码时,先运行 libxr stm32 cubemx-gen,它以脚本模式运行 STM32CubeMX,由 .ioc 生成工程,之后再运行 libxr stm32 setup。STM32CubeMX 弹出的对话框按参数回答:工程由另一版本的 STM32CubeMX 保存时,--firmware keep 沿用原来的固件包(Continue),--firmware migrate 迁移工程;--download 允许 STM32CubeMX 下载缺少的固件包并接受其许可协议。没有对应参数的对话框使运行停止,并报出对话框的内容。Linux 上不回答对话框,遇到对话框即停止。


示例输出​

[信息] 默认的 LibXR 提交:783b157fb3e9e5a7801edc9083834ba3ac49801f
[信息] User/app_main.cpp 使用了 XRobot,继续按 --xrobot 生成(--no-xrobot 可关闭)。
[信息] 保留现有的 LibXR 检出 783b157fb3e9。
[信息] 找到 .ioc 文件:.\STM32F103RC.ioc
[信息] 正在解析 .ioc 文件……
[信息] 正在处理 STM32F103RC.ioc……
[信息] 配置已导出到:.\.config.yaml
[信息] 正在生成 C++ 代码……
[信息] 系统:FreeRTOS
[信息] 已生成 User:未变化 app_main.cpp、app_main.h、flash_map.hpp、libxr_config.yaml
[信息] LibXR.CMake 已是最新,无需修改。
[信息] CMakeLists.txt 已经 include LibXR.CMake。
[信息] [通过] 全部任务已完成。
[信息] 构建:cmake --preset debug && cmake --build --preset debug

生成器只写内容有变化的文件;上例中工程已是最新,四个文件都没有改写。最后一行是 CMakePresets.json 中第一个 preset 的构建命令;Core/Src 中还没有源文件调用 app_main() 时,它前面另有一行写出调用的位置,见使用说明。


输出结构​

执行完成后,项目通常会新增或修改这些文件:

.
├── .config.yaml # 解析生成的配置文件
├── .gitignore
├── .gitattributes
├── .gitmodules
├── User/
│ ├── app_main.cpp # 入口源文件
│ ├── app_main.h # 入口源文件的头文件
│ ├── libxr_config.yaml # LibXR 配置文件
│ └── flash_map.hpp # FLASH 地址映射表
├── cmake/LibXR.CMake # LibXR 构建配置
├── CMakeLists.txt # 自动集成 LibXR
└── Middlewares/Third_Party/LibXR # Git 子模块:LibXR 本体

app_main.cpp​

app_main.cpp 文件包含 LibXR 初始化函数 app_main()。/* User Code Begin N */ 与 /* User Code End N */ 之间的代码在重新生成时保留,其余部分每次重新生成。

生成的外设对象都是 static 对象,app_main() 返回后仍然有效;User Code 中不带 static 的局部对象在 app_main() 返回时析构,长期使用的对象声明为 static。外设对象可以以基类指针传给线程或任务使用,本章的与 XRobot 集成提供了更完整的做法。

下面是一个 STM32F407 工程生成的 app_main.cpp 节选(未加 --xrobot),省略处写作 // ...:

// Generated by `libxr gen` from DevC.ioc and User/libxr_config.yaml.
// Edit only between "User Code Begin" and "User Code End"; the rest is regenerated.
#include "app_main.h"

#include "cdc_uart.hpp"
#include "libxr.hpp"
#include "main.h"
#include "stm32_adc.hpp"
#include "stm32_can.hpp"
// ...

using namespace LibXR;

/* User Code Begin 1 */
/* User Code End 1 */

extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc3;
extern CAN_HandleTypeDef hcan1;
// ...

// DMA buffers (STM32F407IG: no D-cache)
alignas(4) static uint16_t adc1_buf[32];
alignas(4) static uint16_t adc3_buf[16];
// ...
alignas(4) static uint8_t spi1_rx_buf[32];
alignas(4) static uint8_t spi1_tx_buf[32];
// ...

extern "C" void app_main(void)
{
/* User Code Begin 2 */
/* User Code End 2 */

// Timebase and platform
static STM32TimerTimebase timebase(&htim2);
PlatformInit(static_cast<uint32_t>(Thread::Priority::MEDIUM), 1024);
static STM32PowerManager power_manager;

// GPIO
static STM32GPIO USER_KEY(USER_KEY_GPIO_Port, USER_KEY_Pin, EXTI0_IRQn);
static STM32GPIO ACCL_CS(ACCL_CS_GPIO_Port, ACCL_CS_Pin);
// ...

// ADC
// ...
static STM32ADC adc3(&hadc3, adc3_buf, {ADC_CHANNEL_8}, 3.3);
static auto& adc3_adc_channel_8 = adc3.GetChannel(0);
UNUSED(adc3_adc_channel_8);

// ...

// PWM
static STM32PWM pwm_tim1_ch1(&htim1, TIM_CHANNEL_1, false);
// ...

// SPI, UART, I2C, CAN
static STM32SPI spi1(&hspi1, spi1_rx_buf, spi1_tx_buf, 3);
static STM32UART usart1(&huart1, usart1_rx_buf, usart1_tx_buf, 5);
// ...
static STM32I2C i2c1(&hi2c1, i2c1_buf, 3);
// ...
static STM32CAN can1(&hcan1, 5);
// ...

/* User Code Begin 3 */
while (true)
{
Thread::Sleep(UINT32_MAX);
}
/* User Code End 3 */
}
代码块已折叠

外设对象都是 static 对象。DMA 缓冲区有两种写法:带数据 Cache 的芯片上按 Cache 行对齐(见 Cache),其他芯片上是 4 字节对齐的数组。


使用说明​

在工程入口处调用 app_main():

裸机项目​

#include "app_main.h"

int main() {
HAL_Init();
SystemClock_Config();
...
app_main(); // 初始化外设并启动应用层
while (1){
...
}
}

FreeRTOS 项目​

在默认任务 StartDefaultTask 中调用 app_main()。app_main() 在默认任务的栈上运行,默认任务的栈深度在 STM32CubeMX 中设置,需要足够容纳它。

FreeRTOS 的接口(Interface)推荐选择 CMSIS_V2。STM32Cube FW_H7 V1.13.0 等固件包已经移除 CMSIS-RTOS V1 接口层,使用这类固件包的工程只能选择 CMSIS_V2。接口为 CMSIS_V1 的工程迁移到这类固件包时,STM32CubeMX 会移除整个 FreeRTOS,且没有提示。这类工程打开时先选择 Continue 保持原固件包,把接口改为 CMSIS_V2 后再迁移。

选择 CMSIS_V2 后,STM32CubeMX 总是打开 FreeRTOS 软件定时器(configUSE_TIMERS),并多创建一个定时器任务。LibXR 的定时任务(LibXR::Timer)在自己的线程中执行,线程的优先级和栈深度由 PlatformInit() 设置;configUSE_TIMERS 打开时,LibXR 编译时会给出警告。在 Core/Inc/FreeRTOSConfig.h 的 USER CODE BEGIN Defines 区中覆盖以下定义,即可关闭软件定时器,以及依赖它的 CMSIS-RTOS2 osTimer 接口和在中断中设置事件标志的功能,INCLUDE_xTimerPendFunctionCall 保持关闭。在 STM32CubeMX 中关闭了 INCLUDE_xTimerPendFunctionCall 的工程必须加入这段覆盖,否则编译时 freertos_os2.h 报错 #error "Definition INCLUDE_xTimerPendFunctionCall must equal 1 to implement Event Flags API."。该区域的内容在重新生成代码时保留。

/* USER CODE BEGIN Defines */
#undef configUSE_TIMERS
#define configUSE_TIMERS 0
#undef configUSE_OS2_TIMER
#define configUSE_OS2_TIMER 0
#undef configUSE_OS2_EVENTFLAGS_FROM_ISR
#define configUSE_OS2_EVENTFLAGS_FROM_ISR 0
/* USER CODE END Defines */

libxr_config.yaml​

User/libxr_config.yaml 保存代码生成的设置。文件不存在时,libxr gen 按默认值新建;之后每次生成补上缺少的设置,文件中已有的值、注释和生成器不使用的键都保留。外设的设置段只在工程有对应外设时写入,段中每个实例一项,键为小写的实例名,例如 spi1。

下表列出顶层的键,其中 config_version 由用户写入:

键默认值说明
generator新建文件时写入已安装的版本,如 6.0.1固定代码生成器的版本,见与XRobot集成
terminal_source''作为终端的串口,见串口与终端
software_timerpriority: 2、stack_depth: 1024软件定时器线程,见软件定时器
SPItx_buffer_size: 32、rx_buffer_size: 32、dma_section: '';两个方向都开启了 DMA 时另有 dma_enable_min_size: 3见 SPI
I2Cbuffer_size: 32、dma_section: ''、dma_enable_min_size: 3见 I2C
USARTtx_buffer_size: 128、rx_buffer_size: 128、dma_section: ''、tx_queue_size: 5USART、UART 和 LPUART,见串口与终端
ADCbuffer_size: 32、dma_section: ''、vref: 3.3见 ADC
CANqueue_size: 5见 CAN 与 CAN FD
FDCANqueue_size: 5见 CAN 与 CAN FD
USBenable,为 true 时补齐其余设置见串口与终端
Terminalread_buff_size: 32、max_line_size: 32、max_arg_number: 5、max_history_number: 5;生成终端时另有 run_as_thread: false见串口与终端
databaseenable: false、block_size: auto见 Flash 数据库
SYSTEM由 .ioc 得出FreeRTOS、ThreadX 或 None(裸机),见下文
DACinit_voltage: 0.0、vref: 3.3见 DAC
IWDGtimeout_ms: 1000、feed_interval_ms: 250见看门狗
Watchdogrun_as_thread: false、feed_interval_ms: 250有启用的 IWDG 时写入,见看门狗
config_version不写入,按 1 处理文件格式的版本,见下文

SYSTEM 由生成器按 .ioc 写入,读取时忽略,系统总是取自 .ioc。config_version 大于 1 或不是整数时,libxr gen 给出警告,说明较新格式的设置可能不起作用,生成照常进行,这个键保持不变。

以前版本写入的键在重新生成时转换或删除:device_aliases 被删除,生成的对象只以自己的名字注册,其中与对象名不同的别名以警告列出(别名 -> 对象);FlashLayout 被删除,Flash 布局只写在 flash_map.hpp 中;已启用的 USB 外设下的 cdc_tx_fifo_size、cdc_rx_fifo_size 和 cdc_queue_size 转换为 cdc 列表的一项,已有 cdc 时只删除这三个键,见串口与终端;CAN 和 FDCAN 下的大写实例键(如 CAN1)改为小写。


重新生成代码​

修改 User/libxr_config.yaml 或 STM32CubeMX 中的配置后,在工程根目录重新运行:

libxr stm32 setup -d .

只重新生成 User/ 下的文件时,先由 .ioc 写出 .config.yaml,再运行 libxr gen:

libxr parse -d .
libxr gen -i .config.yaml -o User/app_main.cpp

.config.yaml 列在生成的 .gitignore 中,新克隆的工程中没有这个文件,需要先运行 libxr parse。


可选参数​

libxr stm32 setup 的参数如下,libxr stm32 setup --help 同样列出这些参数。

参数说明
-d, --directory指定 STM32 工程根目录(默认:当前目录)
-t, --terminal设置终端外设(如 usart1、usb_fs_cdc),写入 User/libxr_config.yaml 的 terminal_source
--xrobot / --no-xrobot生成或不生成 XR_REGISTER 注册与 XROBOT_MAIN();,见 与XRobot集成;都不写时沿用工程现在的选择
--commit把 LibXR 子模块检出到指定提交;不写时保持现有检出,新加入的子模块检出到本版本 libxr 锁定的提交
--git-source需要克隆 LibXR 时使用的 Git 源:auto(默认)、github、基础地址、仓库地址或本地仓库;.gitmodules 始终记录 GitHub 地址
--git-mirrors以逗号分隔的镜像基础地址或仓库地址,--git-source 为 auto 时参与选择;环境变量 XR_GIT_MIRRORS 写法相同
--verbose输出调试日志

工具链切换​

如需切换 GCC/Clang 或切换 Clang 标准库,可以使用:

libxr stm32 toolchain gcc
libxr stm32 toolchain clang -g
libxr stm32 toolchain clang --newlib
libxr stm32 toolchain clang --picolibc

命令修改 CMakePresets.json 和 cmake/starm-clang.cmake。在 gcc 与 clang 之间切换时,命令删除 build/ 和 cmake-build* 目录:CMake 不会更换已有构建目录的编译器,下次构建在新的构建目录中配置。clang 不带标准库选项时沿用现在的标准库。

-g(hybrid)用 ST Arm Clang 编译、GNU 工具链链接。cmake/starm-clang.cmake 从环境变量 CLANG_GCC_CMSIS_COMPILER(ST Arm Clang 的安装目录,其中有 multilib.gnu_tools_for_stm32.yaml)和 GCC_TOOLCHAIN_ROOT(arm-none-eabi-gcc 所在的 bin 目录)找到 GNU 工具链,构建前需要设置这两个变量。


项目要求​

  • 必须为 STM32CubeMX 导出的 CMake 工程;
  • 必须存在 .ioc 文件;
  • FreeRTOS 必须开启互斥锁(configUSE_MUTEXES);
  • 外设使用 HAL 驱动,并在 Project Manager 中保持代码生成(Generate Code)开启;
  • HAL 时基使用普通定时器,中断优先级设为最高,见时钟基准。

LibXR.CMake​

cmake/LibXR.CMake 开头的 “Project settings” 块保存工程的构建设置。以下是一个 FreeRTOS 工程生成的文件的开头,第一行是生成说明,其后是设置块:

# Generated by `libxr stm32 setup`; edit the values in the "Project settings" block only.

# ---- Project settings --------------------------------------------------------
set(LIBXR_SYSTEM FreeRTOS)
set(LIBXR_DRIVER st)
# User sources of the application; "" when CMakeLists.txt adds them itself.
set(LIBXR_USER_SOURCES_GLOB "${CMAKE_CURRENT_SOURCE_DIR}/User/*.cpp")
# Optimization level of the application in Debug builds and of everything in Release builds;
# "" keeps the level of the toolchain file.
set(LIBXR_OPT_DEBUG "-Og")
set(LIBXR_OPT_RELEASE "")
  • LIBXR_SYSTEM:LibXR 的系统层,由 Core/Inc 中的 FreeRTOSConfig.h 或 app_threadx.h 决定,为 FreeRTOS、ThreadX 或 None(裸机);
  • LIBXR_DRIVER:LibXR 的外设后端,STM32 工程为 st。这两项的含义见 LibXR 的 CMake 配置;
  • XROBOT_MODULES_DIR:User/app_main.cpp 由 --xrobot 生成时加入,见与XRobot集成;
  • LIBXR_USER_SOURCES_GLOB:加入应用目标的源文件,默认为 User/ 下的全部 .cpp 文件;值为 "" 时不加入,由 CMakeLists.txt 自行加入;
  • LIBXR_OPT_DEBUG(默认 -Og):应用目标(含 User/ 下的源文件)在 Debug 构建中的优化级别;Debug 构建中 LibXR 库 xr(XRobot 模块的源文件也加入 xr)和 CubeMX 生成的库(HAL、FreeRTOS 等)使用 -O2;
  • LIBXR_OPT_RELEASE(默认 ""):Release 构建中应用、xr 和这些库的优化级别,值为 "" 时沿用工具链文件的级别(CubeMX 写出的 GCC 为 -Os、ST Arm Clang 为 -Oz)。

文件的其余部分(LibXR、应用目标、库的优化级别和构建产物)由生成器维护。libxr stm32 cmake 和 libxr stm32 setup 改写文件时保留 “Project settings” 块的内容,只按工程校正 LIBXR_SYSTEM 和 XROBOT_MODULES_DIR,并以默认值补上缺少的设置;其余部分重新生成。

旧版本生成的 LibXR.CMake 第一行没有这行生成说明,改写时迁移到上述结构:LIBXR_DRIVER 和 XROBOT_MODULES_DIR 保留;LIBXR_OPT_DEBUG 取旧文件 Debug 块中应用目标的 -O 选项,没有时取工具链文件的 Debug 级别,LIBXR_OPT_RELEASE 取工具链文件的 Release 级别;只有一个模式的 file(GLOB LIBXR_USER_SOURCES ...) 写入 LIBXR_USER_SOURCES_GLOB。生成器不认识的语句都保留:add_subdirectory(LibXR) 之前的放进 “Project settings” 块,之后的放进文件末尾的 “Kept from the earlier LibXR.CMake” 块,这个块在以后改写时同样保留。

旧 Debug 块中其他目标的优化选项与旧模板相同时(xr、FreeRTOS、STM32_Drivers、USB_Device_Library 的 -O2),由新结构重新生成;用户改过的级别和加入的目标连同 if(TARGET ...) 原样放进 “Kept from the earlier LibXR.CMake” 块,包在同样的 if(CMAKE_BUILD_TYPE STREQUAL "Debug") 中。这个块排在库的 -O2 之后,因此这些选项仍然生效,每条记录一条说明。例如旧文件把 xr 改为 -O1、给 ThreadX 加了 -O0 时,Kept 块的末尾为:

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(xr PRIVATE -O1)
if(TARGET ThreadX)
target_compile_options(ThreadX PRIVATE -O0)
endif()
endif()

构建产物​

应用每次链接之后,cmake/LibXR.CMake 用工具链文件中的 CMAKE_OBJCOPY 由 ELF 生成 <工程名>.hex(Intel HEX)和 <工程名>.bin(二进制镜像)。三个文件位于同一目录,用 CubeMX 的 preset 构建时为 build/<preset>/;<工程名> 是 CMakeLists.txt 中设置的 CMAKE_PROJECT_NAME。已有工程的 LibXR.CMake 在下次运行 libxr stm32 cmake 或 libxr stm32 setup 时加入这一步。


相关命令​

命令功能说明
libxr stm32 cubemx-gen仅执行 STM32CubeMX 脚本模式生成
libxr parse解析 .ioc,写出 .config.yaml
libxr gen由 libxr parse 写出的 YAML 生成入口源文件、app_main.h 和 flash_map.hpp,并更新 libxr_config.yaml
libxr stm32 flash-info以 YAML 打印 STM32 型号的内部 Flash 布局
libxr stm32 cmake修改 CMake 构建文件,集成 LibXR
libxr stm32 toolchain切换工具链和标准库

各命令的全部参数及默认值由 libxr <命令> --help 列出,例如 libxr gen --help、libxr stm32 cubemx-gen --help。

libxr 6.0.0 之前的 xr_* 命令(如 xr_cubemx_cfg)仍可使用,参数及其含义与原来相同,运行时会提示对应的新命令,7.0.0 删除;对照表见 CodeGenerator 的 README。

参考​