The Project Story
Embedded register code usually starts from the datasheet,
not from an object model. That is why so many teams stay
with C macros even when they know the tradeoffs: the code
stays direct, the names stay familiar, and the hardware
intent remains visible.
MMIO++ is aimed at that exact moment. It does not try to
hide registers behind a framework dialect. It keeps the
register-programming shape intact and uses C++ types to
block the mistakes that waste time during bring-up:
cross-register mixes, mask-versus-value confusion, and
write forms that look plausible but mean the wrong thing
on the bus.
Why This Is Credible
- The API keeps register call sites close to the way firmware engineers already read datasheets and macro-based headers.
- Compile-fail coverage locks down the category mistakes that raw macros tend to let through.
- QEMU runtime tests exercise register reads and writes at real target addresses instead of stopping at host-side simulation.
- The same register definitions work for live MMIO access and local shadow values, so staged updates do not fork the programming model.
One Register Definition, Two Jobs
Real firmware often needs both styles of work: direct MMIO
access for the live peripheral, and a local register image
when a sequence should be staged before one final write.
MMIO++ keeps those two paths on the same definition instead
of forcing a second API for shadow values.
SPI_MR::Instance<0xFFFE0004u> spiMr;
SPI_MR shadow = spiMr;
shadow.set<SPI_MR::PCS>(1);
shadow.set<SPI_MR::DLY>(7);
spiMr = shadow;