-
Notifications
You must be signed in to change notification settings - Fork 0
/
peripheraltypes.h
433 lines (351 loc) · 14.7 KB
/
peripheraltypes.h
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
#pragma once
#pragma clang diagnostic push
//we ignore the following warnings as this file exists to make hardware registers appear to be simple variables
#pragma ide diagnostic ignored "google-explicit-constructor"
#pragma ide diagnostic ignored "misc-unconventional-assign-operator"
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
#include "cheaptricks.h"
#include "bitbanger.h"
#include "boolish.h"
#include "eztypes.h"
/**
* types used for declaration of peripherals.
*
* The SFR* template classes have a peculiar usage pattern, caused by trying to make all related code be generated inline.
* At hardware module definition time one creates a typedef for each field rather than an instance.
* At each place of use one creates an instance and then assigns to that instance to write a value or simply reference that instance for a read.
* This precludes extern'ing global instances which then must take up real data space (const so not a big cost) and then be accessed via an extra level of indirection compared to what the 'create local instance' can result in.
* If we could create a global instance in a header file but get just one actual object created we would do that.
* Volatile is used to keep the compiler from optimizing away accesses that have physical side effects.
I am working on replacing *'s with &'s, its a statistical thing herein as to which is better.
#pragma ide diagnostic ignored "google-explicit-constructor"
#pragma ide diagnostic ignored "misc-unconventional-assign-operator"
we want these classes to appear to be normal program variables, so that we can replace instances of them with normal program variables to disable them with only changing the declaration.
*/
/** for a private single instance block */
#define soliton(type, address) type& the##type = *reinterpret_cast<type*>(address);
///** @deprecated marker for non-occupied memory location */
//using SKIPPED = const unsigned;
/** marker for an address, will eventually feed into a *reinterpret_cast<unsigned *>() */
using Address = unsigned;//address space of this device.
/* an attempt to suppress warnings about integer to pointer casts, while still leaving that warning on to catch unintentional ones */
union AddressCaster {
unsigned number;
void *pointer;
};
/* this function exists to hide some verbose casting */
template<typename Scalar> INLINETHIS constexpr Scalar &Ref(Address address) {
AddressCaster pun {address};
return *static_cast<Scalar *>(pun.pointer);
}
/** many, but not all, cortex devices put peripheral control registers in the 0x4000 space, and bitband all of that to 0x4200.
* "bitband" is ARM's term for mapping each bit of the lower space into a 32bit word in the bitband region.
This replaces a 3-clock operation that is susceptible to interruption into a one clock operation that is not. That is important if an ISR is modifying the same control word as main thread code.
*/
static constexpr unsigned int BandGroup = 0xE000'0000;//bit banding only applies to 0000 0000 through 000F FFFF in the 2000 and 4000 regions.
static constexpr unsigned BandBit = 0x0200'0000;
INLINETHIS constexpr Address bandShift(Address byteOffset) {
//5: 2^5 bits per 32 bit word. we expect a byte address here, so that one can pass values as read from the stm32 manual.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wbraced-scalar-init" //# leave braces in case Address becomes a real class
return {byteOffset << 5U};
}
/** @return bitband address for given bit (default 0) of @param byte address.
this assumes that the byte address ends in 00, which all of the ones in the st manual do.
*/
INLINETHIS constexpr Address bandFor(Address byteAddress, unsigned bitnum = 0) {
return {(bitnum << 2U) | bandShift(byteAddress) | (byteAddress & BandGroup) | BandBit};
}
#pragma clang diagnostic pop
/** A 32 bit item at a known address.
* when you don't know the address at compile time use one of these, else use an SFRxxx.
* This class essentially wraps the Ref<> template with operator overloads */
class ControlWord {
protected:
volatile unsigned &item;
public:
explicit constexpr ControlWord(Address dynaddr)
: item(Ref<unsigned>(dynaddr)) {
//#done
}
/** we often wish to return one of these, so we ensure the compiler knows it can do a bit copy or even a 'make in place'*/
constexpr ControlWord(const ControlWord &other) = default;
//use ControlBit instead. This one allows the ambiguity of writing a value other than 1 or 0 to a bit band item.
// constexpr ControlWord(Address dynaddr, unsigned bitnum)
// : ControlWord(bandFor(dynaddr, bitnum)) {
// //#done
// }
//using void return as we don't want to trust what the compiler might do with the 'volatile'
INLINETHIS
void operator=(unsigned value) const ISRISH {
item = value;
}
INLINETHIS
void operator|=(unsigned value) const ISRISH {
item |= value;
}
INLINETHIS
void operator&=(unsigned value) const ISRISH {
item &= value;
}
/** mostly exists to appease compiler complaint about ambiguity of assignment. */
INLINETHIS
void operator=(const ControlWord &other) const ISRISH {
item = other.item;
}
//we do want implicit conversions here, the goal of the class is to make accessing a control word look syntactically like accessing a normal variable.
/** it is unproven if volatility is propagated through this wrapper. Check it for your compiler and flags. */
INLINETHIS
operator unsigned() const ISRISH {
return item;
}
};
template<class Mustbe32> struct ControlStruct {
protected:
volatile unsigned &item;
public:
explicit constexpr ControlStruct(Address dynaddr)
: item(Ref<unsigned>(dynaddr)) {
//#done
}
/** we often wish to return one of these, so we ensure the compiler knows it can do a bit copy or even a 'make in place'*/
constexpr ControlStruct(const ControlStruct &other) = default;
INLINETHIS
void operator=(const Mustbe32 &value) const ISRISH {
item = *reinterpret_cast<const unsigned *>(&value);
}
INLINETHIS
void operator=(Mustbe32 &&value) const ISRISH {
item = *reinterpret_cast<const unsigned *>(&value);
}
INLINETHIS
const Mustbe32 operator()() const ISRISH {
unsigned read = item;
return *reinterpret_cast<const Mustbe32 *>(&read);
}
};
/** when you don't know the address at compile time use one of these, else use an SFRxxx.
* This is for fields which are byte aligned and some multiple of 8 bits */
template<typename IntType>
class ControlItem {
protected:
volatile IntType &item;
public:
explicit constexpr ControlItem(Address dynaddr)
: item(Ref<IntType>(dynaddr)) {
//#done
}
/** we often wish to return one of these, so we ensure the compiler knows it can do a bit copy or even a 'make in place'*/
constexpr ControlItem(const ControlItem &other) = default;
//using void return as we don't want to trust what the compiler might do with the 'volatile'
INLINETHIS
void operator=(IntType value) const ISRISH {
item = value;
}
INLINETHIS
void operator|=(IntType value) const ISRISH {
item |= value;
}
INLINETHIS
void operator&=(IntType value) const ISRISH {
item &= value;
}
/** mostly exists to appease compiler complaint about ambiguity of assignment. */
INLINETHIS
void operator=(const ControlItem &other) const ISRISH {
item = other.item;
}
//we do want implicit conversions here, the goal of the class is to make accessing a control word look syntactically like accessing a normal variable.
/** it is unproven if volatility is propagated through this wrapper. Check it for your compiler and flags. */
INLINETHIS
operator IntType() const ISRISH {
return item;
}
};
/** Multiple contiguous bits in a register. Requires register to be R/W.
* For write-only registers declare a union of int with struct of bitfields that describes the register. Manipulate an instance then assign it to an SFR8/16/32.
* Note: This creates a class per sf register field, but the compiler should inline all uses making this a compile time burden but a runtime minimization.
* Note: 'volatile' isn't used here as it is gratuitous when the variable isn't nominally read multiple times in a function.
*/
class ControlField {
volatile unsigned &word;
/** mask gets pre-positioned */
const unsigned mask;
const unsigned pos;
public:
constexpr ControlField(Address sfraddress, unsigned pos, unsigned width)
: word(Ref<unsigned>(sfraddress)), mask(bitMask(pos, width)), pos(pos) {
}
public:
constexpr ControlField(const ControlField &other) = delete;
ControlField() = delete;
// read
INLINETHIS
operator unsigned() const {
return (word & mask) >> pos; //the compiler should render this down to a bitfield extract instruction.
}
/** assign, which for hardware registers might not result in a value equal to the @param value given, @returns the ACTUAL value */
unsigned operator=(unsigned value) const {
word = ((value << pos) & mask) | (word & ~mask); //the compiler should render this down to a bitfield insert instruction.
return operator unsigned();
}
/** increment seems unlikely, someone add a use case else we might make this go away. */
unsigned operator+=(unsigned value) const {
operator=(unsigned() + value);
return operator unsigned();
}
//add more operators as need arises
};
/** single bit, ignoring the possibility it is in bitbanded memory.
* This is NOT derived from ControlField as we can do some optimizations that the compiler might miss (or developer might have disabled) */
class ControlBool : public BoolishRef {
volatile unsigned &word;
/** mask gets pre-positioned */
const unsigned mask;
const unsigned pos;
public:
constexpr ControlBool(Address sfraddress, unsigned pos)
: word(Ref<unsigned>(sfraddress)), mask(bitMask(pos, 1)), pos(pos) {
}
public:
constexpr ControlBool(const ControlField &other) = delete;
ControlBool() = delete;
// read
INLINETHIS
operator bool() const override {
return word & mask;
}
/** assign, which for hardware registers might not result in a value equal to the @param value given, @returns the ACTUAL value */
bool operator=(bool value) const override {
if (value) {
word |= mask;
} else {
word &= ~mask;
}
return operator bool();
}
};
/** only works for bitbanded item! */
struct ControlBit : public ControlWord, BoolishRef {
constexpr ControlBit(Address sfraddress, unsigned bitnum) : ControlWord(bandFor(sfraddress, bitnum)) {
}
// read
inline operator bool() const override {
return item != 0;
}
// write
bool operator=(bool value) const override {
item = value;
return value;
}
/** for bits declared "rc_w0" in the ST RM */
bool flagged() const {//todo:1 make this atomic, else only use each either strictly in an isr or not in an isr
if (item) {
item = 0;
return true;
} else {
return false;
}
}
};
/** a datum at a known absolute address */
template<typename Inttype, Address sfraddress>
struct SFRint {
constexpr SFRint() = default;
// read.
INLINETHIS
operator Inttype() const {
return Ref<Inttype>(sfraddress);
}
// write
INLINETHIS
void operator=(Inttype value) const {
Ref<Inttype>(sfraddress) = value;
}
};
///** making all (conveniently predefined) SFR's unsigned presuming that all hardware values are unsigned. About the only exception is ADC values. */
//template<unsigned sfraddress> using SFR8 = SFRint<uint8_t, sfraddress>;
//template<unsigned sfraddress> using SFR16 = SFRint<uint16_t, sfraddress>;
//template<unsigned sfraddress> using SFR32 = SFRint<uint32_t, sfraddress>;
/** Multiple contiguous bits in a register. Requires register to be R/W.
* For write-only registers declare a union of int with struct of bitfields that describes the register. Manipulate an instance then assign it to an SFR8/16/32.
* Note: This creates a class per sf register field, but the compiler should inline all uses making this a compile time burden but a runtime minimalization.
* Note: 'volatile' isn't used here as it is gratuitous when the variable isn't nominally read multiple times in a function.
*/
template<Address sfraddress, unsigned pos, unsigned width = 1>
class SFRfield {
enum {
/** mask positioned */
mask = bitMask(pos, width)
};
public:
SFRfield(const SFRfield &other) = delete;
constexpr SFRfield() = default; //this constructor is needed due to use of explicit on the other constructor
/** this constructor is intended to be used for setting a value into a register which has no reference other than the assignment, but it is not easy to debug its use when something goes horribly wrong. */
explicit SFRfield(unsigned initlizer) {
this->operator=(initlizer);
}
// read
INLINETHIS
operator unsigned() const {
return (Ref<unsigned>(sfraddress) & mask) >> pos;
}
// write
INLINETHIS
void operator=(unsigned value) const {
Ref<unsigned>(sfraddress) = ((value << pos) & mask) | (Ref<unsigned>(sfraddress) & ~mask);
}
void operator+=(unsigned value) const {
operator=(operator unsigned() + value);//todo:M optimize if compiler doesn't remove the shifts of all but this function's value argument.
}
};
/** single bit, ignoring the possibility it is in bitbanded memory.
* This is NOT derived from SFRfield as we can do some optimizations that the compiler might miss (or developer might have disabled)*/
template<unsigned sfraddress, unsigned pos>
class SFRbit : public BoolishRef {
enum {
mask = bitMask(pos)
};
public:
constexpr SFRbit() = default;
// read
inline operator bool() const override {
return (Ref<unsigned>(sfraddress) & mask) != 0;
}
// write
bool operator=(bool value) const override {
if (value) {
Ref<unsigned>(sfraddress) |= mask;
} else {
Ref<unsigned>(sfraddress) &= ~mask;
}
return value;
}
};
/** if your bit is in bitband space use this instead of SFRbit */
template<unsigned sfraddress, unsigned bitnum>
struct SFRbandbit : public BoolishRef {
enum {
bandAddress = bandFor(sfraddress, bitnum)
, };
// read
INLINETHIS operator bool() const override {
return *(reinterpret_cast<volatile unsigned *>(bandAddress));
}
// write
INLINETHIS bool operator=(bool value) const override {
*(reinterpret_cast<unsigned *>(bandAddress)) = value;
return value;
}
};
/** most cortex devices follow arm's suggestion of using this block for peripherals */
const Address PeripheralBase {0x40000000}; //1<<30
const Address PeripheralBand {0x42000000};//bandFor(PeripheralBase)
//cortexM 'private peripherals'
// the SCB is kinda like a peripheral, but we may just inline this at each point of use. The manual lists both absolute and relative addresses.
INLINETHIS
constexpr Address SCB(unsigned offset) {
return 0xE000'ED00 + offset;
}
#pragma clang diagnostic pop
#pragma clang diagnostic pop