PWM testing/fixes (#2588)
[ExpressLRS.git] / src / lib / PWM / waveform_8266.cpp
blob657b45704d15893d57e24f39cb63c3dd8c851786
1 #if defined(PLATFORM_ESP8266)
2 /***
3 * Slimmed-down version of esp8266_waveform for the ExpressLRS project
4 * 2022-04-27 Created by CapnBry
5 * - Adds the ability to change every servo no matter where it is in the
6 * cycle without blocking.
7 * - Removes analogWrite() functionality. If this is used, it will break
8 * this code, as the standard core_esp8266_waveform_pwm will take over
9 * the timer.
10 ***/
12 esp8266_waveform - General purpose waveform generation and control,
13 supporting outputs on all pins in parallel.
15 Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
17 The core idea is to have a programmable waveform generator with a unique
18 high and low period (defined in microseconds or CPU clock cycles). TIMER1
19 is set to 1-shot mode and is always loaded with the time until the next
20 edge of any live waveforms.
22 Up to one waveform generator per pin supported.
24 Each waveform generator is synchronized to the ESP clock cycle counter, not
25 the timer. This allows for removing interrupt jitter and delay as the
26 counter always increments once per 80MHz clock. Changes to a waveform are
27 contiguous and only take effect on the next waveform transition,
28 allowing for smooth transitions.
30 This replaces older tone(), analogWrite(), and the Servo classes.
32 Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
33 clock cycle count, or an interval measured in CPU clock cycles, but not
34 TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
36 This library is free software; you can redistribute it and/or
37 modify it under the terms of the GNU Lesser General Public
38 License as published by the Free Software Foundation; either
39 version 2.1 of the License, or (at your option) any later version.
41 This library is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 Lesser General Public License for more details.
46 You should have received a copy of the GNU Lesser General Public
47 License along with this library; if not, write to the Free Software
48 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
52 #include <Arduino.h>
53 #include "ets_sys.h"
55 // Maximum delay between IRQs
56 #define MAXIRQUS (10000)
58 // Waveform generator can create tones, PWM, and servos
59 typedef struct {
60 uint32_t nextServiceCycle; // ESP cycle timer when a transition required
61 uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
62 uint32_t timeLowCycles; //
63 uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
64 uint32_t desiredLowCycles; //
65 uint32_t nextHighLowUs; // Waveform ideal (us) "on deck", waiting to be changed next cycle
66 // packed into 32 bits to be atomic read/write, 65535us max
67 uint32_t lastEdge; // Cycle when this generator last changed
68 } Waveform;
70 class WVFState {
71 public:
72 Waveform waveform[17]; // State of all possible pins
73 uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
74 uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
76 // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
77 uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
78 uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
80 // Optimize the NMI inner loop by keeping track of the min and max GPIO that we
81 // are generating. In the common case (1 PWM) these may be the same pin and
82 // we can avoid looking at the other pins.
83 uint16_t startPin = 0;
84 uint16_t endPin = 0;
86 static WVFState wvfState;
89 // Ensure everything is read/written to RAM
90 #define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
92 // Non-speed critical bits
93 #pragma GCC optimize ("Os")
95 // Interrupt on/off control
96 static void timer1Interrupt();
97 static bool timerRunning = false;
99 static __attribute__((noinline)) void initTimer() {
100 if (!timerRunning) {
101 timer1_disable();
102 ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
103 ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
104 timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
105 timerRunning = true;
106 timer1_write(microsecondsToClockCycles(10));
110 static IRAM_ATTR void forceTimerInterrupt() {
111 if (T1L > microsecondsToClockCycles(10)) {
112 T1L = microsecondsToClockCycles(10);
116 // If there are no more scheduled activities, shut down Timer 1.
117 // Otherwise, do nothing.
118 static void disableIdleTimer() {
119 if (timerRunning && !wvfState.waveformEnabled) {
120 ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
121 timer1_disable();
122 timer1_isr_init();
123 timerRunning = false;
127 // Start up a waveform on a pin, or change the current one. Will change to the new
128 // waveform smoothly on next low->high transition. For immediate change, stopWaveform()
129 // first, then it will immediately begin.
130 void startWaveform8266(uint8_t gpio, uint32_t timeHighUS, uint32_t timeLowUS) {
131 if ((gpio > 16) || isFlashInterfacePin(gpio)) {
132 return;
134 Waveform *wave = &wvfState.waveform[gpio];
136 uint32_t mask = 1<<gpio;
137 MEMBARRIER();
138 if (wvfState.waveformEnabled & mask) {
139 wave->nextHighLowUs = (timeHighUS << 16) | timeLowUS;
140 MEMBARRIER();
141 // The waveform will be updated some time in the future on the next period for the signal
142 } else { // if (!(wvfState.waveformEnabled & mask)) {
143 wave->timeHighCycles = wave->desiredHighCycles = microsecondsToClockCycles(timeHighUS);
144 wave->timeLowCycles = wave->desiredLowCycles = microsecondsToClockCycles(timeLowUS);
145 wave->nextHighLowUs = 0;
146 wave->lastEdge = 0;
147 wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
148 wvfState.waveformToEnable |= mask;
149 MEMBARRIER();
150 initTimer();
151 forceTimerInterrupt();
152 while (wvfState.waveformToEnable) {
153 delay(0); // Wait for waveform to update
154 // No mem barrier here, the call to a global function implies global state updated
159 // Stops a waveform on a pin
160 void stopWaveform8266(uint8_t gpio) {
161 // Can't possibly need to stop anything if there is no timer active
162 if (!timerRunning) {
163 return;
165 // If user sends in a pin >16 but <32, this will always point to a 0 bit
166 // If they send >=32, then the shift will result in 0 and it will also return false
167 uint32_t mask = 1<< gpio;
168 if (wvfState.waveformEnabled & mask) {
169 wvfState.waveformToDisable = mask;
170 forceTimerInterrupt();
171 while (wvfState.waveformToDisable) {
172 MEMBARRIER(); // If it wasn't written yet, it has to be by now
173 /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
176 disableIdleTimer();
179 // Speed critical bits
180 #pragma GCC optimize ("O2")
182 // Normally would not want two copies like this, but due to different
183 // optimization levels the inline attribute gets lost if we try the
184 // other version.
185 static inline IRAM_ATTR uint32_t GetCycleCountIRQ() {
186 uint32_t ccount;
187 __asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
188 return ccount;
191 // Find the earliest cycle as compared to right now
192 static inline IRAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
193 uint32_t now = GetCycleCountIRQ();
194 int32_t da = a - now;
195 int32_t db = b - now;
196 return (da < db) ? a : b;
199 // The SDK and hardware take some time to actually get to our NMI code, so
200 // decrement the next IRQ's timer value by a bit so we can actually catch the
201 // real CPU cycle counter we want for the waveforms.
203 // The SDK also sometimes is running at a different speed the the Arduino core
204 // so the ESP cycle counter is actually running at a variable speed.
205 // adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
206 #if F_CPU == 80000000
207 #define DELTAIRQ (microsecondsToClockCycles(9)/4)
208 #define adjust(x) ((x) << (turbo ? 1 : 0))
209 #else
210 #define DELTAIRQ (microsecondsToClockCycles(9)/8)
211 #define adjust(x) ((x) >> 0)
212 #endif
214 // When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
215 #define MINIRQTIME microsecondsToClockCycles(10)
217 static IRAM_ATTR void timer1Interrupt() {
218 // Flag if the core is at 160 MHz, for use by adjust()
219 bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
221 uint32_t now = GetCycleCountIRQ();
222 uint32_t nextEventCycle = now + microsecondsToClockCycles(MAXIRQUS);
223 uint32_t timeoutCycle = now + microsecondsToClockCycles(14);
225 if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
226 // Handle enable/disable requests from main app
227 wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
228 wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
229 wvfState.waveformToEnable = 0;
230 wvfState.waveformToDisable = 0;
231 // No mem barrier. Globals must be written to RAM on ISR exit.
232 // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
233 wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
234 // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
235 wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
238 bool done = false;
239 if (wvfState.waveformEnabled) {
240 do {
241 nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
243 for (auto gpio = wvfState.startPin; gpio <= wvfState.endPin; gpio++) {
244 uint32_t mask = 1<< gpio;
246 // If it's not on, ignore!
247 if (!(wvfState.waveformEnabled & mask)) {
248 continue;
251 Waveform *wave = &wvfState.waveform[gpio];
253 // Check for toggles
254 int32_t cyclesToGo = wave->nextServiceCycle - now;
255 if (cyclesToGo < 0) {
256 uint32_t nextEdgeCycles;
257 uint32_t desired = 0;
258 uint32_t *timeToUpdate;
259 wvfState.waveformState ^= mask;
260 if (wvfState.waveformState & mask) {
261 if (gpio == 16) { // Special handling for GPIO16
262 GP16O = 1;
264 GPOS = mask;
266 if (wave->nextHighLowUs != 0) {
267 // Copy over next full-cycle timings
268 uint32_t next = wave->nextHighLowUs;
269 wave->nextHighLowUs = 0; // indicate the change has taken place
270 wave->timeHighCycles = wave->desiredHighCycles = microsecondsToClockCycles(next >> 16);
271 wave->timeLowCycles = wave->desiredLowCycles = microsecondsToClockCycles(next & 0xffff);
272 wave->lastEdge = 0;
274 if (wave->lastEdge) {
275 desired = wave->desiredLowCycles;
276 timeToUpdate = &wave->timeLowCycles;
278 nextEdgeCycles = wave->timeHighCycles;
279 } else {
280 if (gpio == 16) { // Special handling for GPIO16
281 GP16O = 0;
283 GPOC = mask;
284 desired = wave->desiredHighCycles;
285 timeToUpdate = &wave->timeHighCycles;
286 nextEdgeCycles = wave->timeLowCycles;
288 if (desired) {
289 desired = adjust(desired);
290 int32_t err = desired - (now - wave->lastEdge);
291 if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
292 err /= 2;
293 *timeToUpdate += err;
296 nextEdgeCycles = adjust(nextEdgeCycles);
297 wave->nextServiceCycle = now + nextEdgeCycles;
298 wave->lastEdge = now;
300 nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
303 // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
304 now = GetCycleCountIRQ();
305 int32_t cycleDeltaNextEvent = nextEventCycle - now;
306 int32_t cyclesLeftTimeout = timeoutCycle - now;
307 done = (cycleDeltaNextEvent > cyclesLeftTimeout) || (cyclesLeftTimeout < 0);
308 } while (!done);
309 } // if (wvfState.waveformEnabled)
311 int32_t nextEventCycles = nextEventCycle - now;
313 if (nextEventCycles < MINIRQTIME) {
314 nextEventCycles = MINIRQTIME;
316 nextEventCycles -= DELTAIRQ;
318 // Do it here instead of global function to save time and because we know it's edge-IRQ
319 T1L = nextEventCycles >> (turbo ? 1 : 0);
322 #endif