diff options
Diffstat (limited to 'GUIDE.md')
-rw-r--r-- | GUIDE.md | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..80abfb6 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,178 @@ +## 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<uint8_t, 0x0021>; +``` + +`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<PORTA_OUT, (1 << 0)>; // bit zero +using LED_2 = fr::RegisterMask<PORTA_OUT, (1 << 2)>; // bit two +``` + +We can also give a name to both LEDs: + +```cpp +using LED_ALL = fr::RegisterMask<PORTA_OUT, (1 << 0) | (1 << 2)>; +``` + +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<LED_2>(); +``` + +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<LED_1, LED_2>(); +``` + +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<LED_1::set, LED_2::clear>(); // 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<uint8_t, 0x0041>; + +using LED_3 = fr::RegisterMask<PORTB_OUT, (1 << 5)>; + +// Group the output ports together: +using LEDS = fr::RegisterGroup<PORTA_OUT, PORTB_OUT>; +``` + +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<LED_1, LED_2, LED_3>(); + +LEDS::modify<LED_1::set, + LED_2::clear, + LED_3::toggle>(); // 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<CLOCK_CONTROL, 0x3C>; +``` + +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_DIV::write<0x03>, CLOCK_ENABLE::set>(); +``` + +A `RegisterMaskValue` can also be defined to identify specific values: + +```cpp +using CLOCK_DIV4 = fr::RegisterMaskValue<CLOCK_DIV, 0x03>; +``` + +`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<typename T, uintptr_t Addr> +struct MemoryIO { + using type = T; + constexpr static auto addr = Addr; + + /** + * Reads the register's value. + */ + constexpr static T read() { + return *reinterpret_cast<volatile T*>(Addr); + } + + /** + * Overwrites the register's value. + */ + constexpr static void write(const T& value) { + *reinterpret_cast<volatile T*>(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<typename T, uintptr_t Addr> +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<PortIO, uint8_t, 0x60>; +``` + |