Embedded development with CMake

1 minute read

Using CMake for embedded development is actually not that hard. The main thing you need is a proper toolchain file which sets the necessary variables like CMAKE_C_COMPILER.

Set it via the cmake commandline or with:

set(CMAKE_TOOLCHAIN_FILE arm-gcc-toolchain.cmake)

This should also work in Visual Studio when using its builtin CMake support (i.e. not the CMake VS generator).

The rest should be similar to a normal CMake project setup. You may want to adjust the executable suffix:

set_target_properties(${ProjectName} PROPERTIES SUFFIX .out)

And you’ll need to specify your linker script. That’s straightforward but to get the dependencies right it’s a good idea to create a helper function:

function(target_linker_script target script)
	target_link_libraries(${target} PRIVATE -T "${script}")

	# relink on script change
	get_target_property(_savedDeps ${target} LINK_DEPENDS)
	string(APPEND _savedDeps " ${script}")
	set_target_properties(${target} PROPERTIES LINK_DEPENDS $<TARGET_PROPERTY:INCLUDE_DIRECTORIES> ${script})
endfunction()

Convenience functions

This should be enough to successfully create the firmware in ELF form. Usually you need to convert that to some special format and you also want to get a quick overview of how much memory is used. The following convenience functions will do the job as post-build commands:

function(print_firmware_size target)
	add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_SIZE_UTIL} -A "$<TARGET_FILE:${target}>")
endfunction()

function(run_objcopy target suffix type)
	add_custom_command(TARGET ${target} POST_BUILD
		COMMAND ${CMAKE_OBJCOPY} -v -O${type} "$<TARGET_FILE:${target}>" "$<TARGET_FILE_DIR:${target}>/${target}${suffix}"
	)
endfunction()

Here’s my full template showing how all those functions can be used.