## Guided Overview Let's say you have a couple LEDs that are controlled by memory-mapped registers, a common case for embedded microcontrollers. To begin, you'll need to define the register used to control the LEDs. For an 8-bit register at memory address `0x0021`, you would write: ```cpp using PORTA_OUT = fr::MemRegister; ``` `MemRegister` is an `Register` that uses `MemoryIO` access. Registers have static functions for interacting with their contents; for example, we could now do `PORTA_OUT::write(0x10)` or `auto state = PORTA::read()`. A lot more can be done with registers once we define some register masks. A `RegisterMask` lets us name one or more bits within a register. To name our two LEDs, which are only controlled by single bits, we write: ```cpp using LED_1 = fr::RegisterMask; // bit zero using LED_2 = fr::RegisterMask; // bit two ``` We can also give a name to both LEDs: ```cpp using LED_ALL = fr::RegisterMask; ``` Now, we can control the LEDs either directly or through the register. Direct calls would be like `LED_1::set()`; there is also `clear`, `toggle`, `read`, `write`, and `test`. These calls can also be made through the register using template parameters: ```cpp PORTA_OUT::set(); ``` Registers can take multiple masks at once too. The masks will be merged so that the register is only read and written once: ```cpp PORTA_OUT::toggle(); ``` A `modify` function is also supported, which takes a list of mask operations as shown below. This allows the different operations to be carried out together, still keeping to a single register read and write: ```cpp PORTA_OUT::modify(); // Only have LED_1 turned on ``` What if we need to add a third LED? And what if that LED is on a different register, PORTB? This is where `RegisterGroup` comes in handy: ```cpp using PORTB_OUT = fr::MemRegister; using LED_3 = fr::RegisterMask; // Group the output ports together: using LEDS = fr::RegisterGroup; ``` By grouping the two registers, we can carry out our modification calls without worrying about which mask is for what register. The `RegisterGroup` will take of that, while still merging operations when possible to maintain minimal I/O: ```cpp LEDS::clear(); LEDS::modify(); // You get the idea... ``` ## Other features ### Multi-bit masks Say bits two through five in a register select a clock's prescaler: ```cpp using CLOCK_DIV = fr::RegisterMask; ``` The mask's `write` function will let you write values to the field: ```cpp CLOCK_DIV::write<0x03>(); ``` This will read the register's current value, clear all bits selected by the mask, set the new value `0x03` in the mask's location, then update the register. `write` can also be included in `modify` chains: ```cpp CLOCK_CONTROL::modify, CLOCK_ENABLE::set>(); ``` A `RegisterMaskValue` can also be defined to identify specific values: ```cpp using CLOCK_DIV4 = fr::RegisterMaskValue; ``` `RegisterMaskValue` supports three functions: `set`, which would call `CLOCK_DIV::write<0x03>()`; `clear`, which clears the masked bits; and `test`, which would confirm that the register contains the value `0x03` in the masked bits' location. ### External registers "External" registers are registers that are not memory-mapped. These are also supported in *funreg*, and can even be placed in `RegisterGroup`s with other register types. An "access type" must be defined to specify how the register is accessed. Here is the definition of `MemoryIO`, which is used for memory-mapped registers: ```cpp template struct MemoryIO { using type = T; constexpr static auto addr = Addr; /** * Reads the register's value. */ constexpr static T read() { return *reinterpret_cast(Addr); } /** * Overwrites the register's value. */ constexpr static void write(const T& value) { *reinterpret_cast(Addr) = value; } }; ``` Custom access types should use `MemoryIO` as a template. For example, here is an access type for ports on x86 processors: ```cpp template struct PortIO { using type = T; constexpr static auto addr = Addr; static T read() { T ret; asm volatile("in %0, %1" : "=r" (ret) : "r" (Addr)); return ret; } static void write(const T& value) { asm volatile("out %0, %1" :: "r" (value), "r" (Addr)); } }; ``` Now, just define your register(s) using `ExtRegister`: ```cpp using KEYBOARD = fr::ExtRegister; ```