Kbox-UniStack Docs
STM32 (HAL)

Project Setup (STM32)

Configure an arm-none-eabi CMake project for the STM32 (HAL) platform of kbox-stack — toolchain, the compiler flags that must match the prebuilt library, includes, linking, and the linker script.

This page describes how a consumer project targets the STM32 (HAL) platform: the toolchain, the compiler flags, and how to include the headers and link the prebuilt library. The snippets are taken from the led-indicator example's CMakeLists.txt.

Toolchain

The platform builds with the arm-none-eabi bare-metal GCC toolchain for Cortex-M3. CMake is put into cross-compile mode and pointed at the toolchain binaries:

set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(TOOLCHAIN_PREFIX ${TOOLCHAIN_BIN}/arm-none-eabi-)
set(CMAKE_C_COMPILER   ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_OBJCOPY      ${TOOLCHAIN_PREFIX}objcopy)
set(CMAKE_SIZE         ${TOOLCHAIN_PREFIX}size)

set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY is required because a bare-metal toolchain cannot link a normal test executable during CMake's compiler check.

The example defaults TOOLCHAIN_BIN to the GCC bundled with STM32CubeIDE; pass -DTOOLCHAIN_BIN=/path/to/arm-none-eabi/bin to override it.

Your compiler flags must be ABI-compatible with the prebuilt .a. The library is compiled for Cortex-M3, Thumb, no exceptions, no RTTI. If your CPU/float/ABI flags differ, the archive will not link correctly (or will fail at runtime). Do not change the CPU flags below.

Compiler flags

set(COMMON_FLAGS
    -mcpu=cortex-m3
    -mthumb
    -fdata-sections
    -ffunction-sections
    -Wall
    -fno-exceptions
)

set(C_FLAGS   ${COMMON_FLAGS})
set(CXX_FLAGS ${COMMON_FLAGS} -fno-rtti)
set(ASM_FLAGS -mcpu=cortex-m3 -mthumb -x assembler-with-cpp)
  • -mcpu=cortex-m3 -mthumb — the CPU/instruction set the .a was built for.
  • -fno-exceptions (C and C++) and -fno-rtti (C++) — match the library and keep code size down; the stack does not use exceptions or RTTI.
  • -ffunction-sections -fdata-sections — put each function/object in its own section so the linker can garbage-collect unused ones (paired with --gc-sections below).

Optimization is per-config — Release is size-optimized, Debug is debug-friendly:

set(CMAKE_C_FLAGS_RELEASE   "-Os -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-Os -DNDEBUG")
set(CMAKE_C_FLAGS_DEBUG     "-Og -g")
set(CMAKE_CXX_FLAGS_DEBUG   "-Og -g")

The platform defines identify the MCU and the HAL. WATCHDOG_ENABLED is turned on only in Release so the debugger is not reset while halted:

add_compile_definitions(
    STM32F103xB
    USE_HAL_DRIVER
    $<$<CONFIG:Debug>:DEBUG>
    $<$<CONFIG:Release>:WATCHDOG_ENABLED>
)

The public headers live under dist/include and are reached as <Stack/...>. From the example root, dist/ is three directories up:

include_directories(
    ${CMAKE_SOURCE_DIR}/Core/Inc
    ${CMAKE_SOURCE_DIR}/Drivers/STM32F1xx_HAL_Driver/Inc/Legacy
    ${CMAKE_SOURCE_DIR}/Drivers/STM32F1xx_HAL_Driver/Inc
    ${CMAKE_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32F1xx/Include
    ${CMAKE_SOURCE_DIR}/Drivers/CMSIS/Include
    ${CMAKE_SOURCE_DIR}/../../../dist/include
)

Link the prebuilt archive by relative path:

target_link_libraries(${CMAKE_PROJECT_NAME}.elf PRIVATE
    ${CMAKE_SOURCE_DIR}/../../../dist/lib/libkbox-stack---cortexm3---STM32F103xB.a
)

The link step uses the linker script and enables dead-code stripping:

set(LINK_FLAGS
    -mcpu=cortex-m3
    -mthumb
    -T${LINKER_SCRIPT}
    -Wl,--gc-sections
    -Wl,--no-warn-rwx-segments
    -Wl,-Map=${CMAKE_PROJECT_NAME}.map,--cref
    --specs=nano.specs
    --specs=nosys.specs
    -lc
    -lm
)

-Wl,--gc-sections removes unreferenced sections (works together with -ffunction-sections -fdata-sections). --specs=nano.specs --specs=nosys.specs select newlib-nano and bare-metal syscall stubs.

The concept of the peripheral interfaces the .a implements lives in the portable core: Peripheral Interfaces. The concrete STM32 drivers you construct are on the Peripherals page.

Linker script & flash reservation

The single-image example uses STM32F103TBUX_FLASH.ld and boots directly at 0x08000000. Its memory map reserves the top of flash for the stack's persistent data:

MEMORY
{
  RAM        (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
  FLASH      (rx)  : ORIGIN = 0x8000000,  LENGTH = 125K
  FLASH_USER (r)   : ORIGIN = 0x0801F400, LENGTH = 0xC00   /* pages 125-127 */
}

The reserved region must not be used by application code. FLASH is only 125 KB (not the full 128 KB) precisely so that FLASH_USER (0x0801F4000x0801FFFF, 3 pages) stays free for the stack's persistent configuration and state. The linker script even asserts this window is exactly 3 pages. The address you pass to HalFlash (0x0801, 0xF4000x0801F400) must sit at the start of this reserved window; MakeFlashLayout() partitions the rest.

IDE options

Open led-indicator.code-workspace. The .vscode/ folder ships build tasks and a Cortex-Debug launch configuration (needs the Cortex-Debug extension plus OpenOCD).

The project ships a CubeMX .ioc (led-indicator.ioc) that matches the peripheral configuration in Core/Src/main.cpp (pins, clock tree, USART2/DMA, TIM2, IWDG, EXTI). Open it in CubeMX to inspect or change the hardware setup and regenerate the init code — the application logic lives in USER CODE blocks, which CubeMX preserves across regeneration. It is also a plain CMake project: import it with File → Import → C/C++ → Existing Code as CMake Project (or Import CMake project), then build and debug from the IDE.

Configure and build directly with CMake:

cmake -S . -B build_dbg -DCMAKE_BUILD_TYPE=Debug
cmake --build build_dbg

Add -DTOOLCHAIN_BIN=/path/to/arm-none-eabi/bin if the toolchain is not on the default path.