Skip to content

CMake Guide for Arduino Users

Phil Schatzmann edited this page Oct 5, 2025 · 15 revisions

What is CMake?

CMake is a cross-platform build system generator that helps manage the compilation of C/C++ projects. Instead of writing platform-specific makefiles, you write a CMakeLists.txt file that describes your project, and CMake generates the appropriate build files for your system (Makefiles on Linux/Mac, Visual Studio projects on Windows, etc.).

Why Use CMake for Arduino Projects?

Traditional Arduino projects use the Arduino IDE's simple build system, but for more complex projects, CMake offers several advantages:

  • Cross-platform compatibility: Build on Linux, Windows, and macOS
  • Better dependency management: Handle external libraries easily
  • Integration with IDEs: Works with VS Code, CLion, Qt Creator, and more
  • Advanced build configurations: Debug/Release builds, custom compiler flags
  • Testing support: Integrate unit tests into your build process
  • Scalability: Handle large projects with multiple libraries and executables

Basic CMake Concepts

CMakeLists.txt Files

Every CMake project has one or more CMakeLists.txt files that describe:

  • Project information (name, version, languages)
  • Source files and how to build them
  • Dependencies and libraries to link
  • Compiler flags and build options

Targets

In CMake, everything revolves around targets:

  • Executable targets: Programs you can run (e.g., Arduino sketches)
  • Library targets: Code that can be linked to other targets
  • Interface targets: Header-only libraries

Variables and Properties

CMake uses variables and properties to control the build process:

  • Variables store values (paths, flags, options)
  • Properties are attached to targets and control how they're built

Arduino Emulator Project Structure

This project provides custom CMake functions to simplify building Arduino-style projects:

Arduino-Emulator/
├── CMakeLists.txt                # Main project configuration
├── Arduino.cmake                 # Custom Arduino functions
├── ArduinoCore-API/              # Arduino API headers
├── ArduinoCore-Linux/            # Linux implementation
├── examples/                     # Example sketches
│   ├── blink/
│   │   ├── blink.ino
│   │   └── CMakeLists.txt
│   └── ...
└── build/                        # Generated build files

Custom Arduino Functions

arduino_sketch() Function

The arduino_sketch() function simplifies building Arduino sketches:

arduino_sketch(<name> <ino_file> [LIBRARIES <lib1> <lib2>] [DEFINITIONS <def1> <def2>])

Parameters:

  • name: Name of the executable target
  • ino_file: The .ino source file
  • LIBRARIES: Optional additional libraries to link
  • DEFINITIONS: Optional compile definitions

Examples:

# Simple sketch
arduino_sketch(blink blink.ino)

# Sketch with additional libraries
arduino_sketch(sensor_project sensor.ino LIBRARIES SAM)

arduino_library() Function

The arduino_library() function adds external Arduino libraries:

arduino_library(<name> <path_or_url> [TAG <git_tag>])

Examples:

# From GitHub repository
arduino_library(SAM "https://github.com/pschatzmann/arduino-SAM")

# With specific git tag/branch
arduino_library(SAM "https://github.com/pschatzmann/arduino-SAM" TAG main)

# From local path
arduino_library(MyLibrary "/path/to/local/library")

Basic CMake Workflow (build from a build directory)

The recommended pattern is an out-of-source build inside a build directory. From your project root:

# create and enter the build directory
mkdir -p build
cd build

# Configure the project (run CMake from inside the build directory)
# Add any -D options (example: -DCMAKE_BUILD_TYPE=Debug -DUSE_RPI=ON)
cmake ..

This generates the native build files (Makefiles, Ninja files, Visual Studio projects, ...).

Build from the same build directory:

# Build everything
cmake --build .

# Build a specific target (example: blink)
cmake --build . --target blink

# Parallel build (use multiple cores)
cmake --build . -j 8

Run the produced binary from the build directory (path is relative to build):

./examples/blink/blink

Common CMake Commands

Project Definition

cmake_minimum_required(VERSION 3.11)
project(my_project VERSION 1.0 LANGUAGES C CXX)

Adding Executables

add_executable(my_program main.cpp utils.cpp)

Adding Libraries

add_library(my_library STATIC lib.cpp lib.h)
add_library(my_header_lib INTERFACE)  # Header-only

Linking Libraries

target_link_libraries(my_program my_library pthread)

Setting Properties

set_target_properties(my_program PROPERTIES 
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON)

Compile Options and Definitions

target_compile_options(my_program PRIVATE -Wall -Wextra)
target_compile_definitions(my_program PRIVATE DEBUG=1)

Include Directories

target_include_directories(my_library PUBLIC include/)

Project-Specific Options

This Arduino Emulator project provides several build options:

# Configure from the `build` directory using `cmake ..` and pass -D options to enable features.
# create and enter the build directory first:
mkdir -p build
cd build

# Enable Raspberry Pi support
cmake -DUSE_RPI=ON ..

# Enable HTTPS support
cmake -DUSE_HTTPS=ON ..

# Enable remote API support
cmake -DUSE_REMOTE=ON ..

# Enable FTDI support 
cmake -DUSE_FTDI=ON ..

# Combine multiple options
cmake -DUSE_RPI=ON -DUSE_HTTPS=ON ..

Example: Creating a New Arduino Sketch

  1. Create the sketch directory:

    mkdir examples/my_project
    cd examples/my_project
  2. Write your Arduino code (my_project.ino):

    #include "Arduino.h"
    
    void setup() {
        Serial.begin(115200);
        pinMode(LED_BUILTIN, OUTPUT);
    }
    
    void loop() {
        digitalWrite(LED_BUILTIN, HIGH);
        delay(1000);
        digitalWrite(LED_BUILTIN, LOW);
        delay(1000);
        Serial.println("Blink!");
    }
  3. Create CMakeLists.txt:

    cmake_minimum_required(VERSION 3.11)
    
    # Use the arduino_sketch function
    arduino_sketch(my_project my_project.ino)
  4. Add to parent CMakeLists.txt:

    # In examples/CMakeLists.txt
    add_subdirectory("my_project")
  5. Build and run:

    cmake --build build --target my_project
    ./build/examples/my_project/my_project

Debugging CMake

Common Commands for Troubleshooting

Run the following commands from inside the build directory:

# See all available targets
cmake --build . --target help

# Verbose build output
cmake --build . --verbose

Run the following commands from inside the project root directory:

# Clean and rebuild
rm -rf build && mkdir -p build && cd build && cmake .. && cmake --build .

Common Issues and Solutions

  1. "Target not found": Check spelling and ensure add_subdirectory() is called
  2. "Library not found": Verify library paths and ensure libraries are built first
  3. Compile errors: Check include paths and compiler flags
  4. Link errors: Verify all dependencies are properly linked

Advanced Topics

Conditional Compilation

if(USE_RPI)
    target_compile_definitions(my_target PRIVATE USE_RPI)
    target_link_libraries(my_target gpiod)
endif()

Installing and Using External CMake Libraries from GitHub

CMake provides several ways to include external libraries from GitHub repositories:

Method 1: Using the Arduino Library Function

For Arduino-style libraries, use the project's custom function:

# This automatically clones and sets up the library
arduino_library(MyLib "https://github.com/user/arduino-library.git" TAG v1.0.0)

# Use in your sketch
arduino_sketch(my_sketch main.ino LIBRARIES MyLib)

Method 2: FetchContent (Recommended for CMake 3.14+)

include(FetchContent)

# Declare the external library
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.2
)

# Make it available
FetchContent_MakeAvailable(json)

# Link to your target
target_link_libraries(my_target nlohmann_json::nlohmann_json)

Method 3: ExternalProject (For complex builds)

include(ExternalProject)

ExternalProject_Add(
    external_lib
    GIT_REPOSITORY https://github.com/user/library.git
    GIT_TAG main
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external
    BUILD_COMMAND cmake --build .
    INSTALL_COMMAND cmake --install .
)

Method 4: Git Submodules + add_subdirectory

# Add as submodule
git submodule add https://github.com/user/library.git external/library

# In CMakeLists.txt
add_subdirectory(external/library)
target_link_libraries(my_target library_target)

Best Practices

  1. Use modern CMake: Prefer target_* commands over global settings
  2. Be explicit: Specify PUBLIC, PRIVATE, or INTERFACE for dependencies
  3. Avoid global variables: Use target properties instead
  4. Use functions: Create reusable functions for common patterns
  5. Version constraints: Specify minimum CMake version requirements
  6. Out-of-source builds: Always build in a separate directory
  7. Cache variables: Use option() for user-configurable settings

Resources


This guide should help you understand and work with CMake in the Arduino Emulator project. The custom functions make it easy to add new sketches and libraries while maintaining clean, readable build files.

Clone this wiki locally