Skip to main content

STM32 Code Generation

The libxr stm32 setup command of the code generator (the pip package libxr) sets up an STM32CubeMX project for LibXR: it parses the .ioc file, generates the C++ initialization code and integrates LibXR into the CMake build.


Quick Start​

In the root directory of the STM32CubeMX project, run:

libxr stm32 setup -d .

With the XRobot.xrobot extension in VS Code, the configuration can also be edited on the GUI page of the extension, which runs the generation command after each change.

The command performs these steps in order:

  1. Adds the LibXR submodule Middlewares/Third_Party/LibXR and writes .gitmodules (running git init first when the project is not a Git repository yet); an existing checkout is kept, and --commit switches it to a given commit;
  2. Creates or completes .gitignore and .gitattributes;
  3. Locates the .ioc file and parses it into .config.yaml;
  4. Generates app_main.cpp, app_main.h, flash_map.hpp and libxr_config.yaml in User/;
  5. Generates cmake/LibXR.CMake and includes it from CMakeLists.txt.

When the directory holds only the .ioc file and no code generated by STM32CubeMX yet, run libxr stm32 cubemx-gen first: it runs STM32CubeMX in script mode to generate the project from the .ioc file; then run libxr stm32 setup. The dialogs STM32CubeMX shows are answered as the options say: when the project was saved by another STM32CubeMX version, --firmware keep keeps its firmware package (Continue) and --firmware migrate migrates the project; --download lets STM32CubeMX download a missing firmware package and accept its license. A dialog without a matching option stops the run and reports its content. On Linux dialogs are not answered, and any dialog stops the run.


Example Output​

[INFO] Default LibXR commit: 783b157fb3e9e5a7801edc9083834ba3ac49801f
[INFO] User/app_main.cpp uses XRobot; generating with --xrobot (--no-xrobot turns it off).
[INFO] Keeping the LibXR checkout 783b157fb3e9.
[INFO] Found .ioc file: .\STM32F103RC.ioc
[INFO] Parsing .ioc file...
[INFO] Processing STM32F103RC.ioc...
[INFO] Configuration exported to: .\.config.yaml
[INFO] Generating C++ code...
[INFO] System: FreeRTOS
[INFO] Generated User: unchanged app_main.cpp, app_main.h, flash_map.hpp, libxr_config.yaml
[INFO] LibXR.CMake already up to date, no changes needed.
[INFO] LibXR.CMake already included in CMakeLists.txt.
[INFO] [Pass] All tasks completed.
[INFO] Build: cmake --preset debug && cmake --build --preset debug

The generator writes only files whose content changed; the project above is already up to date, so none of the four files is rewritten. The last line is the build command of the first preset in CMakePresets.json; while no source file in Core/Src calls app_main(), a line before it names where to call it; see How to Use.


Output Structure​

After execution, the project directory contains the following generated or modified files:

.
├── .config.yaml # Parsed configuration from .ioc
├── .gitignore
├── .gitattributes
├── .gitmodules
├── User/
│ ├── app_main.cpp # Entry source
│ ├── app_main.h # Header of the entry source
│ ├── libxr_config.yaml # LibXR runtime configuration
│ └── flash_map.hpp # Flash address mapping table
├── cmake/LibXR.CMake # CMake build config for LibXR
├── CMakeLists.txt # Modified to include LibXR
└── Middlewares/Third_Party/LibXR # LibXR as a Git submodule

app_main.cpp​

The app_main.cpp file contains the LibXR initialization function app_main().

Code between /* User Code Begin N */ and /* User Code End N */ is kept when the file is regenerated; everything else is regenerated every time.

Generated peripheral objects are static objects and stay valid after app_main() returns; local objects in User Code without static are destroyed when app_main() returns, so objects used for the lifetime of the program are declared static. Peripheral objects can be passed to threads or tasks as base class pointers; Integrate with XRobot in this chapter describes a more complete approach.

An excerpt of the app_main.cpp generated for an STM32F407 project (without --xrobot); omitted lines are marked // ...:

// 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 */
}
Code block collapsed

Peripheral objects are static objects. DMA buffers take one of two forms: aligned to the cache line on chips with a data cache (see Cache), and arrays aligned to 4 bytes on the others.


How to Use​

Call app_main() from the entry of the project:

Bare-metal Project​

#include "app_main.h"

int main() {
HAL_Init();
SystemClock_Config();
...
app_main(); // Initialize peripherals and start application
while (1) {
...
}
}

FreeRTOS Project​

Call app_main() in the default task StartDefaultTask. app_main() runs on the stack of the default task, whose stack depth is set in STM32CubeMX and has to hold it.

CMSIS_V2 is the recommended FreeRTOS interface. Firmware packages such as STM32Cube FW_H7 V1.13.0 have removed the CMSIS-RTOS V1 wrapper, so projects on such a package can only use CMSIS_V2. When a project with the CMSIS_V1 interface moves to such a package, STM32CubeMX removes FreeRTOS entirely, without a message. Open such a project with Continue to keep its firmware package, switch the interface to CMSIS_V2, then migrate.

With CMSIS_V2, STM32CubeMX always enables FreeRTOS software timers (configUSE_TIMERS), which adds a timer task. LibXR runs its timed tasks (LibXR::Timer) in its own thread, whose priority and stack depth PlatformInit() sets; when configUSE_TIMERS is on, LibXR warns at compile time. Overriding the following definitions in the USER CODE BEGIN Defines section of Core/Inc/FreeRTOSConfig.h turns off the software timers, together with the CMSIS-RTOS2 osTimer API and event flags set from interrupts, which depend on them; INCLUDE_xTimerPendFunctionCall stays off. A project with INCLUDE_xTimerPendFunctionCall disabled in STM32CubeMX requires this override; without it, freertos_os2.h stops the build with #error "Definition INCLUDE_xTimerPendFunctionCall must equal 1 to implement Event Flags API.". That section is kept when code is regenerated.

/* 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 holds the settings of code generation. When the file does not exist, libxr gen creates it with the defaults; every later generation adds missing settings and keeps the values, comments and keys the generator does not use. A peripheral section is written only when the project has that peripheral, with one entry per instance keyed by the lower-case instance name, such as spi1.

The table lists the top-level keys; config_version is written by the user:

KeyDefaultDescription
generatorThe installed version, such as 6.0.1, written when the file is createdPins the code generator version, see Integrate with XRobot
terminal_source''Serial port of the terminal, see UART and Terminal
software_timerpriority: 2, stack_depth: 1024Software timer thread, see Software Timer
SPItx_buffer_size: 32, rx_buffer_size: 32, dma_section: ''; dma_enable_min_size: 3 as well with DMA in both directionsSee SPI
I2Cbuffer_size: 32, dma_section: '', dma_enable_min_size: 3See I2C
USARTtx_buffer_size: 128, rx_buffer_size: 128, dma_section: '', tx_queue_size: 5USART, UART and LPUART, see UART and Terminal
ADCbuffer_size: 32, dma_section: '', vref: 3.3See ADC
CANqueue_size: 5See CAN & CAN FD
FDCANqueue_size: 5See CAN & CAN FD
USBenable; with true the other settings are filled inSee UART and Terminal
Terminalread_buff_size: 32, max_line_size: 32, max_arg_number: 5, max_history_number: 5; run_as_thread: false as well when a terminal is generatedSee UART and Terminal
databaseenable: false, block_size: autoSee Flash Database
SYSTEMFrom the .iocFreeRTOS, ThreadX or None (bare metal), see below
DACinit_voltage: 0.0, vref: 3.3See DAC
IWDGtimeout_ms: 1000, feed_interval_ms: 250See Watchdog
Watchdogrun_as_thread: false, feed_interval_ms: 250Written when an IWDG is enabled, see Watchdog
config_versionNot written; read as 1Version of the file format, see below

The generator writes SYSTEM from the .ioc and ignores it when reading; the system always comes from the .ioc. When config_version is above 1 or not an integer, libxr gen warns that settings of a newer format may have no effect; generation goes on and the key stays as written.

Keys written by earlier versions are converted or removed on regeneration: device_aliases is removed, generated objects are registered only under their own names, and the aliases in it that differ from their object are listed in a warning (alias -> object); FlashLayout is removed, the Flash layout being written only to flash_map.hpp; cdc_tx_fifo_size, cdc_rx_fifo_size and cdc_queue_size of an enabled USB peripheral become one item of the cdc list, or are only removed when cdc exists, see UART and Terminal; upper-case instance keys under CAN and FDCAN (such as CAN1) become lower case.


Regenerating the Code​

After a change to User/libxr_config.yaml or to the configuration in STM32CubeMX, run again in the project root:

libxr stm32 setup -d .

To regenerate only the files in User/, write .config.yaml from the .ioc first, then run libxr gen:

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

.config.yaml is listed in the generated .gitignore, so a fresh clone of the project does not have it and needs libxr parse first.


Optional Arguments​

The arguments of libxr stm32 setup are listed below; libxr stm32 setup --help lists them as well.

ArgumentDescription
-d, --directorySpecify STM32 project root directory (default: current directory)
-t, --terminalSet the terminal device (such as usart1 or usb_fs_cdc), stored as terminal_source in User/libxr_config.yaml
--xrobot / --no-xrobotEmit XR_REGISTER registrations and XROBOT_MAIN();, or not, see Integrate with XRobot; without either the project keeps its choice
--commitCheck out the LibXR submodule at this commit; without it the existing checkout is kept, and a new submodule starts at the commit this libxr release pins
--git-sourceThe Git source a missing LibXR is cloned from: auto (default), github, a base URL, a repository URL or a local repository; .gitmodules always records the GitHub URL
--git-mirrorsComma-separated mirror base or repository URLs, tried with --git-source auto; the environment variable XR_GIT_MIRRORS takes the same form
--verboseEnable debug logging

Toolchain Switch​

To switch between GCC and Clang or change the Clang standard library, use:

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

The command edits CMakePresets.json and cmake/starm-clang.cmake. Switching between gcc and clang removes the build/ and cmake-build* directories: CMake does not change the compiler of an existing build directory, so the next build configures a new one. clang without a standard library option keeps the current one.

-g (hybrid) compiles with ST Arm Clang and links with the GNU toolchain. cmake/starm-clang.cmake finds the GNU toolchain from the environment variables CLANG_GCC_CMSIS_COMPILER (the ST Arm Clang installation directory, holding multilib.gnu_tools_for_stm32.yaml) and GCC_TOOLCHAIN_ROOT (the bin directory of arm-none-eabi-gcc), so both must be set before building.


Project Requirements​

  • Must be a CMake project exported from STM32CubeMX
  • Must contain a valid .ioc file
  • Must enable Mutex when using FreeRTOS(configUSE_MUTEXES)
  • Peripherals use the HAL driver, with Generate Code left on in the Project Manager
  • The HAL timebase uses a general-purpose timer at the highest interrupt priority, see Time Base

LibXR.CMake​

The "Project settings" block at the top of cmake/LibXR.CMake holds the build settings of the project. Below is the beginning of the file generated for a FreeRTOS project: the generated-file line, then the settings block.

# 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: the LibXR system layer, decided by FreeRTOSConfig.h or app_threadx.h in Core/Inc: FreeRTOS, ThreadX or None (bare metal);
  • LIBXR_DRIVER: the LibXR peripheral backend, st for STM32 projects. Both are described in the LibXR CMake Configuration;
  • XROBOT_MODULES_DIR: added when User/app_main.cpp was generated with --xrobot, see Integrate with XRobot;
  • LIBXR_USER_SOURCES_GLOB: the sources added to the application target, by default every .cpp file in User/; with "" none are added and CMakeLists.txt adds them itself;
  • LIBXR_OPT_DEBUG (default -Og): the Debug optimization level of the application target, including the sources in User/; in Debug builds the LibXR library xr (which also compiles the sources of XRobot Modules) and the libraries CubeMX generates (HAL, FreeRTOS and so on) use -O2;
  • LIBXR_OPT_RELEASE (default ""): the Release level of the application, xr and these libraries; "" keeps the level of the toolchain file (-Os for GCC and -Oz for ST Arm Clang as CubeMX writes them).

The generator maintains the rest of the file (LibXR, the application target, the optimization of the libraries and the build output). When libxr stm32 cmake or libxr stm32 setup rewrites the file, the content of the "Project settings" block is kept: only LIBXR_SYSTEM and XROBOT_MODULES_DIR are corrected for the project, and missing settings are added with their defaults; the rest is generated again.

A LibXR.CMake generated by an earlier version lacks the generated-file line and migrates to this structure when rewritten: LIBXR_DRIVER and XROBOT_MODULES_DIR are kept; LIBXR_OPT_DEBUG takes the -O option of the application target in the old Debug block, or the Debug level of the toolchain files without one, and LIBXR_OPT_RELEASE takes the Release level of the toolchain files; a file(GLOB LIBXR_USER_SOURCES ...) with a single pattern goes into LIBXR_USER_SOURCES_GLOB. Every statement the generator does not recognize is kept: those before add_subdirectory(LibXR) go into the "Project settings" block and those after it into the "Kept from the earlier LibXR.CMake" block at the end of the file, which later rewrites keep as well.

Optimization options of other targets in the old Debug block that match the old template (-O2 for xr, FreeRTOS, STM32_Drivers and USB_Device_Library) are generated again by the new structure; levels the user changed and targets the user added go into the "Kept from the earlier LibXR.CMake" block as written, with their if(TARGET ...), inside the same if(CMAKE_BUILD_TYPE STREQUAL "Debug"). That block comes after the -O2 of the libraries, so these options still apply, and each one is noted in the log. For example, when the old file set xr to -O1 and added -O0 for ThreadX, the Kept block ends with:

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

Build Output​

After each link of the application, cmake/LibXR.CMake makes <project>.hex (Intel HEX) and <project>.bin (binary image) from the ELF with the CMAKE_OBJCOPY of the toolchain file. The three files are in the same directory, build/<preset>/ when built with a CubeMX preset; <project> is the CMAKE_PROJECT_NAME set in CMakeLists.txt. The LibXR.CMake of an existing project gains this step on the next run of libxr stm32 cmake or libxr stm32 setup.


CommandDescription
libxr stm32 cubemx-genRun STM32CubeMX script-mode generation only
libxr parseParses .ioc and writes .config.yaml
libxr genGenerates the entry source, app_main.h and flash_map.hpp from the YAML written by libxr parse, and updates libxr_config.yaml
libxr stm32 flash-infoPrints the internal flash layout of an STM32 model as YAML
libxr stm32 cmakeIntegrates LibXR into the project build system
libxr stm32 toolchainSwitch toolchain and standard library

libxr <command> --help lists all arguments of a command with their defaults, for example libxr gen --help or libxr stm32 cubemx-gen --help.

The xr_* commands of libxr before 6.0.0 (such as xr_cubemx_cfg) still work with their arguments and meanings unchanged: they name their new command when they run and are removed in 7.0.0. The CodeGenerator README lists them.


References​

LibXR STM32 Code Generation Tool Test Project (Github Action)

LibXR CLI tool and documentation (PyPI)