Modern C++23 components for ESP-IDF development.
Full API documentation is available at: https://cleishm.github.io/idfxx/
| Component | Description | Documentation |
|---|---|---|
| Core Infrastructure | ||
| idfxx_core | Core utilities: error handling, memory allocators, chrono, scheduling | API Docs |
| idfxx_hw_support | Hardware support: interrupt allocation and management | API Docs |
| idfxx_log | Type-safe logging with std::format | API Docs |
| System Services | ||
| idfxx_event | Type-safe event loop for asynchronous events | API Docs |
| idfxx_event_group | Type-safe FreeRTOS event group for inter-task synchronization | API Docs |
| idfxx_queue | Type-safe FreeRTOS queue for inter-task communication | API Docs |
| idfxx_task | FreeRTOS task management with join, cooperative stop, and fire-and-forget support | API Docs |
| idfxx_timer | High-resolution timer (esp_timer) | API Docs |
| Peripheral Drivers | ||
| idfxx_gpio | GPIO pin management with ISR support | API Docs |
| idfxx_i2c | I2C master bus and device driver | API Docs |
| idfxx_spi | SPI master bus driver | API Docs |
| idfxx_nvs | Non-Volatile Storage (NVS) wrapper | API Docs |
| Display Drivers | ||
| idfxx_lcd | LCD panel I/O interface for SPI-based displays | API Docs |
| idfxx_lcd_ili9341 | ILI9341 LCD controller driver (240x320) | API Docs |
| idfxx_lcd_touch | LCD touch controller interface | API Docs |
| idfxx_lcd_touch_stmpe610 | STMPE610 resistive touch controller driver | API Docs |
- Modern C++23 - Uses
std::expected, concepts, and other C++23 features - Dual error handling - Both exception-throwing and result-returning APIs
- Type-safe - Compile-time validation where possible
- Zero-overhead abstractions - Minimal runtime cost over raw ESP-IDF APIs
- ESP Component Registry - Install via
idf_component.yml
Several idfxx types provide std::formatter specializations (e.g. gpio, i2c::port,
spi::host_device, core_id, flags<E>). These are controlled by the CONFIG_IDFXX_STD_FORMAT
Kconfig option (under IDFXX menu), which is enabled by default.
Disabling this option prevents component headers from pulling in <format>, which can
significantly reduce image size. Note that idfxx_log requires CONFIG_IDFXX_STD_FORMAT and
will produce a build error if included without it.
- ESP-IDF 5.5 or later
- C++23 compiler (GCC 13+ or Clang 16+)
Add components to your project's idf_component.yml:
dependencies:
cleishm/idfxx_core:
version: "^0.9.0"
cleishm/idfxx_gpio:
version: "^0.9.0"
# Add other components as neededRepository releases use calendar versioning (e.g., v2026.02.10). Individual
components follow semantic versioning independently — a component's version is
only bumped when that component changes. Use caret constraints (e.g., ^0.9.0) in your
idf_component.yml to receive compatible updates.
If CONFIG_COMPILER_CXX_EXCEPTIONS is enabled:
#include <idfxx/gpio>
#include <idfxx/lcd/ili9341>
#include <idfxx/lcd/panel_io>
#include <idfxx/lcd/stmpe610>
#include <idfxx/spi/master>
#include <idfxx/log>
extern "C" void app_main() {
using namespace idfxx;
using namespace frequency_literals;
try {
// Initialize SPI bus for LCD and touch controller
auto spi_bus = std::make_shared<spi::master_bus>(
spi::host_device::spi2,
spi::bus_config{
.mosi_io_num = gpio_23,
.miso_io_num = gpio_19,
.sclk_io_num = gpio_18,
},
spi::dma_chan::ch_auto
);
// Create panel I/O for LCD
auto panel_io = std::make_shared<lcd::panel_io>(
spi_bus,
lcd::panel_io::spi_config{
.cs_gpio = gpio_14,
.dc_gpio = gpio_27,
.spi_mode = 0,
.pclk_freq = 10_MHz,
.trans_queue_depth = 10,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
}
);
// Create ILI9341 LCD panel (240x320)
auto display = std::make_unique<lcd::ili9341>(
panel_io,
lcd::panel::dev_config{
.reset_gpio = gpio_33,
.rgb_element_order = lcd::rgb_element_order::bgr,
.bits_per_pixel = 16,
}
);
// Turn on the display
display->display_on(true);
// Configure backlight GPIO
auto backlight = gpio_32;
backlight.set_direction(gpio::mode::output);
backlight.set_level(true); // Turn on backlight
idfxx::log::info("app", "Display initialized successfully");
} catch (const std::system_error& e) {
idfxx::log::error("app", "Display initialization failed: {}", e.what());
}
}Using try_* methods with std::expected for explicit error handling:
#include <idfxx/gpio>
#include <idfxx/lcd/ili9341>
#include <idfxx/lcd/panel_io>
#include <idfxx/lcd/stmpe610>
#include <idfxx/spi/master>
#include <idfxx/log>
extern "C" void app_main() {
using namespace idfxx;
using namespace frequency_literals;
// Initialize SPI bus for LCD and touch controller
auto spi_bus_res = spi::master_bus::make(
spi::host_device::spi2,
spi::bus_config{
.mosi_io_num = gpio_23,
.miso_io_num = gpio_19,
.sclk_io_num = gpio_18,
},
spi::dma_chan::ch_auto
);
if (!spi_bus_res) {
idfxx::log::error("app", "Failed to initialize SPI bus: {}", spi_bus_res.error().message());
return;
}
auto spi_bus = std::move(*spi_bus_res);
// Create panel I/O for LCD
auto panel_io_res = lcd::panel_io::make(
spi_bus,
lcd::panel_io::spi_config{
.cs_gpio = gpio_14,
.dc_gpio = gpio_27,
.spi_mode = 0,
.pclk_freq = 10_MHz,
.trans_queue_depth = 10,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
}
);
if (!panel_io_res) {
idfxx::log::error("app", "Failed to create panel I/O: {}", panel_io_res.error().message());
return;
}
auto panel_io = std::move(*panel_io_res);
// Create ILI9341 LCD panel (240x320)
auto display_res = lcd::ili9341::make(
panel_io,
lcd::panel::dev_config{
.reset_gpio = gpio_33,
.rgb_element_order = lcd::rgb_element_order::bgr,
.bits_per_pixel = 16,
}
);
if (!display_res) {
idfxx::log::error("app", "Failed to initialize display: {}", display_res.error().message());
return;
}
auto display = std::move(*display_res);
// Turn on the display
if (auto res = display->try_display_on(true); !res) {
idfxx::log::error("app", "Failed to turn on display: {}", res.error().message());
return;
}
// Configure backlight GPIO
auto backlight = gpio_32;
backlight.try_set_direction(gpio::mode::output);
backlight.try_set_level(true); // Turn on backlight
idfxx::log::info("app", "Display initialized successfully");
}idfxx includes comprehensive tests using the Unity test framework. Tests are categorized into two types:
- Software tests - Test error handling, type safety, and logic without hardware dependencies
- Hardware tests - Test actual hardware operations (GPIO pins, NVS flash, SPI peripherals)
Software tests can run in the QEMU ESP32-S3 emulator without physical hardware:
# Set up ESP-IDF environment
source $IDF_PATH/export.sh
# Configure for QEMU
idf.py set-target esp32s3
cp sdkconfig.qemu sdkconfig
idf.py reconfigure
# Build the project
idf.py build
# Create flash image
esptool.py --chip esp32s3 merge_bin \
-o flash_image.bin \
--flash_mode dio --flash_size 4MB --flash_freq 40m \
0x0 build/bootloader/bootloader.bin \
0x8000 build/partition_table/partition-table.bin \
0x10000 build/idfxx.bin
# Run tests in QEMU
qemu-system-xtensa \
-machine esp32s3 \
-nographic \
-drive file=flash_image.bin,if=mtd,format=raw \
-serial mon:stdioQEMU tests automatically exclude hardware-dependent tests tagged with [hw].
All tests, including hardware-dependent tests, can run on physical ESP32-S3 hardware:
# Build and flash to device
idf.py set-target esp32s3
idf.py build flash monitorHardware tests require:
- ESP32-S3 development board
- Available GPIO pins for GPIO tests
- Flash storage for NVS tests
- SPI peripheral availability for SPI tests
Tests are organized by component in components/*/tests/ directories.
GitHub Actions automatically runs:
- Build verification for ESP32-S3 target
- QEMU test execution for software tests
See .github/workflows/build.yml for CI configuration.
All components use the same clang-format configuration. Format code with:
cmake --build build --target formatApache License 2.0 - see LICENSE for details.