A toolkit for firmware development.
libmcu prioritizes simplicity and minimal code size. Dynamic memory allocation is avoided whenever possible, and no linker script tweaks are required.
All code is written in standard C99 for maximum portability. Its modular directory structure, naming conventions, and interface boundaries are designed to be intuitive and scalable across a wide range of embedded platforms.
Documentation for each module is located in its respective subdirectory. You can also find usage examples in examples and test cases in tests.
Feedback, suggestions, and contributions are always welcome.
libmcu is organized into three clear layers:
libmcu/
├── modules/ # Platform-independent logic modules (actor, metrics, pubsub, etc.)
├── interfaces/ # Vendor-neutral HAL abstraction interfaces (UART, GPIO, etc.)
├── ports/ # Platform-specific backend implementations (e.g., STM32 HAL, simulation)
├── project/ # Build integration scripts (e.g., modules.mk, interfaces.mk)
├── tests/ # Unit tests for modules and interfaces
├── examples/ # Integration examples for users
modules/contains logic modules that implement platform-agnostic behaviorinterfaces/defines hardware abstraction interfaces (HAL) that isolate platform-specific detailsports/provides platform-specific implementations of those interfaces (e.g., STM32, ESP-IDF)
This structure clearly separates logic, hardware abstraction, and platform adaptation, making the system easier to maintain and extend.
All public symbols follow the lm_ prefix convention to avoid name collisions
and to maintain a consistent global namespace.
- Actor
- Application Timer
- Button
- Buzzer
- Cleanup
- Command Line Interface
- Common
- DFU
- FSM
- Logging
- Metrics
- Power Management
- PubSub
- RateLim
- Retry with exponential backoff
- Runner
Choose the integration method that matches your platform and build system.
Add as a west module in your manifest:
# west.yml
manifest:
projects:
- name: libmcu
url: https://github.com/libmcu/libmcu.git
revision: main
path: modules/lib/libmcuEnable in prj.conf:
CONFIG_LIBMCU=y
All modules are enabled by default. Individual modules can be toggled via
west build -t menuconfig under the libmcu menu.
Clone or add as a git submodule under your project's components/ directory:
cd components
git submodule add https://github.com/libmcu/libmcu.git libmcuThe root CMakeLists.txt supports both plain CMake and ESP-IDF component
contexts. ESP-IDF detection uses COMMAND idf_component_register, so builds
that define ESP_PLATFORM without entering a real component context still fall
back to the freestanding CMake path.
By default, all modules are enabled. To select only specific modules, set
LIBMCU_MODULES in your project's top-level CMakeLists.txt before
project() so it is visible when ESP-IDF processes components:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(LIBMCU_MODULES actor logging metrics CACHE STRING "" FORCE)
project(my_project)add_subdirectory(<THIRD_PARTY_DIR>/libmcu)or
set(LIBMCU_ROOT <THIRD_PARTY_DIR>/libmcu)
#list(APPEND LIBMCU_MODULES metrics pubsub)
include(${LIBMCU_ROOT}/project/modules.cmake)
#list(APPEND LIBMCU_INTERFACES i2c uart)
include(${LIBMCU_ROOT}/project/interfaces.cmake)
# Add ${LIBMCU_MODULES_SRCS} to your target sources
# Add ${LIBMCU_MODULES_INCS} to your target includesor via FetchContent:
include(FetchContent)
FetchContent_Declare(libmcu
GIT_REPOSITORY https://github.com/libmcu/libmcu.git
GIT_TAG main
)
FetchContent_MakeAvailable(libmcu)LIBMCU_ROOT ?= <THIRD_PARTY_DIR>/libmcu
# The commented lines below are optional. All modules and interfaces included
# by default if not specified.
#LIBMCU_MODULES := actor metrics
include $(LIBMCU_ROOT)/project/modules.mk
<SRC_FILES> += $(LIBMCU_MODULES_SRCS)
<INC_PATHS> += $(LIBMCU_MODULES_INCS)
#LIBMCU_INTERFACES := gpio pwm
include $(LIBMCU_ROOT)/project/interfaces.mk
<SRC_FILES> += $(LIBMCU_INTERFACES_SRCS)
<INC_PATHS> += $(LIBMCU_INTERFACES_INCS)MIT License. See LICENSE file.