From 18aab4815b72a76f1cf735d94e0562c7ec0dc5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 29 Aug 2025 16:04:02 +0200 Subject: [PATCH 1/8] Attempt at better bus memory calculation and estimation - may fix wled#4614 - forces 1st 8 buses to be I2S if parallel I2S is used, followed by RMT --- wled00/FX_fcn.cpp | 35 ++++++++++-- wled00/bus_manager.cpp | 29 +++++----- wled00/bus_wrapper.h | 121 ++++++++++++++++++++--------------------- wled00/cfg.cpp | 11 +--- 4 files changed, 105 insertions(+), 91 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 2f8d5515fd..080f30059c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1171,14 +1171,41 @@ void WS2812FX::finalizeInit() { digitalCount = 0; #endif + DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize()); // create buses/outputs unsigned mem = 0; + unsigned maxI2S = 0; for (const auto &bus : busConfigs) { - mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer - if (mem <= MAX_LED_MEMORY) { - if (BusManager::add(bus) == -1) break; - } else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); + unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer + mem += memB; + // estimate maximum I2S memory usage (only relevant for digital non-2pin busses) + #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) + #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) + const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1)); + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + const bool usesI2S = (useParallelI2S && digitalCount <= 8); + #else + const bool usesI2S = false; + #endif + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) { + #ifdef NPB_CONF_4STEP_CADENCE + constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) + #else + constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) + #endif + unsigned i2sCommonSize = stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1); + if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + } + #endif + if (mem + maxI2S <= MAX_LED_MEMORY) { + BusManager::add(bus); + DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB); + } else { + DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); + break; + } } + DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage()); busConfigs.clear(); busConfigs.shrink_to_fit(); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 99523bba9f..709effb9d3 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -330,7 +330,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const { } size_t BusDigital::getBusSize() const { - return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); + return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer } void BusDigital::setColorOrder(uint8_t colorOrder) { @@ -766,7 +766,8 @@ size_t BusConfig::memUsage(unsigned nr) const { if (Bus::isVirtual(type)) { return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); } else if (Bus::isDigital(type)) { - return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/; + // if any of digital buses uses I2S, there is additional common I2S DMA buffer not accounted for here + return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)); } else if (Bus::isOnOff(type)) { return sizeof(BusOnOff); } else { @@ -782,23 +783,23 @@ size_t BusManager::memUsage() { unsigned maxI2S = 0; #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) unsigned digitalCount = 0; - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) - #define MAX_RMT 4 - #else - #define MAX_RMT 8 - #endif #endif for (const auto &bus : busses) { - unsigned busSize = bus->getBusSize(); + size += bus->getBusSize(); #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - if (bus->isDigital() && !bus->is2Pin()) digitalCount++; - if (PolyBus::isParallelI2S1Output() && digitalCount > MAX_RMT) { - unsigned i2sCommonSize = 3 * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); - if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; - busSize -= i2sCommonSize; + if (bus->isDigital() && !bus->is2Pin()) { + digitalCount++; + if ((PolyBus::isParallelI2S1Output() && digitalCount <= 8) || (!PolyBus::isParallelI2S1Output() && digitalCount == 1)) { + #ifdef NPB_CONF_4STEP_CADENCE + constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) + #else + constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) + #endif + unsigned i2sCommonSize = stepFactor * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); + if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + } } #endif - size += busSize; } return size + maxI2S; } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 2fe077182e..3dfe5db5c8 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -469,20 +469,20 @@ class PolyBus { } static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation - - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (_useParallelI2S && (channel >= 8)) { - // Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number - channel -= 8; - } - #endif - - #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + #if defined(CONFIG_IDF_TARGET_ESP32) + if (channel > 0) { + channel--; + if (_useParallelI2S && channel > 6) channel -= 7; // accommodate I2S1 which is used as 1st bus + } + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + if (_useParallelI2S && channel > 7) channel -= 8; // accommodate I2S1 which is used as 1st bus + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + if (_useParallelI2S && channel > 7) channel -= 8; // accommodate I2S1 which is used as 1st bus #endif - + #endif void* busPtr = nullptr; switch (busType) { case I_NONE: break; @@ -1212,68 +1212,62 @@ class PolyBus { case I_NONE: size = 0; break; #ifdef ESP8266 // UART methods have front + back buffers + small UART - case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_U0_UCS_3: size *= 4; break; // 16 bit - case I_8266_U1_UCS_3: size *= 4; break; // 16 bit - case I_8266_BB_UCS_3: size *= 4; break; // 16 bit - case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels - case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_8266_U1_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_8266_BB_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_8266_U0_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_8266_U1_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_8266_BB_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_8266_U0_UCS_3 : size *= 4; break; // 16 bit + case I_8266_U1_UCS_3 : size *= 4; break; // 16 bit + case I_8266_BB_UCS_3 : size *= 4; break; // 16 bit + case I_8266_U0_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U1_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_BB_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U0_FW6_5 : size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_FW6_5 : size = (size + 2*count)*2; break; // 5channels + case I_8266_BB_FW6_5 : size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_2805_5 : size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_2805_5 : size = (size + 2*count)*2; break; // 5 channels + case I_8266_BB_2805_5 : size = (size + 2*count)*2; break; // 5 channels case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels // DMA methods have front + DMA buffer = ((1+(3+1)) * channels) - case I_8266_DM_NEO_3: size *= 5; break; - case I_8266_DM_NEO_4: size = (size + count)*5; break; - case I_8266_DM_400_3: size *= 5; break; - case I_8266_DM_TM1_4: size = (size + count)*5; break; - case I_8266_DM_TM2_3: size *= 5; break; - case I_8266_DM_UCS_3: size *= 2*5; break; - case I_8266_DM_UCS_4: size = (size + count)*2*5; break; - case I_8266_DM_APA106_3: size *= 5; break; - case I_8266_DM_FW6_5: size = (size + 2*count)*5; break; - case I_8266_DM_2805_5: size = (size + 2*count)*5; break; - case I_8266_DM_TM1914_3: size *= 5; break; + case I_8266_DM_NEO_3 : size *= 5; break; + case I_8266_DM_NEO_4 : size = (size + count)*5; break; + case I_8266_DM_400_3 : size *= 5; break; + case I_8266_DM_TM1_4 : size = (size + count)*5; break; + case I_8266_DM_TM2_3 : size *= 5; break; + case I_8266_DM_UCS_3 : size *= 2*5; break; + case I_8266_DM_UCS_4 : size = (size + count)*2*5; break; + case I_8266_DM_APA106_3 : size *= 5; break; + case I_8266_DM_FW6_5 : size = (size + 2*count)*5; break; + case I_8266_DM_2805_5 : size = (size + 2*count)*5; break; + case I_8266_DM_TM1914_3 : size *= 5; break; case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; #endif #ifdef ARDUINO_ARCH_ESP32 - // RMT buses (1x front and 1x back buffer) - case I_32_RN_NEO_4: size = (size + count)*2; break; - case I_32_RN_TM1_4: size = (size + count)*2; break; - case I_32_RN_UCS_3: size *= 2*2; break; - case I_32_RN_UCS_4: size = (size + count)*2*2; break; - case I_32_RN_FW6_5: size = (size + 2*count)*2; break; - case I_32_RN_2805_5: size = (size + 2*count)*2; break; - case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; - // I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers) + // RMT buses (1x front and 1x back buffer, does not include small RMT buffer) + case I_32_RN_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_32_RN_UCS_3 : size *= 2*2; break; // 16bit + case I_32_RN_UCS_4 : size = (size + count)*2*2; break; // 16bit, 4 channels + case I_32_RN_FW6_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels + // I2S1 bus or paralell I2S1 buses (1x front and 1x back does not include DMA buffer which is front*cadence) #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_3: size *= 4; break; - case I_32_I2_NEO_4: size = (size + count)*4; break; - case I_32_I2_400_3: size *= 4; break; - case I_32_I2_TM1_4: size = (size + count)*4; break; - case I_32_I2_TM2_3: size *= 4; break; - case I_32_I2_UCS_3: size *= 2*4; break; - case I_32_I2_UCS_4: size = (size + count)*2*4; break; - case I_32_I2_APA106_3: size *= 4; break; - case I_32_I2_FW6_5: size = (size + 2*count)*4; break; - case I_32_I2_2805_5: size = (size + 2*count)*4; break; - case I_32_I2_TM1914_3: size *= 4; break; - case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break; + case I_32_I2_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_32_I2_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_32_I2_UCS_3 : size *= 2*2; break; // 16 bit + case I_32_I2_UCS_4 : size = (size + count)*2*2; break; // 16 bit, 4 channels + case I_32_I2_FW6_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_I2_2805_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_I2_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit, 5 channels #endif #endif - // everything else uses 2 buffers - default: size *= 2; break; + default : size *= 2; break; // everything else uses 2 buffers } return size; } @@ -1363,6 +1357,7 @@ class PolyBus { if (_useParallelI2S) { if (num > 15) return I_NONE; if (num < 8) offset = 1; // 8 I2S followed by 8 RMT + if (num < 8) offset = 1; // 8 I2S followed by 8 RMT } else { if (num > 9) return I_NONE; if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index eac6ea25a2..7ef5530429 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -256,9 +256,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); - unsigned mem = 0; unsigned pinsIndex = 0; - unsigned digitalCount = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) { uint8_t defPin[OUTPUT_MAX_PINS]; // if we have less types than requested outputs and they do not align, use last known type to set current type @@ -321,16 +319,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { unsigned start = 0; // analog always has length 1 if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; - BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); - mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0); - if (mem > MAX_LED_MEMORY) { - DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)dataType, (int)count, digitalCount); - break; - } - busConfigs.push_back(defCfg); // use push_back for simplification as we needed defCfg to calculate memory usage + busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); doInitBusses = true; // finalization done in beginStrip() } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage()); } if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus From e922d0f20d17986cee60faef969656d34a6c60cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 29 Aug 2025 21:22:54 +0200 Subject: [PATCH 2/8] Remov double buffer count for ESP8266 (thanks @dedehai) - improve UI calculation --- wled00/bus_wrapper.h | 87 ++++++++++++++++++----------------- wled00/data/settings_leds.htm | 19 +++++--- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 3dfe5db5c8..29d62712a5 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1212,62 +1212,65 @@ class PolyBus { case I_NONE: size = 0; break; #ifdef ESP8266 // UART methods have front + back buffers + small UART - case I_8266_U0_NEO_4 : size = (size + count)*2; break; // 4 channels - case I_8266_U1_NEO_4 : size = (size + count)*2; break; // 4 channels - case I_8266_BB_NEO_4 : size = (size + count)*2; break; // 4 channels - case I_8266_U0_TM1_4 : size = (size + count)*2; break; // 4 channels - case I_8266_U1_TM1_4 : size = (size + count)*2; break; // 4 channels - case I_8266_BB_TM1_4 : size = (size + count)*2; break; // 4 channels - case I_8266_U0_UCS_3 : size *= 4; break; // 16 bit - case I_8266_U1_UCS_3 : size *= 4; break; // 16 bit - case I_8266_BB_UCS_3 : size *= 4; break; // 16 bit - case I_8266_U0_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U1_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_BB_UCS_4 : size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U0_FW6_5 : size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_FW6_5 : size = (size + 2*count)*2; break; // 5channels - case I_8266_BB_FW6_5 : size = (size + 2*count)*2; break; // 5 channels - case I_8266_U0_2805_5 : size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_2805_5 : size = (size + 2*count)*2; break; // 5 channels - case I_8266_BB_2805_5 : size = (size + 2*count)*2; break; // 5 channels - case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - // DMA methods have front + DMA buffer = ((1+(3+1)) * channels) - case I_8266_DM_NEO_3 : size *= 5; break; - case I_8266_DM_NEO_4 : size = (size + count)*5; break; - case I_8266_DM_400_3 : size *= 5; break; + case I_8266_U0_NEO_4 : // fallthrough + case I_8266_U1_NEO_4 : // fallthrough + case I_8266_BB_NEO_4 : // fallthrough + case I_8266_U0_TM1_4 : // fallthrough + case I_8266_U1_TM1_4 : // fallthrough + case I_8266_BB_TM1_4 : size = (size + count); break; // 4 channels + case I_8266_U0_UCS_3 : // fallthrough + case I_8266_U1_UCS_3 : // fallthrough + case I_8266_BB_UCS_3 : size *= 2; break; // 16 bit + case I_8266_U0_UCS_4 : // fallthrough + case I_8266_U1_UCS_4 : // fallthrough + case I_8266_BB_UCS_4 : size = (size + count)*2; break; // 16 bit 4 channels + case I_8266_U0_FW6_5 : // fallthrough + case I_8266_U1_FW6_5 : // fallthrough + case I_8266_BB_FW6_5 : // fallthrough + case I_8266_U0_2805_5 : // fallthrough + case I_8266_U1_2805_5 : // fallthrough + case I_8266_BB_2805_5 : size = (size + 2*count); break; // 5 channels + case I_8266_U0_SM16825_5: // fallthrough + case I_8266_U1_SM16825_5: // fallthrough + case I_8266_BB_SM16825_5: size = (size + 2*count)*2; break; // 16 bit 5 channels + // DMA methods have front + DMA buffer = ((1+(3+1)) * channels; exact value is a bit of mistery - needs a dig into NPB) + case I_8266_DM_NEO_3 : // fallthrough + case I_8266_DM_400_3 : // fallthrough + case I_8266_DM_TM2_3 : // fallthrough + case I_8266_DM_APA106_3 : // fallthrough + case I_8266_DM_TM1914_3 : size *= 5; break; + case I_8266_DM_NEO_4 : // fallthrough case I_8266_DM_TM1_4 : size = (size + count)*5; break; - case I_8266_DM_TM2_3 : size *= 5; break; case I_8266_DM_UCS_3 : size *= 2*5; break; case I_8266_DM_UCS_4 : size = (size + count)*2*5; break; - case I_8266_DM_APA106_3 : size *= 5; break; - case I_8266_DM_FW6_5 : size = (size + 2*count)*5; break; + case I_8266_DM_FW6_5 : // fallthrough case I_8266_DM_2805_5 : size = (size + 2*count)*5; break; - case I_8266_DM_TM1914_3 : size *= 5; break; case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; - #endif - #ifdef ARDUINO_ARCH_ESP32 + #else // RMT buses (1x front and 1x back buffer, does not include small RMT buffer) - case I_32_RN_NEO_4 : size = (size + count)*2; break; // 4 channels + case I_32_RN_NEO_4 : // fallthrough case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels case I_32_RN_UCS_3 : size *= 2*2; break; // 16bit case I_32_RN_UCS_4 : size = (size + count)*2*2; break; // 16bit, 4 channels - case I_32_RN_FW6_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_RN_FW6_5 : // fallthrough case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels - // I2S1 bus or paralell I2S1 buses (1x front and 1x back does not include DMA buffer which is front*cadence) + // I2S1 bus or paralell I2S1 buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD) #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_4 : size = (size + count)*2; break; // 4 channels - case I_32_I2_TM1_4 : size = (size + count)*2; break; // 4 channels - case I_32_I2_UCS_3 : size *= 2*2; break; // 16 bit - case I_32_I2_UCS_4 : size = (size + count)*2*2; break; // 16 bit, 4 channels - case I_32_I2_FW6_5 : size = (size + 2*count)*2; break; // 5 channels - case I_32_I2_2805_5 : size = (size + 2*count)*2; break; // 5 channels - case I_32_I2_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit, 5 channels + case I_32_I2_NEO_3 : // fallthrough + case I_32_I2_400_3 : // fallthrough + case I_32_I2_TM2_3 : // fallthrough + case I_32_I2_APA106_3 : break; // do nothing, I2S uses single buffer + DMA buffer + case I_32_I2_NEO_4 : // fallthrough + case I_32_I2_TM1_4 : size = (size + count); break; // 4 channels + case I_32_I2_UCS_3 : size *= 2; break; // 16 bit + case I_32_I2_UCS_4 : size = (size + count)*2; break; // 16 bit, 4 channels + case I_32_I2_FW6_5 : // fallthrough + case I_32_I2_2805_5 : size = (size + 2*count); break; // 5 channels + case I_32_I2_SM16825_5: size = (size + 2*count)*2; break; // 16 bit, 5 channels #endif - #endif default : size *= 2; break; // everything else uses 2 buffers + #endif } return size; } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 928da11753..9f7e82a077 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -193,18 +193,23 @@ //returns mem usage function getMem(t, n) { if (isAna(t)) return 5; // analog - let len = parseInt(d.getElementsByName("LC"+n)[0].value); - len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too + let len = parseInt(d.Sf["LC"+n].value); + len += parseInt(d.Sf["SL"+n].value); // skipped LEDs are allocated too let dbl = 0; let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t); let mul = 1; if (isDig(t)) { if (is16b(t)) len *= 2; // 16 bit LEDs - if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem + if (is8266() && d.Sf["L0"+n].value == 3) { //8266 DMA uses 5x the mem mul = 5; } - if (maxM >= 10000) { //ESP32 RMT uses double buffer? - mul = 2; + let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3) && !isD2P(t); + if (isC3() || (isS3() && !parallelI2S)) { + mul = 2; // ESP32 RMT uses double buffer + } else if ((is32() || isS2() || isS3) && n > (parallelI2S ? 7 : 0)) { + mul = 2; // ESP32 RMT uses double buffer + } else if ((parallelI2S && n < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer + dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) } } return len * ch * mul + dbl; @@ -260,7 +265,7 @@ // is the field a LED type? var n = s.name.substring(2); var t = parseInt(s.value); - memu += getMem(t, n); // calc memory + memu += getMem(t, parseInt(n)); // calc memory dC += (isDig(t) && !isD2P(t)); setPinConfig(n,t); gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings @@ -298,7 +303,7 @@ var nList = d.Sf.querySelectorAll("#mLC input[name^=L]"); nList.forEach((LC,i)=>{ let nm = LC.name.substring(0,2); // field name - let n = LC.name.substring(2); // bus number + let n = parseInt(LC.name.substring(2)); // bus number let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT if (isDig(t)) { if (sameType == 0) sameType = t; // first bus type From 857a0414252a3c7b83f4fec5c8d2f9a9c6412b21 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 14 Sep 2025 08:57:51 +0200 Subject: [PATCH 3/8] adding mendatory LED buffers to UI memory calculation --- wled00/data/settings_leds.htm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 9f7e82a077..01a9c5aa71 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -212,7 +212,8 @@ dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) } } - return len * ch * mul + dbl; + let pbfr = len * 8; // pixel buffers: global buffer + segment buffer (at least one segment buffer is required) + return len * ch * mul + dbl + pbfr; } function UI(change=false) From 8fc1db0ba2201cae8c444c7858396b6d72982c27 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 14 Sep 2025 12:09:51 +0200 Subject: [PATCH 4/8] adding buffer for transitions to memory calculation, change "error" to "warning" --- wled00/data/settings_leds.htm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 01a9c5aa71..9beaadb009 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -196,6 +196,7 @@ let len = parseInt(d.Sf["LC"+n].value); len += parseInt(d.Sf["SL"+n].value); // skipped LEDs are allocated too let dbl = 0; + let pbfr = len * 12; // pixel buffers: global buffer + 2*segment buffer (one is required, one for transitions) let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t); let mul = 1; if (isDig(t)) { @@ -212,7 +213,6 @@ dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) } } - let pbfr = len * 8; // pixel buffers: global buffer + segment buffer (at least one segment buffer is required) return len * ch * mul + dbl + pbfr; } @@ -387,7 +387,7 @@ gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%, #444 ${bquot}% 100%)`; gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none'; gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange'; - gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (ERROR: Using over ${maxM}B!)` : "") : "800 LEDs per output"; + gId('wreason').innerHTML = (bquot > 80) ? "80% of max LED memory" +(bquot>100 ? ` (WARNING: using over ${maxM}B!)` : "") : "800 LEDs per output"; // calculate power gId('ampwarning').style.display = (parseInt(d.Sf.MA.value,10) > 7200) ? 'inline':'none'; var val = Math.ceil((100 + busMA)/500)/2; From 287d3d4c326ae4e97202a3da09b7ff4ee76ec8e8 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 16 Sep 2025 06:37:08 +0200 Subject: [PATCH 5/8] bugfixes in settings_leds.htm --- wled00/data/settings_leds.htm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 9beaadb009..ed5fc70f2c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -204,13 +204,13 @@ if (is8266() && d.Sf["L0"+n].value == 3) { //8266 DMA uses 5x the mem mul = 5; } - let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3) && !isD2P(t); + let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3()) && !isD2P(t); if (isC3() || (isS3() && !parallelI2S)) { mul = 2; // ESP32 RMT uses double buffer - } else if ((is32() || isS2() || isS3) && n > (parallelI2S ? 7 : 0)) { + } else if ((is32() || isS2() || isS3()) && n > (parallelI2S ? 7 : 0)) { mul = 2; // ESP32 RMT uses double buffer } else if ((parallelI2S && n < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer - dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) + dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: only the bus with largest LED count should be used) } } return len * ch * mul + dbl + pbfr; @@ -266,7 +266,7 @@ // is the field a LED type? var n = s.name.substring(2); var t = parseInt(s.value); - memu += getMem(t, parseInt(n)); // calc memory + memu += getMem(t, n); // calc memory dC += (isDig(t) && !isD2P(t)); setPinConfig(n,t); gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings @@ -304,7 +304,7 @@ var nList = d.Sf.querySelectorAll("#mLC input[name^=L]"); nList.forEach((LC,i)=>{ let nm = LC.name.substring(0,2); // field name - let n = parseInt(LC.name.substring(2)); // bus number + let n = LC.name.substring(2); // bus number let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT if (isDig(t)) { if (sameType == 0) sameType = t; // first bus type From 8884bb5457f1a4700be019b90daa5e6748c850e7 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 19 Sep 2025 19:53:38 +0200 Subject: [PATCH 6/8] fix getDataSize() forESP8266 ESP8266 does not use double buffering. --- wled00/bus_wrapper.h | 72 ++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 29d62712a5..1095ea3b9f 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1113,54 +1113,54 @@ class PolyBus { switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_NEO_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_NEO_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_400_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM1_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM2_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_UCS_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_UCS_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_APA106_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_FW6_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_2805_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses (front + back + small system managed RMT) From e711b74bd3e3125cbc4bac0e82ff680bc4954b3f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 19 Sep 2025 19:58:10 +0200 Subject: [PATCH 7/8] update led settings UI, add new warnings, fix parsing by @blazoncek - new warnings for LED buffer use, deny adding a bus if it exceeds limits - adds recommendations for users (reboot, disable transitions) --- wled00/data/settings_leds.htm | 117 +++++++++++++++++----------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index ed5fc70f2c..2d229445f0 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -24,6 +24,7 @@ function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins function chrID(x) { return String.fromCharCode((x<10?48:55)+x); } + function toNum(c) { let n=c.charCodeAt(0); return (n>=48 && n<=57)?n-48:(n>=65 && n<=90)?n-55:0; } // convert char (0-9A-Z) to number (0-35) function S() { getLoc(); loadJS(getURL('/settings/s.js?p=2'), false, ()=>{ @@ -61,45 +62,41 @@ var nList = d.Sf.querySelectorAll("#mLC input[name^=L]"); nList.forEach((LC,i)=>{ if (!ok) return; // prevent iteration after conflict - let nm = LC.name.substring(0,2); - let n = LC.name.substring(2); + let nm = LC.name.substring(0,2); // field name : /L./ + if (nm.search(/^L[0-4]/) < 0) return; // not pin fields + let n = LC.name.substring(2,3); // bus number (0-Z) let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT // ignore IP address - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { - if (isNet(t)) return; - } + if (isNet(t)) return; //check for pin conflicts - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4") - if (LC.value!="" && LC.value!="-1") { - let p = d.rsvd.concat(d.um_p); // used pin array - d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay - if (p.some((e)=>e==parseInt(LC.value))) { - alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`); - LC.value=""; - LC.focus(); - ok = false; - return; - } else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) { - alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`); - LC.value=""; - LC.focus(); - ok = false; - return; - } - for (j=i+1; j=80) continue; - } - if (nList[j].value!="" && nList[i].value==nList[j].value) { - alert(`Pin conflict between ${LC.name}/${nList[j].name}!`); - nList[j].value=""; - nList[j].focus(); - ok = false; - return; + if (LC.value!="" && LC.value!="-1") { + let p = d.rsvd.concat(d.um_p); // used pin array + d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay + if (p.some((e)=>e==parseInt(LC.value))) { + alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`); + LC.value=""; + LC.focus(); + ok = false; + return; + } else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) { + alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`); + LC.value=""; + LC.focus(); + ok = false; + return; + } + for (j=i+1; j 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);} - if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it) - if (d.Sf.checkValidity()) { - d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case - d.Sf.submit(); //https://stackoverflow.com/q/37323914 + if (bquot > 200) {var msg = "Too many LEDs! Can't handle that!"; alert(msg); e.stopPropagation(); return false;} + else { + if (bquot > 80) {var msg = "Memory usage is high, reboot recommended!\n\rSet transitions to 0 to save memory."; + if (bquot > 100) msg += "\n\rToo many LEDs for me to handle properly!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);} + if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it) + if (d.Sf.checkValidity()) { + d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case + d.Sf.submit(); //https://stackoverflow.com/q/37323914 + } } } function enABL() @@ -196,7 +197,7 @@ let len = parseInt(d.Sf["LC"+n].value); len += parseInt(d.Sf["SL"+n].value); // skipped LEDs are allocated too let dbl = 0; - let pbfr = len * 12; // pixel buffers: global buffer + 2*segment buffer (one is required, one for transitions) + let pbfr = len * 8; // pixel buffers: global buffer + segment buffer (at least one segment buffer is required) let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t); let mul = 1; if (isDig(t)) { @@ -207,10 +208,10 @@ let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3()) && !isD2P(t); if (isC3() || (isS3() && !parallelI2S)) { mul = 2; // ESP32 RMT uses double buffer - } else if ((is32() || isS2() || isS3()) && n > (parallelI2S ? 7 : 0)) { + } else if ((is32() || isS2() || isS3()) && toNum(n) > (parallelI2S ? 7 : 0)) { mul = 2; // ESP32 RMT uses double buffer - } else if ((parallelI2S && n < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer - dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: only the bus with largest LED count should be used) + } else if ((parallelI2S && toNum(n) < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer + dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) } } return len * ch * mul + dbl + pbfr; @@ -264,7 +265,7 @@ LTs.forEach((s,i)=>{ if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options) // is the field a LED type? - var n = s.name.substring(2); + var n = s.name.substring(2,3); // bus number (0-Z) var t = parseInt(s.value); memu += getMem(t, n); // calc memory dC += (isDig(t) && !isD2P(t)); @@ -303,8 +304,8 @@ let sameType = 0; var nList = d.Sf.querySelectorAll("#mLC input[name^=L]"); nList.forEach((LC,i)=>{ - let nm = LC.name.substring(0,2); // field name - let n = LC.name.substring(2); // bus number + let nm = LC.name.substring(0,2); // field name : /L./ + let n = LC.name.substring(2,3); // bus number (0-Z) let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT if (isDig(t)) { if (sameType == 0) sameType = t; // first bus type @@ -313,7 +314,7 @@ // do we have a led count field if (nm=="LC") { let c = parseInt(LC.value,10); //get LED count - if (!customStarts || !startsDirty[n]) gId("ls"+n).value = sLC; //update start value + if (!customStarts || !startsDirty[toNum(n)]) gId("ls"+n).value = sLC; //update start value gId("ls"+n).disabled = !customStarts; //enable/disable field editing if (c) { let s = parseInt(gId("ls"+n).value); //start value @@ -334,7 +335,7 @@ d.Sf["LC"+n].max = maxPB; // update max led count value } // ignore IP address (stored in pins for virtual busses) - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { + if (nm.search(/^L[0-3]/) == 0) { // pin fields if (isVir(t)) { LC.max = 255; LC.min = 0; @@ -346,26 +347,24 @@ } } // check for pin conflicts & color fields - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4") + if (nm.search(/^L[0-4]/) == 0) // pin fields if (LC.value!="" && LC.value!="-1") { let p = d.rsvd.concat(d.um_p); // used pin array d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay for (j=0; je==parseInt(LC.value))) LC.style.color = "red"; else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff"; - } + } else LC.style.color = "#fff"; }); if (is32() || isS2() || isS3()) { if (maxLC > 600 || dC < 2 || sameType <= 0) { From 2f98c480fdee473f1aeaeec851ffe801fc820530 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 21 Sep 2025 22:32:08 +0200 Subject: [PATCH 8/8] revert unnecessary changes --- wled00/FX_fcn.cpp | 1 + wled00/bus_wrapper.h | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 17ffc851b9..18a4e55d2b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1208,6 +1208,7 @@ void WS2812FX::finalizeInit() { BusManager::add(bus); DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB); } else { + errorFlag = ERR_NORAM_PX; // alert UI DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); break; } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 1095ea3b9f..ed2b03be74 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -469,20 +469,20 @@ class PolyBus { } static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation - // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - #if defined(CONFIG_IDF_TARGET_ESP32) - if (channel > 0) { - channel--; - if (_useParallelI2S && channel > 6) channel -= 7; // accommodate I2S1 which is used as 1st bus + + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if (_useParallelI2S && (channel >= 8)) { + // Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number + channel -= 8; } - #elif defined(CONFIG_IDF_TARGET_ESP32S2) - if (_useParallelI2S && channel > 7) channel -= 8; // accommodate I2S1 which is used as 1st bus - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - if (_useParallelI2S && channel > 7) channel -= 8; // accommodate I2S1 which is used as 1st bus #endif - #endif + + #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) + // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation + if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + #endif + void* busPtr = nullptr; switch (busType) { case I_NONE: break; @@ -1360,7 +1360,6 @@ class PolyBus { if (_useParallelI2S) { if (num > 15) return I_NONE; if (num < 8) offset = 1; // 8 I2S followed by 8 RMT - if (num < 8) offset = 1; // 8 I2S followed by 8 RMT } else { if (num > 9) return I_NONE; if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)