From 59deebc961ec86f602504499ca45fda239a04bd6 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 29 Sep 2024 10:00:27 -0400 Subject: [PATCH] Improve PWM on ESP8266 - Better phase updates without dropping samples - Make second pin duty cycle always after first, even inverted --- .../src/core_esp8266_waveform_phase.cpp | 20 +++++++++++-------- wled00/bus_manager.cpp | 11 ++++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp index 60b9b0eb5..b89ec8bc1 100644 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -242,7 +242,7 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); waveform.toSetBits = 1UL << pin; - } else if (alignPhase) { + } else if (alignPhase >= 0) { // @willmmiles new feature wave.mode = WaveformMode::UPDATEPHASE; // recalculate start std::atomic_thread_fence(std::memory_order_release); @@ -303,7 +303,7 @@ static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X static IRAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); - int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + //int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; // ----- @willmmiles begin patch ----- nmiCrashWorkaround(); @@ -341,12 +341,16 @@ static IRAM_ATTR void timer1Interrupt() { break; // @willmmiles new feature case WaveformMode::UPDATEPHASE: - // in WaveformMode::UPDATEPHASE, we recalculate the targets without adjusting the state + // in WaveformMode::UPDATEPHASE, we recalculate the targets if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { - auto& align_wave = waveform.pins[waveform.alignPhase]; - // Go back one cycle - wave.nextPeriodCcy = align_wave.nextPeriodCcy - scaleCcys(align_wave.periodCcys, isCPU2X) + scaleCcys(waveform.phaseCcy, isCPU2X); - wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys, isCPU2X); + // Compute phase shift to realign with target + auto& align_wave = waveform.pins[waveform.alignPhase]; + int32_t shift = static_cast(align_wave.nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X) - wave.nextPeriodCcy); + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); + if (shift > periodCcys/2) shift -= periodCcys; + else if (shift <= -periodCcys/2) shift += periodCcys; + wave.nextPeriodCcy += shift; + wave.endDutyCcy += shift; } default: break; @@ -462,7 +466,7 @@ static IRAM_ATTR void timer1Interrupt() { } now = ESP.getCycleCount(); } - clockDrift = 0; + //clockDrift = 0; } int32_t callbackCcys = 0; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index e631190d4..c3c8a2121 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -565,7 +565,7 @@ void BusPwm::show() { const unsigned analogPeriod = F_CPU / _frequency; const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy constexpr bool dithering = false; - constexpr unsigned bitShift = 7; // 2^7 clocks for dead time + constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz #else // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) @@ -609,8 +609,10 @@ void BusPwm::show() { duty -= deadTime << 1; // shorten duty of larger signal except if full on } } - if (_reversed) duty = maxBri - duty; - + if (_reversed) { + if (i) hPoint += duty; // align start at time zero + duty = maxBri - duty; + } #ifdef ESP8266 //stopWaveform(_pins[i]); // can cause the waveform to miss a cycle. instead we risk crossovers. startWaveformClockCycles(_pins[i], duty, analogPeriod - duty, 0, i ? _pins[0] : -1, hPoint, false); @@ -625,7 +627,8 @@ void BusPwm::show() { ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); #endif - hPoint += duty + (_reversed ? -1 : 1) * deadTime; // offset to cascade the signals + if (!_reversed) hPoint += duty; + hPoint += deadTime; // offset to cascade the signals if (hPoint >= maxBri) hPoint -= maxBri; // offset is out of bounds, reset } }