Using CMake with STM32CubeMX generated projects
This is an example for managing a STM32CubeMX project that uses HAL library by CMake.
The directory layout is something like this:
+-- CMakeLists.txt (1)
+-- arm-toolchain.cmake (2)
+-- CUBEMX/
+-- CMakeLists.txt (3)
+-- CUBEMX.ioc
+-- startup_stm32f030x6.s
+-- STM32F030K6Tx_FLASH.ld
+-- include/
+-- Drivers/
+-- ...
+-- Src/
+-- ...
+-- Inc/
+-- ...
+-- b-cm0/ (4)
+-- b-build/ (5)
The CUBEMX
is the STM32CubeMX project. We have to add 3 files which I marked
with (1)
, (2)
and (3)
above.
In this example target micro-controller is in STM32F030x6
family.
This is CMakeLists.txt
in the top-level directory ((1)
):
# There are two categories of targets:
# build This PC (*_build)
# host ARM uC (*_cm0)
#
# (adopted from gcc terminology)
cmake_minimum_required(VERSION 3.13)
project(STM32Firmware
DESCRIPTION "Firmware for STM32"
LANGUAGES C ASM
)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og3 -DUSE_FULL_ASSERT")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_library(base INTERFACE)
target_compile_options(base
INTERFACE
-Wall
# -Werror
)
target_include_directories(base
INTERFACE
include/
)
if(CMAKE_CROSSCOMPILING)
add_library(base_cm0 INTERFACE)
target_link_libraries(base_cm0
INTERFACE
base
c
m
nosys
)
target_compile_options(base_cm0
INTERFACE
-mcpu=cortex-m0
-mthumb
-fdata-sections
-ffunction-sections
)
target_link_options(base_cm0
INTERFACE
-mcpu=cortex-m0
-mthumb
-specs=nano.specs
-Wl,--gc-sections
)
target_compile_definitions(base_cm0
INTERFACE
USE_HAL_DRIVER
STM32F030x6
)
set(plat cm0)
else() # if(CMAKE_CROSSCOMPILING)
add_library(base_build INTERFACE)
target_link_libraries(base_build
INTERFACE
base
)
target_compile_options(base_build
INTERFACE
-fno-omit-frame-pointer
-fsanitize=address
-fsanitize=undefined
# -fanalyzer # gcc >= 10
)
target_link_options(base_build
INTERFACE
-fno-omit-frame-pointer
-fsanitize=address
-fsanitize=undefined
)
set(plat build)
endif() # if(CMAKE_CROSSCOMPILING)
# add HAL target
function(add_hal_target target)
file(GLOB_RECURSE
${target}_files
"Drivers/*.c"
)
add_library(${target}
${${target}_files}
)
target_link_libraries(${target}
PUBLIC
base_${plat}
)
target_include_directories(${target}
PUBLIC
Inc/
Drivers/STM32F0xx_HAL_Driver/Inc/
Drivers/STM32F0xx_HAL_Driver/Inc/Legacy
Drivers/CMSIS/Device/ST/STM32F0xx/Include
Drivers/CMSIS/Include
)
endfunction()
# auxiliary targets to make it easy to upload the firmware using `st-flash`
# utility
function(uc_add_support_targets target)
# show size of produced executable for all targets
add_custom_target(${target}_size
ALL
COMMAND ${UTL_SIZE} ${target}
DEPENDS ${target}
COMMENT "Size of ${target}"
)
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${UTL_OBJCOPY} -O ihex ${target} ${target}.hex
BYPRODUCTS ${target}.hex
COMMENT "Generating ihex output format"
)
add_custom_target(${target}_bin
${UTL_OBJCOPY} -O binary -S ${target} ${target}.bin
DEPENDS ${target}
BYPRODUCTS ${target}.bin
COMMENT "Generating binary output format"
)
add_custom_target(${target}_upload
st-flash --reset write ${target}.bin 0x8000000
DEPENDS ${target}.bin
COMMENT "Uploading ${target}"
)
add_custom_target(${target}_erase_upload
st-flash erase && st-flash --reset write ${target}.bin 0x8000000
DEPENDS ${target}.bin
COMMENT "Uploading ${target}"
)
endfunction()
add_subdirectory(CUBEMX)
There are two categories of targets: one for the micro-controller and the other
for the build machine (the machine that is currently building the sources).
Tests for the software components should be compiled and run on the build
machine (running tests on a micro-controller doesn't make sense for me).
We enable address and undefined behavior sanitizers and if you're using
gcc version 10 or newer, you can also enable the static analyzer using
-fanalyze
compiler option.
CMakeLists.txt
inside the CUBEMX
directory ((3)
):
if(NOT plat STREQUAL build) # code for micro-controller
set(
cubemx_ld_script
"${CMAKE_CURRENT_SOURCE_DIR}/STM32F030K6Tx_FLASH.ld"
)
add_hal_target(cubemx_hal_${plat})
add_executable(cubemx_${plat}
Src/main.c
Src/stm32f0xx_hal_msp.c
Src/stm32f0xx_it.c
Src/system_stm32f0xx.c
Inc/main.h
Inc/stm32f0xx_hal_conf.h
Inc/stm32f0xx_it.h
startup_stm32f030x6.s
)
target_link_libraries(cubemx_${plat}
PUBLIC
base_${plat}
cubemx_hal_${plat}
cubemx
)
target_link_options(cubemx_${plat}
PUBLIC
-T${cubemx_ld_script}
)
set_target_properties(cubemx_${plat}
PROPERTIES
LINK_DEPENDS ${cubemx_ld_script}
)
uc_add_support_targets(cubemx_${plat})
else() # if(NOT plat STREQUAL build)
## code for build machine (tests)
#
# # Here you can add tests of your software components
# # I commented out this section for simplification.
# add_executable(component-a.test_${plat}
# Test/componenet-a.test.c
# )
# target_link_libraries(component-a.test_${plat}
# PRIVATE
# base_${plat}
# )
endif()
To enable cmake to do the cross-compilation, we need a toolchain file
in the top-level directory ((2)
):
# arm-toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TOOLCHAIN_PREFIX arm-none-eabi-)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -x assembler-with-cpp")
set(UTL_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy)
set(UTL_SIZE ${TOOLCHAIN_PREFIX}size)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
Build
I highly recommend to use Ninja build system as
the generator for cmake. (You can find the package name for you OS
here).
It's much faster than make
. But if you don't want the Ninja generator,
just omit the -G Ninja
option from the following commands.
We build firmware in b-cm0
directory ((4)
) and tests in b-build
directory
((5)
).
Firmware
To build the firmware:
- Prepare the build directory (should be done only once):
# in top-level directory
mkdir b-cm0
cd b-cm0
cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake ..
- Build the target
cmake --build . --target cubemx_cm0
After building the firmware, it will show the output of size
utility.
- Upload the firmware (using
st-flash
program)
cmake --build . --target cubemx_cm0_upload
# or,
# cmake --build . --target cubemx_cm0_erase_upload # first erase the chip, and then upload
Tests
If you have tests (which you must have in any non-trivial project), you can build them easily:
# in top-level directory
mkdir b-build
cd b-build
cmake -G Ninja ..
cmake --build .
More notes
You can manage more than one STM32CubeMX project easily; you only need to add
one extra add_subdirectory(...)
at the end of top-level CMakeLists.txt
((1)
), and one CMakeLists.txt
file inside new project directory
(equivalent to (3)
).
You can also use CMake to run the tests.