CMake on STM32 | Episode 4: get ready for C++

pgradot

Pierre Gradot

Posted on May 2, 2020

CMake on STM32 | Episode 4: get ready for C++

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 */


Enter fullscreen mode Exit fullscreen mode

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 */
  }
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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);
}

}


Enter fullscreen mode Exit fullscreen mode

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 */


Enter fullscreen mode Exit fullscreen mode

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 */


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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/
        )


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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:

  1. Add -fno-exceptions to the compiler options.
  2. 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++!

Alt Text

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.

💖 💪 🙅 🚩
pgradot
Pierre Gradot

Posted on May 2, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related