CMake¶
Learning goals: After studying this material, you will know what is CMake, how it is used and how you can define you own CMake projects to build programs with Pico SDK, FreeRTOS and
make.Introduction¶
The development of CMake was started in year 1999. Despite the fact that it happened over 20 years ago, CMake is still a trusted and well-known build automation system. Unsurprisingly, CMake is used also in our course to do just that: automate the build of projects that are based on Pico SDK and FreeRTOS. How does CMake then do this? At a surface level, the task of CMake is to configure the project so that it is ready to be built by some build system, for example in our case it is
ninja. In more technical terms, CMake generates the directory structures and files that are required by the build system to perform the build process. The power of CMake lies in the fact that it does this in a platform-independent way. It can be used to generate the baseline files for any compiler running on any operating system.CMakeLists¶
Every CMake project includes a
CMakeLists.txt-file, that describes to CMake, how the project is built, what files and dependecies are required, and what is the end result of the build.Commands¶
Every
CMakeLists-file consists of a number of commands, according to which the build environment is setup. Next we will go through some basic commands, that can make up such a file. A link to CMake documentation describing the command is provided along the way to make understanding each of the commands easier.cmake_minimum_required()¶
In the first line of every
CMakeLists-file, the command cmake_minimum_required is called to establish the expected version of CMake itself. Using a right version will ensure that we have all the needed functionalities at our disposal, and that those functionalities work in the expected way in order to be able to build the project successfully.set()¶
Next, we set variable
NAME to value "empty_project" with the command set. The variable that gets set here can be later used in the current CMakeLists-file, or in the files that are included by this file.include()¶
We can include another CMake file with the command
include. With this command, we include the CMake commands that are specified in the given file to the project, and then proceed to run those commands.project()¶
With this function, we can set the name of the project that is currently being compiled. Calling
project at the start of a CMakeLists is common, as with this function we also set the variable PROJECT_NAME.include_directories()¶
With the function
include_directories, we can add desired directories to the project. These directories will be used when the compiler runs after CMake in the resulting build environment that was created by CMake. The purpose of these directories is to store include files for the compilation process.add_subdirectory()¶
This function is used to include a directory containing another
CMakeLists.txt-file into the build environment. When add_subdirectory is called, other files than the CMakeLists file itself are placed in a path specified by the second parameter of the command in the resulting build environment. The CMakeLists.txt in the included folder is processed immediately by CMake before it continues executing any other commands.Putting it all together¶
Now we should be familiar with the basic commands, which make up the
CMakeLists-file. Next, let's look at an example taken from the Pico SDK project of our course's code template. The example is presented below.# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# == EDIT at the beginning. Define the Project name and name for the main target (app).=========
# Define project name
set(APP_NAME JTKJ_app)
# Define main app (app in this folder)
set(MAIN_TARGET hello_blink)
#===============================================================================================
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work =======
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ================================================================================================
# == DO NOT EDIT: SETUP pico SDK and crete project ==============================================
set(PICO_BOARD pico_w CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(${APP_NAME} C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# =================================================================================================
# DO NOT EDIT: Configure FreeRTOS =================================================================
# First include FreeRTOS Kernel libraries
include(FreeRTOS_Kernel_import.cmake)
# Include the configuration files.
# Option 1 ===
# Add general include directory for all libraries of this directory.
# Other libraries would use target_include directories
# set(FREERTOS_CONFIG_DIR "${CMAKE_SOURCE_DIR}/config")
# include_directories(${FREERTOS_CONFIG_DIR})
# ============
# Option 2. ===
# Include config only for FreeRTOS target.
target_include_directories(FreeRTOS-Kernel INTERFACE
${CMAKE_CURRENT_LIST_DIR}/config
)
#=============
# NOTE: The target using FREERTOS should include in target_link_libraries the FREERTOS
# library that fits better: Kernel-Heap1 thru FreeRTOS-Kernel_Heap4 or FreeRTOS-Kernel-Static
# default
# ===============================================================================================
# DO NOT EDIT: Prepare the build to compile different libraries of the project ==================
# Add librabries. The TKJHAT does not use libraries from FreeRTOS
add_subdirectory(libs/TKJHAT)
# Support for usb serial communication
add_subdirectory(libs/usb-serial-debug)
# If created new libraries, include them here.
# You can EDIT it if you add new libraries
# ===============================================================================================
# DO NOT EDIT: Prepare build to compile the different examples ==================================
# Add examples
add_subdirectory(examples/hello_pico)
add_subdirectory(examples/hello_freertos)
add_subdirectory(examples/hello_dual_cdc)
add_subdirectory(examples/hello_microphone)
add_subdirectory(examples/hello_hat)
add_subdirectory(examples/hello_blink_freertos)
# You can edit it if you want to add new examples
# ==============================================================================================
# EDIT: Prepare build to compile the main target (in root src folder) ==========================
# NOTE: If you are creating some example you would need to include these lines
# in the example CMakeLists.txt
# Add the source files to be compiled
# If you add the headers in a different directory, you should use: target_include_directories
add_executable(${MAIN_TARGET}
src/main.c
)
# Links. Add all libraries that application is using. It must at least use the pico_stdlib
# In addition, for the course project you are using at least:
# * FreeRTOS-Kernel -> FreeRTOS functions: tasks queues, timers, lists ...
# * FreeRTOS-Kernel-Heap4 -> Memory allocators for FreeRTOS
# * TKJHAT_SDK -> SDK to control the HAT
# * cfg-dual-usbcdc -> Auxiliar library which creates two serial ports one for sending data an the other for debug
target_link_libraries(${MAIN_TARGET}
pico_stdlib
)
# Include libraries necessaries to control the WiFi. If you are using the internal pico LED in W model, it is also
# necessary
if (PICO_CYW43_SUPPORTED)
target_link_libraries(${MAIN_TARGET} pico_cyw43_arch_none)
endif()
#Support for stdio (printf, fwrite, puts...) via usb or UART.
pico_enable_stdio_usb(${MAIN_TARGET} 1)
pico_enable_stdio_uart(${MAIN_TARGET} 0)
# Create different output files:
# .elf -> Executable with debug info (for GDB)
# .bin -> Raw binary image (for flashing tools)
# .hex -> Intel HEX format image (alternative flashing format)
# .uf2 -> UF2 image (drag & drop to Pico USB drive)
# .map -> Linker map (memory layout & symbols)
# .dis -> Disassembly of the ELF (readable assembly listing)
pico_add_extra_outputs(${MAIN_TARGET})
# add url via pico_set_program_url
pico_set_program_url(${MAIN_TARGET} https://github.com/UniOulu-Ubicomp-Programming-Courses/JTKJ-PicoRTOS-ProjectP)
# ==================================================================================