How to properly use CMake in embedded (RP2040) project structure?

1.9k Views Asked by At

TL;DR

How to use CMakeLists.txt with included project-level libraries in elegant way?

Introduction

Dear Stack Users,

shortly after starting my journey with manually-created build systems (previously using pre-configured STM32CubeIDE, SW4STM32 and MSP430 CCS) which I've encountered by using Raspberry RP2040 I've stumbled upon a tough (subjectively) task of using CMake in a more aware way. Looking through multitude of tutorials, hints and questions (and Pico Project generator) I've managed to build my simple project. Yet I feel that it is done in rather makeshift-alike manner.

Environment

What is the situation: I have a project that uses RP2040 picoSDK - a collection of peripheral configuration functions, a HAL of some sort stored outside of project directory. Think of picoSDK as as external library function. Using this picoSDK I've created a simple application that uses several peripherals. I've divided my project into following structure: Project structure

Connections between my files are as follows: File relations where my main.c file uses one of the module, and module uses two peripherals. All of those files require access to global definitions and picoSDK includes from common.h file.

For the sake of clarity I'm using VS code on Windows 10, I'm compiling with arm-gcc 10.3.1, my CMake version is 3.20.6

Problem and current solution

Now, I've been trying to use the approach (suggested among other in this thread: CMake with subdirectories) with multiple CMakeLists.txt in each directory to create o directory-based library and then add them all by using add_subdirectory() inside of top-level CMakeLists.txt but with no success. CMakeLists.txt that works I've done in following manner:

cmake_minimum_required(VERSION 3.13)

# [Platfrom specific command] Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(ProjectName C CXX ASM)

# [Platfrom specific command] Initialize the Raspberry Pi Pico SDK
pico_sdk_init()

set(sources main.c
            Common.h
            Peripherals/PeripheralA.c
            Peripherals/PeripheralA.h
            Peripherals/PeripheralB.c
            Peripherals/PeripheralB.h
            Modules/ModuleSource.c
            Modules/ModuleSource.h
            Modules/ModuleConfig.h)
            
# Create executable with specified sources
add_executable(ProjectName ${sources})

# Add the standard library to the build (picoSDK inclusion)
target_link_libraries(ProjectName pico_stdlib)

# Add any user requested libraries (a part of picoSDK)
target_link_libraries( ProjectName
        hardware_timer
        )

Disclaimer: Additions of platform specific lines were left for a particular situation where this questions fells into hands of someone proficient with RP2040

The question

Now, I'm feeling that embedded domain is a bit different due to the fact that each part of my project (Peripherals, modules and even main file) needs the access to the base: library for driving the microcontroller (picoSDK here), hence I'd like to ask: what is the suggested approach, what is the outline of CMakeLists.txt content? I know for sure that the project will grow, and keeping everything inside of one CMakeLists.txt file was - according to other replies - not suggested.

I'm looking for hints, good advises and criticism. I'm very grateful for the time spent on reading this post or - of course - on replying to it. In case if this question addresses something that is trivial, already answered or senseless from a proficient user perspective - please accept my apologies.

1

There are 1 best solutions below

0
On BEST ANSWER

The best advice I can give you is to organise your code into libraries. It will decouple the code and open up opportunities for unit testing, simulation, code reuse etc.

You should definitely avoid dependency on common.h in Peripherals and Modules.

Below is an example of how you can create and link libraries to each other and the main project.

Top level CMakeLists.txt

cmake_minimum_required(VERSION 3.13)

# [Platfrom specific command] Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(ProjectName C CXX ASM)

# [Platfrom specific command] Initialize the Raspberry Pi Pico SDK
pico_sdk_init()

# Create executable with specified sources
add_executable(ProjectName main.c)

add_subdirectory(Modules)
add_subdirectory(Peripherals)

# Peripherals depend on pico_stdlib only.
target_link_libraries(Peripherals PUBLIC pico_stdlib)

# Modules depend on peripherals only.
target_link_libraries(Modules PUBLIC Peripherals)

# Link direct dependencies of the project.
target_link_libraries(ProjectName
    PRIVATE
        Modules
        hardware_timer
)

Peripherals/CMakeLists.txt

project(Peripherals)

add_library(${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} ${PROJECT_SOURCE_DIR})

target_sources(${PROJECT_NAME}
    PRIVATE
        Peripherals/PeripheralA.c
        Peripherals/PeripheralA.h
        Peripherals/PeripheralB.c
        Peripherals/PeripheralB.h
)

Modules/CMakeLists.txt

project(Modules)

add_library(${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} ${PROJECT_SOURCE_DIR})

target_sources(${PROJECT_NAME}
    PRIVATE
        Modules/ModuleSource.c
        Modules/ModuleSource.h
        Modules/ModuleConfig.h
)