another attempt to avoid LED glitches during file writing

* mark synchronization variables volatile (thread-safe)
* additional logic in closeFile() to avoid file write while leds are pushed out by NPB driver
* some comments and analysis
* replace "yield()" after file ops with "delay(0)" before operations.
This commit is contained in:
Frank
2025-11-15 00:11:35 +01:00
parent 9f31f2444f
commit dc04ccbde7
3 changed files with 42 additions and 8 deletions

View File

@@ -8,6 +8,7 @@
#if WLED_FS != LITTLEFS && ESP_IDF_VERSION_MAJOR < 4
#include "esp_spiffs.h"
#endif
//#define yield() {delay(0);} // WLEDMM yield() is completely unnecessary on esp32, but delay(0) can reduce task contention
#endif
//WLEDMM seems that 256 is indeed the optimal buffer length
@@ -37,7 +38,20 @@ static File f; // don't export to other cpp files
void closeFile() {
#ifdef ARDUINO_ARCH_ESP32
// WLEDMM: file.close() triggers flash writing. While flash is writing, the NPB RMT driver cannot fill its buffer which may create glitches.
// WLEDMM more precisely (thanks to a web research done by AI):
// the RMT peripheral itself doesnt stall, but the refill path often does. In Arduino-ESP32/WLED
// typical builds, close() that commits flash writes frequently causes enough blocking that the LED pipeline under-runs, resulting in visible glitches.
// So the assumption is practically correct for this project context.
// --> with neopixelBus 2.7.5, the practical ISR stall budget is about 0.080.12 ms — far less than LittleFS flash commit times.
// typical flash write "commit" times are between 0.5ms and 10ms, but they can be a few 100ms in worst case
// --> file reads rarely cause refill stalls compared to writes, but large/fragmented reads can still exceed the ~0.080.12 ms budget.
// esp32 recommendations: use f.setBufferSize() (5121024 for reads is reasonable); use delay(0) after file reads, to reduce task contention
if (!f) {doCloseFile = false; return;} // WLEDMM only do all this hick-hack when f is an open file
unsigned long t_wait = millis();
bool oldLock = suspendStripService;
if (strip.isUpdating()) suspendStripService = true; // WLEDMM schedule short pause to prevent LEDs glitching during flash write
while(strip.isUpdating() && (millis() - t_wait < 72)) delay(1); // WLEDMM try to catch a moment when strip is idle
while(strip.isUpdating() && (millis() - t_wait < 96)) delay(0); // try harder
//if (strip.isUpdating()) USER_PRINTLN("closeFile: strip still updating.");
@@ -47,7 +61,12 @@ void closeFile() {
DEBUGFS_PRINT(F("Close -> "));
uint32_t s = millis();
#endif
if ((suspendStripService == false) && (oldLock == true)) oldLock = false; // update in case of parallel lock release by another task
f.close();
#ifdef ARDUINO_ARCH_ESP32
delay(1); // might help
#endif
suspendStripService = oldLock; // restore previous lock
DEBUGFS_PRINTF("took %d ms\n", millis() - s);
doCloseFile = false;
}
@@ -61,7 +80,7 @@ static bool bufferedFind(const char *target, bool fromStart = true) {
uint32_t s = millis();
#endif
if (!f || !f.size()) return false;
if (!f || !f.size()) return false; // fast return when current file closed, or file size is zero
size_t targetLen = strlen(target);
size_t index = 0;
@@ -466,11 +485,11 @@ static const uint8_t *getPresetCache(size_t &size) {
#endif
// WLEDMM
static bool haveLedmapFile = true;
static bool haveIndexFile = true;
static bool haveSkinFile = true;
static bool haveICOFile = true;
static bool haveCpalFile = true;
static volatile bool haveLedmapFile = true;
static volatile bool haveIndexFile = true;
static volatile bool haveSkinFile = true;
static volatile bool haveICOFile = true;
static volatile bool haveCpalFile = true;
void invalidateFileNameCache() { // reset "file not found" cache
haveLedmapFile = true;
haveIndexFile = true;