MMIO++

The macro-familiar API angle

The point is not to hide register programming behind abstractions. The point is to keep the same register language while making illegal combinations fail at compile time.

What stays familiar

  • Registers are still addressed explicitly through a fixed MMIO address.
  • Fields still expose named states such as ENABLE, RESET, or MASTER.
  • Masks are still first-class and derived from the field layout.
  • Whole-register compositions still read like datasheet snippets.

What changes

Loose macro world

#define SPI_CR_SPIEN_ENABLE  (1u << 0)
#define SPI_MR_MSTR_MASTER   (1u << 0)

SPI_CR = SPI_MR_MSTR_MASTER;   /* wrong register */
SPI_CR = SPI_CR_SPIEN_Msk;     /* mask written as value */
SPI_MR |= SPI_MR_DLY(7);       /* field write via OR */

MMIO++ world

SPI_CR::Instance<0xFFFE0000u> spiCr;
SPI_MR::Instance<0xFFFE0004u> spiMr;

spiCr = SPI_CR::SPIEN::ENABLE;
spiCr &= ~SPI_CR::SPIEN::MASK;
spiMr.set<SPI_MR::DLY>(7);

/* cross-register mixes and mask/value confusion
   are compile-fail cases in this repo */

Public concepts

  • FIELD::VALUE_NAME encodes a specific state of a field.
  • FIELD::MASK is the field bit mask derived from position and width.
  • FIELD::value(x) encodes numeric value-field payloads.
  • REGISTER::MASK is the full-register clear or toggle mask.

Why the split matters

Register code is full of operations that look similar but do different things. OR-ing in an enable bit is not the same operation as clearing a field mask. Writing a field state is not the same operation as assigning the field mask itself.

MMIO++ makes those distinctions part of the public type system instead of leaving them as comments and team conventions.

Examples from the current repo

SPI_CR::Instance<0xFFFE0000u> spiCr;
SPI_MR::Instance<0xFFFE0004u> spiMr;

spiCr = SPI_CR::SPIEN::ENABLE | SPI_CR::SWRST::RESET;
spiCr |= SPI_CR::SPIEN::ENABLE;
spiCr &= ~SPI_CR::SWRST::MASK;
spiCr ^= SPI_CR::SPIEN::MASK;

spiMr = SPI_MR::MSTR::MASTER | SPI_MR::DLY::value(7);
spiMr.set<SPI_MR::MSTR::MASTER>();
spiMr.set(SPI_MR::MSTR::MASTER | SPI_MR::DLY::value(7));
spiMr.set<SPI_MR::DLY>(7);