STM32 代码生成
代码生成器(pip 包 libxr)的 libxr stm32 setup 命令把 STM32CubeMX 工程接入 LibXR:解析 .ioc 文件,生成 C++ 初始化代码,并把 LibXR 接入 CMake 构建。
快速使用
在 STM32CubeMX 工程根目录下执行:
libxr stm32 setup -d .
在 VS Code 中使用 XRobot.xrobot 插件时,也可以在插件提供的 GUI 页面中修改配置,修改后插件自动执行生成命令。
该命令依次完成以下步骤:
- 加入 LibXR 子模块
Middlewares/Third_Party/LibXR并写入.gitmodules(工程还不是 Git 仓库时先执行git init);已有的检出保持不变,--commit切换到指定提交; - 创建或补全
.gitignore和.gitattributes; - 查找
.ioc文件并解析为.config.yaml; - 在
User/下生成app_main.cpp、app_main.h、flash_map.hpp和libxr_config.yaml; - 生成
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_timer | priority: 2、stack_depth: 1024 | 软件定时器线程,见软件定时器 |
SPI | tx_buffer_size: 32、rx_buffer_size: 32、dma_section: '';两个方向都开启了 DMA 时另有 dma_enable_min_size: 3 | 见 SPI |
I2C | buffer_size: 32、dma_section: ''、dma_enable_min_size: 3 | 见 I2C |
USART | tx_buffer_size: 128、rx_buffer_size: 128、dma_section: ''、tx_queue_size: 5 | USART、UART 和 LPUART,见串口与终端 |
ADC | buffer_size: 32、dma_section: ''、vref: 3.3 | 见 ADC |
CAN | queue_size: 5 | 见 CAN 与 CAN FD |
FDCAN | queue_size: 5 | 见 CAN 与 CAN FD |
USB | enable,为 true 时补齐其余设置 | 见串口与终端 |
Terminal | read_buff_size: 32、max_line_size: 32、max_arg_number: 5、max_history_number: 5;生成终端时另有 run_as_thread: false | 见串口与终端 |
database | enable: false、block_size: auto | 见 Flash 数据库 |
SYSTEM | 由 .ioc 得出 | FreeRTOS、ThreadX 或 None(裸机),见下文 |
DAC | init_voltage: 0.0、vref: 3.3 | 见 DAC |
IWDG | timeout_ms: 1000、feed_interval_ms: 250 | 见看门狗 |
Watchdog | run_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。