add guide; couple small fixes
parent
c0ca2d3a37
commit
86c4b3b5ce
@ -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>;
|
||||
```
|
||||
|
Loading…
Reference in New Issue