Using embOS with CMake
This guide shows how to create CMake projects for embOS BSPs using Ninja and the Arm GNU toolchain. CMake will be configured to generate the Ninja build files for a debug and a release configuration. Ninja is a small and fast cross-platform build system designed to be used with build file generators like CMake. Therefore, the CMake project can be used with Windows, Linux, and macOS.
If you want to integrate the CMake project created in this guide in a Visual Studio Code project, please take a look into Using embOS with Visual Studio Code and CMake.
An example project for the SEGGER emPower board and CMake can be downloaded here: File:embOS Classic CortexM GCC Obj SFL V5.20.0.0 VSCode CMake.zip
Requirements
Following tools are required:
- CMake 3.16 or newer
- Ninja
- Arm GNU Toolchain
Furthermore, this guide expects these tools to be made via the system's path environment variable. Should this not be possible, absolute paths need to be used instead.
Project overview
The CMake project consists of two files:
- CMakeLists.txt
- CMakePresets.json
Both files need to be placed in the root directory of the embOS BSP.
This guide uses the BSP for the SEGGER emPower board taken from the embOS-Classic Cortex-M GCC port.
The project structure of the embOS shipment is also kept as it is.
Therefore, the files are placed at .../Start/BoardSupport/Segger/K66FN2M0_emPower/
.
The CMakeLists.txt defines everything required by CMake to generate the Ninja build files. This includes the target platform, toolchain, source files, include paths, macro definitions, compiler options and linker options. The CMakePresets.json file is used to facilitate the usage of CMake as it contains all information that needs to be passed to CMake to generate or build the available configurations of the project.
CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
project(Start_K66FN2M0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99)
set(LINKER_FILE ${PROJECT_SOURCE_DIR}/Setup/MK66FN2M0xxx18_flash.ld)
add_executable(${PROJECT_NAME}
${PROJECT_SOURCE_DIR}/Application/OS_StartLEDBlink.c
${PROJECT_SOURCE_DIR}/DeviceSupport/system_MK66F18.c
${PROJECT_SOURCE_DIR}/DeviceSupport/startup_MK66F18.S
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_RTT.c
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_RTT_ASM_ARMv7M.S
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_RTT_printf.c
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_RTT_Syscalls_GCC.c
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_SYSVIEW.c
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_SYSVIEW_Config_embOS.c
${PROJECT_SOURCE_DIR}/SEGGER/SEGGER_SYSVIEW_embOS.c
${PROJECT_SOURCE_DIR}/Setup/BSP.c
${PROJECT_SOURCE_DIR}/Setup/BSP_UART.c
${PROJECT_SOURCE_DIR}/Setup/HardFaultHandler.S
${PROJECT_SOURCE_DIR}/Setup/JLINKMEM_Process.c
$<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/Setup/OS_Error.c>
${PROJECT_SOURCE_DIR}/Setup/OS_Syscalls.c
${PROJECT_SOURCE_DIR}/Setup/OS_ThreadSafe.c
${PROJECT_SOURCE_DIR}/Setup/RTOSInit_K66FN2M0.c
${PROJECT_SOURCE_DIR}/Setup/SEGGER_HardFaultHandler.c
)
set_target_properties(${PROJECT_NAME} PROPERTIES
SUFFIX .elf
LINK_DEPENDS ${LINKER_FILE}
)
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Debug>:DEBUG=1>
)
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-mthumb
-ffunction-sections
-fdata-sections
-g
$<$<CONFIG:Debug>:-O0>
$<$<CONFIG:Release>:-O3>
-mcpu=cortex-m4
-mfpu=fpv4-sp-d16
-mfloat-abi=softfp
)
target_link_options(${PROJECT_NAME} PRIVATE
-T${LINKER_FILE}
-mthumb
-mcpu=cortex-m4
-mfpu=fpv4-sp-d16
-mfloat-abi=softfp
LINKER:--gc-sections
LINKER:-cref
LINKER:-Map,${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map
)
target_include_directories(${PROJECT_NAME} PRIVATE
${PROJECT_SOURCE_DIR}/DeviceSupport
${PROJECT_SOURCE_DIR}/CoreSupport
${PROJECT_SOURCE_DIR}/SEGGER
${PROJECT_SOURCE_DIR}/Setup
${PROJECT_SOURCE_DIR}/../../../Inc
)
target_link_libraries(${PROJECT_NAME}
$<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/../../../Lib/libosT7VLDP.a>
$<$<CONFIG:Release>:${PROJECT_SOURCE_DIR}/../../../Lib/libosT7VLR.a>
)
The CMakeLists.txt shown above (expand to see the content) starts with the minimum required version.
Next, it defines the target platform and cross-compilation toolchain.
For bare-metal targets CMAKE_SYSTEM_NAME
must be set to "Generic" and CMAKE_SYSTEM_PROCESSOR
is set to arm.
CMAKE_C_COMPILER
and CMAKE_CXX_COMPILER
are set to the C and C++ compilers that CMake is supposed to use.
If these compilers are not made accessible through the system's path environment variable, you will need to provide the absolute path to the compilers.
Since CMake tries to compile a test application in order to check whether the toolchain is working, CMAKE_TRY_COMPILE_TARGET_TYPE
must be set to STATIC_LIBRARY
.
This tells CMake to build a static library instead of an executable when testing the toolchain.
Building an executable would fail at the link stage, because CMake uses the cross-compilation toolchain in the same way as an ordinary toolchain.
project()
creates a project named "Start_K66FN2M0" that can be built using C, C++, and assembly language.
Furthermore, the C99 standard is used by setting CMAKE_C_STANDARD
to "99".
Since the linker file is referenced twice in this file, the file is stored in a variable called LINKER_FILE
.
Then add_executable()
is used to tell CMake that it is supposed to build an executable.
add_executable()
expects the name of the executable and the files required for building the executable.
For the executable name the name of the project is used.
The list of source files passed to add_executable()
contains the generator expression $<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/Setup/OS_Error.c>
.
This generator expression is evaluated during the generation of the build files.
If the build type is set to "Debug" then OS_Error.c is included in the build.
The build type to be used is specified by setting CMAKE_BUILD_TYPE
to either "Debug" or "Release" which is done in the preset file.
set_target_properties()
specifies the .elf
suffix, that shall be appended to the executable, and that the linker file is a dependency of the linker.
This dependency ensures that the linker will link the executable again if the linker file changes.
The remaining part of the CMakeLists.txt specifies the preprocessor macros that are defined when the compiler is called, the include directories, the compiler and linker options and the static libraries to link against.
For the DEBUG
macro and the embOS libraries generator expressions are used again so that they are used in the respective configuration.
CMakePresets.json
CMakePresets.json
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 16,
"patch": 0
},
"configurePresets": [
{
"name": "Default",
"description": "Default configuration",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Output/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "${presetName}"
}
},
{
"name": "Debug",
"inherits": "Default",
"description": "Debug configuration",
"hidden": false
},
{
"name": "Release",
"inherits": "Default",
"description": "Release configuration",
"hidden": false
}
],
"buildPresets": [
{
"name": "Debug",
"configurePreset": "Debug"
},
{
"name": "Release",
"configurePreset": "Release"
}
]
}
The CMakePresets.json file shown above (expand to see the content) contains information that is provided to CMake when CMake is called to generate or build a specific configuration. Without this preset file CMake would need to be called as follows:
$ cmake -B Output/Debug -G Ninja -DCMAKE_BUILD_TYPE=Debug # Generate ninja build files for the debug configuration
$ cmake -B Output/Release -G Ninja -DCMAKE_BUILD_TYPE=Release # Generate ninja build files for the release configuration
$ cmake --build Output/Debug # Build the debug configuration
$ cmake --build Output/Release # Build the release configuration
With a preset file, only the desired preset needs to be passed to CMake, which is either the "Debug" or the "Release" preset. The information required by CMake is then taken from the presets file. If any of the information changes, only the presets file needs to be modified.
$ cmake --preset Debug # Generate ninja build files for the debug configuration
$ cmake --preset Release # Generate ninja build files for the release configuration
$ cmake --build --preset Debug # Build the debug configuration
$ cmake --build --preset Release # Build the release configuration
The presets file contains presets for the configuration and the building of the project.
The Debug and Release configuration presets inherit some of the information from a default configuration preset.
The default configuration preset specifies the CMake generator, which is Ninja, the output directory, and the build type by setting the CMAKE_BUILD_TYPE
variable.
Both, the output directory and the build type, are using the ${presetName}
variable that is evaluated to the preset names of the deriving presets.
For the build presets, only the respective configuration preset must be set.
Conclusion
With the CMakeLists.txt and CMakePresets.json presented in this guide the debug and release configuration can be easily build with the following commands:
Debug configuration:
$ cmake --preset Debug # Generate ninja build files for the debug configuration
$ cmake --build --preset Debug # Build the debug configuration
Release configuration:
$ cmake --preset Release # Generate ninja build files for the release configuration
$ cmake --build --preset Release # Build the release configuration
The built executables can be found in the binary directory specified in the presets file which is ./Output/Debug/Start_K66FN2M0.elf
for the debug configuration and ./Output/Release/Start_K66FN2M0.elf
for the release configuration.
CMake projects can also be integrated into Visual Studio Code projects. Visual Studio Code will then handle all calls to CMake automatically when the build process or the debug session is started in Visual Studio Code. If you want to use this CMake project with Visual Studio Code, please refer to Using embOS with Visual Studio Code and CMake.