STM32 Cmake Quickstart Guide (Linux)

by alderon_creates in Circuits > Microcontrollers

18 Views, 0 Favorites, 0 Comments

STM32 Cmake Quickstart Guide (Linux)

Pasted image 20260102124853.png

This guide will provide details on how to create a build system for a STM32 microcontroller on Debian/Ubuntu Linux. This build system will utilise CMake, STM32 HAL and git as well as some other bits and pieces. I will be using a F4 series STM32, the NUCLEO-F446RE. I will also be using vim, but if you are more familiar with nano, use that in place of the vim commands. I highly recommend reading through the steps before attempting this for yourself.


This guide assumes:

  1. familiarity with using vim or nano
  2. familiarity with navigating a linux file system in terminal
  3. F4 series STM32 is being used

Supplies

  1. STM32 microcontroller board
  2. A cable for your STM32
  3. Have super user privelages

Install Required Packages

To begin, install the following packages using apt:

sudo apt update
sudo apt install -y \
gcc-arm-none-eabi \
gdb-multiarch \
openocd \
cmake \
make \
ninja-build \
git \
stlink-tools \
libusb-1.0-0-dev


The installed versions can be checked using:

arm-none-eabi-gcc --version
openocd --version
cmake --version


Setup Udev Rules for Flasing

It will be necessary to setup the udev rules on linux so that builds have permission to be flashed to the STM board. To create the rules, execute the following:

cd /etc/udev/rules.d
sudo vim 49-stlinkv2.rules

Add the following lines to the newly created file, write the changes and quit.

SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374a", MODE="0666"


Connect your STM32 via the cable to your computer and complete the following to enable the rules and check that permissions are correct.

cd ~/

sudo udevadm control --reload-rules
sudo udevadm trigger

# connect your STM32 board and test that it has permissions to connect
st-info --probe

# If successful you should get a readout similar to:

Found 1 stlink programmers
version: V2J30S20
serial: 0677FF495355878281234523
flash: 524288 (pagesize: 131072)
sram: 131072
chipid: 0x421
dev-type: STM32F446




Create Workspace

Directory Setup

For this guide the home directory will be used (~/). Create a new directory to hold your build system and drivers, and download the F4 series drivers:


mkdir ~/stm32
cd ~/stm32

git clone --recursive https://github.com/STMicroelectronics/STM32CubeF4.git


Your directory tree should now look like this (there are other folders also populated here, but these are the main ones we are concerned with currently):


stm32/
|--STM32CubeF4
| |--Drivers/
| |--CMSIS/
| |--STM32F4xx_HAL_Driver/
| |-- ..


Create an additional directory to hold your project:


mkdir project
cd project


Your directory tree should now look like this:

stm32/
|--project
|--STM32CubeF4
| |--Drivers/
| |--CMSIS/
| |--STM32F4xx_HAL_Driver/
| |-- ..


The working directory structure for the project now needs to be created. Once we are done it will look like the outline below:

mkdir cmake
mkdir linker
mkdir Core
mkdir Core/Src
mkdir Core/Inc
mkdir startup
mkdir build


project/
|--build
|--CMakeLists.txt
|--cmake/
│ |--arm-none-eabi.cmake
|--linker/
│ |--STM32F446RE_FLASH.ld
|--Core/
│ |--Inc/
│ |-- main.h
│ |-- stm32f4xx_hal_conf.h
│ |--Src/
│ |-- main.c
│ |-- system_stm32f4xx.c
|-- startup/
|-- startup_stm32f446xx.s


Top Level CMakeLists

Create CmakeLists.txt and populate it with the below text:

cmake_minimum_required(VERSION 3.22)
project(stm32_test_project C)

set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/linker/STM32F446RE_FLASH.ld)

include(cmake/arm-none-eabi.cmake)

add_executable(${PROJECT_NAME}.elf
Core/Src/main.c
Core/Src/system_stm32f4xx.c
startup/startup_stm32f446xx.s
)

target_include_directories(${PROJECT_NAME}.elf PRIVATE
Core/Inc
../STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Inc
../STM32CubeF4/Drivers/CMSIS/Include
../STM32CubeF4/Drivers/CMSIS/Device/ST/STM32F4xx/Include
)

target_compile_options(${PROJECT_NAME}.elf PRIVATE -Wall -Wextra -fdiagnostics-color=always)

file(GLOB HAL_SRC
../STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Src/*.c
)

target_sources(${PROJECT_NAME}.elf PRIVATE ${HAL_SRC})

target_compile_definitions(${PROJECT_NAME}.elf PRIVATE
STM32F446xx
USE_HAL_DRIVER
)

add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O ihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${CMAKE_SIZE} ${PROJECT_NAME}.elf
)


Linker

In project/linker run the following command or manually copy the file in file explorer and rename it.

cp ../../STM32CubeF4/Projects/STM32F446ZE-Nucleo/Templates/STM32CubeIDE/STM32F446ZETX_FLASH.ld STM32F446RE_FLASH.ld


Startup File

In project/startup run the following command or manually copy the file in file explorer.

cp ../../STM32CubeF4/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.s .


Toolchain File CMake

In project/cmake create arm-none-eabi.cmake and populate it with the below text:

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_ASM_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}-objcopy)
set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}-size)

set(CMAKE_C_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -O0 -g3")

set(CMAKE_EXE_LINKER_FLAGS "-T${LINKER_SCRIPT} -Wl,--gc-sections")

set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)


Template main


In project/Core/Src create main.c and populate it with the below text:


At the same time in project/Core/Inc create an empty file called main.h.


#include "stm32f4xx_hal.h"

int main(void)
{

}


Add system_stm32f4xx

Copy system_stm32f4xx.c and system_stm32f4xx.h into the src and inc directories:

cp ../../../STM32CubeF4/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c .

cd ../Inc

cp ../../../STM32CubeF4/Drivers/CMSIS/Device/ST/STM32F4xx/Include/system_stm32f4xx.h .


Add Configuration Header

The stm32f4xx_hal_conf.h file will also need to be copied into the project. In project/Core/Inc run the following command or manually copy the file in file explorer and rename it.

cp ../../../STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_conf_template.h
./stm32f4xx_hal_conf.h


Fix timebase issues

Before we can get started we need to resolve the fact that HAL comes with three timebase files, but can only use one. The simplest way to do this is to remove the two that you won't use.

cd ~/stm32

# rename the timebase file we will use
mv STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_timebase_tim_template.c STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_timebase.c

# delete the other two timebase files
rm STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_timebase_rtc_wakeup_template.c
rm STM32CubeF4/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_timebase_rtc_alarm_template.c


Build and Flash

Test the Build

Test creating a build by running the following:

cd project/build
cmake -G Ninja ..
ninja


The following files should be created in the /build directory if successful:

  1. nucleo-f446re.elf
  2. nucleo-f446re.bin
  3. nucleo-f446re.hex

Flash the Board

To flash the build to the STM32 execute the following:

cd ../
st-flash write build/nucleo-f446re.bin 0x08000000

Adding a Build Script

No one wants to be continuously changing directories when developing code, building and flashing. We are going to add a build.sh file that can be called with arguments to do our builds for us.


In the project directory create a file called build.sh and populate with the following:

#!/usr/bin/env bash
set -e

# --- CONFIGURATION ---
PROJECT_NAME="stm32_test_project"
BUILD_DIR="build"
LINKER_SCRIPT="linker/STM32F446RE_FLASH.ld"

OPENOCD_INTERFACE="stlink" # or jlink, etc.
OPENOCD_TARGET="stm32f4x"
OPENOCD_CFG="/usr/share/openocd/scripts/interface/${OPENOCD_INTERFACE}.cfg"
OPENOCD_TARGET_CFG="/usr/share/openocd/scripts/target/${OPENOCD_TARGET}.cfg"

ST_FLASH_BIN="${BUILD_DIR}/${PROJECT_NAME}.bin"

# --- HELP ---
if [ "$1" = "help" ]; then
echo "The following arguments can be appended to ./build.sh:"
echo "-----------------------------------------------------"
echo "*clean - cleans the build directory of generated files by ninja"
echo "*flash_openocd - flashes the built code to the STM32 using OpenOCD"
echo "*flash_st - flashes the built code to the STM32 using ST-LINK"
echo "*help - displays this menu"
exit 0
fi

# --- CLEAN OPTION ---
if [ "$1" = "clean" ]; then
rm -rf "${BUILD_DIR}"
echo "Cleaned build directory."
exit 0
fi

# --- BUILD OPTION ---
mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"

# Configure with CMake if build.ninja doesn't exist
if [ ! -f build.ninja ]; then
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
fi

# Build
ninja
cd ..

# --- FLASH OPTION: openocd ---
if [ "$1" = "flash_openocd" ]; then
echo "Flashing with OpenOCD..."
openocd -f "${OPENOCD_CFG}" -f "${OPENOCD_TARGET_CFG}" \
-c "program ${BUILD_DIR}/${PROJECT_NAME}.elf verify reset exit"
exit 0
fi

# --- FLASH OPTION: st_flash ---
if [ "$1" = "flash_st" ]; then
echo "Flashing with ST-LINK..."
st-flash write "${ST_FLASH_BIN}" 0x8000000
exit 0
fi

echo "Build complete. Use './build.sh flash_openocd' or './build.sh flash_st' to flash."


chmod 755 build.sh


Builds and flashing can now be invoked from the top level using the following commands:

# to generate a build
./build.sh

# to remove generated files
./build.sh clean

# to flash an existing build to a STM32 board using OpenOCD
./build.sh flash_openocd

# to flash an existing build to a STM32 board using ST-LINK
./build.sh flash_st

# to display all build arguments
./build.sh help


Blink the On-board LED

Edit your main.c file with the following code and then invoke the following to build and flash. If successful you should see the on board LED flash on and off every half a second. You may need to press the reset button on the board to reinitialise the flashed program.


#include "stm32f4xx_hal.h"

static void SystemClock_Config(void);

int main(void)
{
// intitialise HAL
HAL_Init();

// initialsise system clock
SystemClock_Config();

// enable GPIOA clock - led is on PA5
__HAL_RCC_GPIOA_CLK_ENABLE();

// initialise on board LED -configure PA5 as output
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_5;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &gpio);

while (1)
{
//toggle GPIO_PIN_5
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

// bad implementation to blink LED
HAL_Delay(500);
}
}

// STM32F446RE clock config
static void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;

HAL_RCC_OscConfig(&RCC_OscInitStruct);
}


./build.sh flash_st

Troubleshooting

  1. If you update the project name in cmake or build.sh you will need to update it in the other file too.
  2. If the program won't flash to the board you may need to update ST-Link firmware.

Conclusion

That's everything! You should now have a build system that uses CMake and STM32HAL. From here you can grow your project.

https://cmake.org/ provides resources to help you expand your CMake setup if you need to create a more complex multilevel src structure.

STM23 HAL Resources