Getting started with Raspberry Pico and CMake

When you work with the Raspberry Pi Pico C/C++ SDK, you also need to understand the CMake build system that is used. In my first projects, I was happy to copy and paste the example files and tweak them. Yet, when developing my libraries, new features were required. First, I wanted to have different types of build, like example and test. The example build should compile all examples, and link them with the Pico SDK and my library. The test build should compile the library, link to the unit testing framework, and provide an executable that runs all test files with injected mock files.

Turns out that this is quite a challenge! I could not find a good documentation of CMake essentials from the perspective of a new to C, new to Pico SDK developer. This article explains all you need to know about basic CMake, and after reading you should feel comfortable with configuring, compiling and building your projects. This article is structured into essential “how to” questions, so you can easily skip to the part that you like to do.

This article originally appeared at my blog.

What is CMake about?

  • Configuring: CMake is the tool to configure your project. You call it from the top-level directory with cmake -B build -S ., where build is the output directory, and ./ the directory with the root level cmake file.
  • Compiling: You will build your project using the make command, and this command uses Makefiles that were generated by CMake.
  • Building: You will also build executable files that link to your library code and/or external libraries. This is also done with the make command, and the Makefiles will include all the defined build options of your CMake configuration.

What appears confusing at first, but becomes clearer with experience, is that you need to place a file called CMakeLists.txt in your project root directory and in all subdirectories that contain source code.

Depending on where you define them, these files have different roles, which I separate as follows:

  • main config: The top-level directory includes the root CMake file. This file needs to set essential config options, and it will list all additional directories that will be included when you execute CMake.
  • library config: Directories in which you just build a library, you will put a config file that includes the add_library directive. During the compilation process, you will product library files, which are typically named lib.a.
  • executable config: In directories in which you have executable code, you will put a config file that contains the directive add executable. During building, you will have one and only one C file with a main() method, and the result will be named file.out

In the remainder of this article, I will show a concise example for each type. If you want to see a complete example, check out my pico-dht11-lib project on Github.

Main CMake Config File

cmake_minimum_required(VERSION 3.12)include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake)
pico_sdk_init()
project(pico-shift-register)add_subdirectory(./src)
add_subdirectory(./examples)

Essential commands are:

  • cmake_minimum_version A flag that controls the compatibility of your CMake files with a specific version
  • project The name of this CMake file, its used throughout the build chain
  • add_subdirectory List any other directories that contain a CMakeLists.txt file

Specific for the Raspberry Pico is the include statement to load the Pico SDK, and the custom CMake function pico_sdk_init. It is imperative that you place this at the top of the root config!

Library CMake Config File

file(GLOB FILES *.c *.h)add_library(pico-shift-register ${FILES})target_link_libraries(pico-shift-register pico_stdlib)target_include_directories(pico-shift-register PUBLIC ../include/)

In this file, we see the following statements:

  • file: Collect all files that need to be compiled. You can use a GLOB function as shown here, or explicitly mention the specific files
  • add_library: With this declaration, you express the intent to build a library. The first argument, here its pico-shift-register, is the name of the library, the second argument are the files that will be compiled to create your library.
  • target_link_libraries If you link with other libraries, list them here
  • target_include_directories Libraries need to publish their header files so that you can import them in source code. This statement expresses where to find the files - typically in an include directory of your projects.

Executable CMake Config File

add_executable(8_led_blink 8_led_blink.c)target_link_libraries(8_led_blink pico_stdlib pico-shift-register)pico_add_extra_outputs(8_led_blink)

Here, the meaning of these declarations is:

  • add_executable Defines the intent to create an executable file (.bin, .uf2 etc.), the first argument is the name, the second argument are the source files.
  • target_link_libraries As before, the names of all additional libraries that you want to link with your executable.

The statement pico_add_extra_outputs is again a custom CMake function from the Pico SDK, it will produce additional files for the executable.

Build Commands

# Configure
cmake -B build -S .
# Build the library / produces libpico-shift-register.a.
make -C build/src
# Build the examples / produces 8_led_blink.elf, .uf2 etc.
make -C build/examples

Conclusion

IT Project Manager & Developer