Five EmbedDev logo Five EmbedDev

An Embedded RISC-V Blog

Cross Compiling to Bare Metal RISC-V with CMake.

CMake is an open-source, cross-platform family of tools designed to build, test and package software. In this post I’d like to use it to generate a cross-compiling Makefile.

CMake has great support for detecting it’s local build environment, but it’s less clear on how to use it for cross compiling to embedded targets. I’ve based this article on a tutorial for ARM bare-metal programming.

Using CMake can free you from vendor specific IDEs and toolchains, without needing to create your own build scripts. It can also provide a more automation friendly build environment.

Basic Setup

An example of compiling a stripped down (and non-functional) bare-metal test program is in the scratchpad repo

There are two files used, one to define the target toolchain and one to define the project.

The Basics

In a bare-metal project control over the compiler and linker options is important. Basic configuration is via CMAKE_C_FLAGS and CMAKE_EXE_LINKER_FLAGS.

The example below also allows the linker script to be specified via LINKER_SCRIPT, and the stack size to be controlled via STACK_SIZE.

cmake_minimum_required(VERSION 3.10)

# set the project name
project(test C)

# specify the C++ standard
set ( CMAKE_C_FLAGS "-Os -g -Wall -ffunction-sections")
set ( STACK_SIZE 0xf00 )
set ( TARGET test )

# add the executable

add_executable(${TARGET}.elf ${TARGET}.c startup.c dummy.c) 
SET(LINKER_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}.lds")

set_target_properties(${TARGET}.elf PROPERTIES LINK_DEPENDS "${LINKER_SCRIPT}")
target_include_directories(${TARGET}.elf PRIVATE ../include/ )

# Linker control
SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -nostartfiles   -fno-exceptions  -Xlinker --defsym=__stack_size=${STACK_SIZE} -T ${LINKER_SCRIPT} -Wl,-Map=${TARGET}.map")

Embedded Specialization

I’ve also added an example of creating a hex file version of the firmware image below. It should be easy to adapt the code to create a bin file or other image format.

I like to have the option of quickly inspecting the compiler output, so a disassembly from the final linked elf file and intermediate files is also generated.

# Post processing command to create a disassembly file 
add_custom_command(TARGET ${TARGET}.elf POST_BUILD
        COMMAND ${CMAKE_OBJDUMP} -S  ${TARGET}.elf > ${TARGET}.disasm
        COMMENT "Invoking: Disassemble")

# Post processing command to create a hex file 
add_custom_command(TARGET ${TARGET}.elf POST_BUILD
        COMMAND ${CMAKE_OBJCOPY} -O ihex  ${TARGET}.elf  ${TARGET}.hex
        COMMENT "Invoking: Hexdump")

# Pre-processing command to create disassembly for each source file
foreach (SRC_MODULE test startup dummy)
  add_custom_command(TARGET ${TARGET}.elf 
                     PRE_LINK
                     COMMAND ${CMAKE_OBJDUMP} -S CMakeFiles/${TARGET}.elf.dir/${SRC_MODULE}.c.obj > ${SRC_MODULE}.s
                     COMMENT "Invoking: Disassemble ( CMakeFiles/${TARGET}.elf.dir/${SRC_MODULE}.c.obj)")
endforeach()

TODO

This method uses the CMakeForceCompiler module to bypass CMake’s compiler detection. This feature is depreciated, so this example needs to be updated to use the official compiler identification capabilities.