teensy cmake macros
August 14, 2026 · View on GitHub
A minimal CMake toolchain + macros for cross-compiling Teensy 4.x firmware and libraries with arm-none-eabi-gcc — no Arduino IDE required.
- builds with
cmake+arm-none-eabi-gcc - Teensy 4.0 / 4.1 (tested on 4.1) and the NXP RT1060-EVKB dev board; should be easy to extend to 3.x
- pulls Arduino libraries straight from git, with an optional shared cache (see Dependency caching)
- compiles library code to
.aarchives to avoid unnecessary recompiles, and can link the C++ std library - based on ronj/teensy-cmake-template
Requirements
- arm-none-eabi-gcc
- CMake ≥ 3.24 — the macros use the
$<LINK_GROUP>generator expression
Quick start
1. Toolchain file
Create cmake/toolchain/teensy41.cmake and point COMPILERPATH at your arm-none-eabi-gcc bin/ folder:
set(TEENSY_VERSION 41 CACHE STRING "Teensy version: 40, 41, or 42 (RT1060-EVKB)" FORCE)
set(CPU_CORE_SPEED 600000000 CACHE STRING "CPU core speed in Hz" FORCE)
set(CMAKE_EXE_LINKER_FLAGS "--specs=nano.specs" CACHE INTERNAL "") # needed if you link the C++ std lib
set(COMPILERPATH "/Applications/ARM_10/bin/") # <-- edit for your machine
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
set(CMAKE_C_COMPILER ${COMPILERPATH}arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER ${COMPILERPATH}arm-none-eabi-g++)
set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_C_COMPILER} <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
2. CMakeLists.txt
The macros are pulled in with FetchContent; the Teensy 4 core resolves local-first under TEENSY_LIB_ROOT, else CPM fetches it at a pinned SHA — either way, you don't clone anything by hand:
cmake_minimum_required(VERSION 3.24)
project(my_firmware C CXX)
include(FetchContent)
FetchContent_Declare(teensy_cmake_macros
GIT_REPOSITORY https://github.com/newdigate/teensy-cmake-macros
GIT_TAG main)
FetchContent_MakeAvailable(teensy_cmake_macros)
include(${teensy_cmake_macros_SOURCE_DIR}/CMakeLists.include.txt)
# the Teensy 4 core (resolved automatically; ${teensy_cores_SOURCE_DIR} points at it)
import_arduino_library(cores ${teensy_cores_SOURCE_DIR}/teensy4 avr util)
# Arduino libraries, straight from git (see "Dependency caching" below)
import_arduino_library_git(SPI https://github.com/PaulStoffregen/SPI.git master "")
# your own code
teensy_add_library(my_lib my_lib.cpp) # optional
teensy_add_executable(my_firmware sketch.ino) # .ino, .cpp and .c all work
teensy_target_link_libraries(my_firmware my_lib SPI cores) # link order does not matter
3. Build
mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain/teensy41.cmake
make
You get my_firmware.elf and my_firmware.hex, ready to flash. A passing build means it compiles and links — there is no on-device check.
Converting an existing Arduino/Teensyduino sketch
Already have a sketch? convert-arduino-example-skill is a companion Node.js tool that turns a Teensyduino sketch (or a whole folder of them) into a ready-to-build teensy-cmake-macros project — it generates the CMakeLists.txt, the toolchain file, and a compilable .cpp (injecting Arduino.h and forward prototypes, mapping #includes to git libraries):
node bin/convert-sketch.js path/to/Sketch.ino --board 41
It's a work in progress — check its issues for current limitations.
Macros
| macro | what it does |
|---|---|
teensy_add_executable(TARGET srcs…) | builds TARGET.elf and a TARGET.hex. Sources may be .ino/.cpp (compiled as C++) or .c. |
teensy_add_library(TARGET srcs…) | builds a static library, target named TARGET.o. |
import_arduino_library(NAME ROOT [subdirs…]) | adds a library from a local path and builds it. |
import_arduino_library_git(NAME URL BRANCH PATH [subdirs…]) | fetches a library from git (via CPM) and builds it. PATH is the source subdir ("" = repo root); extra subdirs are added as well. |
teensy_declare_library(NAME SUBDIR URL REF PATH) | registers NAME for resolution by name alone. SUBDIR is relative to TEENSY_LIB_ROOT (the local checkout, sub-path included — e.g. FNET/src); PATH is the subdir inside the fetched repo (. = repo root). |
teensy_resolve_library(NAME OUT_VAR) | resolves a declared library's source dir into OUT_VAR — local-first under TEENSY_LIB_ROOT, else CPM at the pinned REF. For cherry-picking individual files instead of importing the whole library. |
teensy_import_library(NAME [subdirs…]) | teensy_resolve_library + import_arduino_library in one call — the declared-library equivalent of import_arduino_library_git. |
teensy_target_link_libraries(TARGET libs…) | links libraries into TARGET.elf. Order does not matter — the libraries are wrapped in a linker group that re-scans until every cross-reference resolves (circular deps included). |
teensy_include_directories(paths…) | adds extra include directories. |
Example: common PaulStoffregen libraries (repo / branch / source path)
import_arduino_library_git(SPI https://github.com/PaulStoffregen/SPI.git master "")
import_arduino_library_git(SdFat https://github.com/PaulStoffregen/SdFat.git master "src" common DigitalIO ExFatLib FatLib FsLib iostream SdCard SpiDriver)
import_arduino_library_git(SD https://github.com/PaulStoffregen/SD.git Juse_Use_SdFat src)
import_arduino_library_git(Encoder https://github.com/PaulStoffregen/Encoder.git master "")
import_arduino_library_git(Bounce2 https://github.com/PaulStoffregen/Bounce2.git master src)
import_arduino_library_git(SerialFlash https://github.com/PaulStoffregen/SerialFlash.git master "" util)
import_arduino_library_git(Wire https://github.com/PaulStoffregen/Wire.git master "" utility)
import_arduino_library_git(arm_math https://github.com/PaulStoffregen/arm_math.git master src)
import_arduino_library_git(TeensyGFX https://github.com/newdigate/teensy-gfx.git main src)
Declared libraries (local-first, pinned fallback)
teensy_declare_library(NAME <subdir-under-TEENSY_LIB_ROOT> URL REF <sub-path>)
registers a library once; teensy_import_library(NAME [subdirs...]) (or
teensy_resolve_library(NAME OUT_VAR) for cherry-picking) then resolves it
local-first: a checkout at ${TEENSY_LIB_ROOT}/<subdir> wins — working
tree, uncommitted edits included — else the repo is fetched with CPM at the
pinned REF. TEENSY_LIB_ROOT defaults to $HOME/Development (env var or
CMake variable to override); -DTEENSY_FORCE_FETCH=ON ignores local checkouts
(fresh-user simulation). The COREPATH fallback resolves teensy-cores the
same way — pinned, not master. teensy_cores is reserved by the COREPATH
fallback — to use a different core, set COREPATH before including the
macros.
Dependency caching
Git libraries (import_arduino_library_git) are fetched with CPM.cmake, bootstrapped automatically on first use — which may be the COREPATH fallback resolving teensy-cores, not necessarily an explicit git library. By default each build directory clones its own copy. Set the CPM_SOURCE_CACHE environment variable to clone each (repo, branch) once and share it across every project and build directory:
export CPM_SOURCE_CACHE=$HOME/.cache/CPM
The cache is keyed by repo + branch, so different branches of the same library coexist without clobbering each other, while identical pins are downloaded only once. Declared libraries (teensy_declare_library) share the same cache. Projects that use only import_arduino_library (local paths) never download CPM — unless the core fallback needs it (no local teensy-cores checkout and no consumer-supplied COREPATH).
Linking the C++ standard library
If your sketch uses <vector>, <string>, and friends:
set(CMAKE_EXE_LINKER_FLAGS "--specs=nano.specs" CACHE INTERNAL "")
target_link_libraries(my_firmware.elf stdc++)
Note the .elf suffix and the plain target_link_libraries (not the teensy_ wrapper).