CMake on STM32 | Episode 4: get ready for C++
Pierre Gradot
Posted on May 2, 2020
I have been using C++ for the last 4 years... there is no way I get back to C! This episode is not about explaining why I think C++ is better than C. It is simply about running C++ on STM32 with CMake. By the way: we will make LEDs blink!
Adding user code in code generated by STM32CubeMX
If we change the MCU configuration with STM32CubeMX and generate the code again, the previous files are overwritten. How can we create our own program if everything is erased each time the code is generated?
Let's have look at the main()
function in main.c
. After board initialization, it runs the classic infinite loop that (almost?) all embedded systems have:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
Notice the comments: they are tags for the code generator. Code between consecutive USER CODE BEGIN xxx and USER CODE END xxx tags is preserved during code generation.
You will find tags like these in many places in the generated code.
For instance stm32f4xx_it.c
(which contains interrupt service routines) contains many of theses tags. For example:
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
Blink LEDs in C++
Currently the application does nothing because the while loop is empty. Let's create a basic application to toggle LEDs using C++.
First, let's create source/application.hpp
to declare two functions in a pure Arduino style, setup() and loop() :
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void setup();
void loop();
#ifdef __cplusplus
}
#endif
Then, let's create source/application.cpp
to define these functions:
#include "application.hpp"
#include "stm32f4xx_ll_gpio.h"
#include "main.h"
extern "C" {
void setup() {
// For future episodes ;)
}
void loop() {
LL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
LL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
LL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
HAL_Delay(200U);
}
}
We can now call these functions from main()
. First, we have to include our file in main.c
between USER CODE tags:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "application.hpp"
/* USER CODE END Includes */
Then we modify the infinite loop:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
setup();
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
loop();
}
/* USER CODE END 3 */
You can open BSP/BSP.ioc
with STM32CubeMX and generate the code again to check that the modifications are kept.
Compile C++ code
Let's modify CMakeLists.txt
to build our C++ application.
Language settings
C++ language is enabled by default but it is good to configure the version of the standard. ARM GCC 9 supports C++17 so this is the one to use:
enable_language(CXX C ASM)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
Target configuration
We have new files so we have to add them to add_executable()
:
add_executable(${EXECUTABLE} ${STM32CUBEMX_GENERATED_FILES}
source/application.cpp
source/application.hpp)
We have a new directory with headers to we have to modify the include path:
target_include_directories(${EXECUTABLE} PRIVATE
BSP/Inc
BSP/Drivers/STM32F4xx_HAL_Driver/Inc
BSP/Drivers/CMSIS/Device/ST/STM32F4xx/Include
BSP/Drivers/CMSIS/Include
source/
)
Linker options
Let's build... and get some weird errors from the linker:
[ 3%] Linking CXX executable nucleo-f413zh.out
c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld.exe: c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard\libg_nano.a(lib_a-abort.o): in function `abort':
abort.c:(.text.abort+0xa): undefined reference to `_exit'
c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld.exe: c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard\libg_nano.a(lib_a-signalr.o): in function `_kill_r':
signalr.c:(.text._kill_r+0xe): undefined reference to `_kill'
c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld.exe: c:/progra~2/gnutoo~1/92019-~1/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard\libg_nano.a(lib_a-signalr.o): in function `_getpid_r':
signalr.c:(.text._getpid_r+0x0): undefined reference to `_getpid'
collect2.exe: error: ld returned 1 exit status
mingw32-make[3]: Leaving directory 'D:/cmake_stm32/cmake-build-debug'
mingw32-make[3]: *** [CMakeFiles\nucleo-f413zh.out.dir\build.make:435: nucleo-f413zh.out] Error 1
mingw32-make[2]: *** [CMakeFiles\Makefile2:75: CMakeFiles/nucleo-f413zh.out.dir/all] Error 2
mingw32-make[2]: Leaving directory 'D:/cmake_stm32/cmake-build-debug'
mingw32-make[1]: Leaving directory 'D:/cmake_stm32/cmake-build-debug'
mingw32-make[1]: *** [CMakeFiles\Makefile2:82: CMakeFiles/nucleo-f413zh.out.dir/rule] Error 2
mingw32-make: *** [makefile:117: nucleo-f413zh.out] Error 2
We can see in the error messages that the problem comes from libg_nano.a
, the C++ standard library used by our toolchain. Internet gave two solutions:
- Add
-fno-exceptions
to the compiler options. - Change
-specs=nano.specs
to-specs=nosys.specs
in the linker option.
I must admit that my knowledge reaches its limits here, especially on specs files... For this reason, I prefer to use the first solution because I clearly understand its ins and outs and because I don't use exceptions in embedded C++. Note that Google C++ Style Guide discourages exceptions in general.
Conclusion
Hurray! LEDs are blinking thanks to C++!
There wasn't a lot to do: create our files, add them to the build, configure the C++ standard. OK, we had a little linker issue but disabling exceptions was enough to solve it.
In a further episode, we will improve our C++ configuration by adding C++ specific options to the compiler.
Posted on May 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.