/
GpioPin.hpp
194 lines (166 loc) · 7.7 KB
/
GpioPin.hpp
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
#pragma once
#include "Util.hpp"
#include MY_MCU_HEADER
//......................................................................................
////////////////
class
GpioPin
////////////////
{
struct Reg {
u32 MODER, OTYPER, OSPEEDR, PUPDR, IDR, ODR, BSRR, LCKR;
u64 AFR;
u32 BRR;
};
const u32 pin_; //pin 0-15
const u32 pinbm_; //bitmask for bsrr/brr
const bool inv_; //invert?
volatile Reg& reg_; //register struct access
//only called by input() output() alternate() analog(), with interrupts disabled
//gets all registers to a known state (excluding LCKR)
//the output() function will take care of getting the ODR bit set to an 'off' state
auto
deinit ()
{
u32 pin1_clrbm{ compl pinbm_ };
u32 pin2_clrbm{ compl (3u<<(pin_*2)) };
u64 pin4_clrbm{ compl (15u<<(pin_*4)) };
reg_.MODER and_eq pin2_clrbm; //input
reg_.OSPEEDR and_eq pin2_clrbm; //speed0
reg_.OTYPER and_eq pin1_clrbm; //pushpull
reg_.PUPDR and_eq pin2_clrbm; //nopull
reg_.AFR and_eq pin4_clrbm; //gpio
}
public:
enum
INVERT { HIGHISON, LOWISON };
GpioPin (MCU::PIN pin, INVERT inv = HIGHISON)
: pin_( pin bitand 15 ), //pin 0-15
pinbm_( 1<<pin_ ), //pin bitmask
inv_( inv ), //invert?
reg_( *reinterpret_cast<Reg*>(MCU::GPIO_BASE + MCU::GPIO_SPACING*(pin/16)) )
{
//no pin init done here (just enabling this gpio port in rcc)
//the RCC IOPENR register is shared by all Gpio ports, but this should be the
//only code that touches this register and we only set bits here, so irq protection
//is not needed, but... we do not know if there is/will be other code changing these
//IOPENR bits for other reasons (like power saving), so just irq protect for now
//(this gpio enable belongs in an Rcc class, as we are not in charge of the RCC peripheral)
CPU::InterruptLock lock;
MCU::RCCreg.IOPENR or_eq (1<<(pin_/16)); //gpioN rcc clock enable
}
enum //pull applies to input/output/alternate (and a single function)
PULL { NOPULL, PULLUP, PULLDOWN };
enum //speed applies to output/alternate
SPEED { SPEED0, SPEED1, SPEED2, SPEED3 };
enum //odrain applies to output/alternate
OTYPE { PUSHPULL, ODRAIN };
private:
//output and alternate modifiers- PULL|OTYPE|SPEED
//allows optional properties to be applied in any order
template<typename...Ts> auto
modifiers_(PULL e, Ts...ts){ reg_.PUPDR or_eq (e<<(pin_*2)); return modifiers_( ts... ); }
template<typename...Ts> auto
modifiers_(OTYPE e, Ts...ts){ reg_.OTYPER or_eq (1<<pin_); return modifiers_( ts... ); }
template<typename...Ts> auto
modifiers_(SPEED e, Ts...ts){ reg_.OSPEEDR or_eq (e<<(pin_*2)); return modifiers_( ts... ); }
auto modifiers_(){} //no more arguments, break the call chain with empty function
public:
//all pin config goes through deinit(), which rcc enables the port and gets the pins
//registers to a known default state
//provide optional modifier (PULL enum value)
//if no arguments provided you get NOPULL
// obj.input(); obj.input( PULLUP|PULLDOWN );
auto
input (PULL e = NOPULL)
{
CPU::InterruptLock lock;
deinit();
if( e != NOPULL ) reg_.PUPDR or_eq (e<<(pin_*2));
return *this;
}
//provide optional modifiers (enum values) in any order
//if no arguments provided you get NOPULL,PUSHPULL,SPEED0
// obj.output( [PULLUP|PULLDOWN], [ODRAIN], [SPEED1|2|3] )
template<typename...Ts> auto
output (Ts...ts)
{
CPU::InterruptLock lock;
deinit();
off(); //depends on inv_, so make sure pin is high if inv_=true
reg_.MODER or_eq (1<<(pin_*2)); //output
modifiers_( ts... );
return *this; //allow method chaining (so can init to on() or off() if needed)
}
//provide optional modifiers (enum values) in any order
//if no arguments provided you get NOPULL,PUSHPULL,SPEED0
// obj.output( [PULLUP|PULLDOWN], [ODRAIN], [SPEED1|2|3] )
template<typename...Ts>
auto
alternate (MCU::ALTFUNC e, Ts...ts)
{
CPU::InterruptLock lock;
deinit();
reg_.AFR or_eq (e<<(pin_*4));
reg_.MODER or_eq (2<<(pin_*2));
modifiers_( ts... );
//return *this; //nothing else can be done so disallow method chaining
}
//no modifiers available
auto
analog ()
{
CPU::InterruptLock lock;
deinit();
reg_.MODER or_eq (3<<(pin_*2));
//return *this; //no modifiers available, disallow method chaining
}
//the following functions used after pin configuration is applied
/*
if there is a need to allow a specific config change on an existing configured pin
add here as needed
such as a need to change pullups without a pin reconfig-
auto
pull (PULL e)
{
CPU::InterruptLock lock;
reg_.PUPDR = (reg_.PUPDR bitand compl (1<<(pin_*2))) bitor (e<<(pin_*2));
return *this;
}
*/
// write (BSRR/BRR)
auto
high () { reg_.BSRR = pinbm_; return *this; }
auto
low () { reg_.BRR = pinbm_; return *this; }
// variations
auto
on () { return inv_ ? low() : high(); }
auto
off () { return inv_ ? high() : low(); }
auto
on (bool tf) { return tf ? on() : off(); }
auto
toggle () { return isLatHigh() ? low() : high(); }
// read pin (IDR)
auto
isHigh () { return reg_.IDR bitand pinbm_; }
// variations
auto
isLow () { return not isHigh(); }
auto
isOn () { return inv_ ? isLow() : isHigh(); }
auto
isOff () { return not isOn(); }
// read lat (ODR)
bool
isLatHigh () { return reg_.ODR bitand pinbm_; }
// variations
auto
isLatLow () { return not isLatHigh(); }
auto
isLatOn () { return inv_ ? isLatLow() : isLatHigh(); }
auto
isLatOff () { return not isLatOn(); }
}; //GpioPin
//......................................................................................