-
Notifications
You must be signed in to change notification settings - Fork 21
/
FastGPIO.h
562 lines (506 loc) · 16.7 KB
/
FastGPIO.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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
// Copyright Pololu Corporation. For more information, see http://www.pololu.com/
/*! \file FastGPIO.h
FastGPIO is a C++ header library for efficient AVR I/O.
For an overview of the features of this library, see
https://github.com/pololu/fastgpio-arduino.
That is the main repository for this library, though copies may exist in other
repositories.
The FastGPIO::Pin class provides static functions for manipulating pins. See
its class reference for more information.
\class FastGPIO::Pin
@tparam pin The pin number
The FastGPIO::Pin class provides static functions for manipulating pins. This
class can only be used if the pin number is known at compile time, which means
it does not come from a variable that might change and it does not come from the
result of a complicated calculation.
Here is some example code showing how to use this class to blink an LED:
~~~{.cpp}
#include <FastGPIO.h>
#define LED_PIN 13
void setup() {
}
void loop() {
FastGPIO::Pin<LED_PIN>::setOutput(0);
delay(500);
FastGPIO::Pin<LED_PIN>::setOutput(1);
delay(500);
}
~~~
*/
#pragma once
#include <avr/io.h>
#include <stdint.h>
/** @cond */
#define _FG_SBI(mem_addr, bit) asm volatile("sbi %0, %1\n" : \
: "I" (mem_addr - __SFR_OFFSET), "I" (bit))
#define _FG_CBI(mem_addr, bit) asm volatile("cbi %0, %1\n" : \
: "I" (mem_addr - __SFR_OFFSET), "I" (bit))
#define _FG_PIN(port, bit) { _SFR_MEM_ADDR(PIN##port), _SFR_MEM_ADDR(PORT##port), \
_SFR_MEM_ADDR(DDR##port), bit }
/** @endcond */
namespace FastGPIO
{
/** @cond */
/** The IOStruct struct and the pinStructs array below are not documented in
* the Doxygen documentation, but can be used by advanced users of this
* library and are considered to be part of the public API for the purposes
* of semantic versioning.
*/
typedef struct IOStruct
{
uint8_t pinAddr;
uint8_t portAddr;
uint8_t ddrAddr;
uint8_t bit;
volatile uint8_t * pin() const
{
return (volatile uint8_t *)(uint16_t)pinAddr;
}
volatile uint8_t * port() const
{
return (volatile uint8_t *)(uint16_t)portAddr;
}
volatile uint8_t * ddr() const
{
return (volatile uint8_t *)(uint16_t)ddrAddr;
}
} IOStruct;
/** @endcond */
#if defined(__AVR_ATmega48PB__) || (__AVR_ATmega88PB__) || (__AVR_ATmega168PB__) || (__AVR_ATmega328PB__) || defined(ARDUINO_AVR_A_STAR_328PB)
const IOStruct pinStructs[] = {
_FG_PIN(D, 0),
_FG_PIN(D, 1),
_FG_PIN(D, 2),
_FG_PIN(D, 3),
_FG_PIN(D, 4),
_FG_PIN(D, 5),
_FG_PIN(D, 6),
_FG_PIN(D, 7),
_FG_PIN(B, 0),
_FG_PIN(B, 1),
_FG_PIN(B, 2),
_FG_PIN(B, 3),
_FG_PIN(B, 4),
_FG_PIN(B, 5),
_FG_PIN(C, 0),
_FG_PIN(C, 1),
_FG_PIN(C, 2),
_FG_PIN(C, 3),
_FG_PIN(C, 4),
_FG_PIN(C, 5),
_FG_PIN(E, 2),
_FG_PIN(E, 3),
_FG_PIN(E, 0),
_FG_PIN(E, 1),
_FG_PIN(C, 6), // RESET
_FG_PIN(C, 7), // Null pin (IO_NONE)
};
#define IO_D0 0
#define IO_D1 1
#define IO_D2 2
#define IO_D3 3
#define IO_D4 4
#define IO_D5 5
#define IO_D6 6
#define IO_D7 7
#define IO_B0 8
#define IO_B1 9
#define IO_B2 10
#define IO_B3 11
#define IO_B4 12
#define IO_B5 13
#define IO_C0 14
#define IO_C1 15
#define IO_C2 16
#define IO_C3 17
#define IO_C4 18
#define IO_C5 19
#define IO_E2 20
#define IO_E3 21
#define IO_E0 22
#define IO_E1 23
#define IO_C6 24
#define IO_NONE 25
#elif defined(__AVR_ATmega48__) || (__AVR_ATmega48P__) || (__AVR_ATmega88__) || (__AVR_ATmega88P__) || (__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__)
const IOStruct pinStructs[] = {
_FG_PIN(D, 0),
_FG_PIN(D, 1),
_FG_PIN(D, 2),
_FG_PIN(D, 3),
_FG_PIN(D, 4),
_FG_PIN(D, 5),
_FG_PIN(D, 6),
_FG_PIN(D, 7),
_FG_PIN(B, 0),
_FG_PIN(B, 1),
_FG_PIN(B, 2),
_FG_PIN(B, 3),
_FG_PIN(B, 4),
_FG_PIN(B, 5),
_FG_PIN(C, 0),
_FG_PIN(C, 1),
_FG_PIN(C, 2),
_FG_PIN(C, 3),
_FG_PIN(C, 4),
_FG_PIN(C, 5),
_FG_PIN(C, 6), // RESET
_FG_PIN(C, 7), // Null pin (IO_NONE)
};
#define IO_D0 0
#define IO_D1 1
#define IO_D2 2
#define IO_D3 3
#define IO_D4 4
#define IO_D5 5
#define IO_D6 6
#define IO_D7 7
#define IO_B0 8
#define IO_B1 9
#define IO_B2 10
#define IO_B3 11
#define IO_B4 12
#define IO_B5 13
#define IO_C0 14
#define IO_C1 15
#define IO_C2 16
#define IO_C3 17
#define IO_C4 18
#define IO_C5 19
#define IO_C6 20
#define IO_NONE 21
#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
const IOStruct pinStructs[] = {
_FG_PIN(D, 2),
_FG_PIN(D, 3),
_FG_PIN(D, 1),
_FG_PIN(D, 0),
_FG_PIN(D, 4),
_FG_PIN(C, 6),
_FG_PIN(D, 7),
_FG_PIN(E, 6),
_FG_PIN(B, 4),
_FG_PIN(B, 5),
_FG_PIN(B, 6),
_FG_PIN(B, 7),
_FG_PIN(D, 6),
_FG_PIN(C, 7),
_FG_PIN(B, 3),
_FG_PIN(B, 1),
_FG_PIN(B, 2),
_FG_PIN(B, 0),
_FG_PIN(F, 7),
_FG_PIN(F, 6),
_FG_PIN(F, 5),
_FG_PIN(F, 4),
_FG_PIN(F, 1),
_FG_PIN(F, 0),
_FG_PIN(D, 4),
_FG_PIN(D, 7),
_FG_PIN(B, 4),
_FG_PIN(B, 5),
_FG_PIN(B, 6),
_FG_PIN(D, 6),
// Extra pins added by this library and not supported by the
// Arduino GPIO functions:
_FG_PIN(D, 5),
_FG_PIN(E, 2),
_FG_PIN(E, 0) // Null pin (IO_NONE)
};
#define IO_D2 0
#define IO_D3 1
#define IO_D1 2
#define IO_D0 3
#define IO_D4 4
#define IO_C6 5
#define IO_D7 6
#define IO_E6 7
#define IO_B4 8
#define IO_B5 9
#define IO_B6 10
#define IO_B7 11
#define IO_D6 12
#define IO_C7 13
#define IO_B3 14
#define IO_B1 15
#define IO_B2 16
#define IO_B0 17
#define IO_F7 18
#define IO_F6 19
#define IO_F5 20
#define IO_F4 21
#define IO_F1 22
#define IO_F0 23
#define IO_D5 30
#define IO_E2 31
#define IO_NONE 32
#else
#error FastGPIO does not support this board.
#endif
template<uint8_t pin> class Pin
{
public:
/*! \brief Configures the pin to be an output driving low.
*
* This is equivalent to calling setOutput with an argument of 0,
* but it has a simpler implementation which means it is more
* likely to be compiled down to just 2 assembly instructions.
*/
static inline void setOutputLow() __attribute__((always_inline))
{
_FG_CBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
_FG_SBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
}
/*! \brief Configures the pin to be an output driving high.
*
* This is equivalent to calling setOutput with an argument of 1,
* but it has a simpler implementation which means it is more
* likely to be compiled down to just 2 assembly instructions.
*/
static inline void setOutputHigh() __attribute__((always_inline))
{
_FG_SBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
_FG_SBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
}
/*! \brief Configures the pin to be an output and toggles it.
*/
static inline void setOutputToggle() __attribute__((always_inline))
{
setOutputValueToggle();
_FG_SBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
}
/*! \brief Sets the pin as an output.
*
* @param value Should be 0, LOW, or false to drive the pin low. Should
* be 1, HIGH, or true to drive the pin high.
*
* The PORT bit is set before the DDR bit to ensure that the output is
* not accidentally driven to the wrong value during the transition.
*/
static inline void setOutput(bool value) __attribute__((always_inline))
{
setOutputValue(value);
_FG_SBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
}
/*! \brief Sets the output value of the pin to 0.
*
* This is mainly intended to be used on pins that have already been
* configured as an output in order to make the output drive low.
*
* If this is used on an input pin, it has the effect of disabling the
* input pin's pull-up resistor.
*/
static inline void setOutputValueLow() __attribute__((always_inline))
{
_FG_CBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
/*! \brief Sets the output value of the pin to 1.
*
* This is mainly intended to be used on pins that have already been
* configured as an output in order to make the output drive low.
*
* If this is used on an input pin, it has the effect of enabling the
* input pin's pull-up resistor.
*/
static inline void setOutputValueHigh() __attribute__((always_inline))
{
_FG_SBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
/*! \brief Toggles the output value of the pin.
*
* This is mainly intended to be used on pins that have already been
* configured as an output. If the pin was driving low, this function
* changes it to drive high. If the pin was driving high, this function
* changes it to drive low.
*
* If this function is used on an input pin, it has the effect of
* toggling the state of the input pin's pull-up resistor.
*/
static inline void setOutputValueToggle() __attribute__((always_inline))
{
_FG_SBI(pinStructs[pin].pinAddr, pinStructs[pin].bit);
}
/*! \brief Sets the output value of the pin.
*
* @param value Should be 0, LOW, or false to drive the pin low. Should
* be 1, HIGH, or true to drive the pin high.
*
* This is mainly intended to be used on pins that have already been
* configured as an output.
*
* If this function is used on an input pin, it has the effect of
* toggling setting the state of the input pin's pull-up resistor.
*/
static inline void setOutputValue(bool value) __attribute__((always_inline))
{
if (value)
{
_FG_SBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
else
{
_FG_CBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
}
/*! \brief Sets a pin to be a digital input with the internal pull-up
* resistor disabled.
*/
static inline void setInput() __attribute__((always_inline))
{
_FG_CBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
_FG_CBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
/*! \brief Sets a pin to be a digital input with the internal pull-up
* resistor enabled.
*/
static inline void setInputPulledUp() __attribute__((always_inline))
{
_FG_CBI(pinStructs[pin].ddrAddr, pinStructs[pin].bit);
_FG_SBI(pinStructs[pin].portAddr, pinStructs[pin].bit);
}
/*! \brief Reads the input value of the pin.
*
* @return 0 if the pin is low, 1 if the pin is high.
*/
static inline bool isInputHigh() __attribute__((always_inline))
{
return *pinStructs[pin].pin() >> pinStructs[pin].bit & 1;
/* This is another option but it is less efficient in code
like "if (isInputHigh()) { ... }":
bool value;
asm volatile(
"ldi %0, 0\n"
"sbic %2, %1\n"
"ldi %0, 1\n"
: "=d" (value)
: "I" (pinStructs[pin].bit),
"I" (pinStructs[pin].pinAddr - __SFR_OFFSET));
return value;
*/
}
/*! \brief Returns 1 if the pin is configured as an output.
*
* @return 1 if the pin is an output, 0 if it is an input.
*/
static inline bool isOutput() __attribute__((always_inline))
{
return *pinStructs[pin].ddr() >> pinStructs[pin].bit & 1;
}
/*! \brief Returns the output value of the pin.
*
* This is mainly intended to be called on pins that have been
* configured an an output. If it is called on an input pin, the return
* value indicates whether the pull-up resistor is enabled or not.
*/
static inline bool isOutputValueHigh() __attribute__((always_inline))
{
return *pinStructs[pin].port() >> pinStructs[pin].bit & 1;
}
/*! \brief Returns the full 2-bit state of the pin.
*
* Bit 0 of this function's return value is the pin's output value.
* Bit 1 of the return value is the pin direction; a value of 1
* means output. All the other bits are zero.
*/
static uint8_t getState()
{
uint8_t state;
asm volatile(
"ldi %0, 0\n"
"sbic %2, %1\n"
"ori %0, 1\n" // Set state bit 0 to 1 if PORT bit is set.
"sbic %3, %1\n"
"ori %0, 2\n" // Set state bit 1 to 1 if DDR bit is set.
: "=a" (state)
: "I" (pinStructs[pin].bit),
"I" (pinStructs[pin].portAddr - __SFR_OFFSET),
"I" (pinStructs[pin].ddrAddr - __SFR_OFFSET));
return state;
/* Equivalent C++ code:
return isOutput() << 1 | isOutputValueHigh();
*/
}
/*! \brief Sets the full 2-bit state of the pin.
*
* @param state The state of the pin, as returns from getState.
* All bits other than bits 0 and 1 are ignored.
*
* Sometimes this function will need to change both the PORT bit (which
* specifies the output value) and the DDR bit (which specifies whether
* the pin is an output). If the DDR bit is getting set to 0, this
* function changes DDR first, and if it is getting set to 1, this
* function changes DDR last. This guarantees that the intermediate pin
* state is always an input state.
*/
static void setState(uint8_t state)
{
asm volatile(
"bst %0, 1\n" // Set DDR to 0 if needed
"brts .+2\n"
"cbi %3, %1\n"
"bst %0, 0\n" // Copy state bit 0 to PORT bit
"brts .+2\n"
"cbi %2, %1\n"
"brtc .+2\n"
"sbi %2, %1\n"
"bst %0, 1\n" // Set DDR to 1 if needed
"brtc .+2\n"
"sbi %3, %1\n"
:
: "a" (state),
"I" (pinStructs[pin].bit),
"I" (pinStructs[pin].portAddr - __SFR_OFFSET),
"I" (pinStructs[pin].ddrAddr - __SFR_OFFSET));
}
};
/*! This class saves the state of the specified pin in its constructor when
* it is created, and restores the pin to that state in its destructor.
* This can be very useful if a pin is being used for multiple purposes.
* It allows you to write code that temporarily changes the state of the
* pin and is guaranteed to restore the state later.
*
* For example, if you were controlling both a button and an LED using a
* single pin and you wanted to see if the button was pressed without
* affecting the LED, you could write:
*
* ~~~{.cpp}
* bool buttonIsPressed()
* {
* FastGPIO::PinLoan<IO_D5> loan;
* FastGPIO::Pin<IO_D5>::setInputPulledUp();
* _delay_us(10);
* return !FastGPIO::Pin<IO_D5>::isInputHigh();
* }
* ~~~
*
* This is equivalent to:
*
* ~~~{.cpp}
* bool buttonIsPressed()
* {
* uint8_t state = FastGPIO::Pin<IO_D5>::getState();
* FastGPIO::Pin<IO_D5>::setInputPulledUp();
* _delay_us(10);
* bool value = !FastGPIO::Pin<IO_D5>::isInputHigh();
* FastGPIO::Pin<IO_D5>::setState(state);
* return value;
* }
* ~~~
*/
template<uint8_t pin> class PinLoan
{
public:
/*! \brief The state of the pin as returned from FastGPIO::Pin::getState. */
uint8_t state;
PinLoan()
{
state = Pin<pin>::getState();
}
~PinLoan()
{
Pin<pin>::setState(state);
}
};
};
#undef _FG_PIN
#undef _FG_CBI
#undef _FG_SBI