aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--GUIDE.md178
-rw-r--r--README.md38
-rw-r--r--funreg.hpp228
3 files changed, 366 insertions, 78 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>;
+```
+
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<LED1::set, LED2::clear, LED3::set>();
+```
+
+...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
diff --git a/funreg.hpp b/funreg.hpp
index 156b6cf..00cd3a9 100644
--- a/funreg.hpp
+++ b/funreg.hpp
@@ -1,5 +1,5 @@
/**
- * funreg.hpp - Functional memory-mapped register I/O using modern C++.
+ * funreg.hpp - Functional register I/O using modern C++.
* Written by Clyne Sullivan.
* <https://github.com/tcsullivan/funreg>
*/
@@ -7,11 +7,22 @@
#ifndef FUNCTIONAL_REGISTER_IO_H
#define FUNCTIONAL_REGISTER_IO_H
+/**
+ * Comment to disable external/custom register access.
+ * When disabled, only memory-mapped register access is supported.
+ * fr::Register can then also be used instead of fr::MemRegister.
+ */
+#define FUNREG_ENABLE_EXTERNAL_IO
+
#include <stdint.h>
+#ifdef FUNREG_ENABLE_EXTERNAL_IO
+#include <type_traits>
+#endif
+
namespace fr {
-// A utility to measure a bit-mask's offset from bit zero.
+// A utility to measure a bit-mask's offset from bit zero.
template<auto Mask, unsigned int N = 0>
constexpr auto BitOffset = []() constexpr {
if constexpr (Mask & 1)
@@ -21,23 +32,66 @@ constexpr auto BitOffset = []() constexpr {
}();
/**
- * @struct Register
- * @brief Defines a memory-mapped register, given bit-size and address.
- * @tparam T The integer type that matches the size of the register.
+ * @struct MemoryIO
+ * @brief Specifies how to access a memory-mapped register.
+ * @tparam T The size of the register.
* @tparam Addr The memory address of the register.
*
- * Defines a memory-mapped register that is usually accessed with a pointer of
- * type T*, and is located at address Addr.
+ * To create an I/O access type for external register access, use this
+ * structure as a template.
+ */
+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;
+ }
+};
+
+/**
+ * @struct Register
+ * @brief Defines a register, given how to access it.
+ * @tparam Access Specifies register access. See MemoryIO for an example.
*
- * Use only as a type, e.g. "using GPIO_OUT = Register<uint32_t, 0xA0004120>"
+ * When FUNREG_ENABLE_EXTERNAL_IO is not defined, Register assumes MemoryIO
+ * access. The template parameters become that of MemoryIO.
*/
+#ifdef FUNREG_ENABLE_EXTERNAL_IO
+template<typename Access>
+struct Register {
+ using access = Access;
+ using T = typename Access::type;
+ constexpr static auto Addr = Access::addr;
+#else
template<typename T, uintptr_t Addr>
struct Register {
+ using RegAccess = MemoryIO<T, Addr>;
+#endif // FUNREG_ENABLE_EXTERNAL_IO
+
/**
* Gets a pointer to the register.
*/
- constexpr static auto get() {
- return reinterpret_cast<volatile T*>(Addr);
+ constexpr static T read() {
+ return Access::read();
+ }
+
+ /**
+ * Overwrites the register's value.
+ */
+ constexpr static void write(const T& value) {
+ Access::write(value);
}
/**
@@ -45,14 +99,14 @@ struct Register {
*/
template<typename... Masks>
static void set() {
- apply<Masks...>([](auto r, auto m) { *r = *r | m; });
+ apply<Masks...>([](auto r, auto m) { return r | m; });
}
/**
* Sets register bits to '1' according to the given mask.
*/
static void set(const T& mask) {
- *get() = *get() | mask;
+ write(read() | mask);
}
/**
@@ -60,14 +114,14 @@ struct Register {
*/
template<typename... Masks>
static void clear() {
- apply<Masks...>([](auto r, auto m) { *r = *r & ~m; });
+ apply<Masks...>([](auto r, auto m) { return r & ~m; });
}
/**
* Clears register bits to '0' according to the given mask.
*/
static void clear(const T& mask) {
- *get() = *get() & ~mask;
+ write(read() & ~mask);
}
/**
@@ -75,14 +129,14 @@ struct Register {
*/
template<typename... Masks>
static void toggle() {
- apply<Masks...>([](auto r, auto m) { *r = *r ^ m; });
+ apply<Masks...>([](auto r, auto m) { return r ^m; });
}
/**
* Toggles bits in the register according to the given mask.
*/
static void toggle(const T& mask) {
- *get() = *get() ^ mask;
+ write(read() ^ mask);
}
/**
@@ -92,26 +146,10 @@ struct Register {
*/
template<typename... Masks>
static auto read() {
- if constexpr (sizeof...(Masks) > 0) {
- if (((Addr == Masks::reg::addr) | ...)) {
- auto mask =
- ([] {
- return Addr == Masks::reg::addr ? Masks::mask : 0;
- }() | ...);
- return *get() & mask;
- } else {
- return 0;
- }
- } else {
- return *get();
- }
- }
-
- /**
- * Overwrites the entire register with the given value.
- */
- static void write(const T& value) {
- *get() = value;
+ if constexpr (sizeof...(Masks) > 0)
+ return read() & mergeMasks<Masks...>();
+ else
+ return read();
}
/**
@@ -121,17 +159,10 @@ struct Register {
template<typename... Masks>
static bool test() {
if constexpr (sizeof...(Masks) > 0) {
- if (((Addr == Masks::reg::addr) | ...)) {
- auto mask =
- ([] {
- return Addr == Masks::reg::addr ? Masks::mask : 0;
- }() | ...);
- return (*get() & mask) == mask;
- } else {
- return 0;
- }
+ auto mask = mergeMasks<Masks...>();
+ return (read() & mask) == mask;
} else {
- return *get() != 0;
+ return read() != 0;
}
}
@@ -144,17 +175,17 @@ struct Register {
*/
template<typename... Ops>
static void modify() {
- if (((Addr == Ops::reg::addr) | ...)) {
- auto mask = *get();
+ if constexpr ((isThis<typename Ops::reg> | ...)) {
+ auto mask = read();
([&mask] {
- if (Addr == Ops::reg::addr)
+ if constexpr (isThis<typename Ops::reg>)
mask = Ops(mask);
}(), ...);
- *get() = mask;
+ write(mask);
}
}
- // Below is meant for internal use only.
+ // Below is meant for internal use only.
// Applies bit-masks to the register through the provided function.
// The provided function receives a pointer to the register's data and a
@@ -163,21 +194,50 @@ struct Register {
template<typename... Masks>
static void apply(auto fn) {
if constexpr (sizeof...(Masks) > 0) {
- auto mask =
+ auto mask = mergeMasks<Masks...>();
+ if constexpr (mask)
+ write(fn(read(), mask));
+ } else {
+ write(fn(read(), T(0) - 1));
+ }
+ }
+
+ // Takes a list of bit-masks, and returns a merged mask of those which are
+ // meant for this register.
+ template<typename... Masks>
+ static auto mergeMasks() {
+ if constexpr (sizeof...(Masks) > 0) {
+ if constexpr ((isThis<typename Masks::reg> | ...)) {
+ auto mask =
([] {
- return Addr == Masks::reg::addr ? Masks::mask : 0;
+ return isThis<typename Masks::reg> ? Masks::mask : 0;
}() | ...);
- if (mask)
- fn(get(), mask);
+ return mask;
+ } else {
+ return 0;
+ }
} else {
- fn(get(), T(0) - 1);
+ return 0;
}
}
+#ifdef FUNREG_ENABLE_EXTERNAL_IO
+ // Determines if the given register matches this one.
+ template<typename Reg>
+ constexpr static bool isThis = [] {
+ return std::is_same_v<typename Reg::access, access> && Addr == Reg::Addr;
+ }();
+#else
+ // Determines if the given register matches this one.
+ template<typename Reg>
+ constexpr static bool isThis = [] {
+ return Addr == Reg::Addr;
+ }();
+#endif // FUNREG_ENABLE_EXTERNAL_IO
+
Register() = delete;
using type = T;
- constexpr static auto addr = Addr;
};
/**
@@ -203,7 +263,7 @@ struct RegisterMask
*/
struct set {
constexpr set() {
- *Reg::get() = *Reg::get() | Mask;
+ Reg::write(Reg::read() | Mask);
}
// For internal use.
@@ -219,7 +279,7 @@ struct RegisterMask
*/
struct clear {
constexpr clear() {
- *Reg::get() = *Reg::get() & ~Mask;
+ Reg::write(Reg::read() & ~Mask);
}
// For internal use.
@@ -235,7 +295,7 @@ struct RegisterMask
*/
struct toggle {
constexpr toggle() {
- *Reg::get() = *Reg::get() ^ Mask;
+ Reg::write(Reg::read() ^ Mask);
}
// For internal use.
@@ -249,7 +309,7 @@ struct RegisterMask
* Reads from the paired register, applying the bit-mask.
*/
static auto read() {
- return *Reg::get() & Mask;
+ return Reg::read() & Mask;
}
/**
@@ -272,10 +332,10 @@ struct RegisterMask
template<T value>
struct write {
constexpr write() {
- auto r = *Reg::get();
+ auto r = Reg::read();
r &= ~Mask;
r |= value << BitOffset<Mask>;
- *Reg::get() = r;
+ Reg::write(r);
}
// For internal use.
@@ -332,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>);
}
};
@@ -352,11 +412,11 @@ public:
/**
* Sets bits throughout this group's registers according to the given masks.
* Bit-masks for the same register will be merged so that each register is
- * only written once.
+ * only written once.
*/
template<typename... Masks>
static void set() {
- apply<Masks...>([](auto r, auto m) { *r = *r | m; });
+ apply<Masks...>([](auto r, auto m) { return r | m; });
}
/**
@@ -366,7 +426,7 @@ public:
*/
template<typename... Masks>
static void clear() {
- apply<Masks...>([](auto r, auto m) { *r = *r & ~m; });
+ apply<Masks...>([](auto r, auto m) { return r & ~m; });
}
/**
@@ -376,7 +436,7 @@ public:
*/
template<typename... Masks>
static void toggle() {
- apply<Masks...>([](auto r, auto m) { *r = *r ^ m; });
+ apply<Masks...>([](auto r, auto m) { return r ^ m; });
}
/**
@@ -407,6 +467,38 @@ private:
template<typename... RegMasks>
constexpr auto Masks = (RegMasks::mask | ...);
+#ifdef FUNREG_ENABLE_EXTERNAL_IO
+/**
+ * Defines a register that is accessed through memory, i.e. memory-mapped.
+ * @tparam T The variable type used to access the register (e.g. uint32_t).
+ * @tparam Addr The memory address of the register.
+ */
+template<typename T, uintptr_t Addr>
+using MemRegister = Register<MemoryIO<T, Addr>>;
+
+/**
+ * Defines a register that is accessed through external or custom means.
+ * @tparam ExtIO A type that provides access functionality (e.g. MemoryIO).
+ * @tparam T The variable type used to access the register (e.g. uint32_t).
+ * @tparam Addr The memory address of the register.
+ *
+ * Custom access types should be defined using MemoryIO as a template.
+ */
+template<template<typename, uintptr_t> typename ExtIO, typename T, uintptr_t Addr>
+using ExtRegister = Register<ExtIO<T, Addr>>;
+#else
+/**
+ * Defines a register that is accessed through memory, i.e. memory-mapped.
+ * @tparam T The variable type used to access the register (e.g. uint32_t).
+ * @tparam Addr The memory address of the register.
+ *
+ * With external I/O disabled, the Register type may be used directly instead.
+ */
+template<typename T, uintptr_t Addr>
+using MemRegister = Register<T, Addr>;
+#endif // FUNREG_ENABLE_EXTERNAL_IO
+
} // namespace fr
#endif // FUNCTIONAL_REGISTER_IO_H
+