aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClyne Sullivan <clyne@bitgloo.com>2022-08-08 21:03:52 -0400
committerClyne Sullivan <clyne@bitgloo.com>2022-08-08 21:03:52 -0400
commit86c4b3b5ceaa1bd927a443fa254cdab76b44a480 (patch)
tree7497711f941d3e351bb690af9aa1f57cdb2264f7
parentc0ca2d3a373e66f474d21ce34c25b38d953a66c7 (diff)
add guide; couple small fixes
-rw-r--r--GUIDE.md178
-rw-r--r--funreg.hpp4
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>;
+```
+
diff --git a/funreg.hpp b/funreg.hpp
index 6ca44ed..00cd3a9 100644
--- a/funreg.hpp
+++ b/funreg.hpp
@@ -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>);
}
};