diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp old mode 100755 new mode 100644 index 95a347b66..18a4e55d2 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1178,17 +1178,42 @@ 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; + 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 { 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; } } + 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 c2838425e..6801f04ad 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -354,7 +354,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) { @@ -1123,7 +1123,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 { @@ -1139,23 +1140,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 d64ead8d8..b2ff94741 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1121,54 +1121,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) @@ -1220,68 +1220,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_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_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_UCS_3 : size *= 2*5; break; + case I_8266_DM_UCS_4 : size = (size + count)*2*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_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) + #else + // RMT buses (1x front and 1x back buffer, does not include small RMT buffer) + 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 : // 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, does not include DMA buffer which is front*cadence, a bit(?) more for LCD) #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_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 + default : size *= 2; break; // everything else uses 2 buffers #endif - // everything else uses 2 buffers - default: size *= 2; break; } return size; } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 6a11b60b2..840afb51b 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 diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be08cd8e..f9278a7f5 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -25,6 +25,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, ()=>{ @@ -62,48 +63,44 @@ 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 if(isHub75(t)) { return; } // 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() @@ -197,20 +198,27 @@ //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 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)) { 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()) && toNum(n) > (parallelI2S ? 7 : 0)) { + mul = 2; // ESP32 RMT uses double buffer + } 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 + len * 4; // add 4 bytes per LED for segment buffer (TODO: how to account for global buffer?) + return len * ch * mul + dbl + pbfr; } function UI(change=false) @@ -264,7 +272,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 +311,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 +321,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 @@ -340,7 +348,7 @@ } } // 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; @@ -366,26 +374,24 @@ return; // do not check conflicts } // 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) { @@ -407,7 +413,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;