1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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>;
```
|