A modern C++23 embedded systems project demonstrating best practices for hardware abstraction, host-based development, and automated testing.
This project explores:
- Modern C++ in Embedded Systems - Applying C++23 features and software engineering principles to microcontroller development
- Host-Side Simulation - Desktop development and testing environment with Python-based hardware emulation
- Correct-by-Construction Design - Type-safe abstractions and compile-time verification
Status: Educational/demonstrative project (not production-ready)
- ✅ Multi-Platform Support: Host (x86_64), ARM Cortex-M4, ARM Cortex-M7
- ✅ Hardware Abstraction Layers: Clean separation between MCU, Board, and Application layers
- ✅ Host Emulation: ZeroMQ-based IPC with Python hardware simulator
- ✅ Comprehensive Testing: C++ unit tests (Google Test) + Python integration tests (pytest)
- ✅ DevContainer Support: Full VS Code DevContainer configuration
- ✅ CI/CD Pipeline: GitHub Actions for automated builds and tests
- 🚧 Multi-Board: STM32F3 Discovery, STM32F7 Nucleo, nRF52832 DK (partial implementation)
- Install VS Code and the Dev Containers extension
- Open this repository in VS Code
- Press
Ctrl+Shift+Pand select "Dev Containers: Reopen in Container" - Wait for the container to build (first time only)
- Open a terminal and run:
cmake --workflow --preset=host-debug
That's it! The DevContainer includes all tools pre-installed.
# Clone the repository
git clone <repository-url>
cd embedded-cpp
# Run host-debug workflow (build + test)
docker compose run --rm host-debug
# Or run host-release workflow
docker compose run --rm host-releaseRequirements:
- CMake 3.27+
- Ninja
- Clang 18+ (host builds) or ARM GCC (embedded builds)
- Python 3.10+ (for integration tests)
- ZeroMQ (libzmq3-dev)
# Configure for host platform
cmake --preset=host
# Build (Debug)
cmake --build --preset=host --config Debug
# Run tests
ctest --preset=host -C Debug --output-on-failure
# Or use workflow preset (configure + build + test)
cmake --workflow --preset=host-debugembedded-cpp/
├── src/
│ ├── apps/
│ │ ├── blinky/ # Example LED blink application
│ │ └── uart_echo/ # Example UART echo with RxHandler
│ ├── libs/
│ │ ├── common/ # Error handling (std::expected)
│ │ ├── mcu/ # MCU abstraction (Pin, UART, I2C, Delay)
│ │ │ └── host/ # Host emulation with ZeroMQ
│ │ └── board/ # Board abstraction (LEDs, buttons, UART, peripherals)
│ │ ├── host/ # Host board implementation
│ │ ├── stm32f3_discovery/
│ │ ├── stm32f767zi_nucleo/
│ │ └── nrf52832_dk/
│
├── py/host-emulator/ # Python hardware emulator
│ ├── src/emulator.py # Virtual device simulator
│ └── tests/ # Integration tests (pytest)
│
├── cmake/toolchain/ # Cross-compilation toolchains
├── .devcontainer/ # VS Code DevContainer config
├── .github/workflows/ # GitHub Actions CI/CD
└── CLAUDE.md # Comprehensive project documentation
| Category | Technology |
|---|---|
| Language | C++23 |
| Build System | CMake 3.27+ with Ninja Multi-Config |
| Compilers | Clang 18 (host), ARM GCC (embedded) |
| Testing | Google Test (C++), pytest (Python) |
| IPC | ZeroMQ with JSON serialization |
| Embedded Targets | STM32F3, STM32F7, nRF52832 |
| Architectures | ARM Cortex-M4, ARM Cortex-M7 |
| Development | Docker, VS Code DevContainers |
| CI/CD | GitHub Actions |
# Host platform (development/testing)
cmake --workflow --preset=host-debug
cmake --workflow --preset=host-release
# STM32F3 Discovery (ARM Cortex-M4)
cmake --workflow --preset=stm32f3_discovery-release
# ARM Cortex-M7 (base preset)
cmake --workflow --preset=arm-cm7-release# Run all tests
ctest --preset=host -C Debug --output-on-failure
# Run specific test
ctest --preset=host -C Debug -R test_zmq_transportcd py/host-emulator
# Install dependencies
pip install -r requirements.txt
# Run tests (requires built executables)
pytest tests/ --blinky=../../build/host/bin/blinky --uart-echo=../../build/host/bin/uart_echoThe blinky application demonstrates:
- LED blinking at 500ms intervals
- Button interrupt handling (rising edge detection)
- Dependency injection with board abstraction
- Error handling with
std::expected
Run on host emulator:
# Terminal 1: Start Python emulator
cd py/host-emulator
python -m src.emulator
# Terminal 2: Run blinky
cd build/host/bin
./blinkyThe emulator will print pin state changes as the application runs.
The uart_echo application demonstrates:
- UART initialization and configuration
- Event-driven reception with RxHandler callback (similar to Pin interrupts)
- Asynchronous data echoing
- LED toggling on data received
- Greeting message on startup
Run on host emulator:
# Terminal 1: Run uart_echo
cd build/host/bin
./uart_echo
# Terminal 2: Send data via Python
python
>>> from src.emulator import DeviceEmulator
>>> emu = DeviceEmulator()
>>> emu.start()
>>> emu.uart1().send_data([72, 101, 108, 108, 111]) # "Hello"
>>> bytes(emu.uart1().rx_buffer) # See echoed data
>>> emu.uart1().rx_buffer.clear()This project demonstrates:
- Strong Types - Enums and type aliases prevent errors (PinState, PinDirection, Error)
- Type Safety - Explicit casting, no implicit conversions
- Compile-Time Checks -
constexpr, templates, concepts - Correct by Construction - APIs designed to prevent misuse
- Separate Calculation from Doing - Pure functions separate from side effects
- Dependency Inversion - High-level code depends on abstractions
- Single Responsibility - Each class/function has one clear purpose
- Interface Segregation - Small, focused interfaces (InputPin, OutputPin, BidirectionalPin)
- Error Handling -
std::expected<T, Error>instead of exceptions (RTTI disabled)
See Correct-by-Construction and Separate Calculating from Doing for more details.
- Formatting:
.clang-format(Google style, pointer-left alignment) - Linting:
.clang-tidy(comprehensive checks with naming convention enforcement) - Warnings: All warnings are errors (
-Werror) - Standard: C++23 with strict compliance
- RTTI: Disabled (no exceptions, embedded-friendly)
- Work in DevContainer (or use Docker)
- Implement on Host First - Faster iteration, easier debugging
- Write Tests - Unit tests (C++) and integration tests (Python)
- Run Linting -
clang-tidyenforces conventions - Format Code -
clang-formaton modified files - Verify CI - Ensure GitHub Actions passes
- Port to Hardware - Test on actual embedded boards
- CLAUDE.md - Comprehensive project documentation (architecture, conventions, workflows)
- test/README.md - Testing infrastructure details
- py/host-emulator/README.md - Python emulator documentation
This is an educational project. If you'd like to contribute or experiment:
- Use the DevContainer for a consistent environment
- Follow the coding conventions (enforced by clang-tidy)
- Write tests for new features
- Ensure CI passes before submitting PRs
- See CLAUDE.md for detailed guidelines
| Component | Status |
|---|---|
| Host emulation | ✅ Fully working |
| Blinky app | ✅ Fully working |
| UART echo app | ✅ Fully working |
| C++ unit tests | ✅ Fully working |
| Python integration tests | ✅ Fully working |
| Docker/DevContainer | ✅ Fully working |
| CI/CD | ✅ Fully working |
| STM32F3 Discovery | 🚧 Partial (hardware files present) |
| STM32F7 Nucleo | 🚧 Partial (hardware files present) |
| nRF52832 DK |
See LICENSE file for details.
- Built with CMake, Embedded Template Library, ZeroMQ
- Inspired by modern C++ embedded practices and correct-by-construction design