-
Notifications
You must be signed in to change notification settings - Fork 8
CMake Guide for Arduino Users
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.).
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
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
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
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
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
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)
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")
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
cmake_minimum_required(VERSION 3.11)
project(my_project VERSION 1.0 LANGUAGES C CXX)
add_executable(my_program main.cpp utils.cpp)
add_library(my_library STATIC lib.cpp lib.h)
add_library(my_header_lib INTERFACE) # Header-only
target_link_libraries(my_program my_library pthread)
set_target_properties(my_program PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON)
target_compile_options(my_program PRIVATE -Wall -Wextra)
target_compile_definitions(my_program PRIVATE DEBUG=1)
target_include_directories(my_library PUBLIC include/)
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 ..
-
Create the sketch directory:
mkdir examples/my_project cd examples/my_project
-
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!"); }
-
Create CMakeLists.txt:
cmake_minimum_required(VERSION 3.11) # Use the arduino_sketch function arduino_sketch(my_project my_project.ino)
-
Add to parent CMakeLists.txt:
# In examples/CMakeLists.txt add_subdirectory("my_project")
-
Build and run:
cmake --build build --target my_project ./build/examples/my_project/my_project
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 .
-
"Target not found": Check spelling and ensure
add_subdirectory()
is called - "Library not found": Verify library paths and ensure libraries are built first
- Compile errors: Check include paths and compiler flags
- Link errors: Verify all dependencies are properly linked
if(USE_RPI)
target_compile_definitions(my_target PRIVATE USE_RPI)
target_link_libraries(my_target gpiod)
endif()
CMake provides several ways to include external libraries from GitHub repositories:
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)
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)
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 .
)
# 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)
-
Use modern CMake: Prefer
target_*
commands over global settings -
Be explicit: Specify
PUBLIC
,PRIVATE
, orINTERFACE
for dependencies - Avoid global variables: Use target properties instead
- Use functions: Create reusable functions for common patterns
- Version constraints: Specify minimum CMake version requirements
- Out-of-source builds: Always build in a separate directory
-
Cache variables: Use
option()
for user-configurable settings
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.