Skip to content

These module docs are in beta and may be incomplete.

modm:architecture:register: General Purpose Registers

Data structures to provide a native register abstraction.

These data structures are used to describe the relationship of single bits, bit groups and bit configurations in registers with type-safe access.

Registers can be made up of three things:

  • Bits: a single bit (position N),
  • Configurations: a combination of bits where the meaning does not correspond to its numeric value (position [N, M])
  • Values: a numeric value (position [N, M])

Example of an 8-bit register called Control

   7    6    5      4      3      2      1    0
| EN | FS | PRE1 | PRE0 | DEL2 | DEL1 | DEL0 | |
  • Bit 7: Enable
  • Bit 6: Full Scale
  • Configuration [5, 4]: Prescaler
    • 00: Divide by 1
    • 01: Divide by 2
    • 10: Divide by 4
    • 11: Divide by 8
  • Value [3, 1]: Start-Up Delay in ms

Register Bits

The bits can be modelled using strongly-typed enums and the Flags template class as follows:

enum class
Control : uint8_t
{
    EN = Bit7,  ///< bit documentation
    FS = Bit6,

    PRE1 = Bit5,
    PRE0 = Bit4,

    DEL2 = Bit3,
    DEL1 = Bit2,
    DEL0 = Bit1,
};
MODM_FLAGS8(Control);
// expands to
// typedef modm::Flags8< Control >  Control_t;
// and some enum operator overloading magic

You can handle all its register bits as you would expect:

Control_t control = Control::EN;
control = Control::EN | Control::FS;
control &= ~Control::FS;
control |= Control::FS;
control ^= Control::PRE1;
bool isSet = control & Control::FS;

control.reset(Control::PRE1 | Control::PRE0);
control.set(Control::DEL0);

bool noneSet = control.none(Control::PRE1 | Control::PRE0);
bool allSet = control.all(Control::EN | Control::FS);

You still get raw access if you really need it:

uint8_t raw = control.value; // the underlying type
control.value = 0x24;

The access is type-safe, you cannot use bits from two different registers:

enum class Control2 : uint8_t
{
    DIS = Bit4,
    HS = Bit3,
};
MODM_FLAGS8(Control2);

auto control = Control::EN | Control2::HS; // compile error

You can even overload functions on argument type now:

void write(Control_t control);
void write(Control2_t control);

write(Control::EN | Control::FS);  // calls #1
write(Control2::DIS);              // calls #2

Register Configurations

Configurations are also described as a strongly-typed enum and then wrapped into the Configuration template class.

enum class
Prescaler : uint8_t
{
    Div1 = 0,               ///< configuration documentation
    Div2 = int(Control::PRE0),
    Div4 = int(Control::PRE1),
    Div8 = int(Control::PRE1) | int(Control::PRE0),
};
typedef Configuration< Control_t, Prescaler, (Bit5 | Bit4) >  Prescaler_t;

The Prescaler enum values are already shifted in this example (hence the (Bit5 | Bit4) mask), however you can also declare the prescaler values non-shifted and let the wrapper shift it:

enum class Prescaler : uint8_t
{
    Div1 = 0,
    Div2 = 1,
    Div4 = 2,
    Div8 = 3,
};
typedef Configuration<Control_t, Prescaler, 0b11, 4> Prescaler_t;

Why? If you have two or more configurations with the same selections in the same register, you can simply add another one:

typedef Configuration< Control_t, Prescaler, 0b11, 6 >  Prescaler2_t;

Configurations can be used inline:

Control_t control = Control::EN | Prescaler_t(Prescaler::Div2);
Control_t control &= ~Prescaler_t::mask();

But do not have to:

Prescaler_t::set(control, Prescaler::Div2);
Prescaler_t::reset(control);
Prescaler prescaler = Prescaler_t::get(control);

Register Values

Values are described using the Value template class which masks and shifts the value as required. In our example the value has a width of 3 bits and needs to be shifted 1 bit:

typedef Value< Control_t, 3, 1 >  Delay_t;

This can be used the same way as the Configuration:

Control_t control = Control::EN | Prescaler_t(Prescaler::Div2) | Delay_t(4);
Control_t control &= ~Delay_t::mask();

Delay_t::set(control, 7);
Delay_t::reset(control);
uint8_t delay = Delay_t::get(control);

See Typesafe Register Access in C++ for a more detailed background on this implementation.

Content

// Struct
struct modm::Configuration< typename Parent , typename Enum , typename Parent::UnderlyingType Mask, typename Parent::UnderlyingType Position=0 >;
struct modm::Flags< typename Enum , typename T  >;
struct modm::FlagsGroup< T... >;
struct modm::FlagsOperators< typename Enum, typename T >;
struct modm::Register< typename T >;
struct modm::Value< typename Parent , typename Parent::UnderlyingType Width, typename Parent::UnderlyingType Position=0 >;

// Typedef
using modm::Flags16 = typedef Flags<Enum, uint16_t>;
using modm::Flags32 = typedef Flags<Enum, uint32_t>;
using modm::Flags8 = typedef Flags<Enum, uint8_t>;
using modm::Register16 = typedef Register<uint16_t>;
using modm::Register32 = typedef Register<uint32_t>;
using modm::Register8 = typedef Register<uint8_t>;

// Define
#define MODM_FLAGS16(Enum)
#define MODM_FLAGS32(Enum)
#define MODM_FLAGS8(Enum)
#define MODM_TYPE_FLAGS(Parent)

Dependencies

modm:architecture:register modm_architecture_register modm: architecture: register modm_architecture modm: architecture modm_architecture_register->modm_architecture modm_io modm: io modm_architecture_register->modm_io modm_math_utils modm: math: utils modm_architecture_register->modm_math_utils modm_architecture_gpio_expander modm: architecture: gpio.expander modm_architecture_gpio_expander->modm_architecture_register modm_architecture_i2c_multiplexer modm: architecture: i2c.multiplexer modm_architecture_i2c_multiplexer->modm_architecture_register modm_architecture_memory modm: architecture: memory modm_architecture_memory->modm_architecture_register modm_driver_ad7928 modm: driver: ad7928 modm_driver_ad7928->modm_architecture_register modm_driver_block_device_spi_flash modm: driver: block.device: spi.flash modm_driver_block_device_spi_flash->modm_architecture_register modm_driver_bme280 modm: driver: bme280 modm_driver_bme280->modm_architecture_register modm_driver_drv832x_spi modm: driver: drv832x_spi modm_driver_drv832x_spi->modm_architecture_register modm_driver_ds1631 modm: driver: ds1631 modm_driver_ds1631->modm_architecture_register modm_driver_hmc58x modm: driver: hmc58x modm_driver_hmc58x->modm_architecture_register modm_driver_hmc6343 modm: driver: hmc6343 modm_driver_hmc6343->modm_architecture_register modm_driver_itg3200 modm: driver: itg3200 modm_driver_itg3200->modm_architecture_register modm_driver_l3gd20 modm: driver: l3gd20 modm_driver_l3gd20->modm_architecture_register modm_driver_lis302dl modm: driver: lis302dl modm_driver_lis302dl->modm_architecture_register modm_driver_lis3dsh modm: driver: lis3dsh modm_driver_lis3dsh->modm_architecture_register modm_driver_lm75 modm: driver: lm75 modm_driver_lm75->modm_architecture_register modm_driver_lsm303a modm: driver: lsm303a modm_driver_lsm303a->modm_architecture_register modm_driver_ltc2984 modm: driver: ltc2984 modm_driver_ltc2984->modm_architecture_register modm_driver_mcp23x17 modm: driver: mcp23x17 modm_driver_mcp23x17->modm_architecture_register modm_driver_nrf24 modm: driver: nrf24 modm_driver_nrf24->modm_architecture_register modm_driver_pca8574 modm: driver: pca8574 modm_driver_pca8574->modm_architecture_register modm_driver_pca9535 modm: driver: pca9535 modm_driver_pca9535->modm_architecture_register modm_driver_tmp102 modm: driver: tmp102 modm_driver_tmp102->modm_architecture_register modm_driver_tmp175 modm: driver: tmp175 modm_driver_tmp175->modm_architecture_register modm_driver_vl53l0 modm: driver: vl53l0 modm_driver_vl53l0->modm_architecture_register modm_driver_vl6180 modm: driver: vl6180 modm_driver_vl6180->modm_architecture_register modm_platform_adc modm: platform: adc modm_platform_adc->modm_architecture_register modm_platform_spi modm: platform: spi modm_platform_spi->modm_architecture_register modm_platform_timer modm: platform: timer modm_platform_timer->modm_architecture_register modm_platform_uart modm: platform: uart modm_platform_uart->modm_architecture_register