From b4bb5fe4bd93bda8395889a30a33c57cc6227ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20M=C3=B6hle?= <91616163+softhack007@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:18:15 +0100 Subject: [PATCH 1/8] (0.15.x) wait for LEDs output to finish before OS calls that potentially suspend interrupts (#5435) * adding strip.waitForLEDs(waitMS) function * wait for LEDs output to complete before file writing * wait before ESP.getFreeHeap() - main loop * wait before file close (upload) and before getFreeHeap() (json info) * avoid losing "trigger" events due to strip.suspend --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- wled00/FX.h | 6 +++++- wled00/FX_fcn.cpp | 21 +++++++++++++++++++++ wled00/file.cpp | 21 +++++++++++++++++++++ wled00/json.cpp | 4 ++++ wled00/ota_update.cpp | 2 ++ wled00/wled.cpp | 5 ++--- wled00/wled_server.cpp | 7 +++++++ 7 files changed, 62 insertions(+), 4 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 15dfca2303..d3d9dd01f6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -910,6 +910,11 @@ class WS2812FX { { if (_segments.size() < getMaxSegments()) _segments.emplace_back(sStart,sStop,sStartY,sStopY); } inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution inline void resume() { _suspend = false; } // will resume strip.service() execution + inline bool isSuspended() const { return _suspend; } // true if strip.service() execution is suspended + // be nice, but not too nice - wait until LEDs are idle, or maxWaitMS have passed + // on 8266 this call will _not_ wait outside of the main loop context + // returns isUpdating() status after waiting + bool waitForLEDs(unsigned maxWaitMS) const; void restartRuntime(); void setTransitionMode(bool t); @@ -923,7 +928,6 @@ class WS2812FX { inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) - inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request // uint8_t paletteBlend; // obsolete - use global paletteBlend instead of strip.paletteBlend diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a30410daec..85c0eb321c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1837,6 +1837,27 @@ void WS2812FX::waitForIt() { #endif }; +/** + * Be nice, but not too nice - wait until LEDs are idle, or maxWaitMS milliseconds have passed + * On 8266 this call will _not_ wait outside the main loop context + * Function returns isUpdating() status after waiting + */ +bool WS2812FX::waitForLEDs(unsigned maxWaitMS) const { + #ifdef ARDUINO_ARCH_ESP32 + unsigned long waitStart = millis(); + while (strip.isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); + #else + if (can_yield()) { + // If we are in a yieldable context (main loop), wait until the LEDs output finishes + yield(); + unsigned long waitStart = millis(); + while (strip.isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); + yield(); + } + #endif + return isUpdating(); +} + void WS2812FX::setTargetFps(unsigned fps) { if (fps <= 250) _targetFps = fps; if (_targetFps > 0) _frametime = 1000 / _targetFps; diff --git a/wled00/file.cpp b/wled00/file.cpp index 5a169d6450..753c635ef5 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -34,11 +34,23 @@ static File f; // don't export to other cpp files //wrapper to find out how long closing takes void closeFile() { + if (!doCloseFile || !f) { doCloseFile = false; return; } // file not open, or no request to close -> nothing to do, nothing to wait #ifdef WLED_DEBUG_FS DEBUGFS_PRINT(F("Close -> ")); uint32_t s = millis(); #endif + doCloseFile = false; // consume flag early, to reduce the time window for concurrent closing attempts from several tasks. + + // f.close() may enter flash critical sections (interrupts/cache paused), so we wait for LED transmission to finish first to avoid WS281x glitches + // This is most relevant on ESP32-C3/C5/C6, where the RMT driver is very sensitive to interrupt timing. + bool haveSuspended = false; + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; }// prevent that a new strip.show() starts after waiting + strip.waitForLEDs(15); // be nice, but not too nice. Waits up to 15ms + #endif + f.close(); // "if (f)" check is aleady done inside f.close(), and f cannot be nullptr -> no need for double checking before closing the file handle. + if (haveSuspended) strip.resume(); // end of critical section - new LEDs updates are allowed again DEBUGFS_PRINTF("took %lu ms\n", millis() - s); doCloseFile = false; } @@ -438,6 +450,9 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ } #endif if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) { + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) + #endif request->send(request->beginResponse(WLED_FS, path, {}, request->hasArg(F("download")), {})); return true; } @@ -452,6 +467,9 @@ bool copyFile(const char* src_path, const char* dst_path) { return false; } + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) + #endif bool success = true; // is set to false on error File src = WLED_FS.open(src_path, "r"); File dst = WLED_FS.open(dst_path, "w"); @@ -490,6 +508,9 @@ bool compareFiles(const char* path1, const char* path2) { return false; } + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) + #endif bool identical = true; // set to false on mismatch File f1 = WLED_FS.open(path1, "r"); File f2 = WLED_FS.open(path2, "r"); diff --git a/wled00/json.cpp b/wled00/json.cpp index b4388d27ef..423bda4d65 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -849,6 +849,10 @@ void serializeInfo(JsonObject root) root[F("lwip")] = LWIP_VERSION_MAJOR; #endif +#if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + // calling ESP.getFreeHeap() during led update causes glitches on C3 and possibly on 8266, too + strip.waitForLEDs(15); // be nice, but not too nice. Waits up to 15ms. No need to suspend effects - ESP.getFreeHeap() will not need much time +#endif root[F("freeheap")] = getFreeHeapSize(); #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) // Report PSRAM information diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index b078dd89d9..8fc48c1975 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -239,6 +239,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context) UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) strip.suspend(); + strip.waitForLEDs(25); // wait max 25 ms for LED transmissions to finish backupConfig(); // backup current config in case the update ends badly strip.resetSegments(); // free as much memory as you can context->needsRestart = true; @@ -759,6 +760,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) { #endif lastEditTime = millis(); // make sure PIN does not lock during update strip.suspend(); + strip.waitForLEDs(25); // wait max 25 ms for LED transmissions to finish strip.resetSegments(); // Check available heap before attempting allocation diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 24b385cb33..77f1b96130 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -174,11 +174,10 @@ void WLED::loop() #ifdef ESP8266 uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly #else - #ifdef CONFIG_IDF_TARGET_ESP32C3 + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) // calling getContiguousFreeHeap() during led update causes glitches on C3 // this can (probably) be removed once RMT driver for C3 is fixed - unsigned t0 = millis(); - while (strip.isUpdating() && (millis() - t0 < 150)) delay(1); // be nice, but not too nice. Waits up to 150ms + strip.waitForLEDs(150); // be nice, but not too nice. Waits up to 150ms - we are in the main loop, so a new strip.show() cannot start while waiting #endif uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly #endif diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 0b4d0fb546..35b2d0d2d6 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -212,7 +212,14 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, request->_tempFile.write(data,len); } if (isFinal) { + bool haveSuspended = false; + #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) + if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; } // prevent that a new strip.show() starts after waiting + strip.waitForLEDs(25); // calling file.close() during LEDs sendout can cause glitches on C3 and on 8266 + #endif request->_tempFile.close(); + if (haveSuspended) strip.resume(); + if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting...")); From 37de1ce2bda1fd4cd4e833acb2c4fc348c9b15b0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:32:05 +0200 Subject: [PATCH 2/8] overlooked one --- wled00/file.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 753c635ef5..2b59b7e051 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -52,7 +52,6 @@ void closeFile() { f.close(); // "if (f)" check is aleady done inside f.close(), and f cannot be nullptr -> no need for double checking before closing the file handle. if (haveSuspended) strip.resume(); // end of critical section - new LEDs updates are allowed again DEBUGFS_PRINTF("took %lu ms\n", millis() - s); - doCloseFile = false; } //find() that reads and buffers data from file stream in 256-byte blocks. From 71ca244b469ae6bafaab8ac60a41f748a47c1846 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:19:38 +0200 Subject: [PATCH 3/8] modernization for 16.0.0 * use constants instead of raw timeouts * simplify usage - in many cases we don't need #ifdef any more * missed one "wait" on image_loader --- wled00/FX.h | 6 +++++- wled00/FX_fcn.cpp | 16 ++++++++++------ wled00/file.cpp | 14 ++++---------- wled00/image_loader.cpp | 6 ++---- wled00/json.cpp | 4 +--- wled00/ota_update.cpp | 4 ++-- wled00/wled.cpp | 4 +--- wled00/wled_server.cpp | 2 +- 8 files changed, 26 insertions(+), 30 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index d3d9dd01f6..ca58ddaf6c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -810,6 +810,10 @@ class Segment { friend class ParticleSystem1D; }; +// max wait times when waiting until !strip.isUpdating() - use with waitForLEDs(...) +constexpr unsigned STRIP_WAIT_SHORT = 50; // 50 ms - for cases where fluent animations are most important, and risk of flickering is low +constexpr unsigned STRIP_WAIT_MEDIUM = 150; // 150 ms - good balance to avoid flickering on -C3 (good up to 4000 ws2812b LEDs per pin) + // main "strip" class (108 bytes) class WS2812FX { typedef void (*mode_ptr)(); // pointer to mode function @@ -914,7 +918,7 @@ class WS2812FX { // be nice, but not too nice - wait until LEDs are idle, or maxWaitMS have passed // on 8266 this call will _not_ wait outside of the main loop context // returns isUpdating() status after waiting - bool waitForLEDs(unsigned maxWaitMS) const; + bool waitForLEDs(unsigned maxWaitMS, bool always = false) const; void restartRuntime(); void setTransitionMode(bool t); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 85c0eb321c..bbef0c4f8c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1838,11 +1838,15 @@ void WS2812FX::waitForIt() { }; /** - * Be nice, but not too nice - wait until LEDs are idle, or maxWaitMS milliseconds have passed - * On 8266 this call will _not_ wait outside the main loop context - * Function returns isUpdating() status after waiting - */ -bool WS2812FX::waitForLEDs(unsigned maxWaitMS) const { + * Be nice, but not too nice - wait until LEDs are idle, or maxWaitMS milliseconds have passed. + * always=true enforces waiting even when using the RMTHI driver. + * On 8266 this call will _not_ wait outside the main loop context. + * Function returns isUpdating() status after waiting. + **/ +bool WS2812FX::waitForLEDs(unsigned maxWaitMS, bool always) const { + #if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && !defined(ESP8266) + if (!always) return strip.isUpdating(); // no waiting needed if we have the flicker-free RMTHI driver + #endif #ifdef ARDUINO_ARCH_ESP32 unsigned long waitStart = millis(); while (strip.isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); @@ -1855,7 +1859,7 @@ bool WS2812FX::waitForLEDs(unsigned maxWaitMS) const { yield(); } #endif - return isUpdating(); + return strip.isUpdating(); } void WS2812FX::setTargetFps(unsigned fps) { diff --git a/wled00/file.cpp b/wled00/file.cpp index 2b59b7e051..1a78b5201f 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -46,7 +46,7 @@ void closeFile() { bool haveSuspended = false; #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; }// prevent that a new strip.show() starts after waiting - strip.waitForLEDs(15); // be nice, but not too nice. Waits up to 15ms + strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 15ms #endif f.close(); // "if (f)" check is aleady done inside f.close(), and f cannot be nullptr -> no need for double checking before closing the file handle. @@ -449,9 +449,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ } #endif if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) { - #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) - strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) - #endif + strip.waitForLEDs(STRIP_WAIT_SHORT); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) request->send(request->beginResponse(WLED_FS, path, {}, request->hasArg(F("download")), {})); return true; } @@ -466,9 +464,7 @@ bool copyFile(const char* src_path, const char* dst_path) { return false; } - #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) - strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) - #endif + strip.waitForLEDs(STRIP_WAIT_MEDIUM, true); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) bool success = true; // is set to false on error File src = WLED_FS.open(src_path, "r"); File dst = WLED_FS.open(dst_path, "w"); @@ -507,9 +503,7 @@ bool compareFiles(const char* path1, const char* path2) { return false; } - #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) - strip.waitForLEDs(25); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) - #endif + strip.waitForLEDs(STRIP_WAIT_SHORT); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering) bool identical = true; // set to false on mismatch File f1 = WLED_FS.open(path1, "r"); File f2 = WLED_FS.open(path2, "r"); diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index d74afc68d1..1b464d6a6d 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -28,10 +28,7 @@ int fileReadCallback(void) { } int fileReadBlockCallback(void * buffer, int numberOfBytes) { - #ifdef CONFIG_IDF_TARGET_ESP32C3 - unsigned t0 = millis(); - while (strip.isUpdating() && (millis() - t0 < 150)) yield(); // be nice, but not too nice. Waits up to 150ms to avoid glitches - #endif + strip.waitForLEDs(STRIP_WAIT_SHORT); return file.read((uint8_t*)buffer, numberOfBytes); } @@ -137,6 +134,7 @@ byte renderImageToSegment(Segment &seg) { DEBUG_PRINTF_P(PSTR("GIF decoder unsupported file: %s\n"), lastFilename); return IMAGE_ERROR_UNSUPPORTED_FORMAT; } + strip.waitForLEDs(STRIP_WAIT_MEDIUM); if (file) file.close(); if (!openGif(lastFilename)) { gifDecodeFailed = true; diff --git a/wled00/json.cpp b/wled00/json.cpp index 423bda4d65..e434669a44 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -849,10 +849,8 @@ void serializeInfo(JsonObject root) root[F("lwip")] = LWIP_VERSION_MAJOR; #endif -#if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) // calling ESP.getFreeHeap() during led update causes glitches on C3 and possibly on 8266, too - strip.waitForLEDs(15); // be nice, but not too nice. Waits up to 15ms. No need to suspend effects - ESP.getFreeHeap() will not need much time -#endif + strip.waitForLEDs(STRIP_WAIT_SHORT); // be nice, but not too nice. Waits up to 50ms. No need to suspend effects - ESP.getFreeHeap() will not need much time root[F("freeheap")] = getFreeHeapSize(); #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) // Report PSRAM information diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index 8fc48c1975..74c8863ed5 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -239,7 +239,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context) UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) strip.suspend(); - strip.waitForLEDs(25); // wait max 25 ms for LED transmissions to finish + strip.waitForLEDs(STRIP_WAIT_MEDIUM, true); // always wait for LED transmissions to finish backupConfig(); // backup current config in case the update ends badly strip.resetSegments(); // free as much memory as you can context->needsRestart = true; @@ -760,7 +760,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) { #endif lastEditTime = millis(); // make sure PIN does not lock during update strip.suspend(); - strip.waitForLEDs(25); // wait max 25 ms for LED transmissions to finish + strip.waitForLEDs(STRIP_WAIT_SHORT, true); // be sure that LED transmissions have finished strip.resetSegments(); // Check available heap before attempting allocation diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 77f1b96130..aaf4adb6b8 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -174,11 +174,9 @@ void WLED::loop() #ifdef ESP8266 uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly #else - #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) // calling getContiguousFreeHeap() during led update causes glitches on C3 // this can (probably) be removed once RMT driver for C3 is fixed - strip.waitForLEDs(150); // be nice, but not too nice. Waits up to 150ms - we are in the main loop, so a new strip.show() cannot start while waiting - #endif + strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 150ms - we are in the main loop, so a new strip.show() cannot start while waiting uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly #endif if (heap < MIN_HEAP_SIZE - 1024) heapDanger+=5; // allow 1k of "wiggle room" for things that do not respect min heap limits diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 35b2d0d2d6..305ca7a5ef 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -215,7 +215,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, bool haveSuspended = false; #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; } // prevent that a new strip.show() starts after waiting - strip.waitForLEDs(25); // calling file.close() during LEDs sendout can cause glitches on C3 and on 8266 + strip.waitForLEDs(STRIP_WAIT_SHORT, true); // calling file.close() during LEDs sendout can cause glitches on C3 and on 8266 #endif request->_tempFile.close(); if (haveSuspended) strip.resume(); From 0be9f2302df76d30c942e216142ae5c1e7393e14 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:20:59 +0200 Subject: [PATCH 4/8] fox comment comment should match real timeout --- wled00/file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 1a78b5201f..76df2abf29 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -46,7 +46,7 @@ void closeFile() { bool haveSuspended = false; #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; }// prevent that a new strip.show() starts after waiting - strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 15ms + strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 150ms #endif f.close(); // "if (f)" check is aleady done inside f.close(), and f cannot be nullptr -> no need for double checking before closing the file handle. From adbd8e8ee3c6f3f5e782acdd4ec8a7a124113218 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:45:04 +0200 Subject: [PATCH 5/8] wait for LEDs after beginStrip() prevents flickering at startup --- wled00/wled.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index aaf4adb6b8..893675b065 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -491,6 +491,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Initializing strip")); beginStrip(); + strip.waitForLEDs(STRIP_WAIT_MEDIUM); // prevent flickering - beginStrip() calls strip.show() DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); DEBUG_PRINTLN(F("Usermods setup")); From 16d94e7b8781b6361e9b5208313e5cc47e7ee910 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:46:26 +0200 Subject: [PATCH 6/8] minor improvements (suggested by the bunny) * space before comment * use isUpdating() - not strip.isUpdating() - in waitForLEDs() --- wled00/FX_fcn.cpp | 8 ++++---- wled00/file.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bbef0c4f8c..50d269b108 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1845,21 +1845,21 @@ void WS2812FX::waitForIt() { **/ bool WS2812FX::waitForLEDs(unsigned maxWaitMS, bool always) const { #if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && !defined(ESP8266) - if (!always) return strip.isUpdating(); // no waiting needed if we have the flicker-free RMTHI driver + if (!always) return isUpdating(); // no waiting needed if we have the flicker-free RMTHI driver #endif #ifdef ARDUINO_ARCH_ESP32 unsigned long waitStart = millis(); - while (strip.isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); + while (isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); #else if (can_yield()) { // If we are in a yieldable context (main loop), wait until the LEDs output finishes yield(); unsigned long waitStart = millis(); - while (strip.isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); + while (isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1); yield(); } #endif - return strip.isUpdating(); + return isUpdating(); } void WS2812FX::setTargetFps(unsigned fps) { diff --git a/wled00/file.cpp b/wled00/file.cpp index 76df2abf29..431ba24a72 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -45,7 +45,7 @@ void closeFile() { // This is most relevant on ESP32-C3/C5/C6, where the RMT driver is very sensitive to interrupt timing. bool haveSuspended = false; #if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32) - if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; }// prevent that a new strip.show() starts after waiting + if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; } // prevent that a new strip.show() starts after waiting strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 150ms #endif From 098908d9945fce11a026c646227a0e8401343a72 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 23:03:23 +0200 Subject: [PATCH 7/8] fix ancient typo --- wled00/wled.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 893675b065..3913ef9119 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -596,7 +596,7 @@ void WLED::setup() #endif #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //disable brownout detector #endif markOTAvalid(); } From 01e81c286a4189c1c51ad198512553e6afaad8bf Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Jun 2026 23:04:20 +0200 Subject: [PATCH 8/8] short wait in sendDataWs() getFreeHeapSize() can cause glitches. --- wled00/FX.h | 5 +++-- wled00/ws.cpp | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index ca58ddaf6c..707a8befa7 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -811,8 +811,9 @@ class Segment { }; // max wait times when waiting until !strip.isUpdating() - use with waitForLEDs(...) -constexpr unsigned STRIP_WAIT_SHORT = 50; // 50 ms - for cases where fluent animations are most important, and risk of flickering is low -constexpr unsigned STRIP_WAIT_MEDIUM = 150; // 150 ms - good balance to avoid flickering on -C3 (good up to 4000 ws2812b LEDs per pin) +constexpr unsigned STRIP_WAIT_VERYSHORT = 25; // 25 ms - when risk of flickering is low but delays should be avoided +constexpr unsigned STRIP_WAIT_SHORT = 50; // 50 ms - for cases where fluent animations are most important, and risk of flickering is low +constexpr unsigned STRIP_WAIT_MEDIUM = 150; // 150 ms - good balance to avoid flickering on -C3 (good up to 4000 ws2812b LEDs per pin) // main "strip" class (108 bytes) class WS2812FX { diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 6e9038c101..736199231e 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -153,6 +153,7 @@ void sendDataWs(AsyncWebSocketClient * client) DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len); // the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS + strip.waitForLEDs(STRIP_WAIT_VERYSHORT); // wait for LEDs ouptut to finish - prevents glitches on -C3 size_t heap1 = getFreeHeapSize(); DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); AsyncWebSocketBuffer buffer(len);