From 3f36f6f6ffea096fd0993e9ed118833b319112f3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 6 Jan 2020 08:54:31 +0100 Subject: [PATCH 001/152] Allow 100% high or low periods. Let output remain at current level on stopping instead of always turning to low. --- cores/esp8266/core_esp8266_waveform.cpp | 76 +++++++++++++------------ 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 597a8e88ab..97e951e422 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -129,7 +129,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it } - uint32_t mask = 1<nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1); @@ -164,7 +164,7 @@ static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { } static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) { - if (a < b) { + if (a <= b) { return a; } return b; @@ -231,7 +231,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { nextEventCycles = microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { - uint32_t mask = 1<expiryCycle) { - int32_t expiryToGo = wave->expiryCycle - now; - if (expiryToGo < 0) { - // Done, remove! - waveformEnabled &= ~mask; + // Check for toggles + const int32_t cyclesToGo = wave->nextServiceCycle - now; + if (cyclesToGo <= 0) { + if (waveformState & mask ? !wave->nextTimeLowCycles : !wave->nextTimeHighCycles) { + // for 100% duty or idle, don't toggle and set service cycle to maximum + wave->nextServiceCycle = now + nextEventCycles; + } + else { + waveformState ^= mask; + if (waveformState & mask) { if (i == 16) { - GP16O &= ~1; - } else { - ClearGPIO(mask); + GP16O |= 1; // GPIO16 write slow as it's RMW } - continue; - } - } - - // Check for toggles - int32_t cyclesToGo = wave->nextServiceCycle - now; - if (cyclesToGo < 0) { - waveformState ^= mask; - if (waveformState & mask) { - if (i == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); + else { + SetGPIO(mask); + } + wave->nextServiceCycle = now + wave->nextTimeHighCycles; + nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles); } - wave->nextServiceCycle = now + wave->nextTimeHighCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles); - } else { - if (i == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); + else { + if (i == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(mask); + } + wave->nextServiceCycle = now + wave->nextTimeLowCycles; + nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles); } - wave->nextServiceCycle = now + wave->nextTimeLowCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles); } - } else { + } + else { uint32_t deltaCycles = wave->nextServiceCycle - now; nextEventCycles = min_u32(nextEventCycles, deltaCycles); } + + // Disable any waveforms that are done + if (wave->expiryCycle) { + int32_t expiryToGo = wave->expiryCycle - now; + if (expiryToGo <= 0) { + // Done, remove! + waveformEnabled &= ~mask; + } + } } // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur uint32_t now = GetCycleCountIRQ(); int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles); int32_t cyclesLeftTimeout = timeoutCycle - now; - done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0); + done = (cycleDeltaNextEvent <= 0) || (cyclesLeftTimeout <= 0); } while (!done); } // if (waveformEnabled) From ee3ca86e0a4ed0811dccae920378205583c88bdb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Jan 2020 08:05:12 +0100 Subject: [PATCH 002/152] Fix serious jitter issues in previous versions. --- cores/esp8266/core_esp8266_waveform.cpp | 81 +++++++++++-------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 97e951e422..5ebb070d06 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -55,8 +55,8 @@ extern "C" { typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform - uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform + int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform + int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform } Waveform; static Waveform waveform[17]; // State of all possible pins @@ -132,7 +132,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time uint32_t mask = 1UL<nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1); + wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(10); waveformToEnable |= mask; if (!timerRunning) { initTimer(); @@ -163,13 +163,6 @@ static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { return ccount; } -static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) { - if (a <= b) { - return a; - } - return b; -} - // 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 @@ -194,16 +187,6 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { return true; } -// The SDK and hardware take some time to actually get to our NMI code, so -// decrement the next IRQ's timer value by a bit so we can actually catch the -// real CPU cycle counter we want for the waveforms. -#if F_CPU == 80000000 - #define DELTAIRQ (microsecondsToClockCycles(3)) -#else - #define DELTAIRQ (microsecondsToClockCycles(2)) -#endif - - static ICACHE_RAM_ATTR void timer1Interrupt() { // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and @@ -211,8 +194,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS); - uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); + constexpr uint32_t isrTimeoutCycles = microsecondsToClockCycles(14); + const uint32_t isrStart = GetCycleCountIRQ(); + uint32_t nextEventCycle = isrStart + microsecondsToClockCycles(MAXIRQUS); if (waveformToEnable || waveformToDisable) { // Handle enable/disable requests from main app. @@ -228,25 +212,29 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { bool done = false; if (waveformEnabled) { + uint32_t now = GetCycleCountIRQ(); do { - nextEventCycles = microsecondsToClockCycles(MAXIRQUS); + nextEventCycle = now + microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { uint32_t mask = 1UL<nextServiceCycle - now; - if (cyclesToGo <= 0) { + const int32_t nextEventCycles = nextEventCycle - now; + if (cyclesToGo > 0) { + if (nextEventCycles > cyclesToGo) + nextEventCycle = wave->nextServiceCycle; + } + else { if (waveformState & mask ? !wave->nextTimeLowCycles : !wave->nextTimeHighCycles) { // for 100% duty or idle, don't toggle and set service cycle to maximum - wave->nextServiceCycle = now + nextEventCycles; + wave->nextServiceCycle += microsecondsToClockCycles(MAXIRQUS); } else { waveformState ^= mask; @@ -257,8 +245,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { SetGPIO(mask); } - wave->nextServiceCycle = now + wave->nextTimeHighCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles); + wave->nextServiceCycle += wave->nextTimeHighCycles; + if (nextEventCycles > wave->nextTimeHighCycles) + nextEventCycle = wave->nextServiceCycle; } else { if (i == 16) { @@ -267,15 +256,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { ClearGPIO(mask); } - wave->nextServiceCycle = now + wave->nextTimeLowCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles); + wave->nextServiceCycle += wave->nextTimeLowCycles; + if (nextEventCycles > wave->nextTimeLowCycles) + nextEventCycle = wave->nextServiceCycle; } } } - else { - uint32_t deltaCycles = wave->nextServiceCycle - now; - nextEventCycles = min_u32(nextEventCycles, deltaCycles); - } // Disable any waveforms that are done if (wave->expiryCycle) { @@ -285,24 +271,27 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveformEnabled &= ~mask; } } + now = GetCycleCountIRQ(); } // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - uint32_t now = GetCycleCountIRQ(); - int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles); - int32_t cyclesLeftTimeout = timeoutCycle - now; - done = (cycleDeltaNextEvent <= 0) || (cyclesLeftTimeout <= 0); + done = (now - isrStart >= isrTimeoutCycles) || (nextEventCycle - isrStart >= isrTimeoutCycles); } while (!done); } // if (waveformEnabled) - if (timer1CB) { - nextEventCycles = min_u32(nextEventCycles, timer1CB()); + int32_t nextEventCycles; + if (!timer1CB) { + nextEventCycles = nextEventCycle - GetCycleCountIRQ(); } - - if (nextEventCycles < microsecondsToClockCycles(10)) { - nextEventCycles = microsecondsToClockCycles(10); + else { + int32_t callbackCycles = microsecondsToClockCycles(timer1CB()); + nextEventCycles = nextEventCycle - GetCycleCountIRQ(); + if (nextEventCycles > callbackCycles) + nextEventCycles = callbackCycles; } - nextEventCycles -= DELTAIRQ; + + if (nextEventCycles < microsecondsToClockCycles(2)) + nextEventCycles = microsecondsToClockCycles(2); // Do it here instead of global function to save time and because we know it's edge-IRQ #if F_CPU == 160000000 From 233e5f1d746afb131f0fe750b45c86aec1d67071 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Jan 2020 10:06:34 +0100 Subject: [PATCH 003/152] Use ESP.getCycleCount() just like everyone else. --- cores/esp8266/core_esp8266_waveform.cpp | 61 +++++++------------------ 1 file changed, 17 insertions(+), 44 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5ebb070d06..c7f107f427 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -5,13 +5,13 @@ 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 or CPU clock cycles). TIMER1 is - set to 1-shot mode and is always loaded with the time until the next edge - of any live waveforms. + 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 clock cycle counter, not the + 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, @@ -19,9 +19,8 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 - cycles (which may be 2 CPU clock cycles @ 160MHz). + 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 @@ -69,16 +68,6 @@ static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to static uint32_t (*timer1CB)() = NULL; - -// Non-speed critical bits -#pragma GCC optimize ("Os") - -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 bool timerRunning = false; @@ -113,26 +102,22 @@ void setTimer1Callback(uint32_t (*fn)()) { // 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) { - return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); -} - -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { - if ((pin > 16) || isFlashInterfacePin(pin)) { + if ((pin > 16) || isFlashInterfacePin(pin)) { return false; } Waveform *wave = &waveform[pin]; // Adjust to shave off some of the IRQ time, approximately - wave->nextTimeHighCycles = timeHighCycles; - wave->nextTimeLowCycles = timeLowCycles; - wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0; - if (runTimeCycles && !wave->expiryCycle) { + wave->nextTimeHighCycles = microsecondsToClockCycles(timeHighUS); + wave->nextTimeLowCycles = microsecondsToClockCycles(timeLowUS); + wave->expiryCycle = runTimeUS ? ESP.getCycleCount() + microsecondsToClockCycles(runTimeUS) : 0; + if (runTimeUS && !wave->expiryCycle) { wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it } uint32_t mask = 1UL<nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(10); + wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(10); waveformToEnable |= mask; if (!timerRunning) { initTimer(); @@ -151,18 +136,6 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time return true; } -// Speed critical bits -#pragma GCC optimize ("O2") -// Normally would not want two copies like this, but due to different -// optimization levels the inline attribute gets lost if we try the -// other version. - -static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { - uint32_t ccount; - __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); - return ccount; -} - // 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 @@ -195,7 +168,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int endPin = 0; constexpr uint32_t isrTimeoutCycles = microsecondsToClockCycles(14); - const uint32_t isrStart = GetCycleCountIRQ(); + const uint32_t isrStart = ESP.getCycleCount(); uint32_t nextEventCycle = isrStart + microsecondsToClockCycles(MAXIRQUS); if (waveformToEnable || waveformToDisable) { @@ -212,7 +185,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { bool done = false; if (waveformEnabled) { - uint32_t now = GetCycleCountIRQ(); + uint32_t now = ESP.getCycleCount(); do { nextEventCycle = now + microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { @@ -271,7 +244,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveformEnabled &= ~mask; } } - now = GetCycleCountIRQ(); + now = ESP.getCycleCount(); } // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur @@ -281,11 +254,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t nextEventCycles; if (!timer1CB) { - nextEventCycles = nextEventCycle - GetCycleCountIRQ(); + nextEventCycles = nextEventCycle - ESP.getCycleCount(); } else { int32_t callbackCycles = microsecondsToClockCycles(timer1CB()); - nextEventCycles = nextEventCycle - GetCycleCountIRQ(); + nextEventCycles = nextEventCycle - ESP.getCycleCount(); if (nextEventCycles > callbackCycles) nextEventCycles = callbackCycles; } From c462fe0d474846a731ed0772580d3d9f44165582 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Jan 2020 14:08:21 +0100 Subject: [PATCH 004/152] =?UTF-8?q?Highest=20timer=20rate=20at=20which=20t?= =?UTF-8?q?his=20runs=20stable=20appears=20to=20be=202=C2=B5s=20(500kHz).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp8266/core_esp8266_waveform.cpp | 54 +++++++++++-------------- cores/esp8266/core_esp8266_waveform.h | 15 ++++--- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c7f107f427..90eac32000 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -5,9 +5,9 @@ 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. + high and low period (defined in microseconds or CPU cycles). 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. @@ -92,42 +92,42 @@ void setTimer1Callback(uint32_t (*fn)()) { timer1CB = fn; if (!timerRunning && fn) { initTimer(); - timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste + timer1_write(microsecondsToClockCycles(2)); // Cause an interrupt post-haste } else if (timerRunning && !fn && !waveformEnabled) { deinitTimer(); } } +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { + return startWaveformCycles(pin, + microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); +} + // 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) { +int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { if ((pin > 16) || isFlashInterfacePin(pin)) { return false; } - Waveform *wave = &waveform[pin]; + Waveform* wave = &waveform[pin]; // Adjust to shave off some of the IRQ time, approximately - wave->nextTimeHighCycles = microsecondsToClockCycles(timeHighUS); - wave->nextTimeLowCycles = microsecondsToClockCycles(timeLowUS); - wave->expiryCycle = runTimeUS ? ESP.getCycleCount() + microsecondsToClockCycles(runTimeUS) : 0; - if (runTimeUS && !wave->expiryCycle) { - wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it + wave->nextTimeHighCycles = timeHighCycles; + wave->nextTimeLowCycles = timeLowCycles; + wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; + if (runTimeCycles && !wave->expiryCycle) { + wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so prevent setting it } - uint32_t mask = 1UL<nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(10); + wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(2); waveformToEnable |= mask; if (!timerRunning) { initTimer(); - timer1_write(microsecondsToClockCycles(10)); - } else { - // Ensure timely service.... - if (T1L > microsecondsToClockCycles(10)) { - timer1_write(microsecondsToClockCycles(10)); - } } + timer1_write(microsecondsToClockCycles(2)); while (waveformToEnable) { delay(0); // Wait for waveform to update } @@ -146,10 +146,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If they send >=32, then the shift will result in 0 and it will also return false if (waveformEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; - // Must not interfere if Timer is due shortly - if (T1L > microsecondsToClockCycles(10)) { - timer1_write(microsecondsToClockCycles(10)); - } + timer1_write(microsecondsToClockCycles(2)); while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } @@ -266,13 +263,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (nextEventCycles < microsecondsToClockCycles(2)) nextEventCycles = microsecondsToClockCycles(2); - // Do it here instead of global function to save time and because we know it's edge-IRQ -#if F_CPU == 160000000 - T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS -#else - T1L = nextEventCycles; // Already know we're in range by MAXIRQUS -#endif - TEIE |= TEIE1; // Edge int enable + if (clockCyclesPerMicrosecond() == 160) + timer1_write(nextEventCycles / 2); + else + timer1_write(nextEventCycles); } }; diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index e42a17f89f..00c86809b7 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -5,13 +5,13 @@ 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 or CPU clock cycles). TIMER1 is + high and low period (defined in microseconds or CPU cycles). 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 clock cycle counter, not the + 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, @@ -19,9 +19,8 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 - cycles (which may be 2 CPU clock cycles @ 160MHz). + 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 @@ -51,10 +50,10 @@ extern "C" { // If runtimeUS > 0 then automatically stop it after that many usecs. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); -// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles. +// Start or change a waveform of the specified high and low CPU cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many usecs. // Returns true or false on success or failure. -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); +int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); From a97f24738419361d19c373803eb4bd70c5927aff Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Jan 2020 20:55:49 +0100 Subject: [PATCH 005/152] Guard for zero period length undefined waveforms. Fix for zero duty or off cycles and expiring from them. --- cores/esp8266/core_esp8266_waveform.cpp | 28 ++++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 90eac32000..ccc4ee261b 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -107,7 +107,7 @@ int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t // waveform smoothly on next low->high transition. For immediate change, stopWaveform() // first, then it will immediately begin. int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { - if ((pin > 16) || isFlashInterfacePin(pin)) { + if ((pin > 16) || isFlashInterfacePin(pin) || !(timeHighCycles + timeLowCycles)) { return false; } Waveform* wave = &waveform[pin]; @@ -202,34 +202,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCycle = wave->nextServiceCycle; } else { - if (waveformState & mask ? !wave->nextTimeLowCycles : !wave->nextTimeHighCycles) { - // for 100% duty or idle, don't toggle and set service cycle to maximum - wave->nextServiceCycle += microsecondsToClockCycles(MAXIRQUS); - } - else { - waveformState ^= mask; - if (waveformState & mask) { + waveformState ^= mask; + if (waveformState & mask) { + if (wave->nextTimeHighCycles) { if (i == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } else { SetGPIO(mask); } - wave->nextServiceCycle += wave->nextTimeHighCycles; - if (nextEventCycles > wave->nextTimeHighCycles) - nextEventCycle = wave->nextServiceCycle; } - else { + wave->nextServiceCycle += wave->nextTimeHighCycles; + if (nextEventCycles > wave->nextTimeHighCycles) + nextEventCycle = wave->nextServiceCycle; + } + else { + if (wave->nextTimeLowCycles) { if (i == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } else { ClearGPIO(mask); } - wave->nextServiceCycle += wave->nextTimeLowCycles; - if (nextEventCycles > wave->nextTimeLowCycles) - nextEventCycle = wave->nextServiceCycle; } + wave->nextServiceCycle += wave->nextTimeLowCycles; + if (nextEventCycles > wave->nextTimeLowCycles) + nextEventCycle = wave->nextServiceCycle; } } From a2c7125dc03644554918620c91ff5206c2eaa083 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 8 Jan 2020 12:17:54 +0100 Subject: [PATCH 006/152] Cycle precision for expiry instead of special treatment for 0 value. --- cores/esp8266/core_esp8266_waveform.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index ccc4ee261b..429ac6629a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -52,10 +52,11 @@ extern "C" { // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextServiceCycle; // ESP cycle timer when a transition required - uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop + uint32_t nextServiceCycle; // ESP cycle timer when a transition required int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform + uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop + bool hasExpiry; // if false, expiryCycle is not set and must be ignored } Waveform; static Waveform waveform[17]; // State of all possible pins @@ -114,10 +115,8 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy // Adjust to shave off some of the IRQ time, approximately wave->nextTimeHighCycles = timeHighCycles; wave->nextTimeLowCycles = timeLowCycles; - wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; - if (runTimeCycles && !wave->expiryCycle) { - wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so prevent setting it - } + wave->expiryCycle = runTimeCycles; + wave->hasExpiry = static_cast(runTimeCycles); uint32_t mask = 1UL << pin; if (!(waveformEnabled & mask)) { @@ -232,7 +231,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Disable any waveforms that are done - if (wave->expiryCycle) { + if (wave->hasExpiry) { int32_t expiryToGo = wave->expiryCycle - now; if (expiryToGo <= 0) { // Done, remove! From b2e9ad75b1f9c0ebc34a8635ff3a3dc8e2ceedd6 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 8 Jan 2020 12:45:14 +0100 Subject: [PATCH 007/152] Give expiry proper precedence over updating a waveform --- cores/esp8266/core_esp8266_waveform.cpp | 52 ++++++++++++++++--------- cores/esp8266/core_esp8266_waveform.h | 6 ++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 429ac6629a..59862df85a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -50,17 +50,19 @@ extern "C" { #define SetGPIO(a) do { GPOS = a; } while (0) #define ClearGPIO(a) do { GPOC = a; } while (0) +enum class ExpiryState : uint32_t {OFF = 0, ON = 1, UPDATE = 2}; // for UPDATE, the NMI computes the exact expiry cycle and transitions to ON + // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextServiceCycle; // ESP cycle timer when a transition required - int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform - int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform - uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - bool hasExpiry; // if false, expiryCycle is not set and must be ignored + uint32_t nextServiceCycle; // ESP cycle timer when a transition required + volatile int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform + volatile int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform + volatile uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop + volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCycle is zero-based, NMI will recompute } Waveform; static Waveform waveform[17]; // State of all possible pins -static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code +static uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine @@ -115,13 +117,14 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy // Adjust to shave off some of the IRQ time, approximately wave->nextTimeHighCycles = timeHighCycles; wave->nextTimeLowCycles = timeLowCycles; - wave->expiryCycle = runTimeCycles; - wave->hasExpiry = static_cast(runTimeCycles); uint32_t mask = 1UL << pin; if (!(waveformEnabled & mask)) { // Actually set the pin high or low in the IRQ service to guarantee times - wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(2); + uint32_t now = ESP.getCycleCount(); + wave->nextServiceCycle = now + microsecondsToClockCycles(2); + wave->expiryCycle = now + microsecondsToClockCycles(2) + runTimeCycles; + wave->hasExpiry = static_cast(runTimeCycles) ? ExpiryState::ON : ExpiryState::OFF; waveformToEnable |= mask; if (!timerRunning) { initTimer(); @@ -131,7 +134,12 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy delay(0); // Wait for waveform to update } } - + else { + wave->hasExpiry = ExpiryState::OFF; // turn off to make update atomic from NMI + wave->expiryCycle = runTimeCycles; + if (runTimeCycles) + wave->hasExpiry = ExpiryState::UPDATE; + } return true; } @@ -193,10 +201,21 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform *wave = &waveform[i]; - // Check for toggles + // Check for toggles etc. + if (ExpiryState::UPDATE == wave->hasExpiry) { + wave->expiryCycle += wave->nextServiceCycle; + if (waveformState & mask) + wave->expiryCycle += wave->nextTimeLowCycles; // update expiry time to next full period + wave->hasExpiry = ExpiryState::ON; + } const int32_t cyclesToGo = wave->nextServiceCycle - now; + int32_t expiryToGo = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCycle - now : (cyclesToGo + 1); const int32_t nextEventCycles = nextEventCycle - now; - if (cyclesToGo > 0) { + if (cyclesToGo >= expiryToGo) { + // don't update waveform on or after expiring + expiryToGo = 0; + } + else if (cyclesToGo > 0) { if (nextEventCycles > cyclesToGo) nextEventCycle = wave->nextServiceCycle; } @@ -231,12 +250,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Disable any waveforms that are done - if (wave->hasExpiry) { - int32_t expiryToGo = wave->expiryCycle - now; - if (expiryToGo <= 0) { - // Done, remove! - waveformEnabled &= ~mask; - } + if ((ExpiryState::ON == wave->hasExpiry) && expiryToGo <= 0) { + // Done, remove! + waveformEnabled &= ~mask; } now = ESP.getCycleCount(); } diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 00c86809b7..79b867b85d 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -47,11 +47,13 @@ extern "C" { #endif // Start or change a waveform of the specified high and low times on specific pin. -// If runtimeUS > 0 then automatically stop it after that many usecs. +// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next +// full period. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); // Start or change a waveform of the specified high and low CPU cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many usecs. +// If runtimeCycles > 0 then automatically stop it after that many usecs, relative to the next +// full period. // Returns true or false on success or failure. int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); // Stop a waveform, if any, on the specified pin. From 6eb28d345dc80cac046eb508210ebb6032e169be Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Feb 2020 11:24:34 +0100 Subject: [PATCH 008/152] Important comment --- cores/esp8266/core_esp8266_waveform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 59862df85a..a22ef213f9 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -57,7 +57,7 @@ typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required volatile int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform volatile int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform - volatile uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop + volatile uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop. In ExpiryState::UPDATE, temporarily holds relative cycle count volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCycle is zero-based, NMI will recompute } Waveform; @@ -136,7 +136,7 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy } else { wave->hasExpiry = ExpiryState::OFF; // turn off to make update atomic from NMI - wave->expiryCycle = runTimeCycles; + wave->expiryCycle = runTimeCycles; // in ExpiryState::UPDATE, temporarily hold relative cycle count if (runTimeCycles) wave->hasExpiry = ExpiryState::UPDATE; } @@ -203,7 +203,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Check for toggles etc. if (ExpiryState::UPDATE == wave->hasExpiry) { - wave->expiryCycle += wave->nextServiceCycle; + wave->expiryCycle += wave->nextServiceCycle; // in ExpiryState::UPDATE, expiryCycle temporarily holds relative cycle count if (waveformState & mask) wave->expiryCycle += wave->nextTimeLowCycles; // update expiry time to next full period wave->hasExpiry = ExpiryState::ON; From da049c1121d002f0457f68594fa3c8eefde08faa Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Feb 2020 12:29:09 +0100 Subject: [PATCH 009/152] Refactored, identical behavior. --- cores/esp8266/core_esp8266_waveform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a22ef213f9..f42ec745bc 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -229,8 +229,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { SetGPIO(mask); } + wave->nextServiceCycle += wave->nextTimeHighCycles; } - wave->nextServiceCycle += wave->nextTimeHighCycles; if (nextEventCycles > wave->nextTimeHighCycles) nextEventCycle = wave->nextServiceCycle; } @@ -242,8 +242,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { ClearGPIO(mask); } + wave->nextServiceCycle += wave->nextTimeLowCycles; } - wave->nextServiceCycle += wave->nextTimeLowCycles; if (nextEventCycles > wave->nextTimeLowCycles) nextEventCycle = wave->nextServiceCycle; } From 11782681a8bf1febcc07cb2b52c6a84c122106d9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Feb 2020 12:44:59 +0100 Subject: [PATCH 010/152] Use plural for bit arrays. --- cores/esp8266/core_esp8266_waveform.cpp | 60 ++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index f42ec745bc..7e1fe00b6a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -61,13 +61,13 @@ typedef struct { volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCycle is zero-based, NMI will recompute } Waveform; -static Waveform waveform[17]; // State of all possible pins -static uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code -static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code +static Waveform waveforms[17]; // State of all possible pins +static uint32_t waveformsState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code +static volatile uint32_t waveformsEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code -// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine -static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin -static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation +// Enable lock-free by only allowing updates to waveformsState and waveformsEnabled from IRQ service routine +static volatile uint32_t waveformsToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin +static volatile uint32_t waveformsToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation static uint32_t (*timer1CB)() = NULL; @@ -96,7 +96,7 @@ void setTimer1Callback(uint32_t (*fn)()) { if (!timerRunning && fn) { initTimer(); timer1_write(microsecondsToClockCycles(2)); // Cause an interrupt post-haste - } else if (timerRunning && !fn && !waveformEnabled) { + } else if (timerRunning && !fn && !waveformsEnabled) { deinitTimer(); } } @@ -113,24 +113,24 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy if ((pin > 16) || isFlashInterfacePin(pin) || !(timeHighCycles + timeLowCycles)) { return false; } - Waveform* wave = &waveform[pin]; + Waveform* wave = &waveforms[pin]; // Adjust to shave off some of the IRQ time, approximately wave->nextTimeHighCycles = timeHighCycles; wave->nextTimeLowCycles = timeLowCycles; uint32_t mask = 1UL << pin; - if (!(waveformEnabled & mask)) { + if (!(waveformsEnabled & mask)) { // Actually set the pin high or low in the IRQ service to guarantee times uint32_t now = ESP.getCycleCount(); wave->nextServiceCycle = now + microsecondsToClockCycles(2); wave->expiryCycle = now + microsecondsToClockCycles(2) + runTimeCycles; wave->hasExpiry = static_cast(runTimeCycles) ? ExpiryState::ON : ExpiryState::OFF; - waveformToEnable |= mask; + waveformsToEnable |= mask; if (!timerRunning) { initTimer(); } timer1_write(microsecondsToClockCycles(2)); - while (waveformToEnable) { + while (waveformsToEnable) { delay(0); // Wait for waveform to update } } @@ -151,14 +151,14 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (waveformEnabled & (1UL << pin)) { - waveformToDisable = 1UL << pin; + if (waveformsEnabled & (1UL << pin)) { + waveformsToDisable = 1UL << pin; timer1_write(microsecondsToClockCycles(2)); - while (waveformToDisable) { + while (waveformsToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } } - if (!waveformEnabled && !timer1CB) { + if (!waveformsEnabled && !timer1CB) { deinitTimer(); } return true; @@ -175,20 +175,20 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStart = ESP.getCycleCount(); uint32_t nextEventCycle = isrStart + microsecondsToClockCycles(MAXIRQUS); - if (waveformToEnable || waveformToDisable) { + if (waveformsToEnable || waveformsToDisable) { // Handle enable/disable requests from main app. - waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off - waveformState &= ~waveformToEnable; // And clear the state of any just started - waveformToEnable = 0; - waveformToDisable = 0; + waveformsEnabled = (waveformsEnabled & ~waveformsToDisable) | waveformsToEnable; // Set the requested waveforms on/off + waveformsState &= ~waveformsToEnable; // And clear the state of any just started + waveformsToEnable = 0; + waveformsToDisable = 0; // 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) - startPin = __builtin_ffs(waveformEnabled) - 1; + startPin = __builtin_ffs(waveformsEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - endPin = 32 - __builtin_clz(waveformEnabled); + endPin = 32 - __builtin_clz(waveformsEnabled); } bool done = false; - if (waveformEnabled) { + if (waveformsEnabled) { uint32_t now = ESP.getCycleCount(); do { nextEventCycle = now + microsecondsToClockCycles(MAXIRQUS); @@ -196,15 +196,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { uint32_t mask = 1UL<hasExpiry) { wave->expiryCycle += wave->nextServiceCycle; // in ExpiryState::UPDATE, expiryCycle temporarily holds relative cycle count - if (waveformState & mask) + if (waveformsState & mask) wave->expiryCycle += wave->nextTimeLowCycles; // update expiry time to next full period wave->hasExpiry = ExpiryState::ON; } @@ -220,8 +220,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCycle = wave->nextServiceCycle; } else { - waveformState ^= mask; - if (waveformState & mask) { + waveformsState ^= mask; + if (waveformsState & mask) { if (wave->nextTimeHighCycles) { if (i == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW @@ -252,7 +252,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Disable any waveforms that are done if ((ExpiryState::ON == wave->hasExpiry) && expiryToGo <= 0) { // Done, remove! - waveformEnabled &= ~mask; + waveformsEnabled &= ~mask; } now = ESP.getCycleCount(); } @@ -260,7 +260,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur done = (now - isrStart >= isrTimeoutCycles) || (nextEventCycle - isrStart >= isrTimeoutCycles); } while (!done); - } // if (waveformEnabled) + } // if (waveformsEnabled) int32_t nextEventCycles; if (!timer1CB) { From 3fbaaa899e58cb99587d6a38676b4e411b8bde28 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 7 Feb 2020 09:08:49 +0100 Subject: [PATCH 011/152] Fix for completely duty or all off cycle period case. --- cores/esp8266/core_esp8266_waveform.cpp | 36 +++++++++++-------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 7e1fe00b6a..213a6d7ede 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -220,30 +220,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCycle = wave->nextServiceCycle; } else { - waveformsState ^= mask; - if (waveformsState & mask) { - if (wave->nextTimeHighCycles) { - if (i == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW - } - else { - SetGPIO(mask); - } - wave->nextServiceCycle += wave->nextTimeHighCycles; + if (wave->nextTimeHighCycles && (!(waveformsState & mask) || !wave->nextTimeLowCycles)) { + waveformsState |= mask; + if (i == 16) { + GP16O |= 1; // GPIO16 write slow as it's RMW } + else { + SetGPIO(mask); + } + wave->nextServiceCycle += wave->nextTimeHighCycles; if (nextEventCycles > wave->nextTimeHighCycles) nextEventCycle = wave->nextServiceCycle; - } - else { - if (wave->nextTimeLowCycles) { - if (i == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW - } - else { - ClearGPIO(mask); - } - wave->nextServiceCycle += wave->nextTimeLowCycles; + } else if ((waveformsState & mask) || !wave->nextTimeHighCycles) { + waveformsState &= ~mask; + if (i == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(mask); } + wave->nextServiceCycle += wave->nextTimeLowCycles; if (nextEventCycles > wave->nextTimeLowCycles) nextEventCycle = wave->nextServiceCycle; } From 01c5f7a4c61a0c915246a1c99eca52f67b795c34 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 7 Feb 2020 09:36:48 +0100 Subject: [PATCH 012/152] Expiration is explicitly relative to service time. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 213a6d7ede..3434de3c76 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -123,7 +123,7 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy // Actually set the pin high or low in the IRQ service to guarantee times uint32_t now = ESP.getCycleCount(); wave->nextServiceCycle = now + microsecondsToClockCycles(2); - wave->expiryCycle = now + microsecondsToClockCycles(2) + runTimeCycles; + wave->expiryCycle = wave->nextServiceCycle + runTimeCycles; wave->hasExpiry = static_cast(runTimeCycles) ? ExpiryState::ON : ExpiryState::OFF; waveformsToEnable |= mask; if (!timerRunning) { From b2556551cdc67007b40ac15dd978b1c66a02f7fc Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 8 Feb 2020 23:32:15 +0100 Subject: [PATCH 013/152] Comment updated, here it's about cycles not usecs. --- cores/esp8266/core_esp8266_waveform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 79b867b85d..89c7f9beb4 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -52,7 +52,7 @@ extern "C" { // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); // Start or change a waveform of the specified high and low CPU cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many usecs, relative to the next +// If runtimeCycles > 0 then automatically stop it after that many CPU cycles, relative to the next // full period. // Returns true or false on success or failure. int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); From 7b99b85b1961b980e7d9b1c74ef3709168448965 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 9 Feb 2020 16:43:09 +0100 Subject: [PATCH 014/152] Revert misconception of how waveformToEnable/Disable communicates with the NMI handler. --- cores/esp8266/core_esp8266_waveform.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 3434de3c76..94617a7a7e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -66,8 +66,8 @@ static uint32_t waveformsState = 0; // Is the pin high or low, updated in NMI static volatile uint32_t waveformsEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code // Enable lock-free by only allowing updates to waveformsState and waveformsEnabled from IRQ service routine -static volatile uint32_t waveformsToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin -static volatile uint32_t waveformsToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation +static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start exactly one waveform on a inactive pin +static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable exactly one pin from waveform generation static uint32_t (*timer1CB)() = NULL; @@ -125,12 +125,12 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy wave->nextServiceCycle = now + microsecondsToClockCycles(2); wave->expiryCycle = wave->nextServiceCycle + runTimeCycles; wave->hasExpiry = static_cast(runTimeCycles) ? ExpiryState::ON : ExpiryState::OFF; - waveformsToEnable |= mask; + waveformToEnable |= mask; if (!timerRunning) { initTimer(); } timer1_write(microsecondsToClockCycles(2)); - while (waveformsToEnable) { + while (waveformToEnable) { delay(0); // Wait for waveform to update } } @@ -152,9 +152,9 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false if (waveformsEnabled & (1UL << pin)) { - waveformsToDisable = 1UL << pin; + waveformToDisable = 1UL << pin; timer1_write(microsecondsToClockCycles(2)); - while (waveformsToDisable) { + while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } } @@ -175,12 +175,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStart = ESP.getCycleCount(); uint32_t nextEventCycle = isrStart + microsecondsToClockCycles(MAXIRQUS); - if (waveformsToEnable || waveformsToDisable) { + if (waveformToEnable || waveformToDisable) { // Handle enable/disable requests from main app. - waveformsEnabled = (waveformsEnabled & ~waveformsToDisable) | waveformsToEnable; // Set the requested waveforms on/off - waveformsState &= ~waveformsToEnable; // And clear the state of any just started - waveformsToEnable = 0; - waveformsToDisable = 0; + waveformsEnabled = (waveformsEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off + waveformsState &= ~waveformToEnable; // And clear the state of any just started + waveformToEnable = 0; + waveformToDisable = 0; // 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) startPin = __builtin_ffs(waveformsEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) From 22556f0421763524b938a427a39068945d929b08 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 9 Feb 2020 22:17:51 +0100 Subject: [PATCH 015/152] Rewrite to keep phase in sync if period remains same during duty cycle change. Refactor identifies to distinguish CPU clock cycle from waveform cycle. --- cores/esp8266/core_esp8266_waveform.cpp | 122 ++++++++++++------------ cores/esp8266/core_esp8266_waveform.h | 15 +-- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 94617a7a7e..22fb72f0ca 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -5,13 +5,13 @@ 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 or CPU cycles). TIMER1 is + high and low period (defined in microseconds or CPU clock cycles). 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 + Each waveform generator is synchronized to the ESP clock 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, @@ -19,8 +19,9 @@ 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). + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleTime() + clock cycle time, or an interval measured in clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -50,15 +51,15 @@ extern "C" { #define SetGPIO(a) do { GPOS = a; } while (0) #define ClearGPIO(a) do { GPOC = a; } while (0) -enum class ExpiryState : uint32_t {OFF = 0, ON = 1, UPDATE = 2}; // for UPDATE, the NMI computes the exact expiry cycle and transitions to ON +enum class ExpiryState : uint32_t {OFF = 0, ON = 1, UPDATE = 2}; // for UPDATE, the NMI computes the exact expiry ccy and transitions to ON // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextServiceCycle; // ESP cycle timer when a transition required - volatile int32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform - volatile int32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform - volatile uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop. In ExpiryState::UPDATE, temporarily holds relative cycle count - volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCycle is zero-based, NMI will recompute + uint32_t nextPhaseCcy; // ESP clock cycle when a period begins + volatile int32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform + volatile int32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase + volatile uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count + volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCcy is zero-based, NMI will recompute } Waveform; static Waveform waveforms[17]; // State of all possible pins @@ -102,29 +103,29 @@ void setTimer1Callback(uint32_t (*fn)()) { } int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { - return startWaveformCycles(pin, + return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); } // 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 startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { - if ((pin > 16) || isFlashInterfacePin(pin) || !(timeHighCycles + timeLowCycles)) { +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys) { + const auto periodCcys = timeHighCcys + timeLowCcys; + if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys) { return false; } Waveform* wave = &waveforms[pin]; - // Adjust to shave off some of the IRQ time, approximately - wave->nextTimeHighCycles = timeHighCycles; - wave->nextTimeLowCycles = timeLowCycles; + wave->nextTimeDutyCcys = timeHighCcys; + wave->nextTimePeriodCcys = periodCcys; uint32_t mask = 1UL << pin; if (!(waveformsEnabled & mask)) { // Actually set the pin high or low in the IRQ service to guarantee times uint32_t now = ESP.getCycleCount(); - wave->nextServiceCycle = now + microsecondsToClockCycles(2); - wave->expiryCycle = wave->nextServiceCycle + runTimeCycles; - wave->hasExpiry = static_cast(runTimeCycles) ? ExpiryState::ON : ExpiryState::OFF; + wave->nextPhaseCcy = now + microsecondsToClockCycles(2); + wave->expiryCcy = wave->nextPhaseCcy + runTimeCcys; + wave->hasExpiry = static_cast(runTimeCcys) ? ExpiryState::ON : ExpiryState::OFF; waveformToEnable |= mask; if (!timerRunning) { initTimer(); @@ -136,8 +137,8 @@ int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCy } else { wave->hasExpiry = ExpiryState::OFF; // turn off to make update atomic from NMI - wave->expiryCycle = runTimeCycles; // in ExpiryState::UPDATE, temporarily hold relative cycle count - if (runTimeCycles) + wave->expiryCcy = runTimeCcys; // in ExpiryState::UPDATE, temporarily hold relative cycle count + if (runTimeCcys) wave->hasExpiry = ExpiryState::UPDATE; } return true; @@ -171,9 +172,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - constexpr uint32_t isrTimeoutCycles = microsecondsToClockCycles(14); - const uint32_t isrStart = ESP.getCycleCount(); - uint32_t nextEventCycle = isrStart + microsecondsToClockCycles(MAXIRQUS); + constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(14); + const uint32_t isrStartCcy = ESP.getCycleCount(); + uint32_t nextTimerCcy = isrStartCcy + microsecondsToClockCycles(MAXIRQUS); if (waveformToEnable || waveformToDisable) { // Handle enable/disable requests from main app. @@ -189,9 +190,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { bool done = false; if (waveformsEnabled) { - uint32_t now = ESP.getCycleCount(); do { - nextEventCycle = now + microsecondsToClockCycles(MAXIRQUS); + uint32_t now = ESP.getCycleCount(); + nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { uint32_t mask = 1UL<hasExpiry) { - wave->expiryCycle += wave->nextServiceCycle; // in ExpiryState::UPDATE, expiryCycle temporarily holds relative cycle count - if (waveformsState & mask) - wave->expiryCycle += wave->nextTimeLowCycles; // update expiry time to next full period + wave->expiryCcy += wave->nextPhaseCcy; // in ExpiryState::UPDATE, expiryCcy temporarily holds relative CPU cycle count wave->hasExpiry = ExpiryState::ON; } - const int32_t cyclesToGo = wave->nextServiceCycle - now; - int32_t expiryToGo = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCycle - now : (cyclesToGo + 1); - const int32_t nextEventCycles = nextEventCycle - now; - if (cyclesToGo >= expiryToGo) { - // don't update waveform on or after expiring - expiryToGo = 0; - } - else if (cyclesToGo > 0) { - if (nextEventCycles > cyclesToGo) - nextEventCycle = wave->nextServiceCycle; - } - else { - if (wave->nextTimeHighCycles && (!(waveformsState & mask) || !wave->nextTimeLowCycles)) { + const bool noIdle = wave->nextTimePeriodCcys == wave->nextTimeDutyCcys; + const int32_t nextTimerCcys = nextTimerCcy - now; + const int32_t nextEventCcys = wave->nextPhaseCcy + + ((!noIdle && waveformsState & mask) ? wave->nextTimeDutyCcys : 0) - now; + const int32_t expiryCcys = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCcy - now : (nextEventCcys + 1); + if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { + if (wave->nextTimeDutyCcys && (!(waveformsState & mask) || noIdle)) { waveformsState |= mask; if (i == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW @@ -228,10 +221,17 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { SetGPIO(mask); } - wave->nextServiceCycle += wave->nextTimeHighCycles; - if (nextEventCycles > wave->nextTimeHighCycles) - nextEventCycle = wave->nextServiceCycle; - } else if ((waveformsState & mask) || !wave->nextTimeHighCycles) { + if (noIdle) { + wave->nextPhaseCcy += wave->nextTimePeriodCcys; + if (nextTimerCcys > static_cast(wave->nextPhaseCcy - now)) + nextTimerCcy = wave->nextPhaseCcy; + } + else { + if (nextTimerCcys > wave->nextTimeDutyCcys) + nextTimerCcy = wave->nextPhaseCcy + wave->nextTimeDutyCcys; + } + } + else if ((waveformsState & mask) || !wave->nextTimeDutyCcys) { waveformsState &= ~mask; if (i == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW @@ -239,14 +239,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { ClearGPIO(mask); } - wave->nextServiceCycle += wave->nextTimeLowCycles; - if (nextEventCycles > wave->nextTimeLowCycles) - nextEventCycle = wave->nextServiceCycle; + wave->nextPhaseCcy += wave->nextTimePeriodCcys; + if (nextTimerCcys > static_cast(wave->nextPhaseCcy - now)) + nextTimerCcy = wave->nextPhaseCcy; } } // Disable any waveforms that are done - if ((ExpiryState::ON == wave->hasExpiry) && expiryToGo <= 0) { + if ((ExpiryState::ON == wave->hasExpiry) && expiryCcys <= 0) { // Done, remove! waveformsEnabled &= ~mask; } @@ -254,28 +254,28 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - done = (now - isrStart >= isrTimeoutCycles) || (nextEventCycle - isrStart >= isrTimeoutCycles); + done = !waveformsEnabled || (now - isrStartCcy >= isrTimeoutCcys) || (nextTimerCcy - isrStartCcy >= isrTimeoutCcys); } while (!done); } // if (waveformsEnabled) - int32_t nextEventCycles; + int32_t nextTimerCcys; if (!timer1CB) { - nextEventCycles = nextEventCycle - ESP.getCycleCount(); + nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); } else { - int32_t callbackCycles = microsecondsToClockCycles(timer1CB()); - nextEventCycles = nextEventCycle - ESP.getCycleCount(); - if (nextEventCycles > callbackCycles) - nextEventCycles = callbackCycles; + int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); + nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); + if (nextTimerCcys > callbackCcys) + nextTimerCcys = callbackCcys; } - if (nextEventCycles < microsecondsToClockCycles(2)) - nextEventCycles = microsecondsToClockCycles(2); + if (nextTimerCcys < microsecondsToClockCycles(2)) + nextTimerCcys = microsecondsToClockCycles(2); if (clockCyclesPerMicrosecond() == 160) - timer1_write(nextEventCycles / 2); + timer1_write(nextTimerCcys / 2); else - timer1_write(nextEventCycles); + timer1_write(nextTimerCcys); } }; diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 89c7f9beb4..fba53da422 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -5,13 +5,13 @@ 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 or CPU cycles). TIMER1 is + high and low period (defined in microseconds or CPU clock cycles). 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 + Each waveform generator is synchronized to the ESP clock 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, @@ -19,8 +19,9 @@ 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). + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -51,11 +52,11 @@ extern "C" { // full period. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); -// Start or change a waveform of the specified high and low CPU cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many CPU cycles, relative to the next +// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next // full period. // Returns true or false on success or failure. -int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); From 0f4486b2666f4d93bf6b92fd69447a89ef43bcd8 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 11 Feb 2020 10:21:34 +0100 Subject: [PATCH 016/152] Rather iterate even if full-duty or no-duty cycle in period, than too many calculations in NMI handler. --- cores/esp8266/core_esp8266_waveform.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 22fb72f0ca..5db4db29d0 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -207,13 +207,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave->expiryCcy += wave->nextPhaseCcy; // in ExpiryState::UPDATE, expiryCcy temporarily holds relative CPU cycle count wave->hasExpiry = ExpiryState::ON; } - const bool noIdle = wave->nextTimePeriodCcys == wave->nextTimeDutyCcys; const int32_t nextTimerCcys = nextTimerCcy - now; const int32_t nextEventCcys = wave->nextPhaseCcy + - ((!noIdle && waveformsState & mask) ? wave->nextTimeDutyCcys : 0) - now; + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0) - now; const int32_t expiryCcys = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCcy - now : (nextEventCcys + 1); if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { - if (wave->nextTimeDutyCcys && (!(waveformsState & mask) || noIdle)) { + if (wave->nextTimeDutyCcys && !(waveformsState & mask)) { waveformsState |= mask; if (i == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW @@ -221,15 +220,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { SetGPIO(mask); } - if (noIdle) { - wave->nextPhaseCcy += wave->nextTimePeriodCcys; - if (nextTimerCcys > static_cast(wave->nextPhaseCcy - now)) - nextTimerCcy = wave->nextPhaseCcy; - } - else { - if (nextTimerCcys > wave->nextTimeDutyCcys) - nextTimerCcy = wave->nextPhaseCcy + wave->nextTimeDutyCcys; - } + if (nextTimerCcys > wave->nextTimeDutyCcys) + nextTimerCcy = wave->nextPhaseCcy + wave->nextTimeDutyCcys; } else if ((waveformsState & mask) || !wave->nextTimeDutyCcys) { waveformsState &= ~mask; @@ -240,7 +232,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { ClearGPIO(mask); } wave->nextPhaseCcy += wave->nextTimePeriodCcys; - if (nextTimerCcys > static_cast(wave->nextPhaseCcy - now)) + if (nextTimerCcys > wave->nextTimePeriodCcys) nextTimerCcy = wave->nextPhaseCcy; } } From 37b0685f610f48b8371c46487e714d8f65952e57 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 11 Feb 2020 10:48:28 +0100 Subject: [PATCH 017/152] Must fire timer early to reach waveform deadlines, otherwise under some load aggressive jitter occurs. --- cores/esp8266/core_esp8266_waveform.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5db4db29d0..4e0c09e851 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -44,8 +44,8 @@ extern "C" { -// Maximum delay between IRQs -#define MAXIRQUS (10000) +// Maximum delay between IRQs, 1Hz +#define MAXIRQUS (1000000) // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -172,7 +172,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(14); + constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(8); const uint32_t isrStartCcy = ESP.getCycleCount(); uint32_t nextTimerCcy = isrStartCcy + microsecondsToClockCycles(MAXIRQUS); @@ -261,8 +261,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcys = callbackCcys; } - if (nextTimerCcys < microsecondsToClockCycles(2)) + // Firing timer too soon, the NMI occurs before ISR has returned. + // But, must fire timer early to reach deadlines for waveforms. + if (nextTimerCcys < microsecondsToClockCycles(4)) nextTimerCcys = microsecondsToClockCycles(2); + else + nextTimerCcys -= microsecondsToClockCycles(2); if (clockCyclesPerMicrosecond() == 160) timer1_write(nextTimerCcys / 2); From 0a2415d1d45eff601a9e253f52d8d79e74dc7cac Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 11 Feb 2020 19:00:01 +0100 Subject: [PATCH 018/152] Schedule expiry explicitly, too. Needed to keep track of next timer ccy in each iteration, not just when changing level. --- cores/esp8266/core_esp8266_waveform.cpp | 69 ++++++++++++++----------- cores/esp8266/core_esp8266_waveform.h | 1 + 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4e0c09e851..b66c1e1b5c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -3,6 +3,7 @@ supporting outputs on all pins in parallel. Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. The core idea is to have a programmable waveform generator with a unique high and low period (defined in microseconds or CPU clock cycles). TIMER1 is @@ -45,7 +46,7 @@ extern "C" { // Maximum delay between IRQs, 1Hz -#define MAXIRQUS (1000000) +constexpr uint32_t MAXIRQUS = 1000000; // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -56,8 +57,8 @@ enum class ExpiryState : uint32_t {OFF = 0, ON = 1, UPDATE = 2}; // for UPDATE, // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextPhaseCcy; // ESP clock cycle when a period begins - volatile int32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform - volatile int32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase + volatile uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform + volatile uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase volatile uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCcy is zero-based, NMI will recompute } Waveform; @@ -172,7 +173,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(8); + constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(14); const uint32_t isrStartCcy = ESP.getCycleCount(); uint32_t nextTimerCcy = isrStartCcy + microsecondsToClockCycles(MAXIRQUS); @@ -208,45 +209,55 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave->hasExpiry = ExpiryState::ON; } const int32_t nextTimerCcys = nextTimerCcy - now; - const int32_t nextEventCcys = wave->nextPhaseCcy + - ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0) - now; + uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); + int32_t nextEventCcys = nextEventCcy - now; const int32_t expiryCcys = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCcy - now : (nextEventCcys + 1); if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { - if (wave->nextTimeDutyCcys && !(waveformsState & mask)) { - waveformsState |= mask; - if (i == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW - } - else { - SetGPIO(mask); - } - if (nextTimerCcys > wave->nextTimeDutyCcys) - nextTimerCcy = wave->nextPhaseCcy + wave->nextTimeDutyCcys; + waveformsState ^= mask; + if (waveformsState & mask) { + if (wave->nextTimeDutyCcys) { + if (i == 16) { + GP16O |= 1; // GPIO16 write slow as it's RMW + } + else { + SetGPIO(mask); + } + } + nextEventCcy = (wave->nextPhaseCcy + wave->nextTimeDutyCcys); } - else if ((waveformsState & mask) || !wave->nextTimeDutyCcys) { - waveformsState &= ~mask; - if (i == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW - } - else { - ClearGPIO(mask); + else { + if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { + if (i == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(mask); + } } wave->nextPhaseCcy += wave->nextTimePeriodCcys; - if (nextTimerCcys > wave->nextTimePeriodCcys) - nextTimerCcy = wave->nextPhaseCcy; + nextEventCcy = wave->nextPhaseCcy; } } + nextEventCcys = nextEventCcy - now; + if (nextTimerCcys > nextEventCcys) + nextTimerCcy = wave->nextPhaseCcy; // Disable any waveforms that are done - if ((ExpiryState::ON == wave->hasExpiry) && expiryCcys <= 0) { - // Done, remove! - waveformsEnabled &= ~mask; + if ((ExpiryState::ON == wave->hasExpiry)) { + if (expiryCcys <= 0) { + // Done, remove! + waveformsEnabled &= ~mask; + } + else if (static_cast(nextTimerCcy - now) > expiryCcys) + { + nextTimerCcy = wave->expiryCcy; + } } now = ESP.getCycleCount(); } // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - done = !waveformsEnabled || (now - isrStartCcy >= isrTimeoutCcys) || (nextTimerCcy - isrStartCcy >= isrTimeoutCcys); + done = !waveformsEnabled || (now - isrStartCcy > isrTimeoutCcys) || (nextTimerCcy - isrStartCcy > isrTimeoutCcys); } while (!done); } // if (waveformsEnabled) diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index fba53da422..b30a68e33e 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -3,6 +3,7 @@ supporting outputs on all pins in parallel. Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. The core idea is to have a programmable waveform generator with a unique high and low period (defined in microseconds or CPU clock cycles). TIMER1 is From b595e491a8781dc5d512aa5cfbfad6dbcd1d6f07 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 16 Feb 2020 00:18:37 +0100 Subject: [PATCH 019/152] Quick change lets analogWrite keep phase for any duty cycle (including 0% and 100%). --- cores/esp8266/core_esp8266_wiring_pwm.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index b4991310d3..c71d833847 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -56,7 +56,7 @@ extern void __analogWrite(uint8_t pin, int val) { if (pin > 16) { return; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; + uint32_t analogPeriod = 1000000L / analogFreq; if (val < 0) { val = 0; } else if (val > analogScale) { @@ -71,14 +71,8 @@ extern void __analogWrite(uint8_t pin, int val) { uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (low == 0) { - digitalWrite(pin, HIGH); - } else if (high == 0) { - digitalWrite(pin, LOW); - } else { - if (startWaveformClockCycles(pin, high, low, 0)) { - analogMap |= (1 << pin); - } + if (startWaveform(pin, high, low, 0)) { + analogMap |= (1 << pin); } } From e0d9a4897be87ab79e884d17b936fddf4773522c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 20 Feb 2020 14:17:49 +0100 Subject: [PATCH 020/152] Set duration to multiple of period, so tone stops on LOW pin output. --- cores/esp8266/Tone.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 064fdad5df..0d0faaed97 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -23,26 +23,24 @@ #include "Arduino.h" #include "core_esp8266_waveform.h" -#include "user_interface.h" // Which pins have a tone running on them? static uint32_t _toneMap = 0; -static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { +static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) { if (_pin > 16) { return; } pinMode(_pin, OUTPUT); - high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, - low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) + high = std::max(high, (uint32_t)25); // new 20KHz maximum tone frequency, + low = std::max(low, (uint32_t)25); // (25us high + 25us low period = 20KHz) - duration = microsecondsToClockCycles(duration * 1000UL); - duration += high + low - 1; + duration *= 1000UL; duration -= duration % (high + low); - if (startWaveformClockCycles(_pin, high, low, duration)) { + if (startWaveform(_pin, high, low, duration)) { _toneMap |= 1 << _pin; } } @@ -52,7 +50,7 @@ void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) { if (frequency == 0) { noTone(_pin); } else { - uint32_t period = microsecondsToClockCycles(1000000UL) / frequency; + uint32_t period = 1000000L / frequency; uint32_t high = period / 2; uint32_t low = period - high; _startTone(_pin, high, low, duration); @@ -66,7 +64,7 @@ void tone(uint8_t _pin, double frequency, unsigned long duration) { if (frequency < 1.0) { // FP means no exact comparisons noTone(_pin); } else { - double period = (double)microsecondsToClockCycles(1000000UL) / frequency; + double period = 1000000.0 / frequency; uint32_t high = (uint32_t)((period / 2.0) + 0.5); uint32_t low = (uint32_t)(period + 0.5) - high; _startTone(_pin, high, low, duration); From bb04a02c7f8c44d93bb9a41f714c4f3ce1d93ada Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 27 Feb 2020 00:31:40 +0100 Subject: [PATCH 021/152] Improve phase timing --- cores/esp8266/core_esp8266_waveform.cpp | 52 +++++++++++++++---------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b66c1e1b5c..f634e085c6 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -52,7 +52,11 @@ constexpr uint32_t MAXIRQUS = 1000000; #define SetGPIO(a) do { GPOS = a; } while (0) #define ClearGPIO(a) do { GPOC = a; } while (0) -enum class ExpiryState : uint32_t {OFF = 0, ON = 1, UPDATE = 2}; // for UPDATE, the NMI computes the exact expiry ccy and transitions to ON +// for INFINITE, the NMI proceeds on the waveform without expiry deadline. +// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. +// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. +// for INIT, the NMI initializes nextPhaseCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. +enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; // Waveform generator can create tones, PWM, and servos typedef struct { @@ -60,7 +64,7 @@ typedef struct { volatile uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform volatile uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase volatile uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count - volatile ExpiryState hasExpiry; // OFF: expiryCycle (temporarily) ignored. UPDATE: expiryCcy is zero-based, NMI will recompute + volatile WaveformMode mode; } Waveform; static Waveform waveforms[17]; // State of all possible pins @@ -122,11 +126,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo uint32_t mask = 1UL << pin; if (!(waveformsEnabled & mask)) { - // Actually set the pin high or low in the IRQ service to guarantee times - uint32_t now = ESP.getCycleCount(); - wave->nextPhaseCcy = now + microsecondsToClockCycles(2); - wave->expiryCcy = wave->nextPhaseCcy + runTimeCcys; - wave->hasExpiry = static_cast(runTimeCcys) ? ExpiryState::ON : ExpiryState::OFF; + // wave->nextPhaseCcy is initialized by the ISR + wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count + wave->mode = WaveformMode::INIT; waveformToEnable |= mask; if (!timerRunning) { initTimer(); @@ -137,10 +139,10 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo } } else { - wave->hasExpiry = ExpiryState::OFF; // turn off to make update atomic from NMI - wave->expiryCcy = runTimeCcys; // in ExpiryState::UPDATE, temporarily hold relative cycle count + wave->mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + wave->expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count if (runTimeCcys) - wave->hasExpiry = ExpiryState::UPDATE; + wave->mode = WaveformMode::UPDATEEXPIRY; } return true; } @@ -203,26 +205,35 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform *wave = &waveforms[i]; - // Check for toggles etc. - if (ExpiryState::UPDATE == wave->hasExpiry) { - wave->expiryCcy += wave->nextPhaseCcy; // in ExpiryState::UPDATE, expiryCcy temporarily holds relative CPU cycle count - wave->hasExpiry = ExpiryState::ON; + switch (wave->mode) { + case WaveformMode::INIT: + wave->nextPhaseCcy = now; + if (!wave->expiryCcy) { + wave->mode = WaveformMode::INFINITE; + break; + } + case WaveformMode::UPDATEEXPIRY: + wave->expiryCcy += wave->nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave->mode = WaveformMode::EXPIRES; + break; + default: + break; } const int32_t nextTimerCcys = nextTimerCcy - now; uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); int32_t nextEventCcys = nextEventCcy - now; - const int32_t expiryCcys = (ExpiryState:: ON == wave->hasExpiry) ? wave->expiryCcy - now : (nextEventCcys + 1); + const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : (nextEventCcys + 1); if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { waveformsState ^= mask; if (waveformsState & mask) { - if (wave->nextTimeDutyCcys) { + if (wave->nextTimeDutyCcys) { if (i == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } else { SetGPIO(mask); } - } + } nextEventCcy = (wave->nextPhaseCcy + wave->nextTimeDutyCcys); } else { @@ -243,7 +254,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcy = wave->nextPhaseCcy; // Disable any waveforms that are done - if ((ExpiryState::ON == wave->hasExpiry)) { + if ((WaveformMode::EXPIRES == wave->mode)) { if (expiryCcys <= 0) { // Done, remove! waveformsEnabled &= ~mask; @@ -256,8 +267,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } - // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - done = !waveformsEnabled || (now - isrStartCcy > isrTimeoutCcys) || (nextTimerCcy - isrStartCcy > isrTimeoutCcys); + // Exit the loop if the next event, if any, is known to be after the fixed ISR runtime limit, + // and guard against insanely short waveform periods that the ISR cannot maintain + done = !waveformsEnabled || (nextTimerCcy - isrStartCcy > isrTimeoutCcys) || (now - isrStartCcy > isrTimeoutCcys); } while (!done); } // if (waveformsEnabled) From 8c646f36cf0ebe7cdc795876304333abb5252f90 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 1 Mar 2020 09:56:25 +0100 Subject: [PATCH 022/152] Eror causing next Timer IRQ to fail busy-to-off cycle transitions. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index f634e085c6..984bf76890 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -251,7 +251,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } nextEventCcys = nextEventCcy - now; if (nextTimerCcys > nextEventCcys) - nextTimerCcy = wave->nextPhaseCcy; + nextTimerCcy = nextEventCcy; // Disable any waveforms that are done if ((WaveformMode::EXPIRES == wave->mode)) { From 8ab9975dc0d8c4dc7485f79979a9733dbc52b3a3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Mar 2020 09:35:06 +0100 Subject: [PATCH 023/152] Regression fix, don't reset timer if pending shortly. --- cores/esp8266/core_esp8266_waveform.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 984bf76890..9c06ba1b41 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -132,8 +132,12 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo waveformToEnable |= mask; if (!timerRunning) { initTimer(); + timer1_write(microsecondsToClockCycles(2)); + } + else if (T1L > microsecondsToClockCycles(2)) { + // Must not interfere if Timer is due shortly + timer1_write(microsecondsToClockCycles(2)); } - timer1_write(microsecondsToClockCycles(2)); while (waveformToEnable) { delay(0); // Wait for waveform to update } @@ -157,7 +161,10 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If they send >=32, then the shift will result in 0 and it will also return false if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; + // Must not interfere if Timer is due shortly + if (T1L > microsecondsToClockCycles(2)) { timer1_write(microsecondsToClockCycles(2)); + } while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } @@ -286,7 +293,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Firing timer too soon, the NMI occurs before ISR has returned. // But, must fire timer early to reach deadlines for waveforms. - if (nextTimerCcys < microsecondsToClockCycles(4)) + if (nextTimerCcys <= microsecondsToClockCycles(4)) nextTimerCcys = microsecondsToClockCycles(2); else nextTimerCcys -= microsecondsToClockCycles(2); From acd0dcda614e1f1f5384f38a4ea39a31032d5037 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Mar 2020 11:01:46 +0100 Subject: [PATCH 024/152] Rather reschedule ISR instead of busy looping during permitted maximum time. --- cores/esp8266/core_esp8266_waveform.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 9c06ba1b41..1b624fc448 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -271,12 +271,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcy = wave->expiryCcy; } } - now = ESP.getCycleCount(); } - // Exit the loop if the next event, if any, is known to be after the fixed ISR runtime limit, + // Exit the loop if the next event, if any, is sufficiently distant, // and guard against insanely short waveform periods that the ISR cannot maintain - done = !waveformsEnabled || (nextTimerCcy - isrStartCcy > isrTimeoutCcys) || (now - isrStartCcy > isrTimeoutCcys); + done = !waveformsEnabled || (nextTimerCcy - now > microsecondsToClockCycles(4)) || (now - isrStartCcy > isrTimeoutCcys); } while (!done); } // if (waveformsEnabled) From 87b71c2a97df5d033b43643b5eecb2c312a51866 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Mar 2020 13:01:48 +0100 Subject: [PATCH 025/152] Lead time improved for ISR --- cores/esp8266/core_esp8266_waveform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 1b624fc448..27e123bfb8 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -292,10 +292,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Firing timer too soon, the NMI occurs before ISR has returned. // But, must fire timer early to reach deadlines for waveforms. - if (nextTimerCcys <= microsecondsToClockCycles(4)) + if (nextTimerCcys <= microsecondsToClockCycles(6)) nextTimerCcys = microsecondsToClockCycles(2); else - nextTimerCcys -= microsecondsToClockCycles(2); + nextTimerCcys -= microsecondsToClockCycles(4); if (clockCyclesPerMicrosecond() == 160) timer1_write(nextTimerCcys / 2); From 2bae205d488220c02cbcc3a2787c295fce1767a0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Mar 2020 15:25:00 +0100 Subject: [PATCH 026/152] Reduce number of cycle calculations. --- cores/esp8266/core_esp8266_waveform.cpp | 33 +++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 27e123bfb8..285a2a8e4a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -182,9 +182,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - constexpr uint32_t isrTimeoutCcys = microsecondsToClockCycles(14); + constexpr int32_t isrTimeoutCcys = microsecondsToClockCycles(14); const uint32_t isrStartCcy = ESP.getCycleCount(); - uint32_t nextTimerCcy = isrStartCcy + microsecondsToClockCycles(MAXIRQUS); if (waveformToEnable || waveformToDisable) { // Handle enable/disable requests from main app. @@ -198,11 +197,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { endPin = 32 - __builtin_clz(waveformsEnabled); } - bool done = false; + uint32_t now = ESP.getCycleCount(); + int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); + uint32_t nextTimerCcy = now + nextTimerCcys; if (waveformsEnabled) { - do { - uint32_t now = ESP.getCycleCount(); - nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); + bool done = false; + do { + nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { uint32_t mask = 1UL<nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); int32_t nextEventCcys = nextEventCcy - now; const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : (nextEventCcys + 1); @@ -258,7 +258,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } nextEventCcys = nextEventCcy - now; if (nextTimerCcys > nextEventCcys) - nextTimerCcy = nextEventCcy; + nextTimerCcys = nextEventCcys; // Disable any waveforms that are done if ((WaveformMode::EXPIRES == wave->mode)) { @@ -266,25 +266,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Done, remove! waveformsEnabled &= ~mask; } - else if (static_cast(nextTimerCcy - now) > expiryCcys) + else if (nextTimerCcys > expiryCcys) { - nextTimerCcy = wave->expiryCcy; + nextTimerCcys = expiryCcys; } } } + nextTimerCcy = now + nextTimerCcys; + now = ESP.getCycleCount(); + nextTimerCcys = nextTimerCcy - now; // Exit the loop if the next event, if any, is sufficiently distant, // and guard against insanely short waveform periods that the ISR cannot maintain - done = !waveformsEnabled || (nextTimerCcy - now > microsecondsToClockCycles(4)) || (now - isrStartCcy > isrTimeoutCcys); + const int32_t isrRemainingCcys = isrTimeoutCcys - (now - isrStartCcy); + done = !waveformsEnabled || (nextTimerCcys >= isrRemainingCcys) || (isrRemainingCcys <= 0); } while (!done); } // if (waveformsEnabled) - int32_t nextTimerCcys; - if (!timer1CB) { - nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); - } - else { + if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); + // Account for unknown duration of timer1CB(). nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; From deaf2ec6ec27ba5469b7c69d374b6594bb593a44 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Mar 2020 16:01:54 +0100 Subject: [PATCH 027/152] Reactive the gcc optimize pragmas. --- cores/esp8266/core_esp8266_waveform.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 285a2a8e4a..65548e28f3 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -81,6 +81,10 @@ static uint32_t (*timer1CB)() = NULL; static ICACHE_RAM_ATTR void timer1Interrupt(); static bool timerRunning = false; + +// Non-speed critical bits +#pragma GCC optimize ("Os") + static void initTimer() { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); @@ -175,6 +179,9 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { return true; } +// Speed critical bits +#pragma GCC optimize ("O2") + static ICACHE_RAM_ATTR void timer1Interrupt() { // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and From d8e330ea152d46b11993d038ec46ef78a76ccd6c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 6 Mar 2020 13:56:56 +0100 Subject: [PATCH 028/152] Simplify calculation. --- cores/esp8266/core_esp8266_waveform.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 65548e28f3..f8824bdeb7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -234,7 +234,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } - uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); + const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); int32_t nextEventCcys = nextEventCcy - now; const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : (nextEventCcys + 1); if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { @@ -248,7 +248,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(mask); } } - nextEventCcy = (wave->nextPhaseCcy + wave->nextTimeDutyCcys); + nextEventCcys += wave->nextTimeDutyCcys; } else { if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { @@ -260,10 +260,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } wave->nextPhaseCcy += wave->nextTimePeriodCcys; - nextEventCcy = wave->nextPhaseCcy; + nextEventCcys = wave->nextPhaseCcy - now; } } - nextEventCcys = nextEventCcy - now; if (nextTimerCcys > nextEventCcys) nextTimerCcys = nextEventCcys; From da933fd0b66a8bd859d92a8beaeb05d2b6f321e7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 6 Mar 2020 18:31:11 +0100 Subject: [PATCH 029/152] handles overshoot where an updated period is shorter than the previous duty cycle --- cores/esp8266/core_esp8266_waveform.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index f8824bdeb7..e640d12bb0 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -259,8 +259,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { ClearGPIO(mask); } } - wave->nextPhaseCcy += wave->nextTimePeriodCcys; - nextEventCcys = wave->nextPhaseCcy - now; + // handles overshoot where an updated period is shorter than the previous duty cycle + // maintains phase if the previous period is an integer multiple of the new period + do { + wave->nextPhaseCcy += wave->nextTimePeriodCcys; + nextEventCcys = wave->nextPhaseCcy - now; + } while (nextEventCcys <= 0); } } if (nextTimerCcys > nextEventCcys) From 6c7346a158d774c66ff8dfb063543ffb5638a56e Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 7 Mar 2020 22:28:22 +0100 Subject: [PATCH 030/152] Misleading code, there must ever be only one bit set at a time, start and stop block until the ISR has handled and reset the token. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e640d12bb0..2ffbcadc9e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -133,7 +133,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo // wave->nextPhaseCcy is initialized by the ISR wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave->mode = WaveformMode::INIT; - waveformToEnable |= mask; + waveformToEnable = mask; if (!timerRunning) { initTimer(); timer1_write(microsecondsToClockCycles(2)); From f6fbf807306ff64a275a50c30b415b18021ee99c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 8 Mar 2020 10:00:52 +0100 Subject: [PATCH 031/152] Prevent missing a duty cycle unless it is overshot already. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 2ffbcadc9e..5d94291f88 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -264,7 +264,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { wave->nextPhaseCcy += wave->nextTimePeriodCcys; nextEventCcys = wave->nextPhaseCcy - now; - } while (nextEventCcys <= 0); + } while (nextEventCcys <= -static_cast(wave->nextTimeDutyCcys)); } } if (nextTimerCcys > nextEventCcys) From 745c1b299d5c05fa41eb8f938071c6698a017d46 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 9 Mar 2020 11:50:02 +0100 Subject: [PATCH 032/152] Continuously remove distant pending waveform edges from the loop, continuously update now. --- cores/esp8266/core_esp8266_waveform.cpp | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5d94291f88..b8474ac314 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -189,7 +189,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int startPin = 0; static int endPin = 0; - constexpr int32_t isrTimeoutCcys = microsecondsToClockCycles(14); const uint32_t isrStartCcy = ESP.getCycleCount(); if (waveformToEnable || waveformToDisable) { @@ -204,18 +203,22 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { endPin = 32 - __builtin_clz(waveformsEnabled); } - uint32_t now = ESP.getCycleCount(); + // Exit the loop if the next event, if any, is sufficiently distant, + // and guard against insanely short waveform periods that the ISR cannot maintain + int32_t isrRemainingCcys = microsecondsToClockCycles(14); + const uint32_t isrTimeoutCcy = isrStartCcy + isrRemainingCcys; int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); + uint32_t now = isrStartCcy; uint32_t nextTimerCcy = now + nextTimerCcys; - if (waveformsEnabled) { - bool done = false; - do { + uint32_t pendingWaveforms = waveformsEnabled; + if (pendingWaveforms) { + do { nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); for (int i = startPin; i <= endPin; i++) { uint32_t mask = 1UL< expiryCcys) { nextTimerCcys = expiryCcys; } } + + now = ESP.getCycleCount(); + isrRemainingCcys = isrTimeoutCcy - now; + if (nextTimerCcys >= isrRemainingCcys) + pendingWaveforms &= ~mask; } - nextTimerCcy = now + nextTimerCcys; - now = ESP.getCycleCount(); - nextTimerCcys = nextTimerCcy - now; - // Exit the loop if the next event, if any, is sufficiently distant, - // and guard against insanely short waveform periods that the ISR cannot maintain - const int32_t isrRemainingCcys = isrTimeoutCcys - (now - isrStartCcy); - done = !waveformsEnabled || (nextTimerCcys >= isrRemainingCcys) || (isrRemainingCcys <= 0); - } while (!done); - } // if (waveformsEnabled) + } while (pendingWaveforms && isrRemainingCcys > 0); + } // if (pendingWaveforms) if (timer1CB) { + nextTimerCcy = now + nextTimerCcys; int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); From b8770472156d58f096567be819475575b6bb53d2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 9 Mar 2020 12:19:38 +0100 Subject: [PATCH 033/152] Replace volatile for one-way exchange into ISR with memory fence. --- cores/esp8266/core_esp8266_waveform.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b8474ac314..944087de05 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -39,9 +39,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "core_esp8266_waveform.h" #include #include "ets_sys.h" -#include "core_esp8266_waveform.h" +#include extern "C" { @@ -61,10 +62,10 @@ enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextPhaseCcy; // ESP clock cycle when a period begins - volatile uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform - volatile uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase - volatile uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count - volatile WaveformMode mode; + uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform + uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count + WaveformMode mode; } Waveform; static Waveform waveforms[17]; // State of all possible pins @@ -134,6 +135,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave->mode = WaveformMode::INIT; waveformToEnable = mask; + std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { initTimer(); timer1_write(microsecondsToClockCycles(2)); @@ -151,6 +153,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo wave->expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count if (runTimeCcys) wave->mode = WaveformMode::UPDATEEXPIRY; + std::atomic_thread_fence(std::memory_order_release); } return true; } From 42983a935ce74a1a0c77cc433e33e2b024a44bbf Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 9 Mar 2020 17:34:35 +0100 Subject: [PATCH 034/152] Remove redundant stack object. --- cores/esp8266/core_esp8266_waveform.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 944087de05..41202dffbb 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -212,7 +212,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrTimeoutCcy = isrStartCcy + isrRemainingCcys; int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); uint32_t now = isrStartCcy; - uint32_t nextTimerCcy = now + nextTimerCcys; uint32_t pendingWaveforms = waveformsEnabled; if (pendingWaveforms) { do { @@ -299,10 +298,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // if (pendingWaveforms) if (timer1CB) { - nextTimerCcy = now + nextTimerCcys; int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); + nextTimerCcys -= ESP.getCycleCount() - now; if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; } From 6aea9d66641449c767cf9c11e5ad55d2d4ccb938 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 9 Mar 2020 18:36:19 +0100 Subject: [PATCH 035/152] Revert pending waveform removal from loop - corrupts continuous next event computation. --- cores/esp8266/core_esp8266_waveform.cpp | 65 ++++++++++++------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 41202dffbb..6699793d58 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -61,10 +61,10 @@ enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextPhaseCcy; // ESP clock cycle when a period begins + uint32_t nextPhaseCcy; // ESP clock cycle when a period begins uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase - uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count WaveformMode mode; } Waveform; @@ -129,12 +129,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo wave->nextTimeDutyCcys = timeHighCcys; wave->nextTimePeriodCcys = periodCcys; - uint32_t mask = 1UL << pin; - if (!(waveformsEnabled & mask)) { + if (!(waveformsEnabled & (1UL << pin))) { // wave->nextPhaseCcy is initialized by the ISR wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave->mode = WaveformMode::INIT; - waveformToEnable = mask; + waveformToEnable = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { initTimer(); @@ -212,18 +211,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrTimeoutCcy = isrStartCcy + isrRemainingCcys; int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); uint32_t now = isrStartCcy; - uint32_t pendingWaveforms = waveformsEnabled; - if (pendingWaveforms) { + if (waveformsEnabled) { + bool busy; do { nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); - for (int i = startPin; i <= endPin; i++) { - uint32_t mask = 1UL<mode) { case WaveformMode::INIT: @@ -239,29 +236,29 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } - const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & mask) ? wave->nextTimeDutyCcys : 0); + const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); int32_t nextEventCcys = nextEventCcy - now; const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : (nextEventCcys + 1); if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { - waveformsState ^= mask; - if (waveformsState & mask) { + waveformsState ^= 1UL << pin; + if (waveformsState & (1UL << pin)) { if (wave->nextTimeDutyCcys) { - if (i == 16) { + if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } else { - SetGPIO(mask); + SetGPIO(1UL << pin); } } nextEventCcys += wave->nextTimeDutyCcys; } else { if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { - if (i == 16) { + if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } else { - ClearGPIO(mask); + ClearGPIO(1UL << pin); } } // handles overshoot where an updated period is shorter than the previous duty cycle @@ -269,33 +266,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { wave->nextPhaseCcy += wave->nextTimePeriodCcys; nextEventCcys = wave->nextPhaseCcy - now; - } while (nextEventCcys <= -static_cast(wave->nextTimeDutyCcys)); + } while (nextEventCcys < -static_cast(wave->nextTimeDutyCcys)); } } - if (nextTimerCcys > nextEventCcys) - nextTimerCcys = nextEventCcys; // Disable any waveforms that are done if ((WaveformMode::EXPIRES == wave->mode)) { if (expiryCcys <= 0) { // Done, remove! - waveformsEnabled &= ~mask; - pendingWaveforms &= ~mask; + waveformsEnabled ^= 1UL << pin; + // impossibly large value to prevent setting nextTimerCcys + nextEventCcys = microsecondsToClockCycles(MAXIRQUS); } - else if (nextTimerCcys > expiryCcys) + else if (nextEventCcys > expiryCcys) { - nextTimerCcys = expiryCcys; + nextEventCcys = expiryCcys; } } - - now = ESP.getCycleCount(); - isrRemainingCcys = isrTimeoutCcy - now; - if (nextTimerCcys >= isrRemainingCcys) - pendingWaveforms &= ~mask; - } - } while (pendingWaveforms && isrRemainingCcys > 0); - } // if (pendingWaveforms) + if (nextTimerCcys > nextEventCcys) { + nextTimerCcys = nextEventCcys; + } + } + busy = waveformsEnabled && (nextTimerCcys <= isrRemainingCcys); + now = ESP.getCycleCount(); + isrRemainingCcys = isrTimeoutCcy - now; + } while (busy && isrRemainingCcys > 0); + } // if (waveformsEnabled) if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); From 9245e198700544e11476f0cfc749178fcdeeff47 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 22 Mar 2020 12:30:14 +0100 Subject: [PATCH 036/152] Reduce if/do ... while to while --- cores/esp8266/core_esp8266_waveform.cpp | 166 ++++++++++++------------ 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 6699793d58..1f5f002ec7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -20,7 +20,7 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleTime() + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() clock cycle time, or an interval measured in clock cycles, but not TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). @@ -48,6 +48,10 @@ extern "C" { // Maximum delay between IRQs, 1Hz constexpr uint32_t MAXIRQUS = 1000000; +// The SDK and hardware take some time to actually get to our NMI code, so +// decrement the next IRQ's timer value by a bit so we can actually catch the +// real CPU cycle counter we want for the waveforms. +constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(2) : microsecondsToClockCycles(3); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -106,7 +110,7 @@ void setTimer1Callback(uint32_t (*fn)()) { timer1CB = fn; if (!timerRunning && fn) { initTimer(); - timer1_write(microsecondsToClockCycles(2)); // Cause an interrupt post-haste + timer1_write(DELTAIRQ); // Cause an interrupt post-haste } else if (timerRunning && !fn && !waveformsEnabled) { deinitTimer(); } @@ -137,11 +141,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { initTimer(); - timer1_write(microsecondsToClockCycles(2)); + timer1_write(DELTAIRQ); } - else if (T1L > microsecondsToClockCycles(2)) { + else if (T1L > DELTAIRQ) { // Must not interfere if Timer is due shortly - timer1_write(microsecondsToClockCycles(2)); + timer1_write(DELTAIRQ); } while (waveformToEnable) { delay(0); // Wait for waveform to update @@ -168,8 +172,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; // Must not interfere if Timer is due shortly - if (T1L > microsecondsToClockCycles(2)) { - timer1_write(microsecondsToClockCycles(2)); + if (T1L > DELTAIRQ) { + timer1_write(DELTAIRQ); } while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ @@ -211,88 +215,87 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrTimeoutCcy = isrStartCcy + isrRemainingCcys; int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); uint32_t now = isrStartCcy; - if (waveformsEnabled) { - bool busy; - do { - nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); - for (int pin = startPin; pin <= endPin; ++pin) { - // If it's not on, ignore! - if (!(waveformsEnabled & (1UL << pin))) - continue; - - Waveform *wave = &waveforms[pin]; - - switch (wave->mode) { - case WaveformMode::INIT: - wave->nextPhaseCcy = now; - if (!wave->expiryCcy) { - wave->mode = WaveformMode::INFINITE; - break; - } - case WaveformMode::UPDATEEXPIRY: - wave->expiryCcy += wave->nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave->mode = WaveformMode::EXPIRES; - break; - default: + bool busy = waveformsEnabled; + while (busy) { + nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); + for (int pin = startPin; pin <= endPin; ++pin) { + // If it's not on, ignore! + if (!(waveformsEnabled & (1UL << pin))) + continue; + + Waveform *wave = &waveforms[pin]; + + switch (wave->mode) { + case WaveformMode::INIT: + wave->nextPhaseCcy = now; + if (!wave->expiryCcy) { + wave->mode = WaveformMode::INFINITE; break; } - const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); - int32_t nextEventCcys = nextEventCcy - now; - const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : (nextEventCcys + 1); - if (nextEventCcys <= 0 && expiryCcys > nextEventCcys) { - waveformsState ^= 1UL << pin; - if (waveformsState & (1UL << pin)) { - if (wave->nextTimeDutyCcys) { - if (pin == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW - } - else { - SetGPIO(1UL << pin); - } + case WaveformMode::UPDATEEXPIRY: + wave->expiryCcy += wave->nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave->mode = WaveformMode::EXPIRES; + break; + default: + break; + } + const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); + int32_t nextEventCcys = nextEventCcy - now; + const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : 0; + if (nextEventCcys <= 0 && (WaveformMode::EXPIRES != wave->mode || expiryCcys > nextEventCcys)) { + waveformsState ^= 1UL << pin; + if (waveformsState & (1UL << pin)) { + if (wave->nextTimeDutyCcys) { + if (pin == 16) { + GP16O |= 1; // GPIO16 write slow as it's RMW } - nextEventCcys += wave->nextTimeDutyCcys; - } - else { - if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { - if (pin == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW - } - else { - ClearGPIO(1UL << pin); - } + else { + SetGPIO(1UL << pin); } - // handles overshoot where an updated period is shorter than the previous duty cycle - // maintains phase if the previous period is an integer multiple of the new period - do { - wave->nextPhaseCcy += wave->nextTimePeriodCcys; - nextEventCcys = wave->nextPhaseCcy - now; - } while (nextEventCcys < -static_cast(wave->nextTimeDutyCcys)); } + nextEventCcys += wave->nextTimeDutyCcys; } - - // Disable any waveforms that are done - if ((WaveformMode::EXPIRES == wave->mode)) { - if (expiryCcys <= 0) { - // Done, remove! - waveformsEnabled ^= 1UL << pin; - // impossibly large value to prevent setting nextTimerCcys - nextEventCcys = microsecondsToClockCycles(MAXIRQUS); - } - else if (nextEventCcys > expiryCcys) - { - nextEventCcys = expiryCcys; + else { + if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { + if (pin == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(1UL << pin); + } } + // handles overshoot where an updated period is shorter than the previous duty cycle + // maintains phase if the previous period is an integer multiple of the new period + do { + wave->nextPhaseCcy += wave->nextTimePeriodCcys; + nextEventCcys = wave->nextPhaseCcy - now; + } while (nextEventCcys < -static_cast(wave->nextTimeDutyCcys)); } + } - if (nextTimerCcys > nextEventCcys) { - nextTimerCcys = nextEventCcys; + // Disable any waveforms that are done + if ((WaveformMode::EXPIRES == wave->mode)) { + if (expiryCcys <= 0) { + // Done, remove! + waveformsEnabled ^= 1UL << pin; + // impossibly large value to prevent setting nextTimerCcys + nextEventCcys = microsecondsToClockCycles(MAXIRQUS); + } + else if (nextEventCcys > expiryCcys) + { + nextEventCcys = expiryCcys; } } - busy = waveformsEnabled && (nextTimerCcys <= isrRemainingCcys); - now = ESP.getCycleCount(); - isrRemainingCcys = isrTimeoutCcy - now; - } while (busy && isrRemainingCcys > 0); - } // if (waveformsEnabled) + + if (nextTimerCcys > nextEventCcys) { + nextTimerCcys = nextEventCcys; + } + } + busy = waveformsEnabled && (nextTimerCcys <= isrRemainingCcys); + now = ESP.getCycleCount(); + isrRemainingCcys = isrTimeoutCcy - now; + busy &= isrRemainingCcys > 0; + } if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); @@ -303,11 +306,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - // But, must fire timer early to reach deadlines for waveforms. - if (nextTimerCcys <= microsecondsToClockCycles(6)) - nextTimerCcys = microsecondsToClockCycles(2); + if (nextTimerCcys <= DELTAIRQ * 2) + nextTimerCcys = DELTAIRQ; else - nextTimerCcys -= microsecondsToClockCycles(4); + nextTimerCcys -= DELTAIRQ; if (clockCyclesPerMicrosecond() == 160) timer1_write(nextTimerCcys / 2); From 42a065d8612019f809c125ec7fae9766a914ca49 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 23 Mar 2020 13:55:59 +0100 Subject: [PATCH 037/152] Convert relative timings to absolute. --- cores/esp8266/core_esp8266_waveform.cpp | 57 +++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 1f5f002ec7..e5143d6f28 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -209,15 +209,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { endPin = 32 - __builtin_clz(waveformsEnabled); } - // Exit the loop if the next event, if any, is sufficiently distant, - // and guard against insanely short waveform periods that the ISR cannot maintain - int32_t isrRemainingCcys = microsecondsToClockCycles(14); - const uint32_t isrTimeoutCcy = isrStartCcy + isrRemainingCcys; - int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); - uint32_t now = isrStartCcy; + // Exit the loop if the next event, if any, is sufficiently distant. + const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(14); + uint32_t now = ESP.getCycleCount(); + uint32_t nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); bool busy = waveformsEnabled; while (busy) { - nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); + nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore! if (!(waveformsEnabled & (1UL << pin))) @@ -239,10 +237,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } - const uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); - int32_t nextEventCcys = nextEventCcy - now; - const int32_t expiryCcys = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy - now : 0; - if (nextEventCcys <= 0 && (WaveformMode::EXPIRES != wave->mode || expiryCcys > nextEventCcys)) { + uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); + const uint32_t expiryCcy = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy : 0; + if (static_cast(nextEventCcy - now) <= 0 && + (WaveformMode::EXPIRES != wave->mode || static_cast(expiryCcy - nextEventCcy) > 0)) { waveformsState ^= 1UL << pin; if (waveformsState & (1UL << pin)) { if (wave->nextTimeDutyCcys) { @@ -253,7 +251,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } } - nextEventCcys += wave->nextTimeDutyCcys; + nextEventCcy += wave->nextTimeDutyCcys; } else { if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { @@ -268,39 +266,44 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // maintains phase if the previous period is an integer multiple of the new period do { wave->nextPhaseCcy += wave->nextTimePeriodCcys; - nextEventCcys = wave->nextPhaseCcy - now; - } while (nextEventCcys < -static_cast(wave->nextTimeDutyCcys)); + nextEventCcy = wave->nextPhaseCcy; + } while (static_cast(nextEventCcy - now) < -static_cast(wave->nextTimeDutyCcys)); } } // Disable any waveforms that are done if ((WaveformMode::EXPIRES == wave->mode)) { - if (expiryCcys <= 0) { + if (static_cast(expiryCcy - now) <= 0) { // Done, remove! waveformsEnabled ^= 1UL << pin; - // impossibly large value to prevent setting nextTimerCcys - nextEventCcys = microsecondsToClockCycles(MAXIRQUS); + // impossibly large value to prevent setting nextTimerCcy + nextEventCcy = now + microsecondsToClockCycles(MAXIRQUS); } - else if (nextEventCcys > expiryCcys) + else if (static_cast(nextEventCcy - expiryCcy) > 0) { - nextEventCcys = expiryCcys; + nextEventCcy = expiryCcy; } } - if (nextTimerCcys > nextEventCcys) { - nextTimerCcys = nextEventCcys; + if (static_cast(nextTimerCcy - nextEventCcy) > 0) { + nextTimerCcy = nextEventCcy; } + + now = ESP.getCycleCount(); } - busy = waveformsEnabled && (nextTimerCcys <= isrRemainingCcys); - now = ESP.getCycleCount(); - isrRemainingCcys = isrTimeoutCcy - now; - busy &= isrRemainingCcys > 0; + busy = waveformsEnabled && + static_cast(isrTimeoutCcy - nextTimerCcy) >= 0 && + static_cast(isrTimeoutCcy - now) > 0; } - if (timer1CB) { + int32_t nextTimerCcys; + if (!timer1CB) { + nextTimerCcys = nextTimerCcy - now; + } + else { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys -= ESP.getCycleCount() - now; + nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; } From 02e523332b437a97079856cd74eb815a6b98e3d9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 23 Mar 2020 14:50:50 +0100 Subject: [PATCH 038/152] Relax waveform start to possibly cluster phases into same IRQ interval. --- cores/esp8266/core_esp8266_waveform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e5143d6f28..94e18c153e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -143,9 +143,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo initTimer(); timer1_write(DELTAIRQ); } - else if (T1L > DELTAIRQ) { - // Must not interfere if Timer is due shortly - timer1_write(DELTAIRQ); + else if (T1L > microsecondsToClockCycles(14)) { + // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load + timer1_write(microsecondsToClockCycles(14)); } while (waveformToEnable) { delay(0); // Wait for waveform to update From b68d3cb268272ed8609d04d0c407dafe62f9bc6b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 23 Mar 2020 21:30:51 +0100 Subject: [PATCH 039/152] max 12us in ISR seems to work best for servo/fan/led/tone combo test. --- cores/esp8266/core_esp8266_waveform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 94e18c153e..4dcda39cd2 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -143,9 +143,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo initTimer(); timer1_write(DELTAIRQ); } - else if (T1L > microsecondsToClockCycles(14)) { + else if (T1L > microsecondsToClockCycles(12)) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load - timer1_write(microsecondsToClockCycles(14)); + timer1_write(microsecondsToClockCycles(12)); } while (waveformToEnable) { delay(0); // Wait for waveform to update @@ -210,7 +210,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Exit the loop if the next event, if any, is sufficiently distant. - const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(14); + const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(12); uint32_t now = ESP.getCycleCount(); uint32_t nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); bool busy = waveformsEnabled; From 2bc599fb4e265ec45f5c0e984d67f22235b1b8b2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 29 Mar 2020 11:55:24 +0200 Subject: [PATCH 040/152] Restructured code in ISR for expiration, this saves 36 byte IRAM, and improves PWM resolution. --- cores/esp8266/core_esp8266_waveform.cpp | 33 +++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4dcda39cd2..6b2a8c34e9 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -237,10 +237,19 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } + uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); - const uint32_t expiryCcy = (WaveformMode::EXPIRES == wave->mode) ? wave->expiryCcy : 0; - if (static_cast(nextEventCcy - now) <= 0 && - (WaveformMode::EXPIRES != wave->mode || static_cast(expiryCcy - nextEventCcy) > 0)) { + + if (WaveformMode::EXPIRES == wave->mode && static_cast(wave->expiryCcy - now) <= 0) { + // Disable any waveforms that are done + waveformsEnabled ^= 1UL << pin; + // impossibly large value to prevent setting nextTimerCcy + nextEventCcy = now + microsecondsToClockCycles(MAXIRQUS); + } + else if (WaveformMode::EXPIRES == wave->mode && static_cast(nextEventCcy - wave->expiryCcy) > 0) { + nextEventCcy = wave->expiryCcy; + } + else if (static_cast(nextEventCcy - now) <= 0) { waveformsState ^= 1UL << pin; if (waveformsState & (1UL << pin)) { if (wave->nextTimeDutyCcys) { @@ -267,21 +276,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { wave->nextPhaseCcy += wave->nextTimePeriodCcys; nextEventCcy = wave->nextPhaseCcy; - } while (static_cast(nextEventCcy - now) < -static_cast(wave->nextTimeDutyCcys)); - } - } - - // Disable any waveforms that are done - if ((WaveformMode::EXPIRES == wave->mode)) { - if (static_cast(expiryCcy - now) <= 0) { - // Done, remove! - waveformsEnabled ^= 1UL << pin; - // impossibly large value to prevent setting nextTimerCcy - nextEventCcy = now + microsecondsToClockCycles(MAXIRQUS); - } - else if (static_cast(nextEventCcy - expiryCcy) > 0) - { - nextEventCcy = expiryCcy; + } while (static_cast(nextEventCcy - now) <= -static_cast(wave->nextTimeDutyCcys)); } } @@ -292,7 +287,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } busy = waveformsEnabled && - static_cast(isrTimeoutCcy - nextTimerCcy) >= 0 && + static_cast(isrTimeoutCcy - nextTimerCcy) > 0 && static_cast(isrTimeoutCcy - now) > 0; } From aab4a49c728742375fc3857665b4590e8afbb47d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 29 Mar 2020 23:58:47 +0200 Subject: [PATCH 041/152] Simplified overshot detection and 0% / 100% duty cycle. --- cores/esp8266/core_esp8266_waveform.cpp | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 6b2a8c34e9..14d9e44398 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -250,33 +250,35 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCcy = wave->expiryCcy; } else if (static_cast(nextEventCcy - now) <= 0) { - waveformsState ^= 1UL << pin; if (waveformsState & (1UL << pin)) { - if (wave->nextTimeDutyCcys) { + if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { if (pin == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW + GP16O &= ~1; // GPIO16 write slow as it's RMW } else { - SetGPIO(1UL << pin); + ClearGPIO(1UL << pin); } + waveformsState ^= 1UL << pin; } - nextEventCcy += wave->nextTimeDutyCcys; + wave->nextPhaseCcy += wave->nextTimePeriodCcys; + nextEventCcy = wave->nextPhaseCcy; } else { - if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { + // skip duty cycle if overshot before even starting + if (static_cast(nextEventCcy - now) > -static_cast(wave->nextTimeDutyCcys)) { if (pin == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW + GP16O |= 1; // GPIO16 write slow as it's RMW } else { - ClearGPIO(1UL << pin); + SetGPIO(1UL << pin); } + waveformsState ^= 1UL << pin; + nextEventCcy += wave->nextTimeDutyCcys; } - // handles overshoot where an updated period is shorter than the previous duty cycle - // maintains phase if the previous period is an integer multiple of the new period - do { + else { wave->nextPhaseCcy += wave->nextTimePeriodCcys; nextEventCcy = wave->nextPhaseCcy; - } while (static_cast(nextEventCcy - now) <= -static_cast(wave->nextTimeDutyCcys)); + } } } From 5a91f9f77f96680306264105bb27e809b4767df1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 30 Mar 2020 14:40:39 +0200 Subject: [PATCH 042/152] Leave ISR early if rescheduling is more promising than busy-waiting until next edge. --- cores/esp8266/core_esp8266_waveform.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 14d9e44398..fa1949df99 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -212,7 +212,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(12); uint32_t now = ESP.getCycleCount(); - uint32_t nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); + int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); + uint32_t nextTimerCcy = now + nextTimerCcys; bool busy = waveformsEnabled; while (busy) { nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); @@ -264,8 +265,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCcy = wave->nextPhaseCcy; } else { - // skip duty cycle if overshot before even starting - if (static_cast(nextEventCcy - now) > -static_cast(wave->nextTimeDutyCcys)) { + if (wave->nextTimeDutyCcys) { if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } @@ -288,16 +288,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } + nextTimerCcys = nextTimerCcy - now; busy = waveformsEnabled && static_cast(isrTimeoutCcy - nextTimerCcy) > 0 && - static_cast(isrTimeoutCcy - now) > 0; + nextTimerCcys < DELTAIRQ * 2; } - int32_t nextTimerCcys; - if (!timer1CB) { - nextTimerCcys = nextTimerCcy - now; - } - else { + if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); From 6395373044416d6873b16ab0de0b450fb21ac66b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 31 Mar 2020 15:27:16 +0200 Subject: [PATCH 043/152] Stabilized timings. --- cores/esp8266/core_esp8266_waveform.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fa1949df99..58acc47b41 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -110,7 +110,7 @@ void setTimer1Callback(uint32_t (*fn)()) { timer1CB = fn; if (!timerRunning && fn) { initTimer(); - timer1_write(DELTAIRQ); // Cause an interrupt post-haste + timer1_write(microsecondsToClockCycles(2)); // Cause an interrupt post-haste } else if (timerRunning && !fn && !waveformsEnabled) { deinitTimer(); } @@ -141,11 +141,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { initTimer(); - timer1_write(DELTAIRQ); + timer1_write(microsecondsToClockCycles(2)); } - else if (T1L > microsecondsToClockCycles(12)) { + else if (T1L > microsecondsToClockCycles(2)) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load - timer1_write(microsecondsToClockCycles(12)); + timer1_write(microsecondsToClockCycles(2)); } while (waveformToEnable) { delay(0); // Wait for waveform to update @@ -288,10 +288,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } + int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; nextTimerCcys = nextTimerCcy - now; - busy = waveformsEnabled && - static_cast(isrTimeoutCcy - nextTimerCcy) > 0 && - nextTimerCcys < DELTAIRQ * 2; + busy = waveformsEnabled && timerMarginCcys > 0 && + (nextTimerCcys < microsecondsToClockCycles(2) + DELTAIRQ); } if (timer1CB) { @@ -303,8 +303,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= DELTAIRQ * 2) - nextTimerCcys = DELTAIRQ; + if (nextTimerCcys <= microsecondsToClockCycles(2) + DELTAIRQ) + nextTimerCcys = microsecondsToClockCycles(2); else nextTimerCcys -= DELTAIRQ; From 4984f4962694170d9bbb7219676d8bf5fe5df700 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 Apr 2020 12:45:28 +0200 Subject: [PATCH 044/152] Prevent WDT under load. --- cores/esp8266/core_esp8266_waveform.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 58acc47b41..1089b0ae02 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -110,7 +110,7 @@ void setTimer1Callback(uint32_t (*fn)()) { timer1CB = fn; if (!timerRunning && fn) { initTimer(); - timer1_write(microsecondsToClockCycles(2)); // Cause an interrupt post-haste + timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste } else if (timerRunning && !fn && !waveformsEnabled) { deinitTimer(); } @@ -141,11 +141,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { initTimer(); - timer1_write(microsecondsToClockCycles(2)); + timer1_write(microsecondsToClockCycles(1)); } - else if (T1L > microsecondsToClockCycles(2)) { + else if (T1L > microsecondsToClockCycles(6)) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load - timer1_write(microsecondsToClockCycles(2)); + timer1_write(microsecondsToClockCycles(6)); } while (waveformToEnable) { delay(0); // Wait for waveform to update @@ -172,8 +172,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; // Must not interfere if Timer is due shortly - if (T1L > DELTAIRQ) { - timer1_write(DELTAIRQ); + if (T1L > microsecondsToClockCycles(6)) { + timer1_write(microsecondsToClockCycles(6)); } while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ @@ -291,7 +291,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; nextTimerCcys = nextTimerCcy - now; busy = waveformsEnabled && timerMarginCcys > 0 && - (nextTimerCcys < microsecondsToClockCycles(2) + DELTAIRQ); + (nextTimerCcys < microsecondsToClockCycles(6) + DELTAIRQ); } if (timer1CB) { @@ -303,8 +303,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= microsecondsToClockCycles(2) + DELTAIRQ) - nextTimerCcys = microsecondsToClockCycles(2); + if (nextTimerCcys <= microsecondsToClockCycles(6) + DELTAIRQ) + nextTimerCcys = microsecondsToClockCycles(6); else nextTimerCcys -= DELTAIRQ; From eb28b274814d2bde78f0fed24bcdf68aa815989d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 6 Apr 2020 08:27:58 +0200 Subject: [PATCH 045/152] Use clock cycle resolution instead of us for analogWrite. --- cores/esp8266/core_esp8266_waveform.cpp | 24 +++++++++++------------ cores/esp8266/core_esp8266_wiring_pwm.cpp | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 1089b0ae02..383e8caa40 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -65,10 +65,10 @@ enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextPhaseCcy; // ESP clock cycle when a period begins - uint32_t nextTimeDutyCcys; // Add at low->high to keep smooth waveform - uint32_t nextTimePeriodCcys; // Set next phase cycle at low->high to maintain phase - uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count + uint32_t nextPhaseCcy; // ESP clock cycle when a period begins + uint32_t dutyCcys; // Add at low->high to keep smooth waveform + uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count WaveformMode mode; } Waveform; @@ -130,8 +130,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo return false; } Waveform* wave = &waveforms[pin]; - wave->nextTimeDutyCcys = timeHighCcys; - wave->nextTimePeriodCcys = periodCcys; + wave->dutyCcys = timeHighCcys; + wave->periodCcys = periodCcys; if (!(waveformsEnabled & (1UL << pin))) { // wave->nextPhaseCcy is initialized by the ISR @@ -239,7 +239,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { break; } - uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->nextTimeDutyCcys : 0); + uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->dutyCcys : 0); if (WaveformMode::EXPIRES == wave->mode && static_cast(wave->expiryCcy - now) <= 0) { // Disable any waveforms that are done @@ -252,7 +252,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else if (static_cast(nextEventCcy - now) <= 0) { if (waveformsState & (1UL << pin)) { - if (wave->nextTimeDutyCcys != wave->nextTimePeriodCcys) { + if (wave->dutyCcys != wave->periodCcys) { if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -261,11 +261,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } waveformsState ^= 1UL << pin; } - wave->nextPhaseCcy += wave->nextTimePeriodCcys; + wave->nextPhaseCcy += wave->periodCcys; nextEventCcy = wave->nextPhaseCcy; } else { - if (wave->nextTimeDutyCcys) { + if (wave->dutyCcys) { if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } @@ -273,10 +273,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } waveformsState ^= 1UL << pin; - nextEventCcy += wave->nextTimeDutyCcys; + nextEventCcy += wave->dutyCcys; } else { - wave->nextPhaseCcy += wave->nextTimePeriodCcys; + wave->nextPhaseCcy += wave->periodCcys; nextEventCcy = wave->nextPhaseCcy; } } diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index c71d833847..cd3983f471 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -56,7 +56,7 @@ extern void __analogWrite(uint8_t pin, int val) { if (pin > 16) { return; } - uint32_t analogPeriod = 1000000L / analogFreq; + uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; if (val < 0) { val = 0; } else if (val > analogScale) { @@ -71,7 +71,7 @@ extern void __analogWrite(uint8_t pin, int val) { uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (startWaveform(pin, high, low, 0)) { + if (startWaveformClockCycles(pin, high, low, 0)) { analogMap |= (1 << pin); } } From 3155888b7cba4f4e8a94b6d68b03b7231083a5d5 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Apr 2020 12:39:32 +0200 Subject: [PATCH 046/152] Reduce idle calculations in ISR. --- cores/esp8266/core_esp8266_waveform.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 383e8caa40..b2316488fd 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -66,7 +66,8 @@ enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextPhaseCcy; // ESP clock cycle when a period begins - uint32_t dutyCcys; // Add at low->high to keep smooth waveform + uint32_t nextOffCcy; // ESP clock cycle when going from duty to off + uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count WaveformMode mode; @@ -134,7 +135,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo wave->periodCcys = periodCcys; if (!(waveformsEnabled & (1UL << pin))) { - // wave->nextPhaseCcy is initialized by the ISR + // wave->nextPhaseCcy and wave->nextOffCcy are initialized by the ISR wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave->mode = WaveformMode::INIT; waveformToEnable = 1UL << pin; @@ -200,7 +201,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveformToEnable || waveformToDisable) { // Handle enable/disable requests from main app. waveformsEnabled = (waveformsEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off - waveformsState &= ~waveformToEnable; // And clear the state of any just started waveformToEnable = 0; waveformToDisable = 0; // 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) @@ -226,6 +226,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { switch (wave->mode) { case WaveformMode::INIT: + waveformsState &= ~(1UL << pin); // Clear the state of any just started wave->nextPhaseCcy = now; if (!wave->expiryCcy) { wave->mode = WaveformMode::INFINITE; @@ -239,7 +240,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { break; } - uint32_t nextEventCcy = wave->nextPhaseCcy + ((waveformsState & (1UL << pin)) ? wave->dutyCcys : 0); + uint32_t nextEventCcy = (waveformsState & (1UL << pin)) ? wave->nextOffCcy : wave->nextPhaseCcy; if (WaveformMode::EXPIRES == wave->mode && static_cast(wave->expiryCcy - now) <= 0) { // Disable any waveforms that are done @@ -252,6 +253,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else if (static_cast(nextEventCcy - now) <= 0) { if (waveformsState & (1UL << pin)) { + wave->nextPhaseCcy += wave->periodCcys; if (wave->dutyCcys != wave->periodCcys) { if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW @@ -261,7 +263,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } waveformsState ^= 1UL << pin; } - wave->nextPhaseCcy += wave->periodCcys; + else { + wave->nextOffCcy = wave->nextPhaseCcy; + } nextEventCcy = wave->nextPhaseCcy; } else { @@ -273,7 +277,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } waveformsState ^= 1UL << pin; - nextEventCcy += wave->dutyCcys; + wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys; + nextEventCcy = wave->nextOffCcy; } else { wave->nextPhaseCcy += wave->periodCcys; From 6c43c0d099d52e96ede0df9979b2ac997e63bd6d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 7 Apr 2020 20:04:56 +0200 Subject: [PATCH 047/152] Optimize in-ISR time. --- cores/esp8266/core_esp8266_waveform.cpp | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b2316488fd..7d3024ec73 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -212,8 +212,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(12); uint32_t now = ESP.getCycleCount(); - int32_t nextTimerCcys = microsecondsToClockCycles(MAXIRQUS); - uint32_t nextTimerCcy = now + nextTimerCcys; + uint32_t nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); bool busy = waveformsEnabled; while (busy) { nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); @@ -293,12 +292,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } - int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; - nextTimerCcys = nextTimerCcy - now; - busy = waveformsEnabled && timerMarginCcys > 0 && - (nextTimerCcys < microsecondsToClockCycles(6) + DELTAIRQ); + const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; + busy = waveformsEnabled && timerMarginCcys > 0; } + int32_t nextTimerCcys; if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). @@ -306,17 +304,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; } + else { + nextTimerCcys = nextTimerCcy - now; + } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= microsecondsToClockCycles(6) + DELTAIRQ) + if (nextTimerCcys <= microsecondsToClockCycles(6) + DELTAIRQ) { nextTimerCcys = microsecondsToClockCycles(6); - else + } + else { nextTimerCcys -= DELTAIRQ; + } - if (clockCyclesPerMicrosecond() == 160) - timer1_write(nextTimerCcys / 2); - else - timer1_write(nextTimerCcys); + // Do it here instead of global function to save time and because we know it's edge-IRQ + if (clockCyclesPerMicrosecond() == 160) { + T1L = nextTimerCcys >> 1; + } + else { + T1L = nextTimerCcys; + } + TEIE |= TEIE1; // Edge int enable } }; From 14444a9af58fceb762064c4e1720a1049246ff37 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 8 Apr 2020 10:09:03 +0200 Subject: [PATCH 048/152] Support starting new waveform in phase with another running waveform. --- cores/esp8266/core_esp8266_waveform.cpp | 18 ++++++++++++------ cores/esp8266/core_esp8266_waveform.h | 10 ++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 7d3024ec73..2cd4eaf018 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -61,7 +61,7 @@ constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsTo // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. // for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. // for INIT, the NMI initializes nextPhaseCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. -enum class WaveformMode : uint32_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; +enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; // Waveform generator can create tones, PWM, and servos typedef struct { @@ -71,6 +71,7 @@ typedef struct { uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count WaveformMode mode; + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in phase with given pin } Waveform; static Waveform waveforms[17]; // State of all possible pins @@ -117,17 +118,20 @@ void setTimer1Callback(uint32_t (*fn)()) { } } -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, + uint32_t runTimeUS, int8_t alignPhase) { return startWaveformClockCycles(pin, - microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); + microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), + microsecondsToClockCycles(runTimeUS), alignPhase); } // 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 startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys) { +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, + uint32_t runTimeCcys, int8_t alignPhase) { const auto periodCcys = timeHighCcys + timeLowCcys; - if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys) { + if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys || (alignPhase > 16)) { return false; } Waveform* wave = &waveforms[pin]; @@ -138,6 +142,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo // wave->nextPhaseCcy and wave->nextOffCcy are initialized by the ISR wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave->mode = WaveformMode::INIT; + wave->alignPhase = (alignPhase < 0) ? -1 : alignPhase; waveformToEnable = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { @@ -226,7 +231,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { switch (wave->mode) { case WaveformMode::INIT: waveformsState &= ~(1UL << pin); // Clear the state of any just started - wave->nextPhaseCcy = now; + wave->nextPhaseCcy = (waveformsEnabled & (1UL << wave->alignPhase)) ? + waveforms[wave->alignPhase].nextPhaseCcy : now; if (!wave->expiryCcy) { wave->mode = WaveformMode::INFINITE; break; diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index b30a68e33e..2587d0a4e0 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -51,13 +51,19 @@ extern "C" { // Start or change a waveform of the specified high and low times on specific pin. // If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next // full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started in phase with that. // Returns true or false on success or failure. -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, + uint32_t runTimeUS = 0, int8_t alignPhase = -1); // Start or change a waveform of the specified high and low CPU clock cycles on specific pin. // If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next // full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started in phase with that. // Returns true or false on success or failure. -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys); +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, + uint32_t runTimeCcys = 0, int8_t alignPhase = -1); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); From 48f6d85f7e9b2381851da11258113b7e36320bac Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 8 Apr 2020 12:52:14 +0200 Subject: [PATCH 049/152] Align phase for analogWrite PWMs. --- cores/esp8266/core_esp8266_wiring_pwm.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index cd3983f471..94888fee32 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -71,7 +71,9 @@ extern void __analogWrite(uint8_t pin, int val) { uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (startWaveformClockCycles(pin, high, low, 0)) { + // 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) + int phaseReference = __builtin_ffs(analogMap) - 1; + if (startWaveformClockCycles(pin, high, low, 0, phaseReference)) { analogMap |= (1 << pin); } } From ac832a73716f35d5197b41d04a0ef36f1baca1da Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 8 Apr 2020 20:39:27 +0200 Subject: [PATCH 050/152] Tune preshoot, add lost period fast forward. --- cores/esp8266/core_esp8266_waveform.cpp | 52 +++++++++++++------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 2cd4eaf018..8666dc7e88 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -256,38 +256,42 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else if (WaveformMode::EXPIRES == wave->mode && static_cast(nextEventCcy - wave->expiryCcy) > 0) { nextEventCcy = wave->expiryCcy; } - else if (static_cast(nextEventCcy - now) <= 0) { - if (waveformsState & (1UL << pin)) { - wave->nextPhaseCcy += wave->periodCcys; - if (wave->dutyCcys != wave->periodCcys) { - if (pin == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW + else { + int32_t nextEventCcys = nextEventCcy - now; + if (nextEventCcys <= 0) { + uint32_t skipPeriodCcys = (-nextEventCcys / wave->periodCcys) * wave->periodCcys; + if (waveformsState & (1UL << pin)) { + wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; + if (wave->dutyCcys != wave->periodCcys) { + if (pin == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(1UL << pin); + } + waveformsState ^= 1UL << pin; } else { - ClearGPIO(1UL << pin); + wave->nextOffCcy = wave->nextPhaseCcy; } - waveformsState ^= 1UL << pin; + nextEventCcy = wave->nextPhaseCcy; } else { - wave->nextOffCcy = wave->nextPhaseCcy; - } - nextEventCcy = wave->nextPhaseCcy; - } - else { - if (wave->dutyCcys) { - if (pin == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW + if (wave->dutyCcys) { + if (pin == 16) { + GP16O |= 1; // GPIO16 write slow as it's RMW + } + else { + SetGPIO(1UL << pin); + } + waveformsState ^= 1UL << pin; + wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; + nextEventCcy = wave->nextOffCcy; } else { - SetGPIO(1UL << pin); + wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; + nextEventCcy = wave->nextPhaseCcy; } - waveformsState ^= 1UL << pin; - wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys; - nextEventCcy = wave->nextOffCcy; - } - else { - wave->nextPhaseCcy += wave->periodCcys; - nextEventCcy = wave->nextPhaseCcy; } } } From 94626e82f4ec4263f1f34f4704a29504bdca03da Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 9 Apr 2020 11:14:58 +0200 Subject: [PATCH 051/152] Adapt phase sync code from analogWrite to Servo --- libraries/Servo/src/Servo.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index aff9afebbe..d06c9aed23 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -115,7 +115,9 @@ void Servo::writeMicroseconds(int value) _valueUs = value; if (_attached) { _servoMap &= ~(1 << _pin); - if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) { + // 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) + int phaseReference = __builtin_ffs(_servoMap) - 1; + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference)) { _servoMap |= (1 << _pin); } } From ef5fdd02e6e9006ea751d20cbe5517f3bb4da1cb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 9 Apr 2020 14:59:44 +0200 Subject: [PATCH 052/152] Fix for going off 100% duty cycle period. --- cores/esp8266/core_esp8266_waveform.cpp | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 8666dc7e88..ff61b4d8fc 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -260,9 +260,19 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t nextEventCcys = nextEventCcy - now; if (nextEventCcys <= 0) { uint32_t skipPeriodCcys = (-nextEventCcys / wave->periodCcys) * wave->periodCcys; + bool flatLine = wave->nextPhaseCcy == wave->nextOffCcy; if (waveformsState & (1UL << pin)) { - wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; - if (wave->dutyCcys != wave->periodCcys) { + if (wave->dutyCcys == wave->periodCcys) { + wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; + wave->nextOffCcy = wave->nextPhaseCcy; + nextEventCcy = wave->nextPhaseCcy; + } + else if (flatLine) { + wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; + wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; + nextEventCcy = wave->nextOffCcy; + } + else { if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -270,14 +280,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { ClearGPIO(1UL << pin); } waveformsState ^= 1UL << pin; + nextEventCcy = wave->nextPhaseCcy; } - else { - wave->nextOffCcy = wave->nextPhaseCcy; - } - nextEventCcy = wave->nextPhaseCcy; } else { - if (wave->dutyCcys) { + wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; + if (!wave->dutyCcys) { + wave->nextPhaseCcy = wave->nextOffCcy; + } + else { if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } @@ -285,13 +296,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } waveformsState ^= 1UL << pin; - wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; - nextEventCcy = wave->nextOffCcy; - } - else { wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; - nextEventCcy = wave->nextPhaseCcy; } + nextEventCcy = wave->nextOffCcy; } } } From 59d788b1979b8574f4c80cee2daf5635507eb6d1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 10 Apr 2020 21:55:58 +0200 Subject: [PATCH 053/152] Eschew obfuscation. --- cores/esp8266/core_esp8266_waveform.cpp | 74 ++++++++++++------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index ff61b4d8fc..b0820883d6 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -134,15 +134,15 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys || (alignPhase > 16)) { return false; } - Waveform* wave = &waveforms[pin]; - wave->dutyCcys = timeHighCcys; - wave->periodCcys = periodCcys; + Waveform& wave = waveforms[pin]; + wave.dutyCcys = timeHighCcys; + wave.periodCcys = periodCcys; if (!(waveformsEnabled & (1UL << pin))) { - // wave->nextPhaseCcy and wave->nextOffCcy are initialized by the ISR - wave->expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count - wave->mode = WaveformMode::INIT; - wave->alignPhase = (alignPhase < 0) ? -1 : alignPhase; + // wave.nextPhaseCcy and wave.nextOffCcy are initialized by the ISR + wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count + wave.mode = WaveformMode::INIT; + wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; waveformToEnable = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); if (!timerRunning) { @@ -158,10 +158,10 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo } } else { - wave->mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI - wave->expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count + wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count if (runTimeCcys) - wave->mode = WaveformMode::UPDATEEXPIRY; + wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); } return true; @@ -226,51 +226,51 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (!(waveformsEnabled & (1UL << pin))) continue; - Waveform *wave = &waveforms[pin]; + Waveform& wave = waveforms[pin]; - switch (wave->mode) { + switch (wave.mode) { case WaveformMode::INIT: waveformsState &= ~(1UL << pin); // Clear the state of any just started - wave->nextPhaseCcy = (waveformsEnabled & (1UL << wave->alignPhase)) ? - waveforms[wave->alignPhase].nextPhaseCcy : now; - if (!wave->expiryCcy) { - wave->mode = WaveformMode::INFINITE; + wave.nextPhaseCcy = (waveformsEnabled & (1UL << wave.alignPhase)) ? + waveforms[wave.alignPhase].nextPhaseCcy : now; + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; break; } case WaveformMode::UPDATEEXPIRY: - wave->expiryCcy += wave->nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave->mode = WaveformMode::EXPIRES; + wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.mode = WaveformMode::EXPIRES; break; default: break; } - uint32_t nextEventCcy = (waveformsState & (1UL << pin)) ? wave->nextOffCcy : wave->nextPhaseCcy; + uint32_t nextEventCcy = (waveformsState & (1UL << pin)) ? wave.nextOffCcy : wave.nextPhaseCcy; - if (WaveformMode::EXPIRES == wave->mode && static_cast(wave->expiryCcy - now) <= 0) { + if (WaveformMode::EXPIRES == wave.mode && static_cast(wave.expiryCcy - now) <= 0) { // Disable any waveforms that are done waveformsEnabled ^= 1UL << pin; // impossibly large value to prevent setting nextTimerCcy nextEventCcy = now + microsecondsToClockCycles(MAXIRQUS); } - else if (WaveformMode::EXPIRES == wave->mode && static_cast(nextEventCcy - wave->expiryCcy) > 0) { - nextEventCcy = wave->expiryCcy; + else if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) > 0) { + nextEventCcy = wave.expiryCcy; } else { int32_t nextEventCcys = nextEventCcy - now; if (nextEventCcys <= 0) { - uint32_t skipPeriodCcys = (-nextEventCcys / wave->periodCcys) * wave->periodCcys; - bool flatLine = wave->nextPhaseCcy == wave->nextOffCcy; + uint32_t skipPeriodCcys = (-nextEventCcys / wave.periodCcys) * wave.periodCcys; + bool flatLine = wave.nextPhaseCcy == wave.nextOffCcy; if (waveformsState & (1UL << pin)) { - if (wave->dutyCcys == wave->periodCcys) { - wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; - wave->nextOffCcy = wave->nextPhaseCcy; - nextEventCcy = wave->nextPhaseCcy; + if (wave.dutyCcys == wave.periodCcys) { + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + wave.nextOffCcy = wave.nextPhaseCcy; + nextEventCcy = wave.nextPhaseCcy; } else if (flatLine) { - wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; - wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; - nextEventCcy = wave->nextOffCcy; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + nextEventCcy = wave.nextOffCcy; } else { if (pin == 16) { @@ -280,13 +280,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { ClearGPIO(1UL << pin); } waveformsState ^= 1UL << pin; - nextEventCcy = wave->nextPhaseCcy; + nextEventCcy = wave.nextPhaseCcy; } } else { - wave->nextOffCcy = wave->nextPhaseCcy + wave->dutyCcys + skipPeriodCcys; - if (!wave->dutyCcys) { - wave->nextPhaseCcy = wave->nextOffCcy; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; + if (!wave.dutyCcys) { + wave.nextPhaseCcy = wave.nextOffCcy; } else { if (pin == 16) { @@ -296,9 +296,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } waveformsState ^= 1UL << pin; - wave->nextPhaseCcy += wave->periodCcys + skipPeriodCcys; + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; } - nextEventCcy = wave->nextOffCcy; + nextEventCcy = wave.nextOffCcy; } } } From 1057256d8d501f642ffb1302b83f9a99f56c6033 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 10 Apr 2020 23:44:12 +0200 Subject: [PATCH 054/152] Fixed logic for zero duty cycle. --- cores/esp8266/core_esp8266_waveform.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b0820883d6..c3d20c38f9 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -284,11 +284,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; if (!wave.dutyCcys) { - wave.nextPhaseCcy = wave.nextOffCcy; + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + wave.nextOffCcy = wave.nextPhaseCcy; } else { + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } From 3d99249890d7ea5fa1dfbaefa481dabe803ff421 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 11 Apr 2020 02:50:56 +0200 Subject: [PATCH 055/152] Determine generator quantum during same IRQ - this is better than timer resolution, but non-zero. --- cores/esp8266/core_esp8266_waveform.cpp | 41 +++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c3d20c38f9..ae75c931f2 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -51,7 +51,14 @@ constexpr uint32_t MAXIRQUS = 1000000; // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle counter we want for the waveforms. -constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(2) : microsecondsToClockCycles(3); +constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? + microsecondsToClockCycles(4) >> 1 : microsecondsToClockCycles(4); +// The generator has a time quantum for switching wave cycles during the same ISR invocation +constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? + (microsecondsToClockCycles(3) / 2) >> 1 : microsecondsToClockCycles(3) / 2; +// The latency between in-ISR rearming of the timer and the earliest firing +constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -118,24 +125,34 @@ void setTimer1Callback(uint32_t (*fn)()) { } } -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, +int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, uint32_t runTimeUS, int8_t alignPhase) { return startWaveformClockCycles(pin, - microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), + microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS), microsecondsToClockCycles(runTimeUS), alignPhase); } // 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 startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, +int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase) { - const auto periodCcys = timeHighCcys + timeLowCcys; + const auto periodCcys = highCcys + lowCcys; + // correct the upward bias for duty cycles shorter than generator quantum + // effectively rounds to nearest quantum + if (highCcys < QUANTUM) + { + highCcys = 0; + } + else if (lowCcys < QUANTUM) + { + highCcys = periodCcys; + } if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys || (alignPhase > 16)) { return false; } Waveform& wave = waveforms[pin]; - wave.dutyCcys = timeHighCcys; + wave.dutyCcys = highCcys; wave.periodCcys = periodCcys; if (!(waveformsEnabled & (1UL << pin))) { @@ -149,9 +166,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo initTimer(); timer1_write(microsecondsToClockCycles(1)); } - else if (T1L > microsecondsToClockCycles(6)) { + else if (T1L > DELTAIRQ + IRQLATENCY) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load - timer1_write(microsecondsToClockCycles(6)); + timer1_write(microsecondsToClockCycles(1)); } while (waveformToEnable) { delay(0); // Wait for waveform to update @@ -178,8 +195,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; // Must not interfere if Timer is due shortly - if (T1L > microsecondsToClockCycles(6)) { - timer1_write(microsecondsToClockCycles(6)); + if (T1L > DELTAIRQ + IRQLATENCY) { + timer1_write(microsecondsToClockCycles(1)); } while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ @@ -327,8 +344,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= microsecondsToClockCycles(6) + DELTAIRQ) { - nextTimerCcys = microsecondsToClockCycles(6); + if (nextTimerCcys <= IRQLATENCY + DELTAIRQ) { + nextTimerCcys = IRQLATENCY; } else { nextTimerCcys -= DELTAIRQ; From d92a737a41d06b6a76afeb097b359a05b6ba7633 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 12 Apr 2020 22:26:32 +0200 Subject: [PATCH 056/152] Tune timings, fix write barriers and overshoot logic. --- cores/esp8266/core_esp8266_waveform.cpp | 37 ++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index ae75c931f2..cb4af5f0c6 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -50,12 +50,12 @@ extern "C" { constexpr uint32_t MAXIRQUS = 1000000; // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the -// real CPU cycle counter we want for the waveforms. +// real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(4) >> 1 : microsecondsToClockCycles(4); + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // The generator has a time quantum for switching wave cycles during the same ISR invocation constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(3) / 2) >> 1 : microsecondsToClockCycles(3) / 2; + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); @@ -89,7 +89,7 @@ static volatile uint32_t waveformsEnabled = 0; // Is it actively running, update static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start exactly one waveform on a inactive pin static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable exactly one pin from waveform generation -static uint32_t (*timer1CB)() = NULL; +static uint32_t (*timer1CB)() = nullptr; // Interrupt on/off control static ICACHE_RAM_ATTR void timer1Interrupt(); @@ -117,6 +117,7 @@ static void ICACHE_RAM_ATTR deinitTimer() { // Set a callback. Pass in NULL to stop it void setTimer1Callback(uint32_t (*fn)()) { timer1CB = fn; + std::atomic_thread_fence(std::memory_order_release); if (!timerRunning && fn) { initTimer(); timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste @@ -140,11 +141,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum // effectively rounds to nearest quantum - if (highCcys < QUANTUM) + if (highCcys < QUANTUM / 2) { highCcys = 0; } - else if (lowCcys < QUANTUM) + else if (lowCcys < QUANTUM / 2) { highCcys = periodCcys; } @@ -160,8 +161,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave.mode = WaveformMode::INIT; wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; - waveformToEnable = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); + waveformToEnable = 1UL << pin; if (!timerRunning) { initTimer(); timer1_write(microsecondsToClockCycles(1)); @@ -176,10 +177,13 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } else { wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + std::atomic_thread_fence(std::memory_order_release); wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count - if (runTimeCcys) - wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); + if (runTimeCcys) { + wave.mode = WaveformMode::UPDATEEXPIRY; + std::atomic_thread_fence(std::memory_order_release); + } } return true; } @@ -264,7 +268,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { uint32_t nextEventCcy = (waveformsState & (1UL << pin)) ? wave.nextOffCcy : wave.nextPhaseCcy; - if (WaveformMode::EXPIRES == wave.mode && static_cast(wave.expiryCcy - now) <= 0) { + if (WaveformMode::EXPIRES == wave.mode && static_cast(now - wave.expiryCcy) >= 0) { // Disable any waveforms that are done waveformsEnabled ^= 1UL << pin; // impossibly large value to prevent setting nextTimerCcy @@ -274,17 +278,17 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCcy = wave.expiryCcy; } else { - int32_t nextEventCcys = nextEventCcy - now; - if (nextEventCcys <= 0) { - uint32_t skipPeriodCcys = (-nextEventCcys / wave.periodCcys) * wave.periodCcys; - bool flatLine = wave.nextPhaseCcy == wave.nextOffCcy; + const int32_t overshootCcys = now - nextEventCcy; + if (overshootCcys >= 0) { + const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; if (waveformsState & (1UL << pin)) { + const uint32_t skipPeriodCcys = ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys; if (wave.dutyCcys == wave.periodCcys) { - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + wave.nextPhaseCcy += skipPeriodCcys; wave.nextOffCcy = wave.nextPhaseCcy; nextEventCcy = wave.nextPhaseCcy; } - else if (flatLine) { + else if (endOfPeriod) { wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; nextEventCcy = wave.nextOffCcy; @@ -301,6 +305,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { + const uint32_t skipPeriodCcys = (overshootCcys / wave.periodCcys) * wave.periodCcys; if (!wave.dutyCcys) { wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; wave.nextOffCcy = wave.nextPhaseCcy; From 560cca7495e33fa32b2afb94eb1ea13adf500e71 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 13 Apr 2020 12:08:37 +0200 Subject: [PATCH 057/152] Migrate Tone to waveform with CPU cycle precision --- cores/esp8266/Tone.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 0d0faaed97..ec4ff2a839 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -23,24 +23,25 @@ #include "Arduino.h" #include "core_esp8266_waveform.h" +#include "user_interface.h" // Which pins have a tone running on them? static uint32_t _toneMap = 0; -static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) { +static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { if (_pin > 16) { return; } pinMode(_pin, OUTPUT); - high = std::max(high, (uint32_t)25); // new 20KHz maximum tone frequency, - low = std::max(low, (uint32_t)25); // (25us high + 25us low period = 20KHz) + high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, + low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) - duration *= 1000UL; + duration = microsecondsToClockCycles(duration * 1000UL); duration -= duration % (high + low); - if (startWaveform(_pin, high, low, duration)) { + if (startWaveformClockCycles(_pin, high, low, duration)) { _toneMap |= 1 << _pin; } } @@ -50,7 +51,7 @@ void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) { if (frequency == 0) { noTone(_pin); } else { - uint32_t period = 1000000L / frequency; + uint32_t period = microsecondsToClockCycles(1000000UL) / frequency; uint32_t high = period / 2; uint32_t low = period - high; _startTone(_pin, high, low, duration); @@ -64,7 +65,7 @@ void tone(uint8_t _pin, double frequency, unsigned long duration) { if (frequency < 1.0) { // FP means no exact comparisons noTone(_pin); } else { - double period = 1000000.0 / frequency; + double period = (double)microsecondsToClockCycles(1000000UL) / frequency; uint32_t high = (uint32_t)((period / 2.0) + 0.5); uint32_t low = (uint32_t)(period + 0.5) - high; _startTone(_pin, high, low, duration); From c75d1bd2b27f724adb31da5fd8019d7d79a507ff Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 14 Apr 2020 20:03:54 +0200 Subject: [PATCH 058/152] Can do 60kHz PWM. --- cores/esp8266/core_esp8266_wiring_pwm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index 94888fee32..ca62e2900f 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -45,8 +45,8 @@ extern void __analogWriteResolution(int res) { extern void __analogWriteFreq(uint32_t freq) { if (freq < 100) { analogFreq = 100; - } else if (freq > 40000) { - analogFreq = 40000; + } else if (freq > 60000) { + analogFreq = 60000; } else { analogFreq = freq; } From a2222e8cdd186100319adc0474ef775ab0c7dbf0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 15 Apr 2020 11:28:05 +0200 Subject: [PATCH 059/152] Recalibrated timings after performance optimizations. Initialize GPIO if needed. --- cores/esp8266/core_esp8266_waveform.cpp | 56 +++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index cb4af5f0c6..acbfac2164 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -52,10 +52,10 @@ constexpr uint32_t MAXIRQUS = 1000000; // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + (microsecondsToClockCycles(5) / 2) >> 1 : microsecondsToClockCycles(5) / 2; // The generator has a time quantum for switching wave cycles during the same ISR invocation constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); @@ -161,6 +161,15 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave.mode = WaveformMode::INIT; wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + if (!wave.dutyCcys) { + // If initially at zero duty cycle, force GPIO off + if (pin == 16) { + GP16O &= ~1; // GPIO16 write slow as it's RMW + } + else { + ClearGPIO(1UL << pin); + } + } std::atomic_thread_fence(std::memory_order_release); waveformToEnable = 1UL << pin; if (!timerRunning) { @@ -266,68 +275,71 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { break; } - uint32_t nextEventCcy = (waveformsState & (1UL << pin)) ? wave.nextOffCcy : wave.nextPhaseCcy; + const bool duty = waveformsState & (1UL << pin); + uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; if (WaveformMode::EXPIRES == wave.mode && static_cast(now - wave.expiryCcy) >= 0) { // Disable any waveforms that are done waveformsEnabled ^= 1UL << pin; - // impossibly large value to prevent setting nextTimerCcy - nextEventCcy = now + microsecondsToClockCycles(MAXIRQUS); } else if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) > 0) { - nextEventCcy = wave.expiryCcy; + if (static_cast(nextTimerCcy - wave.expiryCcy) > 0) { + nextTimerCcy = wave.expiryCcy; + } } else { const int32_t overshootCcys = now - nextEventCcy; if (overshootCcys >= 0) { - const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; - if (waveformsState & (1UL << pin)) { - const uint32_t skipPeriodCcys = ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys; + if (duty) { + const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; + const uint32_t skipPeriodCcys = ((overshootCcys + wave.dutyCcys) > wave.periodCcys) ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys : 0; if (wave.dutyCcys == wave.periodCcys) { wave.nextPhaseCcy += skipPeriodCcys; wave.nextOffCcy = wave.nextPhaseCcy; nextEventCcy = wave.nextPhaseCcy; } else if (endOfPeriod) { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + // preceeding period had zero off cycle, continue direct into new duty cycle + wave.nextPhaseCcy += skipPeriodCcys; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; + wave.nextPhaseCcy += wave.periodCcys; nextEventCcy = wave.nextOffCcy; } else { + waveformsState ^= 1UL << pin; + nextEventCcy = wave.nextPhaseCcy; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } else { ClearGPIO(1UL << pin); } - waveformsState ^= 1UL << pin; - nextEventCcy = wave.nextPhaseCcy; } } else { - const uint32_t skipPeriodCcys = (overshootCcys / wave.periodCcys) * wave.periodCcys; + const uint32_t skipPeriodCcys = (static_cast(overshootCcys) > wave.periodCcys) ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; + wave.nextPhaseCcy += skipPeriodCcys; if (!wave.dutyCcys) { - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + wave.nextPhaseCcy += wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy; } else { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + skipPeriodCcys; + waveformsState ^= 1UL << pin; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; + wave.nextPhaseCcy += wave.periodCcys; if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } else { SetGPIO(1UL << pin); } - waveformsState ^= 1UL << pin; - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; } nextEventCcy = wave.nextOffCcy; } } - } - - if (static_cast(nextTimerCcy - nextEventCcy) > 0) { - nextTimerCcy = nextEventCcy; + if (static_cast(nextTimerCcy - nextEventCcy) > 0) { + nextTimerCcy = nextEventCcy; + } } now = ESP.getCycleCount(); From 6ab789c349d40403d1e4ba85da0d37514860aff9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 15 Apr 2020 16:09:04 +0200 Subject: [PATCH 060/152] Fix regression for waveform runtime. --- cores/esp8266/core_esp8266_waveform.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index acbfac2164..be24a18a5c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -278,12 +278,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const bool duty = waveformsState & (1UL << pin); uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; - if (WaveformMode::EXPIRES == wave.mode && static_cast(now - wave.expiryCcy) >= 0) { - // Disable any waveforms that are done - waveformsEnabled ^= 1UL << pin; - } - else if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) > 0) { - if (static_cast(nextTimerCcy - wave.expiryCcy) > 0) { + if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) >= 0) { + if (static_cast(now - wave.expiryCcy) >= 0) { + // Disable any waveforms that are done + waveformsEnabled ^= 1UL << pin; + } + else if (static_cast(nextTimerCcy - wave.expiryCcy) > 0) { nextTimerCcy = wave.expiryCcy; } } From ac21bca653f710f89e10915de98c22d52641ca8f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 16 Apr 2020 10:30:22 +0200 Subject: [PATCH 061/152] Test cycle duration values for signed arithmetic safety. --- cores/esp8266/core_esp8266_waveform.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index be24a18a5c..69a228303c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -149,7 +149,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, { highCcys = periodCcys; } - if ((pin > 16) || isFlashInterfacePin(pin) || !periodCcys || (alignPhase > 16)) { + // sanity checks, including mixed signed/unsigned arithmetic safety + if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || + static_cast(periodCcys) <= 0 || highCcys > periodCcys) { return false; } Waveform& wave = waveforms[pin]; From 7a4bb617b546342a541e6500113bf323fdd05340 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 16 Apr 2020 11:09:13 +0200 Subject: [PATCH 062/152] Performance tuning. --- cores/esp8266/core_esp8266_waveform.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 69a228303c..fc490ea911 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -47,7 +47,7 @@ extern "C" { // Maximum delay between IRQs, 1Hz -constexpr uint32_t MAXIRQUS = 1000000; +constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(1000000); // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. @@ -249,10 +249,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(12); uint32_t now = ESP.getCycleCount(); - uint32_t nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); + uint32_t nextEventCcy = now + MAXIRQCCYS; bool busy = waveformsEnabled; while (busy) { - nextTimerCcy = now + microsecondsToClockCycles(MAXIRQUS); for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore! if (!(waveformsEnabled & (1UL << pin))) @@ -278,15 +277,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } const bool duty = waveformsState & (1UL << pin); - uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; + nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) >= 0) { if (static_cast(now - wave.expiryCcy) >= 0) { // Disable any waveforms that are done waveformsEnabled ^= 1UL << pin; } - else if (static_cast(nextTimerCcy - wave.expiryCcy) > 0) { - nextTimerCcy = wave.expiryCcy; + else { + nextEventCcy = wave.expiryCcy; } } else { @@ -339,14 +338,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCcy = wave.nextOffCcy; } } - if (static_cast(nextTimerCcy - nextEventCcy) > 0) { - nextTimerCcy = nextEventCcy; - } } now = ESP.getCycleCount(); } - const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; + const int32_t timerMarginCcys = isrTimeoutCcy - nextEventCcy; busy = waveformsEnabled && timerMarginCcys > 0; } @@ -354,18 +350,21 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); + nextTimerCcys = nextEventCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; } else { - nextTimerCcys = nextTimerCcy - now; + nextTimerCcys = nextEventCcy - now; } // Firing timer too soon, the NMI occurs before ISR has returned. if (nextTimerCcys <= IRQLATENCY + DELTAIRQ) { nextTimerCcys = IRQLATENCY; } + else if (nextTimerCcys >= MAXIRQCCYS) { + nextTimerCcys = MAXIRQCCYS - DELTAIRQ; + } else { nextTimerCcys -= DELTAIRQ; } From ba47d2778fb0294eee98c8b5fab8c2aa28fce39d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 16 Apr 2020 15:33:26 +0200 Subject: [PATCH 063/152] =?UTF-8?q?Performance=20tweak,=20in-ISR=20quantum?= =?UTF-8?q?=20is=20now=201.12=C2=B5s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp8266/core_esp8266_waveform.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fc490ea911..2f5df06c89 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -140,12 +140,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase) { const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum - // effectively rounds to nearest quantum - if (highCcys < QUANTUM / 2) + if (highCcys < 7 * QUANTUM / 10) { highCcys = 0; } - else if (lowCcys < QUANTUM / 2) + else if (lowCcys < 7 * QUANTUM / 10) { highCcys = periodCcys; } @@ -251,9 +250,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { uint32_t now = ESP.getCycleCount(); uint32_t nextEventCcy = now + MAXIRQCCYS; bool busy = waveformsEnabled; - while (busy) { + if (busy) { for (int pin = startPin; pin <= endPin; ++pin) { - // If it's not on, ignore! + // If it's not on, ignore if (!(waveformsEnabled & (1UL << pin))) continue; @@ -268,6 +267,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.mode = WaveformMode::INFINITE; break; } + // fall through case WaveformMode::UPDATEEXPIRY: wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count wave.mode = WaveformMode::EXPIRES; @@ -275,6 +275,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } + } + } + while (busy) { + for (int pin = startPin; pin <= endPin; ++pin) { + // If it's not on, ignore + if (!(waveformsEnabled & (1UL << pin))) + continue; + + Waveform& wave = waveforms[pin]; const bool duty = waveformsState & (1UL << pin); nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; From 1c4de11723fc8378f278a94cc6e91aa74973e787 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 17 Apr 2020 09:06:18 +0200 Subject: [PATCH 064/152] Round up duration instead of down - possibly to zero, which means forever. --- cores/esp8266/Tone.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index ec4ff2a839..064fdad5df 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -40,6 +40,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) duration = microsecondsToClockCycles(duration * 1000UL); + duration += high + low - 1; duration -= duration % (high + low); if (startWaveformClockCycles(_pin, high, low, duration)) { _toneMap |= 1 << _pin; From a9565b286d073c98ff73ffa1758f3eb52743a982 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 19 Apr 2020 10:31:35 +0200 Subject: [PATCH 065/152] Extend phase alignment with optional phase offset. --- cores/esp8266/core_esp8266_waveform.cpp | 15 ++++++++------- cores/esp8266/core_esp8266_waveform.h | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 2f5df06c89..9e923e17a0 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -72,13 +72,13 @@ enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextPhaseCcy; // ESP clock cycle when a period begins + uint32_t nextPhaseCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count uint32_t nextOffCcy; // ESP clock cycle when going from duty to off uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase - uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If ExpiryState::UPDATE, temporarily holds relative ccy count + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; - int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in phase with given pin + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin } Waveform; static Waveform waveforms[17]; // State of all possible pins @@ -127,17 +127,17 @@ void setTimer1Callback(uint32_t (*fn)()) { } int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, - uint32_t runTimeUS, int8_t alignPhase) { + uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS) { return startWaveformClockCycles(pin, microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS), - microsecondsToClockCycles(runTimeUS), alignPhase); + microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS)); } // 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 startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, - uint32_t runTimeCcys, int8_t alignPhase) { + uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum if (highCcys < 7 * QUANTUM / 10) @@ -159,6 +159,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!(waveformsEnabled & (1UL << pin))) { // wave.nextPhaseCcy and wave.nextOffCcy are initialized by the ISR + wave.nextPhaseCcy = phaseOffsetCcys; wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave.mode = WaveformMode::INIT; wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; @@ -262,7 +263,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { case WaveformMode::INIT: waveformsState &= ~(1UL << pin); // Clear the state of any just started wave.nextPhaseCcy = (waveformsEnabled & (1UL << wave.alignPhase)) ? - waveforms[wave.alignPhase].nextPhaseCcy : now; + waveforms[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; break; diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 2587d0a4e0..3de9b5d644 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -52,18 +52,18 @@ extern "C" { // If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next // full period. // If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, -// the new waveform is started in phase with that. +// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, - uint32_t runTimeUS = 0, int8_t alignPhase = -1); + uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0); // Start or change a waveform of the specified high and low CPU clock cycles on specific pin. // If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next // full period. // If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, -// the new waveform is started in phase with that. +// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. // Returns true or false on success or failure. int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, - uint32_t runTimeCcys = 0, int8_t alignPhase = -1); + uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); From a3c0dfe506d6892bf89bd8d82d17a72bcf1de96d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 19 Apr 2020 16:09:17 +0200 Subject: [PATCH 066/152] Slightly better in-ISR quantum approximation for steadier increments. --- cores/esp8266/core_esp8266_waveform.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 9e923e17a0..8bf2ef49d2 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -53,9 +53,9 @@ constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(1000000); // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? (microsecondsToClockCycles(5) / 2) >> 1 : microsecondsToClockCycles(5) / 2; -// The generator has a time quantum for switching wave cycles during the same ISR invocation +// The generator has a measurable time quantum for switching wave cycles during the same ISR invocation constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); + (microsecondsToClockCycles(11) / 10) >> 1 : (microsecondsToClockCycles(11) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); @@ -140,13 +140,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum - if (highCcys < 7 * QUANTUM / 10) + if (highCcys < lowCcys) { - highCcys = 0; + if (highCcys < 3 * QUANTUM / 2) { + highCcys = 0; + } + else if (highCcys < 2 * QUANTUM) { + highCcys = 2 * QUANTUM; + } } - else if (lowCcys < 7 * QUANTUM / 10) - { - highCcys = periodCcys; + else { + if (lowCcys < 3 * QUANTUM / 2) { + highCcys = periodCcys; + } + else if (lowCcys < 2 * QUANTUM) { + highCcys = periodCcys - 2 * QUANTUM; + } } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || @@ -268,7 +277,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.mode = WaveformMode::INFINITE; break; } - // fall through + // fall through case WaveformMode::UPDATEEXPIRY: wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count wave.mode = WaveformMode::EXPIRES; From bd0fea05b43603dc176c2a508acf838f4560b3be Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 19 Apr 2020 18:09:05 +0200 Subject: [PATCH 067/152] Waveform stopped by runtime limit in iSR doesn't deinit the timer, but stopWaveform refuses to do anything if the waveform was stopped by runtime, either. --- cores/esp8266/core_esp8266_waveform.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 8bf2ef49d2..ccf3530b88 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -218,10 +218,10 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If they send >=32, then the shift will result in 0 and it will also return false if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; - // Must not interfere if Timer is due shortly - if (T1L > DELTAIRQ + IRQLATENCY) { - timer1_write(microsecondsToClockCycles(1)); - } + // Must not interfere if Timer is due shortly + if (T1L > DELTAIRQ + IRQLATENCY) { + timer1_write(microsecondsToClockCycles(1)); + } while (waveformToDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } From 9f5743c55b2978218ca4035d3a780ea10d8cf079 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 20 Apr 2020 10:10:57 +0200 Subject: [PATCH 068/152] Improved quantum correction code. --- cores/esp8266/core_esp8266_waveform.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index ccf3530b88..b9ad42d14a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -48,6 +48,8 @@ extern "C" { // Maximum delay between IRQs, 1Hz constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(1000000); +// Maximum servicing time for any single IRQ +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(12); // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. @@ -142,19 +144,19 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // correct the upward bias for duty cycles shorter than generator quantum if (highCcys < lowCcys) { - if (highCcys < 3 * QUANTUM / 2) { + if (highCcys < QUANTUM) { highCcys = 0; } - else if (highCcys < 2 * QUANTUM) { - highCcys = 2 * QUANTUM; + else if (highCcys < ISRTIMEOUTCCYS) { + highCcys += QUANTUM / 2; } } else { - if (lowCcys < 3 * QUANTUM / 2) { + if (lowCcys < QUANTUM) { highCcys = periodCcys; } - else if (lowCcys < 2 * QUANTUM) { - highCcys = periodCcys - 2 * QUANTUM; + else if (lowCcys < ISRTIMEOUTCCYS) { + highCcys -= QUANTUM / 2; } } // sanity checks, including mixed signed/unsigned arithmetic safety @@ -187,7 +189,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, initTimer(); timer1_write(microsecondsToClockCycles(1)); } - else if (T1L > DELTAIRQ + IRQLATENCY) { + else if (T1L > IRQLATENCY + DELTAIRQ) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load timer1_write(microsecondsToClockCycles(1)); } @@ -219,7 +221,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveformsEnabled & (1UL << pin)) { waveformToDisable = 1UL << pin; // Must not interfere if Timer is due shortly - if (T1L > DELTAIRQ + IRQLATENCY) { + if (T1L > IRQLATENCY + DELTAIRQ) { timer1_write(microsecondsToClockCycles(1)); } while (waveformToDisable) { @@ -256,7 +258,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Exit the loop if the next event, if any, is sufficiently distant. - const uint32_t isrTimeoutCcy = isrStartCcy + microsecondsToClockCycles(12); + const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; uint32_t now = ESP.getCycleCount(); uint32_t nextEventCcy = now + MAXIRQCCYS; bool busy = waveformsEnabled; From 9d29fc2c61d2795da6df74e4537ddcbc5447755d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 20 Apr 2020 21:29:27 +0200 Subject: [PATCH 069/152] Fix broken multi-wave generation. --- cores/esp8266/core_esp8266_waveform.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b9ad42d14a..48797ef49d 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -260,9 +260,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; uint32_t now = ESP.getCycleCount(); - uint32_t nextEventCcy = now + MAXIRQCCYS; + uint32_t nextTimerCcy; bool busy = waveformsEnabled; - if (busy) { + if (!busy) { + nextTimerCcy = now + MAXIRQCCYS; + } + else { for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore if (!(waveformsEnabled & (1UL << pin))) @@ -290,6 +293,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } while (busy) { + nextTimerCcy = now + MAXIRQCCYS; for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore if (!(waveformsEnabled & (1UL << pin))) @@ -298,7 +302,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveforms[pin]; const bool duty = waveformsState & (1UL << pin); - nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; + uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) >= 0) { if (static_cast(now - wave.expiryCcy) >= 0) { @@ -339,7 +343,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { - const uint32_t skipPeriodCcys = (static_cast(overshootCcys) > wave.periodCcys) ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; + const uint32_t skipPeriodCcys = (static_cast(overshootCcys) >= wave.periodCcys) ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; wave.nextPhaseCcy += skipPeriodCcys; if (!wave.dutyCcys) { wave.nextPhaseCcy += wave.periodCcys; @@ -361,9 +365,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } + if (static_cast(nextTimerCcy - nextEventCcy) > 0) { + nextTimerCcy = nextEventCcy; + } now = ESP.getCycleCount(); } - const int32_t timerMarginCcys = isrTimeoutCcy - nextEventCcy; + const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = waveformsEnabled && timerMarginCcys > 0; } @@ -371,12 +378,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys = nextEventCcy - ESP.getCycleCount(); + nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) nextTimerCcys = callbackCcys; } else { - nextTimerCcys = nextEventCcy - now; + nextTimerCcys = nextTimerCcy - now; } // Firing timer too soon, the NMI occurs before ISR has returned. From 244153e0cfdd4080873ea52f10a9185193c184fb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 21 Apr 2020 12:47:41 +0200 Subject: [PATCH 070/152] Aggregate GPIO output across inner loop. True phase sync, and now better performance. From e149284e9ccfb0834f7826e7e681808b513d28b4 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 21 Apr 2020 15:18:19 +0200 Subject: [PATCH 071/152] IRQ latency can be reduced from 2 to 1 us now, no WDT etc. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 48797ef49d..36edcdcba4 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -60,7 +60,7 @@ constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? (microsecondsToClockCycles(11) / 10) >> 1 : (microsecondsToClockCycles(11) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) From ab706335f203c3d3b1b221cde0da5ba550fe5c7c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 21 Apr 2020 19:55:36 +0200 Subject: [PATCH 072/152] Improved handling of complete idle cycle miss, progress directly into duty cycle. --- cores/esp8266/core_esp8266_waveform.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 36edcdcba4..fa4917d796 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -318,19 +318,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (overshootCcys >= 0) { if (duty) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; - const uint32_t skipPeriodCcys = ((overshootCcys + wave.dutyCcys) > wave.periodCcys) ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys : 0; - if (wave.dutyCcys == wave.periodCcys) { + uint32_t skipPeriodCcys = 0; + if (overshootCcys + wave.dutyCcys >= wave.periodCcys) { + skipPeriodCcys = ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys; wave.nextPhaseCcy += skipPeriodCcys; + } + if (wave.dutyCcys == wave.periodCcys) { wave.nextOffCcy = wave.nextPhaseCcy; nextEventCcy = wave.nextPhaseCcy; } else if (endOfPeriod) { // preceeding period had zero off cycle, continue direct into new duty cycle - wave.nextPhaseCcy += skipPeriodCcys; wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; wave.nextPhaseCcy += wave.periodCcys; nextEventCcy = wave.nextOffCcy; } + else if (skipPeriodCcys) { + // complete off cycle overshoot, continue direct into new duty cycle + wave.nextOffCcy = wave.nextPhaseCcy - wave.periodCcys + wave.dutyCcys; + nextEventCcy = wave.nextOffCcy; + } else { waveformsState ^= 1UL << pin; nextEventCcy = wave.nextPhaseCcy; @@ -343,8 +350,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { - const uint32_t skipPeriodCcys = (static_cast(overshootCcys) >= wave.periodCcys) ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; - wave.nextPhaseCcy += skipPeriodCcys; + if (static_cast(overshootCcys) >= wave.periodCcys) { + wave.nextPhaseCcy += (overshootCcys / wave.periodCcys) * wave.periodCcys; + } if (!wave.dutyCcys) { wave.nextPhaseCcy += wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy; @@ -379,8 +387,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); // Account for unknown duration of timer1CB(). nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); - if (nextTimerCcys > callbackCcys) + if (nextTimerCcys > callbackCcys) { nextTimerCcys = callbackCcys; + } } else { nextTimerCcys = nextTimerCcy - now; From 4ba59dc55c501b48ee9c4378c7fdc9e80ef4ea28 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 02:58:37 +0200 Subject: [PATCH 073/152] Recalibrated after latest changes and reverts. --- cores/esp8266/core_esp8266_waveform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fa4917d796..baa7c382d0 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -54,13 +54,13 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(12); // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(5) / 2) >> 1 : microsecondsToClockCycles(5) / 2; + microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); // The generator has a measurable time quantum for switching wave cycles during the same ISR invocation constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? (microsecondsToClockCycles(11) / 10) >> 1 : (microsecondsToClockCycles(11) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) From a3bf76113379c851e035a9f4b9701818d38b0028 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 03:29:26 +0200 Subject: [PATCH 074/152] Overshoot compensation for duty cycle results in PWM milestone. --- cores/esp8266/core_esp8266_waveform.cpp | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index baa7c382d0..d61aae1908 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -142,22 +142,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum - if (highCcys < lowCcys) - { - if (highCcys < QUANTUM) { - highCcys = 0; - } - else if (highCcys < ISRTIMEOUTCCYS) { - highCcys += QUANTUM / 2; - } + if (highCcys < QUANTUM) { + highCcys = 0; } - else { - if (lowCcys < QUANTUM) { - highCcys = periodCcys; - } - else if (lowCcys < ISRTIMEOUTCCYS) { - highCcys -= QUANTUM / 2; - } + else if (lowCcys < QUANTUM) { + highCcys = periodCcys; } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || @@ -350,17 +339,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { - if (static_cast(overshootCcys) >= wave.periodCcys) { - wave.nextPhaseCcy += (overshootCcys / wave.periodCcys) * wave.periodCcys; - } + uint32_t skipPeriodCcys = static_cast(overshootCcys) >= wave.periodCcys ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; if (!wave.dutyCcys) { - wave.nextPhaseCcy += wave.periodCcys; + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; wave.nextOffCcy = wave.nextPhaseCcy; } else { waveformsState ^= 1UL << pin; - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; - wave.nextPhaseCcy += wave.periodCcys; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; + wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } From e2fe47764d44ecd7769286b4548d95521b45d1d9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 11:39:23 +0200 Subject: [PATCH 075/152] Adjustments to duty/idle cycle to mitigate effects of floating duty cycle logic. --- cores/esp8266/core_esp8266_waveform.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index d61aae1908..d15cbc4fc1 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -142,12 +142,19 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { const auto periodCcys = highCcys + lowCcys; // correct the upward bias for duty cycles shorter than generator quantum - if (highCcys < QUANTUM) { + if (highCcys <= QUANTUM / 2) { highCcys = 0; } - else if (lowCcys < QUANTUM) { + else if (highCcys < ISRTIMEOUTCCYS - QUANTUM) + { + highCcys -= QUANTUM / 2; + } + else if (lowCcys <= QUANTUM / 2) { highCcys = periodCcys; } + else if (lowCcys < ISRTIMEOUTCCYS) { + highCcys -= 3 * QUANTUM / 2 * (ISRTIMEOUTCCYS - lowCcys) / ISRTIMEOUTCCYS; + } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { From 3286bf74083c4a43a338888d4cb7beb3f2339387 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 14:02:51 +0200 Subject: [PATCH 076/152] Remove implicit condition from loop guard and fix timer restart duration --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index d15cbc4fc1..34ef9ad3ca 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -373,7 +373,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; - busy = waveformsEnabled && timerMarginCcys > 0; + busy = timerMarginCcys > 0; } int32_t nextTimerCcys; From 237bdfdfc59f14da7ebf800d9b86dcbd90959ec1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 15:24:03 +0200 Subject: [PATCH 077/152] Host all static globals in an anonymous static struct. --- cores/esp8266/core_esp8266_waveform.cpp | 94 +++++++++++++------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 34ef9ad3ca..a48e208e23 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -83,20 +83,26 @@ typedef struct { int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin } Waveform; -static Waveform waveforms[17]; // State of all possible pins -static uint32_t waveformsState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code -static volatile uint32_t waveformsEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code +namespace { -// Enable lock-free by only allowing updates to waveformsState and waveformsEnabled from IRQ service routine -static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start exactly one waveform on a inactive pin -static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + static struct { + Waveform pins[17]; // State of all possible pins + uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code + volatile uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code -static uint32_t (*timer1CB)() = nullptr; + // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine + volatile uint32_t toEnable = 0; // Message to the NMI handler to start exactly one waveform on a inactive pin + volatile uint32_t toDisable = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + + uint32_t(*timer1CB)() = nullptr; + + bool timer1Running = false; + } waveform; + +} // Interrupt on/off control static ICACHE_RAM_ATTR void timer1Interrupt(); -static bool timerRunning = false; - // Non-speed critical bits #pragma GCC optimize ("Os") @@ -106,24 +112,24 @@ static void initTimer() { ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); - timerRunning = true; + waveform.timer1Running = true; } static void ICACHE_RAM_ATTR deinitTimer() { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); timer1_disable(); timer1_isr_init(); - timerRunning = false; + waveform.timer1Running = false; } // Set a callback. Pass in NULL to stop it void setTimer1Callback(uint32_t (*fn)()) { - timer1CB = fn; + waveform.timer1CB = fn; std::atomic_thread_fence(std::memory_order_release); - if (!timerRunning && fn) { + if (!waveform.timer1Running && fn) { initTimer(); timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste - } else if (timerRunning && !fn && !waveformsEnabled) { + } else if (waveform.timer1Running && !fn && !waveform.enabled) { deinitTimer(); } } @@ -160,11 +166,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, static_cast(periodCcys) <= 0 || highCcys > periodCcys) { return false; } - Waveform& wave = waveforms[pin]; + Waveform& wave = waveform.pins[pin]; wave.dutyCcys = highCcys; wave.periodCcys = periodCcys; - if (!(waveformsEnabled & (1UL << pin))) { + if (!(waveform.enabled & (1UL << pin))) { // wave.nextPhaseCcy and wave.nextOffCcy are initialized by the ISR wave.nextPhaseCcy = phaseOffsetCcys; wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count @@ -180,8 +186,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } } std::atomic_thread_fence(std::memory_order_release); - waveformToEnable = 1UL << pin; - if (!timerRunning) { + waveform.toEnable = 1UL << pin; + if (!waveform.timer1Running) { initTimer(); timer1_write(microsecondsToClockCycles(1)); } @@ -189,7 +195,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load timer1_write(microsecondsToClockCycles(1)); } - while (waveformToEnable) { + while (waveform.toEnable) { delay(0); // Wait for waveform to update } } @@ -209,22 +215,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // 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) { + if (!waveform.timer1Running) { return false; } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (waveformsEnabled & (1UL << pin)) { - waveformToDisable = 1UL << pin; + if (waveform.enabled & (1UL << pin)) { + waveform.toDisable = 1UL << pin; // Must not interfere if Timer is due shortly if (T1L > IRQLATENCY + DELTAIRQ) { timer1_write(microsecondsToClockCycles(1)); } - while (waveformToDisable) { + while (waveform.toDisable) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } } - if (!waveformsEnabled && !timer1CB) { + if (!waveform.enabled && !waveform.timer1CB) { deinitTimer(); } return true; @@ -242,38 +248,38 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); - if (waveformToEnable || waveformToDisable) { + if (waveform.toEnable || waveform.toDisable) { // Handle enable/disable requests from main app. - waveformsEnabled = (waveformsEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off - waveformToEnable = 0; - waveformToDisable = 0; + waveform.enabled = (waveform.enabled & ~waveform.toDisable) | waveform.toEnable; // Set the requested waveforms on/off + waveform.toEnable = 0; + waveform.toDisable = 0; // 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) - startPin = __builtin_ffs(waveformsEnabled) - 1; + startPin = __builtin_ffs(waveform.enabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - endPin = 32 - __builtin_clz(waveformsEnabled); + endPin = 32 - __builtin_clz(waveform.enabled); } // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; uint32_t now = ESP.getCycleCount(); uint32_t nextTimerCcy; - bool busy = waveformsEnabled; + bool busy = waveform.enabled; if (!busy) { nextTimerCcy = now + MAXIRQCCYS; } else { for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore - if (!(waveformsEnabled & (1UL << pin))) + if (!(waveform.enabled & (1UL << pin))) continue; - Waveform& wave = waveforms[pin]; + Waveform& wave = waveform.pins[pin]; switch (wave.mode) { case WaveformMode::INIT: - waveformsState &= ~(1UL << pin); // Clear the state of any just started - wave.nextPhaseCcy = (waveformsEnabled & (1UL << wave.alignPhase)) ? - waveforms[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; + waveform.states &= ~(1UL << pin); // Clear the state of any just started + wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? + waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; break; @@ -292,18 +298,18 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcy = now + MAXIRQCCYS; for (int pin = startPin; pin <= endPin; ++pin) { // If it's not on, ignore - if (!(waveformsEnabled & (1UL << pin))) + if (!(waveform.enabled & (1UL << pin))) continue; - Waveform& wave = waveforms[pin]; + Waveform& wave = waveform.pins[pin]; - const bool duty = waveformsState & (1UL << pin); + const bool duty = waveform.states & (1UL << pin); uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) >= 0) { if (static_cast(now - wave.expiryCcy) >= 0) { // Disable any waveforms that are done - waveformsEnabled ^= 1UL << pin; + waveform.enabled ^= 1UL << pin; } else { nextEventCcy = wave.expiryCcy; @@ -335,7 +341,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEventCcy = wave.nextOffCcy; } else { - waveformsState ^= 1UL << pin; + waveform.states ^= 1UL << pin; nextEventCcy = wave.nextPhaseCcy; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW @@ -352,7 +358,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextOffCcy = wave.nextPhaseCcy; } else { - waveformsState ^= 1UL << pin; + waveform.states ^= 1UL << pin; wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; if (pin == 16) { @@ -377,8 +383,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } int32_t nextTimerCcys; - if (timer1CB) { - int32_t callbackCcys = microsecondsToClockCycles(timer1CB()); + if (waveform.timer1CB) { + int32_t callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); // Account for unknown duration of timer1CB(). nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) { From 6e352ea6f7bd5c8e1f9796d858cde924050155df Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 20:48:41 +0200 Subject: [PATCH 078/152] Busy wait directly for next pending event and go to that pin. --- cores/esp8266/core_esp8266_waveform.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a48e208e23..f008fdbcd1 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -294,9 +294,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } } + int nextPin = startPin; while (busy) { nextTimerCcy = now + MAXIRQCCYS; - for (int pin = startPin; pin <= endPin; ++pin) { + int stopPin = nextPin; + int pin = nextPin; + do { // If it's not on, ignore if (!(waveform.enabled & (1UL << pin))) continue; @@ -375,11 +378,18 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (static_cast(nextTimerCcy - nextEventCcy) > 0) { nextTimerCcy = nextEventCcy; + nextPin = pin; } now = ESP.getCycleCount(); - } + } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); + const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; + if (busy) { + while (static_cast(nextTimerCcy - now) > 0) { + now = ESP.getCycleCount(); + } + } } int32_t nextTimerCcys; From 5b6be8b072fa2a2d20a350d5d4771cf1b4728803 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 22 Apr 2020 23:56:09 +0200 Subject: [PATCH 079/152] Record nextEventCcy in waveform struct to save a few cycles. --- cores/esp8266/core_esp8266_waveform.cpp | 43 ++++++++++++------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index f008fdbcd1..802f85b775 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -74,6 +74,7 @@ enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { + uint32_t nextEventCcy; // ESP clock cycle when switching wave cycle, or expiring wave. uint32_t nextPhaseCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count uint32_t nextOffCcy; // ESP clock cycle when going from duty to off uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase @@ -280,6 +281,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states &= ~(1UL << pin); // Clear the state of any just started wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; + wave.nextEventCcy = wave.nextPhaseCcy; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; break; @@ -305,23 +307,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { continue; Waveform& wave = waveform.pins[pin]; - - const bool duty = waveform.states & (1UL << pin); - uint32_t nextEventCcy = duty ? wave.nextOffCcy : wave.nextPhaseCcy; - - if (WaveformMode::EXPIRES == wave.mode && static_cast(nextEventCcy - wave.expiryCcy) >= 0) { - if (static_cast(now - wave.expiryCcy) >= 0) { + const int32_t overshootCcys = now - wave.nextEventCcy; + if (overshootCcys >= 0) { + if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done waveform.enabled ^= 1UL << pin; } else { - nextEventCcy = wave.expiryCcy; - } - } - else { - const int32_t overshootCcys = now - nextEventCcy; - if (overshootCcys >= 0) { - if (duty) { + uint32_t nextEdgeCcy; + if (waveform.states & (1UL << pin)) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; uint32_t skipPeriodCcys = 0; if (overshootCcys + wave.dutyCcys >= wave.periodCcys) { @@ -330,22 +324,22 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } if (wave.dutyCcys == wave.periodCcys) { wave.nextOffCcy = wave.nextPhaseCcy; - nextEventCcy = wave.nextPhaseCcy; + nextEdgeCcy = wave.nextPhaseCcy; } else if (endOfPeriod) { // preceeding period had zero off cycle, continue direct into new duty cycle wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; wave.nextPhaseCcy += wave.periodCcys; - nextEventCcy = wave.nextOffCcy; + nextEdgeCcy = wave.nextOffCcy; } else if (skipPeriodCcys) { // complete off cycle overshoot, continue direct into new duty cycle wave.nextOffCcy = wave.nextPhaseCcy - wave.periodCcys + wave.dutyCcys; - nextEventCcy = wave.nextOffCcy; + nextEdgeCcy = wave.nextOffCcy; } else { waveform.states ^= 1UL << pin; - nextEventCcy = wave.nextPhaseCcy; + nextEdgeCcy = wave.nextPhaseCcy; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -371,25 +365,30 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } } - nextEventCcy = wave.nextOffCcy; + nextEdgeCcy = wave.nextOffCcy; } + + wave.nextEventCcy = + (WaveformMode::EXPIRES == wave.mode && static_cast(nextEdgeCcy - wave.expiryCcy) > 0) ? + wave.expiryCcy : nextEdgeCcy; } } - if (static_cast(nextTimerCcy - nextEventCcy) > 0) { - nextTimerCcy = nextEventCcy; + if (static_cast(nextTimerCcy - wave.nextEventCcy) > 0) { + nextTimerCcy = wave.nextEventCcy; nextPin = pin; } + now = ESP.getCycleCount(); } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; - if (busy) { + if (busy) { while (static_cast(nextTimerCcy - now) > 0) { now = ESP.getCycleCount(); } - } + } } int32_t nextTimerCcys; From 25dbff5d3e2d830e34dd972d2106ecaf1a362dcd Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 01:52:06 +0200 Subject: [PATCH 080/152] Adapt duty cycle modification to only fix full duty and all idle cases. --- cores/esp8266/core_esp8266_waveform.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 802f85b775..90c100a11a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -152,16 +152,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (highCcys <= QUANTUM / 2) { highCcys = 0; } - else if (highCcys < ISRTIMEOUTCCYS - QUANTUM) - { - highCcys -= QUANTUM / 2; - } else if (lowCcys <= QUANTUM / 2) { highCcys = periodCcys; } - else if (lowCcys < ISRTIMEOUTCCYS) { - highCcys -= 3 * QUANTUM / 2 * (ISRTIMEOUTCCYS - lowCcys) / ISRTIMEOUTCCYS; - } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { From 9f79f4322359576a60f13f1be73cb2e8f5875bf2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 11:29:16 +0200 Subject: [PATCH 081/152] Remember next pin to operate between IRQs. --- cores/esp8266/core_esp8266_waveform.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 90c100a11a..4e60b70b19 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -239,6 +239,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // we can avoid looking at the other pins. static int startPin = 0; static int endPin = 0; + static int nextPin = 0; const uint32_t isrStartCcy = ESP.getCycleCount(); @@ -272,8 +273,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { switch (wave.mode) { case WaveformMode::INIT: waveform.states &= ~(1UL << pin); // Clear the state of any just started - wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? - waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; + if (waveform.enabled & (1UL << wave.alignPhase)) { + wave.nextPhaseCcy = waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy; + } + else { + wave.nextPhaseCcy = now; + // Immediately due, go straight to it. Also good for initial wave. + nextPin = pin; + } wave.nextEventCcy = wave.nextPhaseCcy; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; @@ -289,7 +296,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } } - int nextPin = startPin; while (busy) { nextTimerCcy = now + MAXIRQCCYS; int stopPin = nextPin; From 936985e2ed005a7771da75997740a545c8830fa0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 13:38:04 +0200 Subject: [PATCH 082/152] Don't set pinMode each time on already running PWM or Tone. --- cores/esp8266/Tone.cpp | 8 +++++--- cores/esp8266/core_esp8266_wiring_pwm.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 064fdad5df..27726b28d0 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -34,7 +34,9 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat return; } - pinMode(_pin, OUTPUT); + if (!(_toneMap & 1UL << _pin)) { + pinMode(_pin, OUTPUT); + } high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) @@ -43,7 +45,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat duration += high + low - 1; duration -= duration % (high + low); if (startWaveformClockCycles(_pin, high, low, duration)) { - _toneMap |= 1 << _pin; + _toneMap |= 1UL << _pin; } } @@ -86,6 +88,6 @@ void noTone(uint8_t _pin) { return; } stopWaveform(_pin); - _toneMap &= ~(1 << _pin); + _toneMap &= ~(1UL << _pin); digitalWrite(_pin, 0); } diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index ca62e2900f..7f2c57bba2 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -63,14 +63,18 @@ extern void __analogWrite(uint8_t pin, int val) { val = analogScale; } + if (analogMap & 1UL << pin) { // Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ // val: the duty cycle: between 0 (always off) and 255 (always on). // So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH) - analogMap &= ~(1 << pin); + analogMap &= ~(1 << pin); + } + else { + pinMode(pin, OUTPUT); + } uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; - pinMode(pin, OUTPUT); // 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) int phaseReference = __builtin_ffs(analogMap) - 1; if (startWaveformClockCycles(pin, high, low, 0, phaseReference)) { From 5fc33d0f606bea55ea4e03150386cb1326225ce6 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 15:14:57 +0200 Subject: [PATCH 083/152] Remove quantum, correct irq latency from testing,reuse isr timeout from master et al --- cores/esp8266/core_esp8266_waveform.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4e60b70b19..fcf90485f8 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -49,18 +49,15 @@ extern "C" { // Maximum delay between IRQs, 1Hz constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(1000000); // Maximum servicing time for any single IRQ -constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(12); +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); -// The generator has a measurable time quantum for switching wave cycles during the same ISR invocation -constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(11) / 10) >> 1 : (microsecondsToClockCycles(11) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -148,13 +145,6 @@ int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { const auto periodCcys = highCcys + lowCcys; - // correct the upward bias for duty cycles shorter than generator quantum - if (highCcys <= QUANTUM / 2) { - highCcys = 0; - } - else if (lowCcys <= QUANTUM / 2) { - highCcys = periodCcys; - } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { From a23506668e2b853b99b222c4e17df383cf8f15fc Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 17:34:42 +0200 Subject: [PATCH 084/152] Move updating "now" out of inner loop, prevents float between pins that are in phase lock. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fcf90485f8..96006c8548 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -368,9 +368,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextPin = pin; } - now = ESP.getCycleCount(); } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); + now = ESP.getCycleCount(); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; if (busy) { From b0bf1447397c775c32924729c8c71e757376352f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 17:49:33 +0200 Subject: [PATCH 085/152] Merge init loop with action loop again. --- cores/esp8266/core_esp8266_waveform.cpp | 60 +++++++++++-------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 96006c8548..7a0873ce0c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -252,40 +252,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (!busy) { nextTimerCcy = now + MAXIRQCCYS; } - else { - for (int pin = startPin; pin <= endPin; ++pin) { - // If it's not on, ignore - if (!(waveform.enabled & (1UL << pin))) - continue; - - Waveform& wave = waveform.pins[pin]; - - switch (wave.mode) { - case WaveformMode::INIT: - waveform.states &= ~(1UL << pin); // Clear the state of any just started - if (waveform.enabled & (1UL << wave.alignPhase)) { - wave.nextPhaseCcy = waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy; - } - else { - wave.nextPhaseCcy = now; - // Immediately due, go straight to it. Also good for initial wave. - nextPin = pin; - } - wave.nextEventCcy = wave.nextPhaseCcy; - if (!wave.expiryCcy) { - wave.mode = WaveformMode::INFINITE; - break; - } - // fall through - case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave.mode = WaveformMode::EXPIRES; - break; - default: - break; - } - } + else if (!(waveform.enabled & (1UL << nextPin))) { + nextPin = startPin; } + bool initPins = true; while (busy) { nextTimerCcy = now + MAXIRQCCYS; int stopPin = nextPin; @@ -296,6 +266,29 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { continue; Waveform& wave = waveform.pins[pin]; + + if (initPins) { + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~(1UL << pin); // Clear the state of any just started + wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? + waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; + wave.nextEventCcy = wave.nextPhaseCcy; + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.mode = WaveformMode::EXPIRES; + initPins = false; // only one pin per IRQ + break; + default: + break; + } + } + const int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { @@ -370,6 +363,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); + initPins = false; now = ESP.getCycleCount(); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; From 948601ce90f7532488a00ab5463d84c04a7882cd Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 23 Apr 2020 22:53:41 +0200 Subject: [PATCH 086/152] Adaptive PWM frequency and floating duty cycle. --- cores/esp8266/core_esp8266_waveform.cpp | 39 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 7a0873ce0c..4d8d4476cc 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -296,32 +296,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.enabled ^= 1UL << pin; } else { + const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; + const uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? + ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; - uint32_t skipPeriodCcys = 0; - if (overshootCcys + wave.dutyCcys >= wave.periodCcys) { - skipPeriodCcys = ((overshootCcys + wave.dutyCcys) / wave.periodCcys) * wave.periodCcys; - wave.nextPhaseCcy += skipPeriodCcys; + if (fwdPeriods) { + wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; } - if (wave.dutyCcys == wave.periodCcys) { + if (!idleCcys) { wave.nextOffCcy = wave.nextPhaseCcy; nextEdgeCcy = wave.nextPhaseCcy; } else if (endOfPeriod) { - // preceeding period had zero off cycle, continue direct into new duty cycle + // preceeding period had zero idle cycle, continue direct into new duty cycle wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; wave.nextPhaseCcy += wave.periodCcys; nextEdgeCcy = wave.nextOffCcy; } - else if (skipPeriodCcys) { - // complete off cycle overshoot, continue direct into new duty cycle - wave.nextOffCcy = wave.nextPhaseCcy - wave.periodCcys + wave.dutyCcys; + else if (fwdPeriods) { + // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods + wave.nextOffCcy = wave.nextPhaseCcy - (fwdPeriods + 1) * idleCcys; nextEdgeCcy = wave.nextOffCcy; } else { waveform.states ^= 1UL << pin; - nextEdgeCcy = wave.nextPhaseCcy; + nextEdgeCcy = wave.nextPhaseCcy + overshootCcys; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -331,15 +332,25 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } else { - uint32_t skipPeriodCcys = static_cast(overshootCcys) >= wave.periodCcys ? (overshootCcys / wave.periodCcys) * wave.periodCcys : 0; if (!wave.dutyCcys) { - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + wave.nextPhaseCcy += wave.periodCcys + fwdPeriods * wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy; } else { waveform.states ^= 1UL << pin; - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; - wave.nextPhaseCcy += wave.periodCcys + skipPeriodCcys; + if (fwdPeriods) + { + const uint32_t fwdPeriodsCcys = fwdPeriods * wave.periodCcys; + // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods + wave.nextOffCcy = + wave.nextPhaseCcy + fwdPeriods * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriodsCcys); + wave.nextPhaseCcy += fwdPeriodsCcys; + } + else + { + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; + wave.nextPhaseCcy += wave.periodCcys; + } if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW } From 944575543da6649fc39745ff22269189414d52d0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 24 Apr 2020 14:46:16 +0200 Subject: [PATCH 087/152] Predictive static frequency scaling. --- cores/esp8266/core_esp8266_waveform.cpp | 58 +++++++++++++++++-------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4d8d4476cc..5ac05e995e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -55,6 +55,9 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); +// The generator has a measurable time quantum for switching wave cycles during the same ISR invocation +constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? + (microsecondsToClockCycles(50) / 10) >> 1 : (microsecondsToClockCycles(50) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); @@ -144,7 +147,28 @@ int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, // first, then it will immediately begin. int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { - const auto periodCcys = highCcys + lowCcys; + uint32_t periodCcys = highCcys + lowCcys; + // correct period for cycles shorter than generator quantum + if (highCcys < QUANTUM) { + if (highCcys < QUANTUM / 2) { + highCcys = 0; + periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + } + else { + highCcys *= 5; + periodCcys *= 5; + } + } + else if (lowCcys < QUANTUM) { + if (lowCcys < QUANTUM / 2) { + periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + highCcys = periodCcys; + } + else { + highCcys *= 5; + periodCcys *= 5; + } + } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { @@ -268,26 +292,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; if (initPins) { - switch (wave.mode) { - case WaveformMode::INIT: - waveform.states &= ~(1UL << pin); // Clear the state of any just started + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~(1UL << pin); // Clear the state of any just started wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; - wave.nextEventCcy = wave.nextPhaseCcy; - if (!wave.expiryCcy) { - wave.mode = WaveformMode::INFINITE; - break; - } - // fall through - case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave.mode = WaveformMode::EXPIRES; - initPins = false; // only one pin per IRQ - break; - default: + wave.nextEventCcy = wave.nextPhaseCcy; + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; break; } + // fall through + case WaveformMode::UPDATEEXPIRY: + wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.mode = WaveformMode::EXPIRES; + initPins = false; // only one pin per IRQ + break; + default: + break; } + } const int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { @@ -348,7 +372,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; wave.nextPhaseCcy += wave.periodCcys; } if (pin == 16) { From 0ec53e47eb131cb4ff83bf8a31afc8547ee4d5b3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 24 Apr 2020 15:16:24 +0200 Subject: [PATCH 088/152] Dynamic frequency down-scaling --- cores/esp8266/core_esp8266_waveform.cpp | 30 +++++-------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5ac05e995e..d2d03f723d 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -55,9 +55,6 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); -// The generator has a measurable time quantum for switching wave cycles during the same ISR invocation -constexpr uint32_t QUANTUM = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(50) / 10) >> 1 : (microsecondsToClockCycles(50) / 10); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); @@ -148,27 +145,6 @@ int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { uint32_t periodCcys = highCcys + lowCcys; - // correct period for cycles shorter than generator quantum - if (highCcys < QUANTUM) { - if (highCcys < QUANTUM / 2) { - highCcys = 0; - periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; - } - else { - highCcys *= 5; - periodCcys *= 5; - } - } - else if (lowCcys < QUANTUM) { - if (lowCcys < QUANTUM / 2) { - periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; - highCcys = periodCcys; - } - else { - highCcys *= 5; - periodCcys *= 5; - } - } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { @@ -341,7 +317,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else if (fwdPeriods) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods + // plus dynamically scale frequency wave.nextOffCcy = wave.nextPhaseCcy - (fwdPeriods + 1) * idleCcys; + wave.dutyCcys *= 2; + wave.periodCcys *= 2; nextEdgeCcy = wave.nextOffCcy; } else { @@ -366,9 +345,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { { const uint32_t fwdPeriodsCcys = fwdPeriods * wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods + // plus dynamically scale frequency wave.nextOffCcy = wave.nextPhaseCcy + fwdPeriods * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriodsCcys); wave.nextPhaseCcy += fwdPeriodsCcys; + wave.dutyCcys *= 2; + wave.periodCcys *= 2; } else { From 1d0f9146574e0233dc6b48c34e08d78ba51e33f2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 24 Apr 2020 19:22:39 +0200 Subject: [PATCH 089/152] Frequency scaling is only for PWM-like applications, anything needing real time duty cycles or frequency must be able to fail on overload. --- cores/esp8266/core_esp8266_waveform.cpp | 18 ++++++++++++++---- cores/esp8266/core_esp8266_waveform.h | 8 ++++++-- cores/esp8266/core_esp8266_wiring_pwm.cpp | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index d2d03f723d..b814ba12d7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -79,6 +79,7 @@ typedef struct { uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings } Waveform; namespace { @@ -133,17 +134,17 @@ void setTimer1Callback(uint32_t (*fn)()) { } int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, - uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS) { + uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { return startWaveformClockCycles(pin, microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS), - microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS)); + microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm); } // 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 startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, - uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys) { + uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { uint32_t periodCcys = highCcys + lowCcys; // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || @@ -153,6 +154,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, Waveform& wave = waveform.pins[pin]; wave.dutyCcys = highCcys; wave.periodCcys = periodCcys; + wave.autoPwm = autoPwm; if (!(waveform.enabled & (1UL << pin))) { // wave.nextPhaseCcy and wave.nextOffCcy are initialized by the ISR @@ -303,6 +305,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveform.states & (1UL << pin)) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; if (fwdPeriods) { + // for hard real time, like servos, this is unacceptable + if (!wave.autoPwm) { + panic(); + } wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; } if (!idleCcys) { @@ -343,6 +349,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states ^= 1UL << pin; if (fwdPeriods) { + // for hard real time, like servos, this is unacceptable + if (!wave.autoPwm) { + panic(); + } const uint32_t fwdPeriodsCcys = fwdPeriods * wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods // plus dynamically scale frequency @@ -354,7 +364,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; + wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; wave.nextPhaseCcy += wave.periodCcys; } if (pin == 16) { diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 3de9b5d644..61cb999664 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -53,17 +53,21 @@ extern "C" { // full period. // If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, // the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, - uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0); + uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false); // Start or change a waveform of the specified high and low CPU clock cycles on specific pin. // If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next // full period. // If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, // the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. // Returns true or false on success or failure. int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, - uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0); + uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index 7f2c57bba2..6f655716db 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -77,7 +77,7 @@ extern void __analogWrite(uint8_t pin, int val) { uint32_t low = analogPeriod - high; // 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) int phaseReference = __builtin_ffs(analogMap) - 1; - if (startWaveformClockCycles(pin, high, low, 0, phaseReference)) { + if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) { analogMap |= (1 << pin); } } From f8e07f50901117f31dbaedcfb5c01150aa6d48cb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 24 Apr 2020 21:24:54 +0200 Subject: [PATCH 090/152] Conserve IRAM cache, resort to best effort. --- cores/esp8266/core_esp8266_waveform.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b814ba12d7..02053414a7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -291,8 +291,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } - const int32_t overshootCcys = now - wave.nextEventCcy; + int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { + if (!wave.autoPwm) { + // for best effort hard timings + overshootCcys = 0; + } if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done waveform.enabled ^= 1UL << pin; @@ -305,10 +309,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveform.states & (1UL << pin)) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; if (fwdPeriods) { - // for hard real time, like servos, this is unacceptable - if (!wave.autoPwm) { - panic(); - } wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; } if (!idleCcys) { @@ -349,10 +349,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states ^= 1UL << pin; if (fwdPeriods) { - // for hard real time, like servos, this is unacceptable - if (!wave.autoPwm) { - panic(); - } const uint32_t fwdPeriodsCcys = fwdPeriods * wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods // plus dynamically scale frequency From ea01c25a33167215b55324be5f35446d78369214 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 25 Apr 2020 01:20:29 +0200 Subject: [PATCH 091/152] Directly scale frequency for all duty/all idle waves to reasonable maximum, reduces thrashing. --- cores/esp8266/core_esp8266_waveform.cpp | 40 +++++++++++++++---------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 02053414a7..704fa83d46 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -47,7 +47,7 @@ extern "C" { // Maximum delay between IRQs, 1Hz -constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(1000000); +constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // The SDK and hardware take some time to actually get to our NMI code, so @@ -146,6 +146,14 @@ int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { uint32_t periodCcys = highCcys + lowCcys; + if (periodCcys < MAXIRQCCYS) { + if (!highCcys) { + periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + } + else if (!lowCcys) { + highCcys = periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + } + } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || highCcys > periodCcys) { @@ -270,26 +278,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; if (initPins) { - switch (wave.mode) { - case WaveformMode::INIT: - waveform.states &= ~(1UL << pin); // Clear the state of any just started + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~(1UL << pin); // Clear the state of any just started wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; - wave.nextEventCcy = wave.nextPhaseCcy; - if (!wave.expiryCcy) { - wave.mode = WaveformMode::INFINITE; + wave.nextEventCcy = wave.nextPhaseCcy; + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.mode = WaveformMode::EXPIRES; + initPins = false; // only one pin per IRQ + break; + default: break; } - // fall through - case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave.mode = WaveformMode::EXPIRES; - initPins = false; // only one pin per IRQ - break; - default: - break; } - } int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { From d17a7b9129de7af187b2a640d2278a945fa1627e Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 25 Apr 2020 16:33:41 +0200 Subject: [PATCH 092/152] Getting the math right beats permanently reducing PWM frequency. --- cores/esp8266/core_esp8266_waveform.cpp | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 704fa83d46..3b4c14827c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -310,31 +310,31 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.enabled ^= 1UL << pin; } else { + // get true accumulated overshoot + overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.nextOffCcy : wave.nextPhaseCcy); const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; - const uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? + uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; - if (fwdPeriods) { - wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; - } if (!idleCcys) { + wave.nextPhaseCcy += (fwdPeriods + 1) * wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy; nextEdgeCcy = wave.nextPhaseCcy; } else if (endOfPeriod) { // preceeding period had zero idle cycle, continue direct into new duty cycle + wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; wave.nextPhaseCcy += wave.periodCcys; nextEdgeCcy = wave.nextOffCcy; } else if (fwdPeriods) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods - // plus dynamically scale frequency - wave.nextOffCcy = wave.nextPhaseCcy - (fwdPeriods + 1) * idleCcys; - wave.dutyCcys *= 2; - wave.periodCcys *= 2; + fwdPeriods = (now + wave.periodCcys - wave.nextOffCcy) / wave.dutyCcys; + wave.nextOffCcy += fwdPeriods * wave.dutyCcys; + wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; nextEdgeCcy = wave.nextOffCcy; } else { @@ -350,21 +350,17 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPhaseCcy += wave.periodCcys + fwdPeriods * wave.periodCcys; + wave.nextPhaseCcy += (fwdPeriods + 1) * wave.periodCcys; wave.nextOffCcy = wave.nextPhaseCcy; } else { waveform.states ^= 1UL << pin; if (fwdPeriods) { - const uint32_t fwdPeriodsCcys = fwdPeriods * wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods - // plus dynamically scale frequency wave.nextOffCcy = - wave.nextPhaseCcy + fwdPeriods * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriodsCcys); - wave.nextPhaseCcy += fwdPeriodsCcys; - wave.dutyCcys *= 2; - wave.periodCcys *= 2; + wave.nextPhaseCcy + (1 * fwdPeriods + 1) * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriods * wave.periodCcys); + wave.nextPhaseCcy += (1 * fwdPeriods + 1) * wave.periodCcys; } else { From 2ff07a1d1406dfe82d4d9d919c8d32950635ba2a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 25 Apr 2020 21:29:30 +0200 Subject: [PATCH 093/152] Rename identifier to help think about the problem. --- cores/esp8266/core_esp8266_waveform.cpp | 65 +++++++++++++------------ 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 3b4c14827c..e5594f4efb 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -66,14 +66,14 @@ constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. // for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. -// for INIT, the NMI initializes nextPhaseCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. +// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextEventCcy; // ESP clock cycle when switching wave cycle, or expiring wave. - uint32_t nextPhaseCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count - uint32_t nextOffCcy; // ESP clock cycle when going from duty to off + uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count + uint32_t endDutyCcy; // ESP clock cycle when going from duty to off uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count @@ -165,8 +165,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, wave.autoPwm = autoPwm; if (!(waveform.enabled & (1UL << pin))) { - // wave.nextPhaseCcy and wave.nextOffCcy are initialized by the ISR - wave.nextPhaseCcy = phaseOffsetCcys; + // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR + wave.nextPeriodCcy = phaseOffsetCcys; wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave.mode = WaveformMode::INIT; wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; @@ -281,16 +281,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { switch (wave.mode) { case WaveformMode::INIT: waveform.states &= ~(1UL << pin); // Clear the state of any just started - wave.nextPhaseCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? - waveform.pins[wave.alignPhase].nextPhaseCcy + wave.nextPhaseCcy : now; - wave.nextEventCcy = wave.nextPhaseCcy; + wave.nextPeriodCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? + waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy : now; + wave.nextEventCcy = wave.nextPeriodCcy; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; break; } // fall through case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPhaseCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.expiryCcy += wave.nextPeriodCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count wave.mode = WaveformMode::EXPIRES; initPins = false; // only one pin per IRQ break; @@ -311,35 +311,37 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { // get true accumulated overshoot - overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.nextOffCcy : wave.nextPhaseCcy); + overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { - const bool endOfPeriod = wave.nextPhaseCcy == wave.nextOffCcy; + // up to and including this period 100% duty + const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; + // active configuration and forward 100% duty if (!idleCcys) { - wave.nextPhaseCcy += (fwdPeriods + 1) * wave.periodCcys; - wave.nextOffCcy = wave.nextPhaseCcy; - nextEdgeCcy = wave.nextPhaseCcy; + wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; + nextEdgeCcy = wave.nextPeriodCcy; } else if (endOfPeriod) { // preceeding period had zero idle cycle, continue direct into new duty cycle - wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys; - wave.nextPhaseCcy += wave.periodCcys; - nextEdgeCcy = wave.nextOffCcy; + wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; + wave.nextPeriodCcy += wave.periodCcys; + nextEdgeCcy = wave.endDutyCcy; } else if (fwdPeriods) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods - fwdPeriods = (now + wave.periodCcys - wave.nextOffCcy) / wave.dutyCcys; - wave.nextOffCcy += fwdPeriods * wave.dutyCcys; - wave.nextPhaseCcy += fwdPeriods * wave.periodCcys; - nextEdgeCcy = wave.nextOffCcy; + fwdPeriods = (overshootCcys + wave.periodCcys) / wave.dutyCcys; + wave.endDutyCcy += fwdPeriods * wave.dutyCcys; + wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + nextEdgeCcy = wave.endDutyCcy; } else { waveform.states ^= 1UL << pin; - nextEdgeCcy = wave.nextPhaseCcy + overshootCcys; + nextEdgeCcy = wave.nextPeriodCcy + overshootCcys; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -350,22 +352,23 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPhaseCcy += (fwdPeriods + 1) * wave.periodCcys; - wave.nextOffCcy = wave.nextPhaseCcy; + wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; } else { waveform.states ^= 1UL << pin; if (fwdPeriods) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods - wave.nextOffCcy = - wave.nextPhaseCcy + (1 * fwdPeriods + 1) * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriods * wave.periodCcys); - wave.nextPhaseCcy += (1 * fwdPeriods + 1) * wave.periodCcys; + wave.endDutyCcy = + wave.nextPeriodCcy + (fwdPeriods + 1) * wave.dutyCcys + + (overshootCcys + wave.dutyCcys - fwdPeriods * wave.periodCcys); + wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; } else { - wave.nextOffCcy = wave.nextPhaseCcy + wave.dutyCcys + overshootCcys; - wave.nextPhaseCcy += wave.periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys + overshootCcys; + wave.nextPeriodCcy += wave.periodCcys; } if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW @@ -374,7 +377,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { SetGPIO(1UL << pin); } } - nextEdgeCcy = wave.nextOffCcy; + nextEdgeCcy = wave.endDutyCcy; } wave.nextEventCcy = From a07f12c84dab6dfadc7d53d2f0abd85a0ce7e595 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 26 Apr 2020 09:54:31 +0200 Subject: [PATCH 094/152] AutoPwm correction moved to correct location - after overshoot recalc - and allow limited duty floating --- cores/esp8266/core_esp8266_waveform.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e5594f4efb..e80fccb2ea 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -301,10 +301,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { - if (!wave.autoPwm) { - // for best effort hard timings - overshootCcys = 0; - } if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done waveform.enabled ^= 1UL << pin; @@ -315,6 +311,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; + if (fwdPeriods && !wave.autoPwm) { + // for best effort hard timings - allow only limited duty cycle floating + fwdPeriods = 0; + overshootCcys = 0; + } uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { // up to and including this period 100% duty From d0a79d6b94d19a2702e2e2ab316bd1dc9f423f6c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 26 Apr 2020 13:03:12 +0200 Subject: [PATCH 095/152] Finish overshoot math fixes. --- cores/esp8266/core_esp8266_waveform.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e80fccb2ea..fbbc279ef8 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -318,9 +318,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { - // up to and including this period 100% duty + // up to and including this period 100% duty const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; - // active configuration and forward 100% duty + // active configuration and forward 100% duty if (!idleCcys) { wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; @@ -333,15 +333,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy; } - else if (fwdPeriods) { - // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods - fwdPeriods = (overshootCcys + wave.periodCcys) / wave.dutyCcys; - wave.endDutyCcy += fwdPeriods * wave.dutyCcys; - wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; - nextEdgeCcy = wave.endDutyCcy; - } else { waveform.states ^= 1UL << pin; + // the idle cycle code updating for the next period will approximate the duty/idle ratio. nextEdgeCcy = wave.nextPeriodCcy + overshootCcys; if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW @@ -360,7 +354,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states ^= 1UL << pin; if (fwdPeriods) { - // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by skipPeriods + // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods wave.endDutyCcy = wave.nextPeriodCcy + (fwdPeriods + 1) * wave.dutyCcys + (overshootCcys + wave.dutyCcys - fwdPeriods * wave.periodCcys); From 2360dfe23979fa4da64b827fea06df73bb0c441c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 28 Apr 2020 12:26:56 +0200 Subject: [PATCH 096/152] First set pin mode, then digital write. --- libraries/Servo/src/Servo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index d06c9aed23..cacbbebba4 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -69,8 +69,8 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value) { if (!_attached) { - digitalWrite(pin, LOW); pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); _pin = pin; _attached = true; } From 9d6130cf839b4cf449a783fe7450075672a55891 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 28 Apr 2020 16:40:44 +0200 Subject: [PATCH 097/152] Simplify calculations, fix non-autoPwm for servo use, where exact duty is needed, idle is elastic. --- cores/esp8266/core_esp8266_waveform.cpp | 39 +++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fbbc279ef8..44fdaacb12 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -54,7 +54,7 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); + microsecondsToClockCycles(4) >> 1 : microsecondsToClockCycles(4); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); @@ -306,18 +306,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.enabled ^= 1UL << pin; } else { + const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; // get true accumulated overshoot overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); - const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; - if (fwdPeriods && !wave.autoPwm) { - // for best effort hard timings - allow only limited duty cycle floating - fwdPeriods = 0; - overshootCcys = 0; - } uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { + if (!wave.autoPwm) { + overshootCcys = 0; + } // up to and including this period 100% duty const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; // active configuration and forward 100% duty @@ -328,7 +326,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else if (endOfPeriod) { // preceeding period had zero idle cycle, continue direct into new duty cycle - wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + if (fwdPeriods) { + wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + // adapt expiry such that it occurs during intended cycle + if (WaveformMode::EXPIRES == wave.mode) + wave.expiryCcy += fwdPeriods * wave.periodCcys; + } wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy; @@ -352,18 +355,18 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { waveform.states ^= 1UL << pin; + wave.endDutyCcy = now + wave.dutyCcys; + wave.nextPeriodCcy += wave.periodCcys; if (fwdPeriods) { - // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - wave.endDutyCcy = - wave.nextPeriodCcy + (fwdPeriods + 1) * wave.dutyCcys + - (overshootCcys + wave.dutyCcys - fwdPeriods * wave.periodCcys); - wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; - } - else - { - wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys + overshootCcys; - wave.nextPeriodCcy += wave.periodCcys; + if (wave.autoPwm) { + // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods + wave.endDutyCcy += (fwdPeriods + 1) * wave.dutyCcys - fwdPeriods * wave.periodCcys; + } + wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + // adapt expiry such that it occurs during intended cycle + if (WaveformMode::EXPIRES == wave.mode) + wave.expiryCcy += fwdPeriods * wave.periodCcys; } if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW From c8f300fc228ddc2bbbb37f9d93849d3a7ba19fb8 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 29 Apr 2020 11:34:05 +0200 Subject: [PATCH 098/152] Move wave initialization and modification outside the inner loop. --- cores/esp8266/core_esp8266_waveform.cpp | 80 ++++++++++++------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 44fdaacb12..e728f83585 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -90,8 +90,8 @@ namespace { volatile uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine - volatile uint32_t toEnable = 0; // Message to the NMI handler to start exactly one waveform on a inactive pin - volatile uint32_t toDisable = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + volatile int32_t toSet = -1; // Message to the NMI handler to start/modify exactly one waveform + volatile int32_t toDisable = -1; // Message to the NMI handler to disable exactly one pin from waveform generation uint32_t(*timer1CB)() = nullptr; @@ -180,7 +180,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } } std::atomic_thread_fence(std::memory_order_release); - waveform.toEnable = 1UL << pin; + waveform.toSet = pin; if (!waveform.timer1Running) { initTimer(); timer1_write(microsecondsToClockCycles(1)); @@ -189,20 +189,20 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load timer1_write(microsecondsToClockCycles(1)); } - while (waveform.toEnable) { - delay(0); // Wait for waveform to update - } } else { wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI std::atomic_thread_fence(std::memory_order_release); wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count - std::atomic_thread_fence(std::memory_order_release); if (runTimeCcys) { wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); + waveform.toSet = pin; } } + while (waveform.toSet >= 0) { + delay(0); // Wait for waveform to update + } return true; } @@ -215,12 +215,12 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false if (waveform.enabled & (1UL << pin)) { - waveform.toDisable = 1UL << pin; + waveform.toDisable = pin; // Must not interfere if Timer is due shortly if (T1L > IRQLATENCY + DELTAIRQ) { timer1_write(microsecondsToClockCycles(1)); } - while (waveform.toDisable) { + while (waveform.toDisable >= 0) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ } } @@ -242,21 +242,45 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { static int nextPin = 0; const uint32_t isrStartCcy = ESP.getCycleCount(); - - if (waveform.toEnable || waveform.toDisable) { + const uint32_t toSetMask = waveform.toSet >= 0 ? 1UL << waveform.toSet : 0; + const uint32_t toDisableMask = waveform.toDisable >= 0 ? 1UL << waveform.toDisable : 0; + if ((toSetMask && !(waveform.enabled & toSetMask)) || toDisableMask) { // Handle enable/disable requests from main app. - waveform.enabled = (waveform.enabled & ~waveform.toDisable) | waveform.toEnable; // Set the requested waveforms on/off - waveform.toEnable = 0; - waveform.toDisable = 0; + waveform.enabled = (waveform.enabled & ~toDisableMask) | toSetMask; // Set the requested waveforms on/off // 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) startPin = __builtin_ffs(waveform.enabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) endPin = 32 - __builtin_clz(waveform.enabled); + waveform.toDisable = -1; + } + + uint32_t now = ESP.getCycleCount(); + + if (toSetMask) { + Waveform& wave = waveform.pins[waveform.toSet]; + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~toSetMask; // Clear the state of any just started + wave.nextPeriodCcy = (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) ? + waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy : now; + wave.nextEventCcy = wave.nextPeriodCcy; + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + wave.expiryCcy += wave.nextPeriodCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.mode = WaveformMode::EXPIRES; + break; + default: + break; + } + waveform.toSet = -1; } // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; - uint32_t now = ESP.getCycleCount(); uint32_t nextTimerCcy; bool busy = waveform.enabled; if (!busy) { @@ -265,7 +289,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else if (!(waveform.enabled & (1UL << nextPin))) { nextPin = startPin; } - bool initPins = true; while (busy) { nextTimerCcy = now + MAXIRQCCYS; int stopPin = nextPin; @@ -277,28 +300,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; - if (initPins) { - switch (wave.mode) { - case WaveformMode::INIT: - waveform.states &= ~(1UL << pin); // Clear the state of any just started - wave.nextPeriodCcy = (waveform.enabled & (1UL << wave.alignPhase)) ? - waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy : now; - wave.nextEventCcy = wave.nextPeriodCcy; - if (!wave.expiryCcy) { - wave.mode = WaveformMode::INFINITE; - break; - } - // fall through - case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPeriodCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave.mode = WaveformMode::EXPIRES; - initPins = false; // only one pin per IRQ - break; - default: - break; - } - } - int32_t overshootCcys = now - wave.nextEventCcy; if (overshootCcys >= 0) { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { @@ -328,7 +329,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // preceeding period had zero idle cycle, continue direct into new duty cycle if (fwdPeriods) { wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; - // adapt expiry such that it occurs during intended cycle + // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) wave.expiryCcy += fwdPeriods * wave.periodCcys; } @@ -391,7 +392,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); - initPins = false; now = ESP.getCycleCount(); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; From 7565dc2617ae8de6d35f0e44d4bf62af7e7d982b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 29 Apr 2020 18:07:49 +0200 Subject: [PATCH 099/152] Some optimizing. --- cores/esp8266/core_esp8266_waveform.cpp | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e728f83585..af4adb21b0 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -300,38 +300,35 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; - int32_t overshootCcys = now - wave.nextEventCcy; - if (overshootCcys >= 0) { + if (static_cast(now - wave.nextEventCcy) >= 0) { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done waveform.enabled ^= 1UL << pin; } else { const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; - // get true accumulated overshoot - overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); - uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? + // get true accumulated overshoot, guaranteed >= 0 in this spot + const uint32_t overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); + const uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; + const uint32_t fwdPeriodCcys = fwdPeriods * wave.periodCcys; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { - if (!wave.autoPwm) { - overshootCcys = 0; - } // up to and including this period 100% duty const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; // active configuration and forward 100% duty if (!idleCcys) { - wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; + wave.nextPeriodCcy += fwdPeriodCcys + wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; nextEdgeCcy = wave.nextPeriodCcy; } else if (endOfPeriod) { // preceeding period had zero idle cycle, continue direct into new duty cycle if (fwdPeriods) { - wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + wave.nextPeriodCcy += fwdPeriodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += fwdPeriods * wave.periodCcys; + wave.expiryCcy += fwdPeriodCcys; } wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; wave.nextPeriodCcy += wave.periodCcys; @@ -339,8 +336,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { waveform.states ^= 1UL << pin; + nextEdgeCcy = wave.nextPeriodCcy; // the idle cycle code updating for the next period will approximate the duty/idle ratio. - nextEdgeCcy = wave.nextPeriodCcy + overshootCcys; + if (wave.autoPwm) { + nextEdgeCcy += overshootCcys; + } if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } @@ -351,7 +351,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; + wave.nextPeriodCcy += fwdPeriodCcys + wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; } else { @@ -362,12 +362,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { { if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - wave.endDutyCcy += (fwdPeriods + 1) * wave.dutyCcys - fwdPeriods * wave.periodCcys; + wave.endDutyCcy += wave.dutyCcys - fwdPeriods * idleCcys; } - wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; + wave.nextPeriodCcy += fwdPeriodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += fwdPeriods * wave.periodCcys; + wave.expiryCcy += fwdPeriodCcys; } if (pin == 16) { GP16O |= 1; // GPIO16 write slow as it's RMW From e0fbc99ffa75c72a43715a04a0b81ed792d0abdc Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 29 Apr 2020 20:39:19 +0200 Subject: [PATCH 100/152] Updating "now" in the inner loop should lessen interference --- cores/esp8266/core_esp8266_waveform.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index af4adb21b0..323bc818f2 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -389,10 +389,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcy = wave.nextEventCcy; nextPin = pin; } - + now = ESP.getCycleCount(); } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); - now = ESP.getCycleCount(); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; if (busy) { From 742199f0bfe85372ed5d0c35ab9ff3a9cd282a3a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 29 Apr 2020 21:50:04 +0200 Subject: [PATCH 101/152] Finally get rid of volatile and use atomic thread fence memory barriers, great for ISR performance. --- cores/esp8266/core_esp8266_waveform.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 323bc818f2..3ae5e42e80 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -87,11 +87,11 @@ namespace { static struct { Waveform pins[17]; // State of all possible pins uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code - volatile uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code + uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine - volatile int32_t toSet = -1; // Message to the NMI handler to start/modify exactly one waveform - volatile int32_t toDisable = -1; // Message to the NMI handler to disable exactly one pin from waveform generation + int32_t toSet = -1; // Message to the NMI handler to start/modify exactly one waveform + int32_t toDisable = -1; // Message to the NMI handler to disable exactly one pin from waveform generation uint32_t(*timer1CB)() = nullptr; @@ -112,6 +112,7 @@ static void initTimer() { ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); waveform.timer1Running = true; + timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste } static void ICACHE_RAM_ATTR deinitTimer() { @@ -124,10 +125,9 @@ static void ICACHE_RAM_ATTR deinitTimer() { // Set a callback. Pass in NULL to stop it void setTimer1Callback(uint32_t (*fn)()) { waveform.timer1CB = fn; - std::atomic_thread_fence(std::memory_order_release); + std::atomic_thread_fence(std::memory_order_acq_rel); if (!waveform.timer1Running && fn) { initTimer(); - timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste } else if (waveform.timer1Running && !fn && !waveform.enabled) { deinitTimer(); } @@ -164,6 +164,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, wave.periodCcys = periodCcys; wave.autoPwm = autoPwm; + std::atomic_thread_fence(std::memory_order_acquire); if (!(waveform.enabled & (1UL << pin))) { // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR wave.nextPeriodCcy = phaseOffsetCcys; @@ -183,7 +184,6 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, waveform.toSet = pin; if (!waveform.timer1Running) { initTimer(); - timer1_write(microsecondsToClockCycles(1)); } else if (T1L > IRQLATENCY + DELTAIRQ) { // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load @@ -200,8 +200,10 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, waveform.toSet = pin; } } + std::atomic_thread_fence(std::memory_order_acq_rel); while (waveform.toSet >= 0) { - delay(0); // Wait for waveform to update + delay(0); // Wait for waveform to update + std::atomic_thread_fence(std::memory_order_acquire); } return true; } @@ -214,14 +216,17 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false + std::atomic_thread_fence(std::memory_order_acquire); if (waveform.enabled & (1UL << pin)) { waveform.toDisable = pin; // Must not interfere if Timer is due shortly if (T1L > IRQLATENCY + DELTAIRQ) { timer1_write(microsecondsToClockCycles(1)); } + std::atomic_thread_fence(std::memory_order_acq_rel); while (waveform.toDisable >= 0) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + std::atomic_thread_fence(std::memory_order_acquire); } } if (!waveform.enabled && !waveform.timer1CB) { From ee69ab92a8a14fa7f9a9db9e396580ce49b75a9a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 30 Apr 2020 18:06:19 +0200 Subject: [PATCH 102/152] Improved idle cycle overshoot mitigation. --- cores/esp8266/core_esp8266_waveform.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 3ae5e42e80..4d3d08c05a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -343,8 +343,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states ^= 1UL << pin; nextEdgeCcy = wave.nextPeriodCcy; // the idle cycle code updating for the next period will approximate the duty/idle ratio. - if (wave.autoPwm) { - nextEdgeCcy += overshootCcys; + if (wave.autoPwm && wave.dutyCcys >= microsecondsToClockCycles(3)) { + nextEdgeCcy += (overshootCcys / wave.dutyCcys) * idleCcys; } if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW @@ -361,15 +361,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { waveform.states ^= 1UL << pin; - wave.endDutyCcy = now + wave.dutyCcys; wave.nextPeriodCcy += wave.periodCcys; - if (fwdPeriods) - { + wave.endDutyCcy = now + wave.dutyCcys; + if (fwdPeriods) { + wave.nextPeriodCcy += fwdPeriodCcys; if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - wave.endDutyCcy += wave.dutyCcys - fwdPeriods * idleCcys; + wave.endDutyCcy += fwdPeriods * wave.dutyCcys; } - wave.nextPeriodCcy += fwdPeriodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) wave.expiryCcy += fwdPeriodCcys; From e8d25b094a7a48685227edd81d2d9dc6b5a21d73 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 1 May 2020 12:19:59 +0200 Subject: [PATCH 103/152] Improved duty cycle overshoot mitigation. Case for investigation: 3% (shl 5) vs. 1.5% (shl 6), either less fuzz, but a few marked stray spots, or more fuzz, but no bumps in counter-PWM travel test. --- cores/esp8266/core_esp8266_waveform.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4d3d08c05a..3c93c66e24 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -339,13 +339,18 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy; } + else if (wave.autoPwm && (overshootCcys << 6) > wave.periodCcys && wave.nextEventCcy == wave.endDutyCcy) { + uint32_t adjPeriods = ((overshootCcys << 6) - 1) / wave.periodCcys; + wave.nextPeriodCcy += adjPeriods * wave.periodCcys; + // adapt expiry such that it occurs during intended cycle + if (WaveformMode::EXPIRES == wave.mode) { + wave.expiryCcy += adjPeriods * wave.periodCcys; + } + nextEdgeCcy = wave.endDutyCcy + adjPeriods * wave.dutyCcys; + } else { waveform.states ^= 1UL << pin; nextEdgeCcy = wave.nextPeriodCcy; - // the idle cycle code updating for the next period will approximate the duty/idle ratio. - if (wave.autoPwm && wave.dutyCcys >= microsecondsToClockCycles(3)) { - nextEdgeCcy += (overshootCcys / wave.dutyCcys) * idleCcys; - } if (pin == 16) { GP16O &= ~1; // GPIO16 write slow as it's RMW } From 916db2e8ca4e326b1e7c712a1524d23a8df05694 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 2 May 2020 10:03:07 +0200 Subject: [PATCH 104/152] Move startPin etc. into common static struct --- cores/esp8266/core_esp8266_waveform.cpp | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 3c93c66e24..a34985ca5d 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -96,6 +96,13 @@ namespace { uint32_t(*timer1CB)() = nullptr; bool timer1Running = false; + + // Optimize the NMI inner loop by keeping track of the min and max GPIO that we + // are generating. In the common case (1 PWM) these may be the same pin and + // we can avoid looking at the other pins. + int startPin = 0; + int endPin = 0; + int nextPin = 0; } waveform; } @@ -239,13 +246,6 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { #pragma GCC optimize ("O2") static ICACHE_RAM_ATTR void timer1Interrupt() { - // Optimize the NMI inner loop by keeping track of the min and max GPIO that we - // are generating. In the common case (1 PWM) these may be the same pin and - // we can avoid looking at the other pins. - static int startPin = 0; - static int endPin = 0; - static int nextPin = 0; - const uint32_t isrStartCcy = ESP.getCycleCount(); const uint32_t toSetMask = waveform.toSet >= 0 ? 1UL << waveform.toSet : 0; const uint32_t toDisableMask = waveform.toDisable >= 0 ? 1UL << waveform.toDisable : 0; @@ -253,9 +253,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Handle enable/disable requests from main app. waveform.enabled = (waveform.enabled & ~toDisableMask) | toSetMask; // Set the requested waveforms on/off // 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) - startPin = __builtin_ffs(waveform.enabled) - 1; + waveform.startPin = __builtin_ffs(waveform.enabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - endPin = 32 - __builtin_clz(waveform.enabled); + waveform.endPin = 32 - __builtin_clz(waveform.enabled); waveform.toDisable = -1; } @@ -291,13 +291,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (!busy) { nextTimerCcy = now + MAXIRQCCYS; } - else if (!(waveform.enabled & (1UL << nextPin))) { - nextPin = startPin; + else if (!(waveform.enabled & (1UL << waveform.nextPin))) { + waveform.nextPin = waveform.startPin; } while (busy) { nextTimerCcy = now + MAXIRQCCYS; - int stopPin = nextPin; - int pin = nextPin; + int stopPin = waveform.nextPin; + int pin = waveform.nextPin; do { // If it's not on, ignore if (!(waveform.enabled & (1UL << pin))) @@ -396,10 +396,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (static_cast(nextTimerCcy - wave.nextEventCcy) > 0) { nextTimerCcy = wave.nextEventCcy; - nextPin = pin; + waveform.nextPin = pin; } now = ESP.getCycleCount(); - } while ((pin = (pin < endPin) ? pin + 1 : startPin, pin != stopPin)); + } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin, pin != stopPin)); const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; busy = timerMarginCcys > 0; From 864a2bf72e643d8e7a292c12633513608b762ffb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 2 May 2020 16:31:11 +0200 Subject: [PATCH 105/152] Persist next event cycle across ISR invocations, like initPin was before. --- cores/esp8266/core_esp8266_waveform.cpp | 53 ++++++++++++++----------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a34985ca5d..e09f396c22 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -103,6 +103,7 @@ namespace { int startPin = 0; int endPin = 0; int nextPin = 0; + uint32_t nextEventCcy; } waveform; } @@ -259,15 +260,21 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.toDisable = -1; } - uint32_t now = ESP.getCycleCount(); - if (toSetMask) { Waveform& wave = waveform.pins[waveform.toSet]; switch (wave.mode) { case WaveformMode::INIT: waveform.states &= ~toSetMask; // Clear the state of any just started - wave.nextPeriodCcy = (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) ? - waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy : now; + if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { + wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + if (static_cast(waveform.nextEventCcy - wave.nextPeriodCcy) > 0) { + waveform.nextEventCcy = wave.nextPeriodCcy; + } + } + else { + wave.nextPeriodCcy = ESP.getCycleCount(); + waveform.nextEventCcy = wave.nextPeriodCcy; + } wave.nextEventCcy = wave.nextPeriodCcy; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; @@ -286,18 +293,25 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; - uint32_t nextTimerCcy; - bool busy = waveform.enabled; - if (!busy) { - nextTimerCcy = now + MAXIRQCCYS; + bool busy; + if (!waveform.enabled) { + busy = false; + waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQCCYS; } - else if (!(waveform.enabled & (1UL << waveform.nextPin))) { - waveform.nextPin = waveform.startPin; + else { + busy = static_cast(isrTimeoutCcy - waveform.nextEventCcy) > 0; + if (!(waveform.enabled & (1UL << waveform.nextPin))) { + waveform.nextPin = waveform.startPin; + } } while (busy) { - nextTimerCcy = now + MAXIRQCCYS; int stopPin = waveform.nextPin; int pin = waveform.nextPin; + uint32_t now; + do { + now = ESP.getCycleCount(); + } while (static_cast(waveform.nextEventCcy - now) > 0); + waveform.nextEventCcy = now + MAXIRQCCYS; do { // If it's not on, ignore if (!(waveform.enabled & (1UL << pin))) @@ -394,33 +408,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } - if (static_cast(nextTimerCcy - wave.nextEventCcy) > 0) { - nextTimerCcy = wave.nextEventCcy; + if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { + waveform.nextEventCcy = wave.nextEventCcy; waveform.nextPin = pin; } - now = ESP.getCycleCount(); } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin, pin != stopPin)); - const int32_t timerMarginCcys = isrTimeoutCcy - nextTimerCcy; - busy = timerMarginCcys > 0; - if (busy) { - while (static_cast(nextTimerCcy - now) > 0) { - now = ESP.getCycleCount(); - } - } + busy = static_cast(isrTimeoutCcy - waveform.nextEventCcy) > 0; } int32_t nextTimerCcys; if (waveform.timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys = nextTimerCcy - ESP.getCycleCount(); + nextTimerCcys = waveform.nextEventCcy - ESP.getCycleCount(); if (nextTimerCcys > callbackCcys) { nextTimerCcys = callbackCcys; } } else { - nextTimerCcys = nextTimerCcy - now; + nextTimerCcys = waveform.nextEventCcy - ESP.getCycleCount(); } // Firing timer too soon, the NMI occurs before ISR has returned. From b0682a909f2bc207ca1d6e45de858630b639aa7d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 2 May 2020 20:08:00 +0200 Subject: [PATCH 106/152] Recalibrated DELTAIRQ and IRQLATENCY. Tested @ 3x 40kHz PWM + 440Hz Tone --- cores/esp8266/core_esp8266_waveform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e09f396c22..d651cf463f 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -54,10 +54,10 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(4) >> 1 : microsecondsToClockCycles(4); + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); + (microsecondsToClockCycles(5) / 2) >> 1 : (microsecondsToClockCycles(5) / 2); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) From 4af850560050841a70caeae54319fa5b91e36399 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 3 May 2020 17:27:24 +0200 Subject: [PATCH 107/152] CPU clock to Timer1 ccy correction must be dynamic even when BSP is compiled for fixed CPU clock. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index d651cf463f..21ab3d148f 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -442,7 +442,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Do it here instead of global function to save time and because we know it's edge-IRQ - if (clockCyclesPerMicrosecond() == 160) { + if (CPU2X & 1) { T1L = nextTimerCcys >> 1; } else { From b8617420e57209dd5e8a50f85cc95c2d2f55e7fd Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 3 May 2020 19:31:10 +0200 Subject: [PATCH 108/152] Corrected use of Timer1 registers and add rationale to Timer1 use in comment. Recalibrate for improved frequence downscaling @ 80MHz and 160MHz. --- cores/esp8266/core_esp8266_waveform.cpp | 66 ++++++++++++------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 21ab3d148f..28234e0876 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -44,20 +44,18 @@ #include "ets_sys.h" #include -extern "C" { - -// Maximum delay between IRQs, 1Hz -constexpr int32_t MAXIRQCCYS = microsecondsToClockCycles(10000); +// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz +constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(100000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. -constexpr int32_t DELTAIRQ = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); +constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? + microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); // The latency between in-ISR rearming of the timer and the earliest firing -constexpr int32_t IRQLATENCY = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(5) / 2) >> 1 : (microsecondsToClockCycles(5) / 2); +constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? + (microsecondsToClockCycles(3) / 2) >> 1 : microsecondsToClockCycles(3) / 2; // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -120,7 +118,7 @@ static void initTimer() { ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); waveform.timer1Running = true; - timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste + timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); // Cause an interrupt post-haste } static void ICACHE_RAM_ATTR deinitTimer() { @@ -130,6 +128,8 @@ static void ICACHE_RAM_ATTR deinitTimer() { waveform.timer1Running = false; } +extern "C" { + // Set a callback. Pass in NULL to stop it void setTimer1Callback(uint32_t (*fn)()) { waveform.timer1CB = fn; @@ -154,12 +154,12 @@ int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { uint32_t periodCcys = highCcys + lowCcys; - if (periodCcys < MAXIRQCCYS) { + if (periodCcys < MAXIRQTICKSCCYS) { if (!highCcys) { - periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; } else if (!lowCcys) { - highCcys = periodCcys = (MAXIRQCCYS / periodCcys) * periodCcys; + highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; } } // sanity checks, including mixed signed/unsigned arithmetic safety @@ -193,9 +193,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!waveform.timer1Running) { initTimer(); } - else if (T1L > IRQLATENCY + DELTAIRQ) { - // Must not interfere if Timer is due shortly, cluster phases to reduce interrupt load - timer1_write(microsecondsToClockCycles(1)); + else if (((CPU2X & 1) ? T1V << 1 : T1V) > IRQLATENCYCCYS + DELTAIRQCCYS) { + // Must not interfere if Timer is due shortly + timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } } else { @@ -228,8 +228,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveform.enabled & (1UL << pin)) { waveform.toDisable = pin; // Must not interfere if Timer is due shortly - if (T1L > IRQLATENCY + DELTAIRQ) { - timer1_write(microsecondsToClockCycles(1)); + if (((CPU2X & 1) ? T1V << 1 : T1V) > IRQLATENCYCCYS + DELTAIRQCCYS) { + timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } std::atomic_thread_fence(std::memory_order_acq_rel); while (waveform.toDisable >= 0) { @@ -243,6 +243,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { return true; } +}; + // Speed critical bits #pragma GCC optimize ("O2") @@ -296,7 +298,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { bool busy; if (!waveform.enabled) { busy = false; - waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQCCYS; + waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQTICKSCCYS; } else { busy = static_cast(isrTimeoutCcy - waveform.nextEventCcy) > 0; @@ -311,7 +313,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { now = ESP.getCycleCount(); } while (static_cast(waveform.nextEventCcy - now) > 0); - waveform.nextEventCcy = now + MAXIRQCCYS; + waveform.nextEventCcy = now + MAXIRQTICKSCCYS; do { // If it's not on, ignore if (!(waveform.enabled & (1UL << pin))) @@ -354,7 +356,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEdgeCcy = wave.endDutyCcy; } else if (wave.autoPwm && (overshootCcys << 6) > wave.periodCcys && wave.nextEventCcy == wave.endDutyCcy) { - uint32_t adjPeriods = ((overshootCcys << 6) - 1) / wave.periodCcys; + uint32_t adjPeriods = (overshootCcys << 6) / wave.periodCcys; wave.nextPeriodCcy += adjPeriods * wave.periodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) { @@ -431,24 +433,20 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= IRQLATENCY + DELTAIRQ) { - nextTimerCcys = IRQLATENCY; + if (nextTimerCcys <= IRQLATENCYCCYS + DELTAIRQCCYS) { + nextTimerCcys = IRQLATENCYCCYS; } - else if (nextTimerCcys >= MAXIRQCCYS) { - nextTimerCcys = MAXIRQCCYS - DELTAIRQ; + else if (nextTimerCcys >= MAXIRQTICKSCCYS + DELTAIRQCCYS) { + nextTimerCcys = MAXIRQTICKSCCYS; } else { - nextTimerCcys -= DELTAIRQ; + nextTimerCcys -= DELTAIRQCCYS; } - // Do it here instead of global function to save time and because we know it's edge-IRQ - if (CPU2X & 1) { - T1L = nextTimerCcys >> 1; - } - else { - T1L = nextTimerCcys; - } + // Register access is fast and edge IRQ was configured before. + // Timer is 80MHz fixed. 160MHz binaries need scaling, + // 80MHz binaries in 160MHz boost (SDK) need NMI scaling + // to maintain duty/idle ratio. + T1L = CPU2X & 1 ? nextTimerCcys >> 1 : nextTimerCcys; TEIE |= TEIE1; // Edge int enable } - -}; From 04ab1e07a16238001371a470fbfb60a07310a3de Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 4 May 2020 16:20:14 +0200 Subject: [PATCH 109/152] Let duty cycle overshoot correction depend on relative impact compareed to both period and duty. --- cores/esp8266/core_esp8266_waveform.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 28234e0876..235e85248a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -164,7 +164,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || - static_cast(periodCcys) <= 0 || highCcys > periodCcys) { + static_cast(periodCcys) <= 0 || + (periodCcys >> 6) == 0 || + highCcys > periodCcys) { return false; } Waveform& wave = waveform.pins[pin]; @@ -355,8 +357,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy; } - else if (wave.autoPwm && (overshootCcys << 6) > wave.periodCcys && wave.nextEventCcy == wave.endDutyCcy) { - uint32_t adjPeriods = (overshootCcys << 6) / wave.periodCcys; + else if (wave.autoPwm && + overshootCcys >= (wave.periodCcys >> 6) && + overshootCcys >= (wave.dutyCcys >> 3) && + wave.nextEventCcy == wave.endDutyCcy) { + uint32_t adjPeriods = overshootCcys / (wave.periodCcys >> 6); wave.nextPeriodCcy += adjPeriods * wave.periodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) { From 60a09e4a169618c56fd6d7f91f85c2c2dcd9c8c6 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 5 May 2020 03:12:11 +0200 Subject: [PATCH 110/152] 80MHz/160MHz specific code can be compile-time selected in general, only NMI is affected by apparent CPU frequency scaling in SDK code. --- cores/esp8266/core_esp8266_waveform.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 235e85248a..e2d4e49bea 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -195,9 +195,9 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!waveform.timer1Running) { initTimer(); } - else if (((CPU2X & 1) ? T1V << 1 : T1V) > IRQLATENCYCCYS + DELTAIRQCCYS) { + else if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { // Must not interfere if Timer is due shortly - timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); + timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } } else { @@ -230,8 +230,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { if (waveform.enabled & (1UL << pin)) { waveform.toDisable = pin; // Must not interfere if Timer is due shortly - if (((CPU2X & 1) ? T1V << 1 : T1V) > IRQLATENCYCCYS + DELTAIRQCCYS) { - timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); + if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { + timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } std::atomic_thread_fence(std::memory_order_acq_rel); while (waveform.toDisable >= 0) { From 4e50fbc2d2bd432010efa6efaad3ccdbe142cb92 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 5 May 2020 20:58:27 +0200 Subject: [PATCH 111/152] Seems that removing the redudant resetting of edge interrupt mode shaves 0.5us off rearm latency. --- cores/esp8266/core_esp8266_waveform.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e2d4e49bea..a7e352b74f 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -55,7 +55,7 @@ constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(3) / 2) >> 1 : microsecondsToClockCycles(3) / 2; + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // Set/clear GPIO 0-15 by bitmask #define SetGPIO(a) do { GPOS = a; } while (0) @@ -453,5 +453,4 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // 80MHz binaries in 160MHz boost (SDK) need NMI scaling // to maintain duty/idle ratio. T1L = CPU2X & 1 ? nextTimerCcys >> 1 : nextTimerCcys; - TEIE |= TEIE1; // Edge int enable } From 9d8685932fc572bfc6a23300c08e973784ca8ad9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 5 May 2020 22:05:21 +0200 Subject: [PATCH 112/152] Recalibrated delta irq ccys. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a7e352b74f..35dd50a654 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -52,7 +52,7 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(3) >> 1 : microsecondsToClockCycles(3); + microsecondsToClockCycles(5) >> 1 : microsecondsToClockCycles(5); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); From 6fe4732299fb8a3eeae8b5f71e7272c4e34d1bb4 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 6 May 2020 18:06:57 +0200 Subject: [PATCH 113/152] Off-by-one in 100% duty overshoot correction. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 35dd50a654..291764875e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -341,7 +341,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; // active configuration and forward 100% duty if (!idleCcys) { - wave.nextPeriodCcy += fwdPeriodCcys + wave.periodCcys; + wave.nextPeriodCcy += fwdPeriodCcys; wave.endDutyCcy = wave.nextPeriodCcy; nextEdgeCcy = wave.nextPeriodCcy; } From 688533735aeac6d4a5d5230c62b2c84c2335f873 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 6 May 2020 19:26:41 +0200 Subject: [PATCH 114/152] Simple register writes. --- cores/esp8266/core_esp8266_waveform.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 291764875e..c83620fa5a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -57,10 +57,6 @@ constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) - // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. // for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. @@ -184,10 +180,10 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!wave.dutyCcys) { // If initially at zero duty cycle, force GPIO off if (pin == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW + GP16O = 0; } else { - ClearGPIO(1UL << pin); + GPOC = 1UL << pin; } } std::atomic_thread_fence(std::memory_order_release); @@ -373,10 +369,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states ^= 1UL << pin; nextEdgeCcy = wave.nextPeriodCcy; if (pin == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW + GP16O = 0; } else { - ClearGPIO(1UL << pin); + GPOC = 1UL << pin; } } } @@ -400,10 +396,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.expiryCcy += fwdPeriodCcys; } if (pin == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW + GP16O = 1; } else { - SetGPIO(1UL << pin); + GPOS = 1UL << pin; } } nextEdgeCcy = wave.endDutyCcy; From 41f6bb5c548dbb1bb692989f6cbad514c02b3be3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 6 May 2020 22:49:44 +0200 Subject: [PATCH 115/152] Memory fences checked and joining events into same loop iteration that are close to one another. --- cores/esp8266/core_esp8266_waveform.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c83620fa5a..b1e9899c7c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -188,6 +188,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } std::atomic_thread_fence(std::memory_order_release); waveform.toSet = pin; + std::atomic_thread_fence(std::memory_order_release); if (!waveform.timer1Running) { initTimer(); } @@ -225,11 +226,11 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { std::atomic_thread_fence(std::memory_order_acquire); if (waveform.enabled & (1UL << pin)) { waveform.toDisable = pin; + std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } - std::atomic_thread_fence(std::memory_order_acq_rel); while (waveform.toDisable >= 0) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ std::atomic_thread_fence(std::memory_order_acquire); @@ -411,7 +412,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } - if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { + // join events below performance threshold apart + if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > IRQLATENCYCCYS) { waveform.nextEventCcy = wave.nextEventCcy; waveform.nextPin = pin; } From 89dd855c50b9a2edc5868dcac212fc56ab70668b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 6 May 2020 23:31:56 +0200 Subject: [PATCH 116/152] Shorten progression when going off 100% duty. --- cores/esp8266/core_esp8266_waveform.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b1e9899c7c..c1ee05db07 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -331,20 +331,20 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); const uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; - const uint32_t fwdPeriodCcys = fwdPeriods * wave.periodCcys; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { // up to and including this period 100% duty const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; // active configuration and forward 100% duty if (!idleCcys) { - wave.nextPeriodCcy += fwdPeriodCcys; + wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; nextEdgeCcy = wave.nextPeriodCcy; } else if (endOfPeriod) { // preceeding period had zero idle cycle, continue direct into new duty cycle - if (fwdPeriods) { + if (fwdPeriods >= 2) { + const uint32_t fwdPeriodCcys = (fwdPeriods - 1) * wave.periodCcys; wave.nextPeriodCcy += fwdPeriodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) @@ -379,7 +379,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPeriodCcy += fwdPeriodCcys + wave.periodCcys; + wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; } else { @@ -387,6 +387,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; wave.endDutyCcy = now + wave.dutyCcys; if (fwdPeriods) { + const uint32_t fwdPeriodCcys = fwdPeriods * wave.periodCcys; wave.nextPeriodCcy += fwdPeriodCcys; if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods From 86afd501083fc07f088e7d3fea11eb79a0737b37 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 7 May 2020 02:06:49 +0200 Subject: [PATCH 117/152] Code simplifications. --- cores/esp8266/core_esp8266_waveform.cpp | 47 +++++++++++-------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c1ee05db07..7cc0a7111c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -161,8 +161,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || - (periodCcys >> 6) == 0 || - highCcys > periodCcys) { + static_cast(highCcys) < 0 || static_cast(lowCcys) < 0 || + (periodCcys >> 6) == 0) { return false; } Waveform& wave = waveform.pins[pin]; @@ -313,6 +313,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { now = ESP.getCycleCount(); } while (static_cast(waveform.nextEventCcy - now) > 0); waveform.nextEventCcy = now + MAXIRQTICKSCCYS; + do { // If it's not on, ignore if (!(waveform.enabled & (1UL << pin))) @@ -320,39 +321,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; - if (static_cast(now - wave.nextEventCcy) >= 0) { + const uint32_t overshootCcys = now - wave.nextEventCcy; + if (static_cast(overshootCcys) >= 0) { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done waveform.enabled ^= 1UL << pin; } else { - const uint32_t idleCcys = wave.periodCcys - wave.dutyCcys; - // get true accumulated overshoot, guaranteed >= 0 in this spot - const uint32_t overshootCcys = now - ((waveform.states & (1UL << pin)) ? wave.endDutyCcy : wave.nextPeriodCcy); - const uint32_t fwdPeriods = static_cast(overshootCcys) >= idleCcys ? - ((overshootCcys + wave.dutyCcys) / wave.periodCcys) : 0; + const uint32_t fwdPeriods = (overshootCcys + wave.dutyCcys) / wave.periodCcys; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { - // up to and including this period 100% duty - const bool endOfPeriod = wave.nextPeriodCcy == wave.endDutyCcy; - // active configuration and forward 100% duty - if (!idleCcys) { - wave.nextPeriodCcy += fwdPeriods * wave.periodCcys; - wave.endDutyCcy = wave.nextPeriodCcy; - nextEdgeCcy = wave.nextPeriodCcy; + // active configuration and forward are 100% duty + if (wave.periodCcys == wave.dutyCcys) { + wave.nextPeriodCcy += wave.periodCcys; + nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy; } - else if (endOfPeriod) { - // preceeding period had zero idle cycle, continue direct into new duty cycle - if (fwdPeriods >= 2) { + else if (wave.nextPeriodCcy == wave.endDutyCcy) { + // preceeding period had zero idle cycle, continue directly into new duty cycle + if (fwdPeriods > 1) { const uint32_t fwdPeriodCcys = (fwdPeriods - 1) * wave.periodCcys; wave.nextPeriodCcy += fwdPeriodCcys; // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) + if (WaveformMode::EXPIRES == wave.mode) { wave.expiryCcy += fwdPeriodCcys; + } } - wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; + nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; wave.nextPeriodCcy += wave.periodCcys; - nextEdgeCcy = wave.endDutyCcy; } else if (wave.autoPwm && overshootCcys >= (wave.periodCcys >> 6) && @@ -367,9 +362,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextEdgeCcy = wave.endDutyCcy + adjPeriods * wave.dutyCcys; } else { - waveform.states ^= 1UL << pin; nextEdgeCcy = wave.nextPeriodCcy; - if (pin == 16) { + waveform.states &= ~(1UL << pin); + if (16 == pin) { GP16O = 0; } else { @@ -379,11 +374,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPeriodCcy += (fwdPeriods + 1) * wave.periodCcys; + wave.nextPeriodCcy += wave.periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; } else { - waveform.states ^= 1UL << pin; wave.nextPeriodCcy += wave.periodCcys; wave.endDutyCcy = now + wave.dutyCcys; if (fwdPeriods) { @@ -397,7 +391,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (WaveformMode::EXPIRES == wave.mode) wave.expiryCcy += fwdPeriodCcys; } - if (pin == 16) { + waveform.states |= 1UL << pin; + if (16 == pin) { GP16O = 1; } else { From b70702014b50a71819153b4d88005bcfe6f5655b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 8 May 2020 19:09:26 +0200 Subject: [PATCH 118/152] Dynamically map pins out from in-ISR handling based on next event timing. Major performance boost. --- cores/esp8266/core_esp8266_waveform.cpp | 42 ++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 7cc0a7111c..5820a256bf 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -294,29 +294,26 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; - bool busy; - if (!waveform.enabled) { - busy = false; - waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQTICKSCCYS; - } - else { - busy = static_cast(isrTimeoutCcy - waveform.nextEventCcy) > 0; + uint32_t busyPins = waveform.enabled; + if (busyPins) { + if (static_cast(waveform.nextEventCcy - isrTimeoutCcy) >= 0) { + busyPins = 0; + } + else { + waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQTICKSCCYS; + } if (!(waveform.enabled & (1UL << waveform.nextPin))) { waveform.nextPin = waveform.startPin; } } - while (busy) { + while (busyPins) { int stopPin = waveform.nextPin; int pin = waveform.nextPin; - uint32_t now; - do { - now = ESP.getCycleCount(); - } while (static_cast(waveform.nextEventCcy - now) > 0); - waveform.nextEventCcy = now + MAXIRQTICKSCCYS; + uint32_t now = ESP.getCycleCount(); do { // If it's not on, ignore - if (!(waveform.enabled & (1UL << pin))) + if (!(busyPins & (1UL << pin))) continue; Waveform& wave = waveform.pins[pin]; @@ -325,7 +322,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (static_cast(overshootCcys) >= 0) { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { // Disable any waveforms that are done - waveform.enabled ^= 1UL << pin; + waveform.enabled &= ~(1UL << pin); + busyPins &= ~(1UL << pin); } else { const uint32_t fwdPeriods = (overshootCcys + wave.dutyCcys) / wave.periodCcys; @@ -408,14 +406,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } } - // join events below performance threshold apart - if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > IRQLATENCYCCYS) { - waveform.nextEventCcy = wave.nextEventCcy; - waveform.nextPin = pin; + if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { + busyPins &= ~(1UL << pin); + // join events below performance threshold apart + if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > IRQLATENCYCCYS) { + waveform.nextEventCcy = wave.nextEventCcy; + waveform.nextPin = pin; + } } - } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin, pin != stopPin)); - busy = static_cast(isrTimeoutCcy - waveform.nextEventCcy) > 0; + } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); } int32_t nextTimerCcys; From e71d86ff92c939aaabfea36264e52c602b1dc94a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 8 May 2020 19:42:18 +0200 Subject: [PATCH 119/152] Reverting maximum IRQ period to 10ms. This sets the wave reprogramming rate to 100Hz max. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5820a256bf..262bc3d19f 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -45,7 +45,7 @@ #include // Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz -constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(100000); +constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); // The SDK and hardware take some time to actually get to our NMI code, so From 6855ad10614f4f4b4fc89f37bbf37f820c6144c7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 9 May 2020 02:51:12 +0200 Subject: [PATCH 120/152] Revert recent change that is the most likely cause of reported PWM frequency drop regression. --- cores/esp8266/core_esp8266_waveform.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 262bc3d19f..8bcf6d3281 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -408,8 +408,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { busyPins &= ~(1UL << pin); - // join events below performance threshold apart - if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > IRQLATENCYCCYS) { + if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { waveform.nextEventCcy = wave.nextEventCcy; waveform.nextPin = pin; } From ddecf8a6b288ef885a41962a2fb749d037bac131 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 9 May 2020 15:38:01 +0200 Subject: [PATCH 121/152] Much simplified overshoot mitigation code. --- cores/esp8266/core_esp8266_waveform.cpp | 44 ++++++++----------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 8bcf6d3281..6770f328f8 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -161,8 +161,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, // sanity checks, including mixed signed/unsigned arithmetic safety if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || static_cast(periodCcys) <= 0 || - static_cast(highCcys) < 0 || static_cast(lowCcys) < 0 || - (periodCcys >> 6) == 0) { + static_cast(highCcys) < 0 || static_cast(lowCcys) < 0) { return false; } Waveform& wave = waveform.pins[pin]; @@ -326,7 +325,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { busyPins &= ~(1UL << pin); } else { - const uint32_t fwdPeriods = (overshootCcys + wave.dutyCcys) / wave.periodCcys; uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { // active configuration and forward are 100% duty @@ -334,30 +332,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy; } - else if (wave.nextPeriodCcy == wave.endDutyCcy) { - // preceeding period had zero idle cycle, continue directly into new duty cycle - if (fwdPeriods > 1) { - const uint32_t fwdPeriodCcys = (fwdPeriods - 1) * wave.periodCcys; - wave.nextPeriodCcy += fwdPeriodCcys; - // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) { - wave.expiryCcy += fwdPeriodCcys; - } - } - nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy + wave.dutyCcys; - wave.nextPeriodCcy += wave.periodCcys; - } - else if (wave.autoPwm && - overshootCcys >= (wave.periodCcys >> 6) && - overshootCcys >= (wave.dutyCcys >> 3) && - wave.nextEventCcy == wave.endDutyCcy) { - uint32_t adjPeriods = overshootCcys / (wave.periodCcys >> 6); - wave.nextPeriodCcy += adjPeriods * wave.periodCcys; + else if (wave.autoPwm && now >= wave.nextPeriodCcy) { + const uint32_t adj = 2 + ((overshootCcys / wave.periodCcys) << 1); + // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods + nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; + wave.nextPeriodCcy += adj * wave.periodCcys; // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) { - wave.expiryCcy += adjPeriods * wave.periodCcys; - } - nextEdgeCcy = wave.endDutyCcy + adjPeriods * wave.dutyCcys; + if (WaveformMode::EXPIRES == wave.mode) + wave.expiryCcy += adj * wave.periodCcys; } else { nextEdgeCcy = wave.nextPeriodCcy; @@ -378,16 +360,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { wave.nextPeriodCcy += wave.periodCcys; wave.endDutyCcy = now + wave.dutyCcys; - if (fwdPeriods) { - const uint32_t fwdPeriodCcys = fwdPeriods * wave.periodCcys; - wave.nextPeriodCcy += fwdPeriodCcys; + if (overshootCcys >= wave.dutyCcys) { + const uint32_t adj = 1 + ((overshootCcys / wave.dutyCcys) << 1); + wave.nextPeriodCcy += adj * wave.periodCcys; if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - wave.endDutyCcy += fwdPeriods * wave.dutyCcys; + wave.endDutyCcy += adj * wave.dutyCcys; } // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += fwdPeriodCcys; + wave.expiryCcy += adj * wave.periodCcys; } waveform.states |= 1UL << pin; if (16 == pin) { From 111583b51252f07b03f922970bc40c80271f79d3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 9 May 2020 22:24:49 +0200 Subject: [PATCH 122/152] Fixing overshoot mitigation, 3x 880Hz, 256 states now. --- cores/esp8266/core_esp8266_waveform.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 6770f328f8..9892249abd 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -332,8 +332,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += wave.periodCcys; nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy; } - else if (wave.autoPwm && now >= wave.nextPeriodCcy) { - const uint32_t adj = 2 + ((overshootCcys / wave.periodCcys) << 1); + else if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { + const uint32_t adj = (overshootCcys + wave.dutyCcys) / wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; wave.nextPeriodCcy += adj * wave.periodCcys; @@ -360,8 +360,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { wave.nextPeriodCcy += wave.periodCcys; wave.endDutyCcy = now + wave.dutyCcys; - if (overshootCcys >= wave.dutyCcys) { - const uint32_t adj = 1 + ((overshootCcys / wave.dutyCcys) << 1); + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { + const uint32_t adj = (overshootCcys + wave.dutyCcys) / wave.periodCcys; wave.nextPeriodCcy += adj * wave.periodCcys; if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods From aa18a70d22ea02e3b3c2cfa210d9f5dbdb48f499 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 10 May 2020 18:28:56 +0200 Subject: [PATCH 123/152] Increase resolution by keeping reference time moving forward earlier. --- cores/esp8266/core_esp8266_waveform.cpp | 35 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 9892249abd..7d65616550 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -269,11 +269,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; if (static_cast(waveform.nextEventCcy - wave.nextPeriodCcy) > 0) { waveform.nextEventCcy = wave.nextPeriodCcy; + waveform.nextPin = waveform.toSet; } } else { - wave.nextPeriodCcy = ESP.getCycleCount(); + wave.nextPeriodCcy = isrStartCcy; waveform.nextEventCcy = wave.nextPeriodCcy; + waveform.nextPin = waveform.toSet; } wave.nextEventCcy = wave.nextPeriodCcy; if (!wave.expiryCcy) { @@ -293,23 +295,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; + uint32_t isrNextEventCcy; uint32_t busyPins = waveform.enabled; - if (busyPins) { + if (waveform.enabled) { if (static_cast(waveform.nextEventCcy - isrTimeoutCcy) >= 0) { busyPins = 0; } else { - waveform.nextEventCcy = ESP.getCycleCount() + MAXIRQTICKSCCYS; - } - if (!(waveform.enabled & (1UL << waveform.nextPin))) { - waveform.nextPin = waveform.startPin; + isrNextEventCcy = waveform.nextEventCcy; + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; + if (!(waveform.enabled & (1UL << waveform.nextPin))) { + waveform.nextPin = waveform.startPin; + } } } - while (busyPins) { - int stopPin = waveform.nextPin; - int pin = waveform.nextPin; - uint32_t now = ESP.getCycleCount(); + else { + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; + } + const int stopPin = waveform.nextPin; + int pin = stopPin; + while (busyPins) { + uint32_t now; + do { + now = ESP.getCycleCount(); + } while (static_cast(isrNextEventCcy - now) > 0); + isrNextEventCcy = isrTimeoutCcy; do { // If it's not on, ignore if (!(busyPins & (1UL << pin))) @@ -395,7 +406,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.nextPin = pin; } } + else if (static_cast(isrNextEventCcy - wave.nextEventCcy) > 0) { + isrNextEventCcy = wave.nextEventCcy; + } + now = ESP.getCycleCount(); } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); } From b43d4b004774a9e223cbe44eb9b7a836e72cd95a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 11 May 2020 00:21:55 +0200 Subject: [PATCH 124/152] Mitigation logic for ESP8266 SDK boosting to 160MHz during some WiFi ops. --- cores/esp8266/core_esp8266_waveform.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 7d65616550..078098f8cb 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -246,6 +246,19 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // Speed critical bits #pragma GCC optimize ("O2") +// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. +// Using constexpr makes sure that the CPU clock frequency is compile-time fixed. +static ICACHE_RAM_ATTR uint32_t __attribute__((noinline)) getScaledCcyCount(uint32_t ref) { + constexpr bool cpuFreq80MHz = clockCyclesPerMicrosecond() == 80; + const uint32_t elapsed = ESP.getCycleCount() - ref; + if (cpuFreq80MHz) { + return ref + ((CPU2X & 1) ? elapsed >> 1 : elapsed); + } + else { + return ref + ((CPU2X & 1) ? elapsed : elapsed << 1); + } +} + static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); const uint32_t toSetMask = waveform.toSet >= 0 ? 1UL << waveform.toSet : 0; @@ -318,7 +331,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { while (busyPins) { uint32_t now; do { - now = ESP.getCycleCount(); + now = getScaledCcyCount(isrStartCcy); } while (static_cast(isrNextEventCcy - now) > 0); isrNextEventCcy = isrTimeoutCcy; do { @@ -410,7 +423,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { isrNextEventCcy = wave.nextEventCcy; } - now = ESP.getCycleCount(); + now = getScaledCcyCount(isrStartCcy); } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); } @@ -418,13 +431,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveform.timer1CB) { int32_t callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); // Account for unknown duration of timer1CB(). - nextTimerCcys = waveform.nextEventCcy - ESP.getCycleCount(); + nextTimerCcys = waveform.nextEventCcy - getScaledCcyCount(isrStartCcy); if (nextTimerCcys > callbackCcys) { nextTimerCcys = callbackCcys; } } else { - nextTimerCcys = waveform.nextEventCcy - ESP.getCycleCount(); + nextTimerCcys = waveform.nextEventCcy - getScaledCcyCount(isrStartCcy); } // Firing timer too soon, the NMI occurs before ISR has returned. From e978ac4037d1b06edcdf56f4af86ccdcb6b2884a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 11 May 2020 10:20:15 +0200 Subject: [PATCH 125/152] Event timestamps are all recorded for compile-time CPU frequency, the timer ticks conversion must be set at compile-time also. The SDK WiFi 160MHz boost mitigation temporarily handles the CPU clock running twice as fast. --- cores/esp8266/core_esp8266_waveform.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 078098f8cb..4847ae7974 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -452,8 +452,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Register access is fast and edge IRQ was configured before. - // Timer is 80MHz fixed. 160MHz binaries need scaling, - // 80MHz binaries in 160MHz boost (SDK) need NMI scaling - // to maintain duty/idle ratio. - T1L = CPU2X & 1 ? nextTimerCcys >> 1 : nextTimerCcys; + // Timer is 80MHz fixed. 160MHz binaries need scaling. + // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. + // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. + constexpr bool cpuFreq80MHz = clockCyclesPerMicrosecond() == 80; + T1L = cpuFreq80MHz ? nextTimerCcys : nextTimerCcys >> 1; } From 3b18d5116d5b3ba5783361e45dd7b4d37755cf68 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 12 May 2020 11:44:31 +0200 Subject: [PATCH 126/152] Expired pins must not be checked for next event. --- cores/esp8266/core_esp8266_waveform.cpp | 33 +++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 4847ae7974..9e70a9395a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -342,13 +342,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; const uint32_t overshootCcys = now - wave.nextEventCcy; - if (static_cast(overshootCcys) >= 0) { - if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy) { - // Disable any waveforms that are done - waveform.enabled &= ~(1UL << pin); - busyPins &= ~(1UL << pin); - } - else { + if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy && + static_cast(overshootCcys) >= 0) { + // Disable any waveforms that are done + waveform.enabled &= ~(1UL << pin); + busyPins &= ~(1UL << pin); + } + else { + if (static_cast(overshootCcys) >= 0) { uint32_t nextEdgeCcy; if (waveform.states & (1UL << pin)) { // active configuration and forward are 100% duty @@ -410,17 +411,17 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { (WaveformMode::EXPIRES == wave.mode && static_cast(nextEdgeCcy - wave.expiryCcy) > 0) ? wave.expiryCcy : nextEdgeCcy; } - } - if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { - busyPins &= ~(1UL << pin); - if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { - waveform.nextEventCcy = wave.nextEventCcy; - waveform.nextPin = pin; + if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { + busyPins &= ~(1UL << pin); + if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { + waveform.nextEventCcy = wave.nextEventCcy; + waveform.nextPin = pin; + } + } + else if (static_cast(isrNextEventCcy - wave.nextEventCcy) > 0) { + isrNextEventCcy = wave.nextEventCcy; } - } - else if (static_cast(isrNextEventCcy - wave.nextEventCcy) > 0) { - isrNextEventCcy = wave.nextEventCcy; } now = getScaledCcyCount(isrStartCcy); From b45a2d2f354689b337c2661ef1c1f66b451304bb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 11 May 2020 14:28:13 +0200 Subject: [PATCH 127/152] Recalibrate after latest changes. --- cores/esp8266/core_esp8266_waveform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 9e70a9395a..a637c1bf01 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -47,15 +47,15 @@ // Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ -constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(14); +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // The SDK and hardware take some time to actually get to our NMI code, so // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(5) >> 1 : microsecondsToClockCycles(5); + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); + (microsecondsToClockCycles(3) / 2) >> 1 : (microsecondsToClockCycles(3) / 2); // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. From 784692b9d133f3a10847187b296fe16e26b7e92d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 12 May 2020 17:15:16 +0200 Subject: [PATCH 128/152] Save a few bytes code. --- cores/esp8266/core_esp8266_waveform.cpp | 27 ++++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a637c1bf01..31566a4295 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -170,7 +170,8 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, wave.autoPwm = autoPwm; std::atomic_thread_fence(std::memory_order_acquire); - if (!(waveform.enabled & (1UL << pin))) { + const uint32_t pinBit = 1UL << pin; + if (!(waveform.enabled & pinBit)) { // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR wave.nextPeriodCcy = phaseOffsetCcys; wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count @@ -182,7 +183,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, GP16O = 0; } else { - GPOC = 1UL << pin; + GPOC = pinBit; } } std::atomic_thread_fence(std::memory_order_release); @@ -223,7 +224,8 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false std::atomic_thread_fence(std::memory_order_acquire); - if (waveform.enabled & (1UL << pin)) { + const uint32_t pinBit = 1UL << pin; + if (waveform.enabled & pinBit) { waveform.toDisable = pin; std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly @@ -335,8 +337,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } while (static_cast(isrNextEventCcy - now) > 0); isrNextEventCcy = isrTimeoutCcy; do { + const uint32_t pinBit = 1UL << pin; // If it's not on, ignore - if (!(busyPins & (1UL << pin))) + if (!(busyPins & pinBit)) continue; Waveform& wave = waveform.pins[pin]; @@ -345,13 +348,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy && static_cast(overshootCcys) >= 0) { // Disable any waveforms that are done - waveform.enabled &= ~(1UL << pin); - busyPins &= ~(1UL << pin); + waveform.enabled &= ~pinBit; + busyPins &= ~pinBit; } else { if (static_cast(overshootCcys) >= 0) { uint32_t nextEdgeCcy; - if (waveform.states & (1UL << pin)) { + if (waveform.states & pinBit) { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { wave.nextPeriodCcy += wave.periodCcys; @@ -368,12 +371,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { nextEdgeCcy = wave.nextPeriodCcy; - waveform.states &= ~(1UL << pin); + waveform.states &= ~pinBit; if (16 == pin) { GP16O = 0; } else { - GPOC = 1UL << pin; + GPOC = pinBit; } } } @@ -396,12 +399,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (WaveformMode::EXPIRES == wave.mode) wave.expiryCcy += adj * wave.periodCcys; } - waveform.states |= 1UL << pin; + waveform.states |= pinBit; if (16 == pin) { GP16O = 1; } else { - GPOS = 1UL << pin; + GPOS = pinBit; } } nextEdgeCcy = wave.endDutyCcy; @@ -413,7 +416,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { - busyPins &= ~(1UL << pin); + busyPins &= ~pinBit; if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { waveform.nextEventCcy = wave.nextEventCcy; waveform.nextPin = pin; From 2d591ebf303d340e8bca01513d5e961709bacbfd Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 14 May 2020 19:33:18 +0200 Subject: [PATCH 129/152] Guards are in place, so xor rather than and bitwise not. --- cores/esp8266/core_esp8266_waveform.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 31566a4295..82eb12ea70 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -348,8 +348,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy && static_cast(overshootCcys) >= 0) { // Disable any waveforms that are done - waveform.enabled &= ~pinBit; - busyPins &= ~pinBit; + waveform.enabled ^= pinBit; + busyPins ^= pinBit; } else { if (static_cast(overshootCcys) >= 0) { @@ -371,7 +371,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { nextEdgeCcy = wave.nextPeriodCcy; - waveform.states &= ~pinBit; + waveform.states ^= pinBit; if (16 == pin) { GP16O = 0; } @@ -416,7 +416,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { - busyPins &= ~pinBit; + busyPins ^= pinBit; if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { waveform.nextEventCcy = wave.nextEventCcy; waveform.nextPin = pin; From dd100645e78f55dea4e594adcca2e050422a95eb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 15 May 2020 20:42:14 +0200 Subject: [PATCH 130/152] Reduce memory use. --- cores/esp8266/core_esp8266_waveform.cpp | 51 ++++++++++++------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 82eb12ea70..b7fb78aa8f 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -65,7 +65,6 @@ enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextEventCcy; // ESP clock cycle when switching wave cycle, or expiring wave. uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count uint32_t endDutyCcy; // ESP clock cycle when going from duty to off uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase @@ -292,7 +291,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.nextEventCcy = wave.nextPeriodCcy; waveform.nextPin = waveform.toSet; } - wave.nextEventCcy = wave.nextPeriodCcy; if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; break; @@ -344,33 +342,34 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; - const uint32_t overshootCcys = now - wave.nextEventCcy; - if (WaveformMode::EXPIRES == wave.mode && wave.nextEventCcy == wave.expiryCcy && - static_cast(overshootCcys) >= 0) { + uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; + if (WaveformMode::EXPIRES == wave.mode && + static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && + static_cast(now - wave.expiryCcy) >= 0) { // Disable any waveforms that are done waveform.enabled ^= pinBit; busyPins ^= pinBit; } else { + const uint32_t overshootCcys = now - waveNextEventCcy; if (static_cast(overshootCcys) >= 0) { - uint32_t nextEdgeCcy; if (waveform.states & pinBit) { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { wave.nextPeriodCcy += wave.periodCcys; - nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy; + waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; } else if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { const uint32_t adj = (overshootCcys + wave.dutyCcys) / wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - nextEdgeCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; + waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; wave.nextPeriodCcy += adj * wave.periodCcys; // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) wave.expiryCcy += adj * wave.periodCcys; } else { - nextEdgeCcy = wave.nextPeriodCcy; + waveNextEventCcy = wave.nextPeriodCcy; waveform.states ^= pinBit; if (16 == pin) { GP16O = 0; @@ -407,23 +406,23 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { GPOS = pinBit; } } - nextEdgeCcy = wave.endDutyCcy; + waveNextEventCcy = wave.endDutyCcy; } - wave.nextEventCcy = - (WaveformMode::EXPIRES == wave.mode && static_cast(nextEdgeCcy - wave.expiryCcy) > 0) ? - wave.expiryCcy : nextEdgeCcy; + if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) > 0) { + waveNextEventCcy = wave.expiryCcy; + } } - if (static_cast(wave.nextEventCcy - isrTimeoutCcy) >= 0) { + if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) { busyPins ^= pinBit; - if (static_cast(waveform.nextEventCcy - wave.nextEventCcy) > 0) { - waveform.nextEventCcy = wave.nextEventCcy; + if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { + waveform.nextEventCcy = waveNextEventCcy; waveform.nextPin = pin; } } - else if (static_cast(isrNextEventCcy - wave.nextEventCcy) > 0) { - isrNextEventCcy = wave.nextEventCcy; + else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { + isrNextEventCcy = waveNextEventCcy; } } @@ -431,17 +430,15 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); } - int32_t nextTimerCcys; + int32_t callbackCcys = 0; if (waveform.timer1CB) { - int32_t callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); - // Account for unknown duration of timer1CB(). - nextTimerCcys = waveform.nextEventCcy - getScaledCcyCount(isrStartCcy); - if (nextTimerCcys > callbackCcys) { - nextTimerCcys = callbackCcys; - } + callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); } - else { - nextTimerCcys = waveform.nextEventCcy - getScaledCcyCount(isrStartCcy); + const uint32_t now = getScaledCcyCount(isrStartCcy); + int32_t nextTimerCcys = waveform.nextEventCcy - now; + // Account for unknown duration of timer1CB(). + if (waveform.timer1CB && nextTimerCcys > callbackCcys) { + nextTimerCcys = callbackCcys; } // Firing timer too soon, the NMI occurs before ISR has returned. From 557f3567d79d1c10e9bfdf15d7e698f73b67f366 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 17 May 2020 17:00:41 +0200 Subject: [PATCH 131/152] SDK boost to 160MHz may last across multiple ISR invocations, therefore adjust target ccy instead of ccount. --- cores/esp8266/core_esp8266_waveform.cpp | 44 +++++++++++-------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b7fb78aa8f..1a853653e1 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -67,8 +67,8 @@ enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, typedef struct { uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count uint32_t endDutyCcy; // ESP clock cycle when going from duty to off - uint32_t dutyCcys; // Set next off cycle at low->high to maintain phase - uint32_t periodCcys; // Set next phase cycle at low->high to maintain phase + int32_t dutyCcys; // Set next off cycle at low->high to maintain phase + int32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin @@ -249,14 +249,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. -static ICACHE_RAM_ATTR uint32_t __attribute__((noinline)) getScaledCcyCount(uint32_t ref) { +static inline ICACHE_RAM_ATTR int32_t scaleCcys(int32_t ccys) { constexpr bool cpuFreq80MHz = clockCyclesPerMicrosecond() == 80; - const uint32_t elapsed = ESP.getCycleCount() - ref; if (cpuFreq80MHz) { - return ref + ((CPU2X & 1) ? elapsed >> 1 : elapsed); + return ((CPU2X & 1) ? ccys << 1 : ccys); } else { - return ref + ((CPU2X & 1) ? elapsed : elapsed << 1); + return ((CPU2X & 1) ? ccys : ccys >> 1); } } @@ -297,7 +296,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // fall through case WaveformMode::UPDATEEXPIRY: - wave.expiryCcy += wave.nextPeriodCcy; // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy); wave.mode = WaveformMode::EXPIRES; break; default: @@ -329,10 +329,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { const int stopPin = waveform.nextPin; int pin = stopPin; while (busyPins) { - uint32_t now; - do { - now = getScaledCcyCount(isrStartCcy); - } while (static_cast(isrNextEventCcy - now) > 0); + while (static_cast(isrNextEventCcy - ESP.getCycleCount()) > 0) { + } isrNextEventCcy = isrTimeoutCcy; do { const uint32_t pinBit = 1UL << pin; @@ -343,6 +341,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; + const uint32_t now = ESP.getCycleCount(); if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && static_cast(now - wave.expiryCcy) >= 0) { @@ -356,11 +355,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveform.states & pinBit) { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { - wave.nextPeriodCcy += wave.periodCcys; + wave.nextPeriodCcy += scaleCcys(wave.periodCcys); waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; } else if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { - const uint32_t adj = (overshootCcys + wave.dutyCcys) / wave.periodCcys; + const uint32_t adj = (overshootCcys + scaleCcys(wave.dutyCcys)) / wave.periodCcys; // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; wave.nextPeriodCcy += adj * wave.periodCcys; @@ -381,14 +380,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { if (!wave.dutyCcys) { - wave.nextPeriodCcy += wave.periodCcys; + wave.nextPeriodCcy += scaleCcys(wave.periodCcys); wave.endDutyCcy = wave.nextPeriodCcy; } else { - wave.nextPeriodCcy += wave.periodCcys; - wave.endDutyCcy = now + wave.dutyCcys; + wave.nextPeriodCcy += scaleCcys(wave.periodCcys); + wave.endDutyCcy = now + scaleCcys(wave.dutyCcys); if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { - const uint32_t adj = (overshootCcys + wave.dutyCcys) / wave.periodCcys; + const uint32_t adj = (overshootCcys + scaleCcys(wave.dutyCcys)) / wave.periodCcys; wave.nextPeriodCcy += adj * wave.periodCcys; if (wave.autoPwm) { // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods @@ -425,16 +424,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { isrNextEventCcy = waveNextEventCcy; } } - - now = getScaledCcyCount(isrStartCcy); } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); } int32_t callbackCcys = 0; if (waveform.timer1CB) { - callbackCcys = microsecondsToClockCycles(waveform.timer1CB()); + callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB())); } - const uint32_t now = getScaledCcyCount(isrStartCcy); + const uint32_t now = ESP.getCycleCount(); int32_t nextTimerCcys = waveform.nextEventCcy - now; // Account for unknown duration of timer1CB(). if (waveform.timer1CB && nextTimerCcys > callbackCcys) { @@ -454,8 +451,5 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Register access is fast and edge IRQ was configured before. // Timer is 80MHz fixed. 160MHz binaries need scaling. - // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. - // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. - constexpr bool cpuFreq80MHz = clockCyclesPerMicrosecond() == 80; - T1L = cpuFreq80MHz ? nextTimerCcys : nextTimerCcys >> 1; + T1L = (CPU2X & 1) ? nextTimerCcys >> 1 : nextTimerCcys; } From 2e1ecc72df36d6d25527053f4f3555a624592d18 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 20 May 2020 20:34:38 +0200 Subject: [PATCH 132/152] Overshoot mitigation w/o PWM frequency change. --- cores/esp8266/core_esp8266_waveform.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 1a853653e1..6a1eeb7573 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -359,13 +359,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; } else if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { - const uint32_t adj = (overshootCcys + scaleCcys(wave.dutyCcys)) / wave.periodCcys; - // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy + adj * wave.dutyCcys; - wave.nextPeriodCcy += adj * wave.periodCcys; + waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys) - overshootCcys; + wave.nextPeriodCcy += scaleCcys(wave.periodCcys); // adapt expiry such that it occurs during intended cycle if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += adj * wave.periodCcys; + wave.expiryCcy += scaleCcys(wave.periodCcys); } else { waveNextEventCcy = wave.nextPeriodCcy; @@ -387,15 +385,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy += scaleCcys(wave.periodCcys); wave.endDutyCcy = now + scaleCcys(wave.dutyCcys); if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { - const uint32_t adj = (overshootCcys + scaleCcys(wave.dutyCcys)) / wave.periodCcys; - wave.nextPeriodCcy += adj * wave.periodCcys; - if (wave.autoPwm) { - // maintain phase, maintain duty/idle ratio, temporarily reduce frequency by fwdPeriods - wave.endDutyCcy += adj * wave.dutyCcys; - } - // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += adj * wave.periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; } waveform.states |= pinBit; if (16 == pin) { From b7f1b958d9640ad9a1b69df3428e82b4a14e0615 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 22 May 2020 00:55:06 +0200 Subject: [PATCH 133/152] New PWM overshoot mitigation code keeps frequency. Averages duty between consecutive periods. --- cores/esp8266/core_esp8266_waveform.cpp | 66 ++++++++++++++++--------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 6a1eeb7573..392219c5eb 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -66,13 +66,14 @@ enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count - uint32_t endDutyCcy; // ESP clock cycle when going from duty to off - int32_t dutyCcys; // Set next off cycle at low->high to maintain phase - int32_t periodCcys; // Set next phase cycle at low->high to maintain phase - uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count + uint32_t endDutyCcy; // ESP clock cycle when going from duty to off + int32_t dutyCcys; // Set next off cycle at low->high to maintain phase + int32_t adjDutyCcys; // Temporary correction for next period + int32_t periodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; - int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin - bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings } Waveform; namespace { @@ -165,6 +166,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } Waveform& wave = waveform.pins[pin]; wave.dutyCcys = highCcys; + wave.adjDutyCcys = 0; wave.periodCcys = periodCcys; wave.autoPwm = autoPwm; @@ -352,38 +354,58 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { const uint32_t overshootCcys = now - waveNextEventCcy; if (static_cast(overshootCcys) >= 0) { + const int32_t periodCcys = scaleCcys(wave.periodCcys); if (waveform.states & pinBit) { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { - wave.nextPeriodCcy += scaleCcys(wave.periodCcys); + wave.nextPeriodCcy += periodCcys; waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; } - else if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { - waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys) - overshootCcys; - wave.nextPeriodCcy += scaleCcys(wave.periodCcys); - // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += scaleCcys(wave.periodCcys); - } else { - waveNextEventCcy = wave.nextPeriodCcy; - waveform.states ^= pinBit; - if (16 == pin) { - GP16O = 0; + if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { + wave.endDutyCcy += periodCcys - overshootCcys; + wave.nextPeriodCcy += periodCcys; + if (static_cast(now - wave.endDutyCcy) >= 0) { + waveNextEventCcy = wave.nextPeriodCcy; + } + else { + waveNextEventCcy = wave.endDutyCcy; + } + // adapt expiry such that it occurs during intended cycle + if (WaveformMode::EXPIRES == wave.mode) + wave.expiryCcy += periodCcys; + } + else if (wave.autoPwm) { + wave.adjDutyCcys = overshootCcys; + waveNextEventCcy = wave.nextPeriodCcy; } else { - GPOC = pinBit; + waveNextEventCcy = wave.nextPeriodCcy; + } + if (waveNextEventCcy == wave.nextPeriodCcy) { + waveform.states ^= pinBit; + if (16 == pin) { + GP16O = 0; + } + else { + GPOC = pinBit; + } } } } else { if (!wave.dutyCcys) { - wave.nextPeriodCcy += scaleCcys(wave.periodCcys); + wave.nextPeriodCcy += periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; } else { - wave.nextPeriodCcy += scaleCcys(wave.periodCcys); - wave.endDutyCcy = now + scaleCcys(wave.dutyCcys); + wave.nextPeriodCcy += periodCcys; + int32_t dutyCcys = scaleCcys(wave.dutyCcys); + if (dutyCcys > wave.adjDutyCcys) { + dutyCcys -= wave.adjDutyCcys; + } + wave.adjDutyCcys = 0; + wave.endDutyCcy = now + dutyCcys; if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { wave.endDutyCcy = wave.nextPeriodCcy; } From 05307a17e2868768f5b8754b4665aeb4c5348a4f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 22 May 2020 19:16:03 +0200 Subject: [PATCH 134/152] Small refactoring, remove code path that is never taken even at 3x25kHz/1023 PWM. --- cores/esp8266/core_esp8266_waveform.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 392219c5eb..e1c121bb3c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -362,22 +362,19 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; } else { - if (wave.autoPwm && static_cast(now - wave.nextPeriodCcy) >= 0) { - wave.endDutyCcy += periodCcys - overshootCcys; - wave.nextPeriodCcy += periodCcys; - if (static_cast(now - wave.endDutyCcy) >= 0) { - waveNextEventCcy = wave.nextPeriodCcy; + if (wave.autoPwm) { + if (static_cast(now - wave.nextPeriodCcy) >= 0) { + wave.endDutyCcy += periodCcys - overshootCcys; + wave.nextPeriodCcy += periodCcys; + waveNextEventCcy = wave.endDutyCcy; + // adapt expiry such that it occurs during intended cycle + if (WaveformMode::EXPIRES == wave.mode) + wave.expiryCcy += periodCcys; } else { - waveNextEventCcy = wave.endDutyCcy; + wave.adjDutyCcys = overshootCcys; + waveNextEventCcy = wave.nextPeriodCcy; } - // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += periodCcys; - } - else if (wave.autoPwm) { - wave.adjDutyCcys = overshootCcys; - waveNextEventCcy = wave.nextPeriodCcy; } else { waveNextEventCcy = wave.nextPeriodCcy; From 83c6b6733cbb0106b992d5b01164d704c37d1300 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 22 May 2020 23:56:04 +0200 Subject: [PATCH 135/152] Don't ever skip off duty, no matter if late or infinitely short. --- cores/esp8266/core_esp8266_waveform.cpp | 37 +++++++++---------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e1c121bb3c..3fcf752891 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -359,36 +359,21 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { wave.nextPeriodCcy += periodCcys; - waveNextEventCcy = wave.endDutyCcy = wave.nextPeriodCcy; + wave.endDutyCcy = wave.nextPeriodCcy; } else { if (wave.autoPwm) { - if (static_cast(now - wave.nextPeriodCcy) >= 0) { - wave.endDutyCcy += periodCcys - overshootCcys; - wave.nextPeriodCcy += periodCcys; - waveNextEventCcy = wave.endDutyCcy; - // adapt expiry such that it occurs during intended cycle - if (WaveformMode::EXPIRES == wave.mode) - wave.expiryCcy += periodCcys; - } - else { - wave.adjDutyCcys = overshootCcys; - waveNextEventCcy = wave.nextPeriodCcy; - } + wave.adjDutyCcys += overshootCcys; } - else { - waveNextEventCcy = wave.nextPeriodCcy; + waveform.states ^= pinBit; + if (16 == pin) { + GP16O = 0; } - if (waveNextEventCcy == wave.nextPeriodCcy) { - waveform.states ^= pinBit; - if (16 == pin) { - GP16O = 0; - } - else { - GPOC = pinBit; - } + else { + GPOC = pinBit; } } + waveNextEventCcy = wave.nextPeriodCcy; } else { if (!wave.dutyCcys) { @@ -400,8 +385,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t dutyCcys = scaleCcys(wave.dutyCcys); if (dutyCcys > wave.adjDutyCcys) { dutyCcys -= wave.adjDutyCcys; + wave.adjDutyCcys = 0; + } + else { + wave.adjDutyCcys -= dutyCcys; + dutyCcys = 0; } - wave.adjDutyCcys = 0; wave.endDutyCcy = now + dutyCcys; if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { wave.endDutyCcy = wave.nextPeriodCcy; From 5165ba2f05cf80865e8ff486df5e5bb8b9d9ee65 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 23 May 2020 15:52:50 +0200 Subject: [PATCH 136/152] Shed speed-up code that didn't speed up things. --- cores/esp8266/core_esp8266_waveform.cpp | 33 +++---------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 3fcf752891..0f024d07dd 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -96,7 +96,6 @@ namespace { // we can avoid looking at the other pins. int startPin = 0; int endPin = 0; - int nextPin = 0; uint32_t nextEventCcy; } waveform; @@ -284,13 +283,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; if (static_cast(waveform.nextEventCcy - wave.nextPeriodCcy) > 0) { waveform.nextEventCcy = wave.nextPeriodCcy; - waveform.nextPin = waveform.toSet; } } else { wave.nextPeriodCcy = isrStartCcy; waveform.nextEventCcy = wave.nextPeriodCcy; - waveform.nextPin = waveform.toSet; } if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; @@ -310,31 +307,13 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; - uint32_t isrNextEventCcy; - uint32_t busyPins = waveform.enabled; - if (waveform.enabled) { - if (static_cast(waveform.nextEventCcy - isrTimeoutCcy) >= 0) { - busyPins = 0; - } - else { - isrNextEventCcy = waveform.nextEventCcy; - waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; - if (!(waveform.enabled & (1UL << waveform.nextPin))) { - waveform.nextPin = waveform.startPin; - } - } - } - else { + uint32_t busyPins = (static_cast(waveform.nextEventCcy - isrTimeoutCcy) < 0) ? waveform.enabled : 0; + if (!waveform.enabled) { waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; } - const int stopPin = waveform.nextPin; - int pin = stopPin; while (busyPins) { - while (static_cast(isrNextEventCcy - ESP.getCycleCount()) > 0) { - } - isrNextEventCcy = isrTimeoutCcy; - do { + for (int pin = waveform.startPin; pin <= waveform.endPin; ++pin) { const uint32_t pinBit = 1UL << pin; // If it's not on, ignore if (!(busyPins & pinBit)) @@ -415,14 +394,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { busyPins ^= pinBit; if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { waveform.nextEventCcy = waveNextEventCcy; - waveform.nextPin = pin; } } - else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { - isrNextEventCcy = waveNextEventCcy; - } } - } while ((pin = (pin < waveform.endPin) ? pin + 1 : waveform.startPin) != stopPin); + } } int32_t callbackCcys = 0; From 12e475546c0ca59b8f484db5cbe3a9a3c198ce1b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 23 May 2020 22:36:10 +0200 Subject: [PATCH 137/152] Must always recompute new waveform.nextEventCcy if there is any busy pin. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 0f024d07dd..b8400e3902 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -308,7 +308,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; uint32_t busyPins = (static_cast(waveform.nextEventCcy - isrTimeoutCcy) < 0) ? waveform.enabled : 0; - if (!waveform.enabled) { + if (!waveform.enabled || busyPins) { waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; } From 16956e9adbd2cb94e020431b08e0cb6b6152a3f7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 24 May 2020 02:59:33 +0200 Subject: [PATCH 138/152] Break out of ISR if timespan to next event allows, instead of busy waiting and stealing CPU cycles from userland. --- cores/esp8266/core_esp8266_waveform.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index b8400e3902..2bc216f3be 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -312,7 +312,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; } + uint32_t now = ESP.getCycleCount(); + uint32_t isrNextEventCcy = now; while (busyPins) { + if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS + DELTAIRQCCYS) { + waveform.nextEventCcy = isrNextEventCcy; + break; + } + isrNextEventCcy = waveform.nextEventCcy; for (int pin = waveform.startPin; pin <= waveform.endPin; ++pin) { const uint32_t pinBit = 1UL << pin; // If it's not on, ignore @@ -322,7 +329,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; - const uint32_t now = ESP.getCycleCount(); if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && static_cast(now - wave.expiryCcy) >= 0) { @@ -396,7 +402,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.nextEventCcy = waveNextEventCcy; } } + else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { + isrNextEventCcy = waveNextEventCcy; + } } + now = ESP.getCycleCount(); } } @@ -404,7 +414,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { if (waveform.timer1CB) { callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB())); } - const uint32_t now = ESP.getCycleCount(); + now = ESP.getCycleCount(); int32_t nextTimerCcys = waveform.nextEventCcy - now; // Account for unknown duration of timer1CB(). if (waveform.timer1CB && nextTimerCcys > callbackCcys) { From 76469840e04dbe30caffeefdf539b0eedc3e7ee5 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 26 May 2020 15:28:15 +0200 Subject: [PATCH 139/152] Minor code simplification. --- cores/esp8266/core_esp8266_waveform.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 2bc216f3be..75abc01551 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -337,8 +337,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { busyPins ^= pinBit; } else { - const uint32_t overshootCcys = now - waveNextEventCcy; - if (static_cast(overshootCcys) >= 0) { + const int32_t overshootCcys = now - waveNextEventCcy; + if (overshootCcys >= 0) { const int32_t periodCcys = scaleCcys(wave.periodCcys); if (waveform.states & pinBit) { // active configuration and forward are 100% duty @@ -361,12 +361,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveNextEventCcy = wave.nextPeriodCcy; } else { + wave.nextPeriodCcy += periodCcys; if (!wave.dutyCcys) { - wave.nextPeriodCcy += periodCcys; wave.endDutyCcy = wave.nextPeriodCcy; } else { - wave.nextPeriodCcy += periodCcys; int32_t dutyCcys = scaleCcys(wave.dutyCcys); if (dutyCcys > wave.adjDutyCcys) { dutyCcys -= wave.adjDutyCcys; @@ -377,7 +376,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { dutyCcys = 0; } wave.endDutyCcy = now + dutyCcys; - if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) >= 0) { + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { wave.endDutyCcy = wave.nextPeriodCcy; } waveform.states |= pinBit; From ab1b4c15d2f1e6092c9b5cfe752ab105332f700d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 May 2020 11:38:44 +0200 Subject: [PATCH 140/152] Improve code efficiency. --- cores/esp8266/core_esp8266_waveform.cpp | 31 ++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 75abc01551..c9a070f81e 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -84,8 +84,8 @@ namespace { uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine - int32_t toSet = -1; // Message to the NMI handler to start/modify exactly one waveform - int32_t toDisable = -1; // Message to the NMI handler to disable exactly one pin from waveform generation + int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform + int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation uint32_t(*timer1CB)() = nullptr; @@ -187,7 +187,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } } std::atomic_thread_fence(std::memory_order_release); - waveform.toSet = pin; + waveform.toSetBits = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); if (!waveform.timer1Running) { initTimer(); @@ -204,11 +204,11 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (runTimeCcys) { wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); - waveform.toSet = pin; + waveform.toSetBits = 1UL << pin; } } std::atomic_thread_fence(std::memory_order_acq_rel); - while (waveform.toSet >= 0) { + while (waveform.toSetBits) { delay(0); // Wait for waveform to update std::atomic_thread_fence(std::memory_order_acquire); } @@ -226,13 +226,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { std::atomic_thread_fence(std::memory_order_acquire); const uint32_t pinBit = 1UL << pin; if (waveform.enabled & pinBit) { - waveform.toDisable = pin; + waveform.toDisableBits = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); } - while (waveform.toDisable >= 0) { + while (waveform.toDisableBits) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ std::atomic_thread_fence(std::memory_order_acquire); } @@ -262,23 +262,22 @@ static inline ICACHE_RAM_ATTR int32_t scaleCcys(int32_t ccys) { static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); - const uint32_t toSetMask = waveform.toSet >= 0 ? 1UL << waveform.toSet : 0; - const uint32_t toDisableMask = waveform.toDisable >= 0 ? 1UL << waveform.toDisable : 0; - if ((toSetMask && !(waveform.enabled & toSetMask)) || toDisableMask) { + if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { // Handle enable/disable requests from main app. - waveform.enabled = (waveform.enabled & ~toDisableMask) | toSetMask; // Set the requested waveforms on/off + waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off // 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) waveform.startPin = __builtin_ffs(waveform.enabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) waveform.endPin = 32 - __builtin_clz(waveform.enabled); - waveform.toDisable = -1; + waveform.toDisableBits = 0; } - if (toSetMask) { - Waveform& wave = waveform.pins[waveform.toSet]; + if (waveform.toSetBits) { + const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1; + Waveform& wave = waveform.pins[toSetPin]; switch (wave.mode) { case WaveformMode::INIT: - waveform.states &= ~toSetMask; // Clear the state of any just started + waveform.states &= ~waveform.toSetBits; // Clear the state of any just started if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; if (static_cast(waveform.nextEventCcy - wave.nextPeriodCcy) > 0) { @@ -302,7 +301,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { default: break; } - waveform.toSet = -1; + waveform.toSetBits = 0; } // Exit the loop if the next event, if any, is sufficiently distant. From 3d8865da2d0ed9fe1ad69bda4e32f5231bff9ba2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 May 2020 15:44:55 +0200 Subject: [PATCH 141/152] Improved performance of loop. --- cores/esp8266/core_esp8266_waveform.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c9a070f81e..c2b06e54b9 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -91,11 +91,6 @@ namespace { bool timer1Running = false; - // Optimize the NMI inner loop by keeping track of the min and max GPIO that we - // are generating. In the common case (1 PWM) these may be the same pin and - // we can avoid looking at the other pins. - int startPin = 0; - int endPin = 0; uint32_t nextEventCcy; } waveform; @@ -266,9 +261,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Handle enable/disable requests from main app. waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off // 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) - waveform.startPin = __builtin_ffs(waveform.enabled) - 1; - // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - waveform.endPin = 32 - __builtin_clz(waveform.enabled); waveform.toDisableBits = 0; } @@ -319,11 +311,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { break; } isrNextEventCcy = waveform.nextEventCcy; - for (int pin = waveform.startPin; pin <= waveform.endPin; ++pin) { + uint32_t loopPins = busyPins; + while (loopPins) { + const int pin = __builtin_ffsl(loopPins) - 1; const uint32_t pinBit = 1UL << pin; - // If it's not on, ignore - if (!(busyPins & pinBit)) - continue; + loopPins ^= pinBit; Waveform& wave = waveform.pins[pin]; From 60bfa928d5c31035b0604a7b6cca30ba53209d0c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 28 May 2020 03:54:30 +0200 Subject: [PATCH 142/152] Recalibrated. --- cores/esp8266/core_esp8266_waveform.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index c2b06e54b9..a8dc9be41d 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -52,10 +52,10 @@ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // decrement the next IRQ's timer value by a bit so we can actually catch the // real CPU cycle count we want for the waveforms. constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? - (microsecondsToClockCycles(3) / 2) >> 1 : (microsecondsToClockCycles(3) / 2); + microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. @@ -108,7 +108,7 @@ static void initTimer() { ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); waveform.timer1Running = true; - timer1_write(CPU2X & 1 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); // Cause an interrupt post-haste + timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste } static void ICACHE_RAM_ATTR deinitTimer() { @@ -189,7 +189,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, } else if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { // Must not interfere if Timer is due shortly - timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); + timer1_write(IRQLATENCYCCYS); } } else { @@ -225,7 +225,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { - timer1_write((clockCyclesPerMicrosecond() == 160) ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1)); + timer1_write(IRQLATENCYCCYS); } while (waveform.toDisableBits) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ From b78329bfcf8f36bd5952247cae77ccc564417b4d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 28 May 2020 16:11:46 +0200 Subject: [PATCH 143/152] No positive effect of lead time inclusion was found during testing, remove this code. Maximum period duration limit is implicit to timer, consider it documented constraint, don't runtime check in ISR. --- cores/esp8266/core_esp8266_waveform.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index a8dc9be41d..582ac7c823 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -48,11 +48,6 @@ constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); -// The SDK and hardware take some time to actually get to our NMI code, so -// decrement the next IRQ's timer value by a bit so we can actually catch the -// real CPU cycle count we want for the waveforms. -constexpr int32_t DELTAIRQCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); // The latency between in-ISR rearming of the timer and the earliest firing constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); @@ -187,7 +182,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!waveform.timer1Running) { initTimer(); } - else if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { + else if (T1V > ((clockCyclesPerMicrosecond() == 160) ? IRQLATENCYCCYS >> 1 : IRQLATENCYCCYS)) { // Must not interfere if Timer is due shortly timer1_write(IRQLATENCYCCYS); } @@ -224,7 +219,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { waveform.toDisableBits = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly - if (T1V > ((clockCyclesPerMicrosecond() == 160) ? (IRQLATENCYCCYS + DELTAIRQCCYS) >> 1 : IRQLATENCYCCYS + DELTAIRQCCYS)) { + if (T1V > ((clockCyclesPerMicrosecond() == 160) ? IRQLATENCYCCYS >> 1 : IRQLATENCYCCYS)) { timer1_write(IRQLATENCYCCYS); } while (waveform.toDisableBits) { @@ -306,7 +301,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { uint32_t now = ESP.getCycleCount(); uint32_t isrNextEventCcy = now; while (busyPins) { - if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS + DELTAIRQCCYS) { + if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS) { waveform.nextEventCcy = isrNextEventCcy; break; } @@ -412,15 +407,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= IRQLATENCYCCYS + DELTAIRQCCYS) { + if (nextTimerCcys <= IRQLATENCYCCYS) { nextTimerCcys = IRQLATENCYCCYS; } - else if (nextTimerCcys >= MAXIRQTICKSCCYS + DELTAIRQCCYS) { - nextTimerCcys = MAXIRQTICKSCCYS; - } - else { - nextTimerCcys -= DELTAIRQCCYS; - } // Register access is fast and edge IRQ was configured before. // Timer is 80MHz fixed. 160MHz binaries need scaling. From f2dc71b5785026fd98080277d2edd97baada0b26 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 30 May 2020 10:53:20 +0200 Subject: [PATCH 144/152] =?UTF-8?q?Fix=20WDT=20when=20at=20160MHz=20CPU=20?= =?UTF-8?q?clock=20the=20Timer1=20is=20set=20below=201=C2=B5s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp8266/core_esp8266_waveform.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 582ac7c823..6b8c46f6b7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -49,8 +49,7 @@ constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // The latency between in-ISR rearming of the timer and the earliest firing -constexpr int32_t IRQLATENCYCCYS = clockCyclesPerMicrosecond() == 160 ? - microsecondsToClockCycles(1) >> 1 : microsecondsToClockCycles(1); +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(1); // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. @@ -406,12 +405,17 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcys = callbackCcys; } + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + constexpr bool cpuFreq160MHz = clockCyclesPerMicrosecond() == 160; + if (cpuFreq160MHz || CPU2X & 1) { + nextTimerCcys >>= 1; + } + // Firing timer too soon, the NMI occurs before ISR has returned. if (nextTimerCcys <= IRQLATENCYCCYS) { nextTimerCcys = IRQLATENCYCCYS; } // Register access is fast and edge IRQ was configured before. - // Timer is 80MHz fixed. 160MHz binaries need scaling. - T1L = (CPU2X & 1) ? nextTimerCcys >> 1 : nextTimerCcys; + T1L = nextTimerCcys; } From 7e247c27ba95a9b5a063a9802df76f4ed585aad7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 30 May 2020 11:22:01 +0200 Subject: [PATCH 145/152] =?UTF-8?q?Consolidate=20160MHz=20constexpr=20chec?= =?UTF-8?q?k,=20finish=201=C2=B5s=20minimum=20for=20Timer1=20fix.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp8266/core_esp8266_waveform.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 6b8c46f6b7..1e4b580542 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -44,6 +44,8 @@ #include "ets_sys.h" #include +// Timer is 80MHz fixed. 160MHz CPU frequency need scaling. +constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160; // Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ @@ -181,7 +183,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, if (!waveform.timer1Running) { initTimer(); } - else if (T1V > ((clockCyclesPerMicrosecond() == 160) ? IRQLATENCYCCYS >> 1 : IRQLATENCYCCYS)) { + else if (T1V > IRQLATENCYCCYS) { // Must not interfere if Timer is due shortly timer1_write(IRQLATENCYCCYS); } @@ -218,7 +220,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { waveform.toDisableBits = 1UL << pin; std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly - if (T1V > ((clockCyclesPerMicrosecond() == 160) ? IRQLATENCYCCYS >> 1 : IRQLATENCYCCYS)) { + if (T1V > IRQLATENCYCCYS) { timer1_write(IRQLATENCYCCYS); } while (waveform.toDisableBits) { @@ -240,12 +242,11 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. static inline ICACHE_RAM_ATTR int32_t scaleCcys(int32_t ccys) { - constexpr bool cpuFreq80MHz = clockCyclesPerMicrosecond() == 80; - if (cpuFreq80MHz) { - return ((CPU2X & 1) ? ccys << 1 : ccys); + if (ISCPUFREQ160MHZ) { + return ((CPU2X & 1) ? ccys : ccys >> 1); } else { - return ((CPU2X & 1) ? ccys : ccys >> 1); + return ((CPU2X & 1) ? ccys << 1 : ccys); } } @@ -406,8 +407,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. - constexpr bool cpuFreq160MHz = clockCyclesPerMicrosecond() == 160; - if (cpuFreq160MHz || CPU2X & 1) { + if (ISCPUFREQ160MHZ || CPU2X & 1) { nextTimerCcys >>= 1; } From 440c444045fb2b8cbc90a023921e6556b574d160 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 31 May 2020 13:12:57 +0200 Subject: [PATCH 146/152] Test for non-zero before subtract should improve performance. --- cores/esp8266/core_esp8266_waveform.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 1e4b580542..fd8663e760 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -353,14 +353,14 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } else { int32_t dutyCcys = scaleCcys(wave.dutyCcys); - if (dutyCcys > wave.adjDutyCcys) { + if (dutyCcys <= wave.adjDutyCcys) { + dutyCcys >>= 1; + wave.adjDutyCcys -= dutyCcys; + } + else if (wave.adjDutyCcys) { dutyCcys -= wave.adjDutyCcys; wave.adjDutyCcys = 0; } - else { - wave.adjDutyCcys -= dutyCcys; - dutyCcys = 0; - } wave.endDutyCcy = now + dutyCcys; if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { wave.endDutyCcy = wave.nextPeriodCcy; From 610fe5a618b7a4bb78c7db6391215ebf8afe49dc Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 7 Jun 2020 20:21:34 +0200 Subject: [PATCH 147/152] Reviewers/tested noted they were seeing WDT, and this change appeared to fix that. --- cores/esp8266/core_esp8266_waveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index fd8663e760..03fb8beeb9 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -51,7 +51,7 @@ constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // The latency between in-ISR rearming of the timer and the earliest firing -constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(1); +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. From 0f1c4234e87a20099bc2959fe23a3b6ae79b9bfa Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 7 Jun 2020 21:49:49 +0200 Subject: [PATCH 148/152] More expressive use of parentheses and alias CPU2X for reduced code size. --- cores/esp8266/core_esp8266_waveform.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 03fb8beeb9..5c29167c2c 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -241,16 +241,17 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. -static inline ICACHE_RAM_ATTR int32_t scaleCcys(int32_t ccys) { +static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) { if (ISCPUFREQ160MHZ) { - return ((CPU2X & 1) ? ccys : ccys >> 1); + return isCPU2X ? ccys : (ccys >> 1); } else { - return ((CPU2X & 1) ? ccys << 1 : ccys); + return isCPU2X ? (ccys << 1) : ccys; } } static ICACHE_RAM_ATTR void timer1Interrupt() { + const bool isCPU2X = CPU2X & 1; const uint32_t isrStartCcy = ESP.getCycleCount(); if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { // Handle enable/disable requests from main app. @@ -282,7 +283,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // fall through case WaveformMode::UPDATEEXPIRY: // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count - wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy); + wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); wave.mode = WaveformMode::EXPIRES; break; default: @@ -325,7 +326,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { else { const int32_t overshootCcys = now - waveNextEventCcy; if (overshootCcys >= 0) { - const int32_t periodCcys = scaleCcys(wave.periodCcys); + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); if (waveform.states & pinBit) { // active configuration and forward are 100% duty if (wave.periodCcys == wave.dutyCcys) { @@ -352,7 +353,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wave.endDutyCcy = wave.nextPeriodCcy; } else { - int32_t dutyCcys = scaleCcys(wave.dutyCcys); + int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X); if (dutyCcys <= wave.adjDutyCcys) { dutyCcys >>= 1; wave.adjDutyCcys -= dutyCcys; @@ -397,7 +398,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t callbackCcys = 0; if (waveform.timer1CB) { - callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB())); + callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X); } now = ESP.getCycleCount(); int32_t nextTimerCcys = waveform.nextEventCcy - now; @@ -407,7 +408,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. - if (ISCPUFREQ160MHZ || CPU2X & 1) { + if (ISCPUFREQ160MHZ || isCPU2X) { nextTimerCcys >>= 1; } From 59d314377bb26288980d45547d3ae190f0c13dcb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 7 Jun 2020 23:50:46 +0200 Subject: [PATCH 149/152] =?UTF-8?q?Bug=20fix:=20at=20160MHz=20compiled,=20?= =?UTF-8?q?don't=20force=20minimum=20Timer1=20latency=20to=202=C2=B5s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp8266/core_esp8266_waveform.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 5c29167c2c..e4eaaa31c4 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -407,16 +407,16 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { nextTimerCcys = callbackCcys; } + // Firing timer too soon, the NMI occurs before ISR has returned. + if (nextTimerCcys < IRQLATENCYCCYS) { + nextTimerCcys = IRQLATENCYCCYS; + } + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. if (ISCPUFREQ160MHZ || isCPU2X) { nextTimerCcys >>= 1; } - // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys <= IRQLATENCYCCYS) { - nextTimerCcys = IRQLATENCYCCYS; - } - // Register access is fast and edge IRQ was configured before. T1L = nextTimerCcys; } From bc7d279d95f5b172ccbbe7c169a1073c62474d0e Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 8 Jun 2020 19:17:00 +0200 Subject: [PATCH 150/152] Alternate CPU frequency scaling mitigation. --- cores/esp8266/core_esp8266_waveform.cpp | 50 ++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e4eaaa31c4..738435d606 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -51,7 +51,11 @@ constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // The latency between in-ISR rearming of the timer and the earliest firing -constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); +constexpr int32_t IRQLATENCYCCYS = ISCPUFREQ160MHZ ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); +// The SDK and hardware take some time to actually get to our NMI code +constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. @@ -251,8 +255,9 @@ static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool i } static ICACHE_RAM_ATTR void timer1Interrupt() { - const bool isCPU2X = CPU2X & 1; const uint32_t isrStartCcy = ESP.getCycleCount(); + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy - DELTAIRQCCYS; + const bool isCPU2X = CPU2X & 1; if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { // Handle enable/disable requests from main app. waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off @@ -268,13 +273,9 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { waveform.states &= ~waveform.toSetBits; // Clear the state of any just started if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; - if (static_cast(waveform.nextEventCcy - wave.nextPeriodCcy) > 0) { - waveform.nextEventCcy = wave.nextPeriodCcy; - } } else { - wave.nextPeriodCcy = isrStartCcy; - waveform.nextEventCcy = wave.nextPeriodCcy; + wave.nextPeriodCcy = waveform.nextEventCcy; } if (!wave.expiryCcy) { wave.mode = WaveformMode::INFINITE; @@ -294,10 +295,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Exit the loop if the next event, if any, is sufficiently distant. const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; - uint32_t busyPins = (static_cast(waveform.nextEventCcy - isrTimeoutCcy) < 0) ? waveform.enabled : 0; - if (!waveform.enabled || busyPins) { - waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; - } + uint32_t busyPins = waveform.enabled; + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; uint32_t now = ESP.getCycleCount(); uint32_t isrNextEventCcy = now; @@ -315,6 +314,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; + if (clockDrift) { + wave.endDutyCcy += clockDrift; + wave.nextPeriodCcy += clockDrift; + wave.expiryCcy += clockDrift; + } + uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && @@ -394,6 +399,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } now = ESP.getCycleCount(); } + clockDrift = 0; } int32_t callbackCcys = 0; @@ -401,22 +407,24 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X); } now = ESP.getCycleCount(); - int32_t nextTimerCcys = waveform.nextEventCcy - now; + int32_t nextEventCcys = waveform.nextEventCcy - now; // Account for unknown duration of timer1CB(). - if (waveform.timer1CB && nextTimerCcys > callbackCcys) { - nextTimerCcys = callbackCcys; + if (waveform.timer1CB && nextEventCcys > callbackCcys) { + waveform.nextEventCcy = now + callbackCcys; + nextEventCcys = callbackCcys; } - // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextTimerCcys < IRQLATENCYCCYS) { - nextTimerCcys = IRQLATENCYCCYS; + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + if (isCPU2X) { + nextEventCcys >>= 1; } - // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. - if (ISCPUFREQ160MHZ || isCPU2X) { - nextTimerCcys >>= 1; + // Firing timer too soon, the NMI occurs before ISR has returned. + if (nextEventCcys < IRQLATENCYCCYS) { + waveform.nextEventCcy = now + IRQLATENCYCCYS; + nextEventCcys = IRQLATENCYCCYS; } // Register access is fast and edge IRQ was configured before. - T1L = nextTimerCcys; + T1L = nextEventCcys; } From 12a4c65f0824fe668dfb747eeeee2df8939826a6 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 9 Jun 2020 09:11:49 +0200 Subject: [PATCH 151/152] Handle time-of-flight in the right spot. --- cores/esp8266/core_esp8266_waveform.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 738435d606..952e1fd197 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -51,8 +51,7 @@ constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); // Maximum servicing time for any single IRQ constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); // The latency between in-ISR rearming of the timer and the earliest firing -constexpr int32_t IRQLATENCYCCYS = ISCPUFREQ160MHZ ? - microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); // The SDK and hardware take some time to actually get to our NMI code constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); @@ -256,7 +255,7 @@ static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool i static ICACHE_RAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); - int32_t clockDrift = isrStartCcy - waveform.nextEventCcy - DELTAIRQCCYS; + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; const bool isCPU2X = CPU2X & 1; if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { // Handle enable/disable requests from main app. @@ -415,14 +414,21 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { } // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + int32_t deltaIrqCcys = DELTAIRQCCYS; + int32_t irqLatencyCcys = IRQLATENCYCCYS; if (isCPU2X) { nextEventCcys >>= 1; + deltaIrqCcys >>= 1; + irqLatencyCcys >>= 1; } // Firing timer too soon, the NMI occurs before ISR has returned. - if (nextEventCcys < IRQLATENCYCCYS) { - waveform.nextEventCcy = now + IRQLATENCYCCYS; - nextEventCcys = IRQLATENCYCCYS; + if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) { + waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS; + nextEventCcys = irqLatencyCcys; + } + else { + nextEventCcys -= deltaIrqCcys; } // Register access is fast and edge IRQ was configured before. From e4892fa767b7f807b6fd7863a3632bcf2075f66a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 4 Nov 2020 12:22:29 +0100 Subject: [PATCH 152/152] Remove _toneMap from Tone.cpp --- cores/esp8266/Tone.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 27726b28d0..601b1df51d 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -25,18 +25,12 @@ #include "core_esp8266_waveform.h" #include "user_interface.h" -// Which pins have a tone running on them? -static uint32_t _toneMap = 0; - - static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { if (_pin > 16) { return; } - if (!(_toneMap & 1UL << _pin)) { - pinMode(_pin, OUTPUT); - } + pinMode(_pin, OUTPUT); high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) @@ -44,9 +38,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat duration = microsecondsToClockCycles(duration * 1000UL); duration += high + low - 1; duration -= duration % (high + low); - if (startWaveformClockCycles(_pin, high, low, duration)) { - _toneMap |= 1UL << _pin; - } + startWaveformClockCycles(_pin, high, low, duration); } @@ -88,6 +80,5 @@ void noTone(uint8_t _pin) { return; } stopWaveform(_pin); - _toneMap &= ~(1UL << _pin); digitalWrite(_pin, 0); }