diff options
author | Clyne Sullivan <clyne@bitgloo.com> | 2022-08-08 21:03:52 -0400 |
---|---|---|
committer | Clyne Sullivan <clyne@bitgloo.com> | 2022-08-08 21:03:52 -0400 |
commit | 86c4b3b5ceaa1bd927a443fa254cdab76b44a480 (patch) | |
tree | 7497711f941d3e351bb690af9aa1f57cdb2264f7 | |
parent | c0ca2d3a373e66f474d21ce34c25b38d953a66c7 (diff) |
add guide; couple small fixes
-rw-r--r-- | GUIDE.md | 178 | ||||
-rw-r--r-- | funreg.hpp | 4 |
2 files changed, 180 insertions, 2 deletions
diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..6d3b702 --- /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>; +``` + +`fr::MemRegister` is an `fr::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 +`fr::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: + +```cpp +PORTA_OUT::toggle<LED_1, LED_2>(); +``` + +They also support a `modify` function, which takes a list of mask operations as +shown below. Through `modify`, the register is only read and written once, +minimizing I/O. + +```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 what you could do: + +```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 defining a `RegisterGroup`, we can make the same calls to modify the LEDs as +we would with the single register. `RegisterGroup` will direct masks to their +appropriate registers, while merging operations on the same register to +maintain that 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, bitwise-OR the value `0x03` to the mask's location, then write the new +value to the register. + +`write` can also be included in `modify` chains: + +```cpp +CLOCK_CONTROL::modify<CLOCK_DIV::write<0x03>, CLOCK_ENABLE::set>(); +``` + +You may also define a `RegisterMaskValue` to name a specific value: + +```cpp +using CLOCK_DIV4 = fr::RegisterMaskValue<CLOCK_DIV, 0x03>; +``` + +Three functions are supported for `RegisterMaskValue`: `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 +memory-mapped or 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>; +``` + @@ -46,7 +46,7 @@ struct MemoryIO { constexpr static auto addr = Addr; /** - * Gets a pointer to the register. + * Reads the register's value. */ constexpr static T read() { return *reinterpret_cast<volatile T*>(Addr); @@ -392,7 +392,7 @@ struct RegisterMaskValue * Tests if this value is currently set in the register. */ static bool test() { - return Mask::read() & (value << BitOffset<Mask>); + return (Mask::read() & Mask::mask) == (value << BitOffset<Mask>); } }; |