From 86c4b3b5ceaa1bd927a443fa254cdab76b44a480 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 8 Aug 2022 21:03:52 -0400 Subject: add guide; couple small fixes --- GUIDE.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 GUIDE.md (limited to 'GUIDE.md') 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; +``` + +`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; // 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: + +```cpp +PORTA_OUT::toggle(); +``` + +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(); // 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; + +using LED_3 = fr::RegisterMask; + +// Group the output ports together: +using LEDS = fr::RegisterGroup; +``` + +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(); + +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, 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_ENABLE::set>(); +``` + +You may also define a `RegisterMaskValue` to name a specific value: + +```cpp +using CLOCK_DIV4 = fr::RegisterMaskValue; +``` + +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 +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; +``` + -- cgit v1.2.3 From a35ded8145691d7f616a498da432dcbced8bb0ef Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 9 Aug 2022 22:06:06 -0400 Subject: update documentation --- GUIDE.md | 32 ++++++++++++++++---------------- README.md | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 26 deletions(-) (limited to 'GUIDE.md') diff --git a/GUIDE.md b/GUIDE.md index 6d3b702..80abfb6 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -10,12 +10,12 @@ For an 8-bit register at memory address `0x0021`, you would write: using PORTA_OUT = fr::MemRegister; ``` -`fr::MemRegister` is an `fr::Register` that uses `MemoryIO` access. Registers +`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 -`fr::RegisterMask` lets us name one or more bits within a register. +`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: @@ -40,15 +40,16 @@ These calls can also be made through the register using template parameters: PORTA_OUT::set(); ``` -Registers can take multiple masks at once: +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(); ``` -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. +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 @@ -57,7 +58,7 @@ 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 what you could do: +This is where `RegisterGroup` comes in handy: ```cpp using PORTB_OUT = fr::MemRegister; @@ -68,10 +69,9 @@ using LED_3 = fr::RegisterMask; using LEDS = fr::RegisterGroup; ``` -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: +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(); @@ -98,8 +98,8 @@ 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. +mask, set the new value `0x03` in the mask's location, then update the +register. `write` can also be included in `modify` chains: @@ -107,13 +107,13 @@ value to the register. CLOCK_CONTROL::modify, CLOCK_ENABLE::set>(); ``` -You may also define a `RegisterMaskValue` to name a specific value: +A `RegisterMaskValue` can also be defined to identify specific values: ```cpp using CLOCK_DIV4 = fr::RegisterMaskValue; ``` -Three functions are supported for `RegisterMaskValue`: `set`, which would call +`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. @@ -122,7 +122,7 @@ bits' location. "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. +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: diff --git a/README.md b/README.md index c46b295..7ca829c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,34 @@ -# funreg: Functional Memory-mapped Register I/O +# funreg: Functional Register I/O using modern C++ -*funreg* provides a functional approach to operating on memory-mapped registers -with zero overhead. This library primarily targets embedded firmware, where -these types of operations are frequently encountered. +*funreg* provides a functional approach to interacting with registers. +The library includes support for memory-mapped registers; however, other types +of registers can be supported through creating a simple access interface. -What makes this library unique is its ability to carry out multiple register -operations with a single function call, reducing this to a single register read -and write. Further, registers can be organized into "groups": these groups can -receive a list of operations for any of the contained registers, and will -optimize down to a single read and write for each register. +A unique feature of this library is its ability to handle multiple register +operations with a single function call; these operations will be merged +together so that the register is only read and written once. -A tutorial or guide will be added soon. +Registers may also be organized into groups. These groups can similarly receive +a list of operations, which will be directed the to the appropriate registers +for the same single-read-single-write process. + +For example, LEDs can be controlled by a microcontroller with a single call: + +```cpp +LEDS::modify(); +``` + +...no matter if the LEDs use different registers, or if any of them are +controlled by an external circuit rather than a built-in IO peripheral. + +See `GUIDE.md` for a walk-through of the available functionality. + +## Feature overview + +* Define registers of any size, at any address, with optional custom access interface +* Define register masks to name the bits of registers +* Define register groups so ease programming (e.g. define an `RTC` group to work with all real-time clock registers at once) +* Make modifications through groups, masks, or the registers directly ## Requirements -- cgit v1.2.3