-
Notifications
You must be signed in to change notification settings - Fork 7
/
SAMD51_InterruptTimer.cpp
154 lines (125 loc) · 4.55 KB
/
SAMD51_InterruptTimer.cpp
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
/*
SAMD51_InterruptTimer.h
Atmel SAMD51 interrupt service routine timer library
For e.g. Adafruit M4 Metro/Feather/ItsyBitsy Express
Method names mimic the 'ZeroTimer' library by Tamasa (@EHbtj) for easily
switching compilation between SAMD21 and SAMD51 microprocessor boards.
See https://github.com/EHbtj/ZeroTimer for the SAMD21 library.
Dennis van Gils
04-01-2021
*/
#include "Arduino.h"
#include "SAMD51_InterruptTimer.h"
// Adafruit M4 code (cores/arduino/startup.c) configures these clock generators:
// 120MHz - GCLK0
// 100MHz - GCLK2
// 48MHz - GCLK1
// 12MHz - GCLK4
#define GCLK1_HZ 48000000
#define TIMER_PRESCALER_DIV 1024
void (*func1)();
static inline void TC3_wait_for_sync() {
while (TC3->COUNT16.SYNCBUSY.reg != 0) {}
}
void TC_Timer::startTimer(unsigned long period, void (*f)()) {
// Enable the TC bus clock, use clock generator 1
GCLK->PCHCTRL[TC3_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val |
(1 << GCLK_PCHCTRL_CHEN_Pos);
while (GCLK->SYNCBUSY.reg > 0);
TC3->COUNT16.CTRLA.bit.ENABLE = 0;
// Use match mode so that the timer counter resets when the count matches the
// compare register
TC3->COUNT16.WAVE.bit.WAVEGEN = TC_WAVE_WAVEGEN_MFRQ;
TC3_wait_for_sync();
// Enable the compare interrupt
TC3->COUNT16.INTENSET.reg = 0;
TC3->COUNT16.INTENSET.bit.MC0 = 1;
// Enable IRQ
NVIC_EnableIRQ(TC3_IRQn);
func1 = f;
setPeriod(period);
}
void TC_Timer::stopTimer() {
TC3->COUNT16.CTRLA.bit.ENABLE = 0;
}
void TC_Timer::restartTimer(unsigned long period) {
// Enable the TC bus clock, use clock generator 1
GCLK->PCHCTRL[TC3_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val |
(1 << GCLK_PCHCTRL_CHEN_Pos);
while (GCLK->SYNCBUSY.reg > 0);
TC3->COUNT16.CTRLA.bit.ENABLE = 0;
// Use match mode so that the timer counter resets when the count matches the
// compare register
TC3->COUNT16.WAVE.bit.WAVEGEN = TC_WAVE_WAVEGEN_MFRQ;
TC3_wait_for_sync();
// Enable the compare interrupt
TC3->COUNT16.INTENSET.reg = 0;
TC3->COUNT16.INTENSET.bit.MC0 = 1;
// Enable IRQ
NVIC_EnableIRQ(TC3_IRQn);
setPeriod(period);
}
void TC_Timer::setPeriod(unsigned long period) {
int prescaler;
uint32_t TC_CTRLA_PRESCALER_DIVN;
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV1024;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV256;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV64;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV16;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV4;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV2;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_PRESCALER_DIV1;
TC3_wait_for_sync();
if (period > 300000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV1024;
prescaler = 1024;
} else if (80000 < period && period <= 300000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV256;
prescaler = 256;
} else if (20000 < period && period <= 80000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV64;
prescaler = 64;
} else if (10000 < period && period <= 20000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV16;
prescaler = 16;
} else if (5000 < period && period <= 10000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV8;
prescaler = 8;
} else if (2500 < period && period <= 5000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV4;
prescaler = 4;
} else if (1000 < period && period <= 2500) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV2;
prescaler = 2;
} else if (period <= 1000) {
TC_CTRLA_PRESCALER_DIVN = TC_CTRLA_PRESCALER_DIV1;
prescaler = 1;
}
TC3->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIVN;
TC3_wait_for_sync();
int compareValue = (int)(GCLK1_HZ / (prescaler/((float)period / 1000000))) - 1;
// Make sure the count is in a proportional position to where it was
// to prevent any jitter or disconnect when changing the compare value.
TC3->COUNT16.COUNT.reg = map(TC3->COUNT16.COUNT.reg, 0,
TC3->COUNT16.CC[0].reg, 0, compareValue);
TC3->COUNT16.CC[0].reg = compareValue;
TC3_wait_for_sync();
TC3->COUNT16.CTRLA.bit.ENABLE = 1;
TC3_wait_for_sync();
}
void TC3_Handler() {
// If this interrupt is due to the compare register matching the timer count
if (TC3->COUNT16.INTFLAG.bit.MC0 == 1) {
TC3->COUNT16.INTFLAG.bit.MC0 = 1;
(*func1)();
}
}
TC_Timer TC;