forked from esp8266/Arduino
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathcore_esp8266_waveform.c
319 lines (276 loc) · 10.7 KB
/
core_esp8266_waveform.c
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
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds). TIMER1 is set to 1-shot
mode and is always loaded with the time until the next edge of any live
waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleTime()
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "core_esp8266_waveform.h"
// Need speed, not size, here
#pragma GCC optimize ("O2")
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// If the cycles from now to an event are below this value, perform it anyway since IRQs take longer than this
#define CYCLES_FLUFF (100)
// Macro to get count of predefined array elements
#define countof(a) ((size_t)(sizeof(a)/sizeof(a[0])))
// Set/clear *any* GPIO
#define SetGPIOPin(a) do { if (a < 16) { GPOS |= (1<<a); } else { GP16O |= 1; } } while (0)
#define ClearGPIOPin(a) do { if (a < 16) { GPOC |= (1<<a); } else { GP16O &= ~1; } } while (0)
// Set/clear GPIO 0-15
#define SetGPIO(a) do { GPOS = a; } while (0)
#define ClearGPIO(a) do { GPOC = a; } while (0)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t timeLeftCycles; // For time-limited waveform, how many ESP cycles left
const uint16_t gpioMask; // Mask instead of value to speed IRQ loop
const uint16_t gpio16Mask; // Mask instead of value to speed IRQ loop
unsigned state : 1; // Current state of this pin
unsigned nextTimeHighCycles : 31; // Copy over low->high to keep smooth waveform
unsigned enabled : 1; // Is this GPIO generating a waveform?
unsigned nextTimeLowCycles : 31; // Copy over high->low to keep smooth waveform
} Waveform;
// These can be accessed in interrupts, so ensure to bracket access with SEI/CLI
static Waveform waveform[] = {
{0, 0, 1<<0, 0, 0, 0, 0, 0}, // GPIO0
{0, 0, 1<<1, 0, 0, 0, 0, 0}, // GPIO1
{0, 0, 1<<2, 0, 0, 0, 0, 0},
{0, 0, 1<<3, 0, 0, 0, 0, 0},
{0, 0, 1<<4, 0, 0, 0, 0, 0},
{0, 0, 1<<5, 0, 0, 0, 0, 0},
// GPIOS 6-8 not allowed, used for flash
// GPIO 9 and 10 only allowed in 2-bit flash mode
#if !isFlashInterfacePin(9)
{0, 0, 1<<9, 0, 0, 0, 0, 0},
{0, 0, 1<<10, 0, 0, 0, 0, 0},
#endif
// GPIO 11 not allowed, used for flash
{0, 0, 1<<12, 0, 0, 0, 0, 0},
{0, 0, 1<<13, 0, 0, 0, 0, 0},
{0, 0, 1<<14, 0, 0, 0, 0, 0},
{0, 0, 1<<15, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0} // GPIO16
};
static uint32_t (*timer1CB)() = NULL;;
// Helper functions
static inline ICACHE_RAM_ATTR uint32_t MicrosecondsToCycles(uint32_t microseconds) {
return clockCyclesPerMicrosecond() * microseconds;
}
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
if (a < b) {
return a;
}
return b;
}
static inline ICACHE_RAM_ATTR void ReloadTimer(uint32_t a) {
// Below a threshold you actually miss the edge IRQ, so ensure enough time
if (a > 32) {
timer1_write(a);
} else {
timer1_write(32);
}
}
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static uint8_t timerRunning = false;
static uint32_t lastCycleCount = 0; // Last ESP cycle counter on running the interrupt routine
static void initTimer() {
timer1_disable();
timer1_isr_init();
timer1_attachInterrupt(timer1Interrupt);
lastCycleCount = GetCycleCount();
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
}
static void ICACHE_RAM_ATTR deinitTimer() {
timer1_attachInterrupt(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
timer1CB = fn;
if (!timerRunning && fn) {
initTimer();
} else if (timerRunning && !fn) {
int cnt = 0;
for (size_t i = 0; i < countof(waveform); i++) {
cnt += waveform[i].enabled ? 1 : 0;
}
if (!cnt) {
deinitTimer();
}
}
ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
Waveform *wave = NULL;
for (size_t i = 0; i < countof(waveform); i++) {
if (((pin == 16) && waveform[i].gpio16Mask==1) || ((pin != 16) && (waveform[i].gpioMask == 1<<pin))) {
wave = (Waveform*) & (waveform[i]);
break;
}
}
if (!wave) {
return false;
}
// To safely update the packed bitfields we need to stop interrupts while setting them as we could
// get an IRQ in the middle of a multi-instruction mask-and-set required to change them which would
// then cause an IRQ update of these values (.enabled only, for now) to be lost.
ets_intr_lock();
wave->nextTimeHighCycles = MicrosecondsToCycles(timeHighUS) - 70; // Take out some time for IRQ codepath
wave->nextTimeLowCycles = MicrosecondsToCycles(timeLowUS) - 70; // Take out some time for IRQ codepath
wave->timeLeftCycles = MicrosecondsToCycles(runTimeUS);
if (!wave->enabled) {
wave->state = 0;
// Actually set the pin high or low in the IRQ service to guarantee times
wave->nextServiceCycle = GetCycleCount() + MicrosecondsToCycles(1);
wave->enabled = 1;
if (!timerRunning) {
initTimer();
}
ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste
}
// Re-enable interrupts here since we're done with the update
ets_intr_unlock();
return true;
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
for (size_t i = 0; i < countof(waveform); i++) {
if (!waveform[i].enabled) {
continue; // Skip fast to next one, can't need to stop this one since it's not running
}
if (((pin == 16) && waveform[i].gpio16Mask) || ((pin != 16) && (waveform[i].gpioMask == 1<<pin))) {
// Note that there is no interrupt unsafety here. The IRQ can only ever change .enabled from 1->0
// We're also doing that, so even if an IRQ occurred it would still stay as 0.
waveform[i].enabled = 0;
int cnt = timer1CB ? 1 : 0;
for (size_t i = 0; (cnt == 0) && (i < countof(waveform)); i++) {
cnt += waveform[i].enabled ? 1 : 0;
}
if (!cnt) {
deinitTimer();
}
return true;
}
}
return false;
}
static ICACHE_RAM_ATTR void timer1Interrupt() {
uint32_t nextEventCycles;
#if F_CPU == 160000000
uint8_t cnt = 20;
#else
uint8_t cnt = 10;
#endif
do {
nextEventCycles = MicrosecondsToCycles(MAXIRQUS);
for (size_t i = 0; i < countof(waveform); i++) {
Waveform *wave = &waveform[i];
uint32_t now;
// If it's not on, ignore!
if (!wave->enabled) {
continue;
}
// Check for toggles
now = GetCycleCount();
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
wave->state = !wave->state;
if (wave->state) {
SetGPIO(wave->gpioMask);
if (wave->gpio16Mask) {
GP16O |= wave->gpio16Mask; // GPIO16 write slow as it's RMW
}
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
} else {
ClearGPIO(wave->gpioMask);
if (wave->gpio16Mask) {
GP16O &= ~wave->gpio16Mask;
}
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
}
} else {
uint32_t deltaCycles = wave->nextServiceCycle - now;
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
}
}
} while (--cnt && (nextEventCycles < MicrosecondsToCycles(4)));
uint32_t curCycleCount = GetCycleCount();
uint32_t deltaCycles = curCycleCount - lastCycleCount;
lastCycleCount = curCycleCount;
// Check for timed-out waveforms out of the high-frequency toggle loop
for (size_t i = 0; i < countof(waveform); i++) {
Waveform *wave = &waveform[i];
if (wave->timeLeftCycles) {
// Check for unsigned underflow with new > old
if (deltaCycles >= wave->timeLeftCycles) {
// Done, remove!
wave->enabled = false;
ClearGPIO(wave->gpioMask);
GP16O &= ~wave->gpio16Mask;
} else {
uint32_t newTimeLeftCycles = wave->timeLeftCycles - deltaCycles;
wave->timeLeftCycles = newTimeLeftCycles;
}
}
}
if (timer1CB) {
nextEventCycles = min_u32(nextEventCycles, timer1CB());
}
#if F_CPU == 160000000
if (nextEventCycles <= 5 * MicrosecondsToCycles(1)) {
nextEventCycles = MicrosecondsToCycles(1) / 2;
} else {
nextEventCycles -= 5 * MicrosecondsToCycles(1);
}
nextEventCycles = nextEventCycles >> 1;
#else
if (nextEventCycles <= 6 * MicrosecondsToCycles(1)) {
nextEventCycles = MicrosecondsToCycles(1) / 2;
} else {
nextEventCycles -= 6 * MicrosecondsToCycles(1);
}
#endif
ReloadTimer(nextEventCycles);
}