One main.cpp. Any microcontroller.¶
A from-scratch C++23 framework for microcontrollers: write your app once, recompile it
for a different board by changing one line — no #ifdef, no RTOS required, no IDE required.
Mistakes are caught at compile time, in errors a beginner can read.
#include <alloy/board.hpp>
using namespace alloy::literals;
int main() {
board::init();
while (true) {
board::led.toggle();
alloy::sleep_for(500ms);
}
}
$ uv tool install alloy-embedded # or: pipx install alloy-embedded
$ alloy new hello --board esp32_devkit && cd hello
$ alloy run # build + flash + serial monitor
$ alloy build --board nucleo_g071rb # same code, different MCU — one flag
Why alloy¶
-
Truly portable
The same
main.cpprecompiles for another board by changing one config line. Portability comes from board roles andif constexpr (board::caps::x)— zero preprocessor conditionals, enforced by CI. -
Safe at compile time
A wrong pin route is a compile error that names the pin, not a runtime surprise. Peripherals are selected by type, with no vtables and no heap — the zero-cost claim is real.
-
Trivially easy
uv tool install alloy-embedded && alloy new && alloy run→ a blinking LED in minutes. No IDE, no vendor code generator, no CMake to hand-write. -
Data-driven
Facts are generated, behavior is hand-written. Adding a chip that reuses known peripheral IP costs data only — no new C++, no new emitter branches.
-
Updatable in the field
A/B firmware slots computed from chip data, a trial boot that rolls back on its own, and optional Ed25519-signed images — over a plain UART.
-
Testable without hardware
Firmware boots on an emulated MCU generated from the same chip data, and CI asserts on real UART output — drivers, coroutines and the whole update lifecycle.
Supported boards¶
The same 14-line main.cpp runs on all of these. The last column says how far each has actually
been proven — a distinction alloy keeps everywhere.
| Family | Core | Board | Proven |
|---|---|---|---|
| ST STM32G0 | Cortex-M0+ | Nucleo-G0B1RE | silicon I²C, ADC, DMA, PWM, RTC, DAC, CAN, watchdog, flash |
| ST STM32G0 | Cortex-M0+ | Nucleo-G071RB | silicon PLL, GPIO, UART echo · 8 emulation gates |
| Microchip SAME70 | Cortex-M7 | SAM E70 Xplained | silicon PLLA, PIO, USART, I²C, ADC, DMA, EEPROM, Ethernet |
| Espressif ESP32 | Xtensa LX6 | WROVER-KIT, DevKit | silicon boot chain, UART, LEDC PWM |
| Raspberry Pi RP2040 | 2× Cortex-M0+ | RP2040-Zero | silicon boot2+CRC, 125 MHz clock, WS2812 |
| ST STM32F7 | Cortex-M7 | Nucleo-F722ZE | emulation boot + async runtime |
| Raspberry Pi RP2040 | 2× Cortex-M0+ | Raspberry Pi Pico | compiles hardware validation pending |
| ST STM32F7 | Cortex-M7 | Nucleo-F767ZI | compiles the network examples only |
What “silicon” means here
A commit reports the peripheral running on that named board, with observed register values or byte-level results. These are maintainer self-reports — there is no hardware CI runner. See the README for the per-family driver matrix.
Your board isn't listed?
alloy is designed so a new board is data, not code. See Adding a board — if it reuses a peripheral IP alloy already models, it costs zero new C++.
What you get¶
- Peripherals: GPIO, UART, SPI, I²C, ADC, PWM, DMA, timers, watchdog, RTC, DAC, CAN, on-chip flash/NVM — coverage varies by family; GPIO and UART are everywhere, CAN and DAC are one chip.
- Async without an RTOS: C++20 coroutines with no heap and no dynamic allocation.
- A single CLI:
new,build,flash,monitor,run,emulate,image,update,test— see all commands. - A driver ecosystem: sensors, displays and clocks you vendor with one command — grown outside the core, portable by construction.
- Field updates: A/B slots, trial boot with automatic rollback, and signed images.
- An IDE, if you want one: a VS Code extension with a visual pin/clock configurator — driven entirely by the CLI.
- Host-testable app logic: the scheduler and drivers run on your laptop against fakes, so you unit-test without hardware.
- CI that executes, not just compiles: firmware is booted under Renode and asserted on real UART output.