diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp index 6be3a92640..3ec46f6f77 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp @@ -60,8 +60,8 @@ class RgbRotaryEncoderUsermod : public Usermod // …then set only the LED pin _pins[0] = static_cast(ledIo); BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0); - - ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1); + busCfg.iType = BusManager::getI(busCfg.type, busCfg.pins, busCfg.driverType); // assign internal bus type and output driver + ledBus = new BusDigital(busCfg); if (!ledBus->isOk()) { cleanup(); return; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 063d3a6bb3..4a920096d7 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -120,6 +120,9 @@ void WS2812FX::setUpMatrix() { for (unsigned i=0; i %p\n"), &orig, this); if (this != &orig) { // clean destination - if (name) { p_free(name); name = nullptr; } + if (name) { p_free(name); } + name = nullptr; if (_t) stopTransition(); // also erases _t deallocateData(); p_free(pixels); + pixels = nullptr; // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); // erase pointers to allocated data data = nullptr; _dataLen = 0; - pixels = nullptr; if (!stop) return *this; // nothing to do if segment is inactive/invalid // copy source data if (orig.pixels) { @@ -1159,66 +1160,71 @@ void WS2812FX::finalizeInit() { _hasWhiteChannel = _isOffRefreshRequired = false; BusManager::removeAll(); - + // TODO: ideally we would free everything segment related here to reduce fragmentation (pixel buffers, ledamp, segments, etc) but that somehow leads to heap corruption if touchig any of the buffers. unsigned digitalCount = 0; #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - // determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT) - unsigned maxLedsOnBus = 0; - unsigned busType = 0; + // validate the bus config: count I2S buses and check if they meet requirements + unsigned i2sBusCount = 0; + for (const auto &bus : busConfigs) { if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) { digitalCount++; - if (busType == 0) busType = bus.type; // remember first bus type - if (busType != bus.type) { - DEBUG_PRINTF_P(PSTR("Mixed digital bus types detected! Forcing single I2S output.\n")); - useParallelI2S = false; // mixed bus types, no parallel I2S - } - if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count; + if (bus.driverType == 1) + i2sBusCount++; } } - DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount); - // we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3 - if (maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses - else useParallelI2S = false; // enforce single I2S - digitalCount = 0; + DEBUG_PRINTF_P(PSTR("Digital buses: %u, I2S buses: %u\n"), digitalCount, i2sBusCount); + + // Determine parallel vs single I2S usage (used for memory calculation only) + bool useParallelI2S = false; + #if defined(CONFIG_IDF_TARGET_ESP32S3) + // ESP32-S3 always uses parallel LCD driver for I2S + if (i2sBusCount > 0) { + useParallelI2S = true; + } + #else + if (i2sBusCount > 1) { + useParallelI2S = true; + } + #endif #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) { - unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer + unsigned mem = 0; // memory estimation including DMA buffer for I2S and pixel buffers + unsigned I2SdmaMem = 0; + for (auto &bus : busConfigs) { + // assign bus types: call to getI() determines bus types/drivers, allocates and tracks polybus channels + // store the result in iType for later use during bus creation (getI() must only be called once per BusConfig) + // note: this needs to be determined for all buses prior to creating them as it also determines parallel I2S usage + bus.iType = BusManager::getI(bus.type, bus.pins, bus.driverType); + } + for (auto &bus : busConfigs) { + unsigned memB = bus.memUsage(); // does not include DMA/RMT buffer but includes pixel buffers (segment buffer + global buffer) mem += memB; - // estimate maximum I2S memory usage (only relevant for digital non-2pin busses) + // estimate maximum I2S memory usage (only relevant for digital non-2pin busses when I2S is enabled) #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 + bool usesI2S = (bus.iType & 0x01) == 0; // I2S bus types are even numbered, can't use bus.driverType == 1 as getI() may have defaulted to RMT 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; + unsigned i2sCommonMem = (stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1)); + if (useParallelI2S) i2sCommonMem *= 8; // parallel I2S uses 8 channels, requiring 8x the DMA buffer size (common buffer shared between all parallel busses) + if (i2sCommonMem > I2SdmaMem) I2SdmaMem = i2sCommonMem; } #endif - if (mem + maxI2S <= MAX_LED_MEMORY) { + if (mem + I2SdmaMem <= MAX_LED_MEMORY + 1024) { // +1k to allow some margin to not drop buses that are allowed in UI (calculation here includes bus overhead) 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); + DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) not created."), (int)bus.type, (int)bus.count); break; } } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage()); + DEBUG_PRINTF_P(PSTR("Estimated buses + pixel-buffers size: %uB\n"), mem + I2SdmaMem); busConfigs.clear(); busConfigs.shrink_to_fit(); @@ -1249,7 +1255,6 @@ void WS2812FX::finalizeInit() { deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) // allocate frame buffer after matrix has been set up (gaps!) - p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it // use PSRAM if available: there is no measurable perfomance impact between PSRAM and DRAM on S2/S3 with QSPI PSRAM for this buffer _pixels = static_cast(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR)); DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t)); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5cc0eb2c95..65e52f25f7 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -27,7 +27,6 @@ extern char cmDNS[]; extern bool cctICused; -extern bool useParallelI2S; // functions to get/set bits in an array - based on functions created by Brandon for GOL // toDo : make this a class that's completely defined in a header file @@ -117,15 +116,17 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) const { } -BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) +BusDigital::BusDigital(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) , _skip(bc.skipAmount) //sacrificial pixels , _colorOrder(bc.colorOrder) , _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsMax(bc.milliAmpsMax) +, _driverType(bc.driverType) // Store driver preference (0=RMT, 1=I2S) { DEBUGBUS_PRINTLN(F("Bus: Creating digital bus.")); if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; } _frequencykHz = 0U; _colorSum = 0; @@ -139,28 +140,32 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) _pins[1] = bc.pins[1]; _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined } - _iType = PolyBus::getI(bc.type, _pins, nr); + + _iType = bc.iType; // reuse the iType that was determined by polyBus in getI() in finalizeInit() if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; } _hasRgb = hasRGB(bc.type); _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip); _valid = (_busPtr != nullptr) && bc.count > 0; // fix for wled#4759 if (_valid) for (unsigned i = 0; i < _skip; i++) { PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here) } - DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"), - _valid?"S":"Uns", - (int)nr, + else { + cleanup(); + } + DEBUGBUS_PRINTF_P(PSTR("Bus len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u, driver:%s] mA=%d/%d %s\n"), (int)bc.count, (int)bc.type, (int)_hasRgb, (int)_hasWhite, (int)_hasCCT, (unsigned)_pins[0], is2Pin(bc.type)?(unsigned)_pins[1]:255U, (unsigned)_iType, - (int)_milliAmpsPerLed, (int)_milliAmpsMax + isI2S() ? "I2S" : "RMT", + (int)_milliAmpsPerLed, (int)_milliAmpsMax, + _valid ? " " : "FAILED" ); } @@ -351,6 +356,10 @@ std::vector BusDigital::getLEDTypes() { }; } +bool BusDigital::isI2S() { + return (_iType & 0x01) == 0; // I2S types have even iType values +} + void BusDigital::begin() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); @@ -1105,47 +1114,20 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const { #endif // *************************************************************************** -//utility to get the approx. memory usage of a given BusConfig -size_t BusConfig::memUsage(unsigned nr) const { +//utility to get the approx. memory usage of a given BusConfig inclduding segmentbuffer and global buffer (4 bytes per pixel) +size_t BusConfig::memUsage() const { + size_t mem = (count + skipAmount) * 8; // 8 bytes per pixel for segment + global buffer if (Bus::isVirtual(type)) { - return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); + mem += sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); // note: getNumberOfChannels() includes CCT channel if applicable but virtual buses do not use CCT channel buffer } else if (Bus::isDigital(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)); + mem += sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, iType); } else if (Bus::isOnOff(type)) { - return sizeof(BusOnOff); + mem += sizeof(BusOnOff); } else { - return sizeof(BusPwm); - } -} - - -size_t BusManager::memUsage() { - // when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers - // front buffers are always allocated per bus - unsigned size = 0; - unsigned maxI2S = 0; - #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - unsigned digitalCount = 0; - #endif - for (const auto &bus : busses) { - size += bus->getBusSize(); - #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - 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 + mem += sizeof(BusPwm); } - return size + maxI2S; + return mem; } int BusManager::add(const BusConfig &bc) { @@ -1166,7 +1148,7 @@ int BusManager::add(const BusConfig &bc) { busses.push_back(make_unique(bc)); #endif } else if (Bus::isDigital(bc.type)) { - busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); + busses.push_back(make_unique(bc)); } else if (Bus::isOnOff(bc.type)) { busses.push_back(make_unique(bc)); } else { @@ -1204,49 +1186,35 @@ String BusManager::getLEDTypesJSONString() { return json; } -void BusManager::useParallelOutput() { - DEBUGBUS_PRINTLN(F("Bus: Enabling parallel I2S.")); - PolyBus::setParallelI2S1Output(); +uint8_t BusManager::getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference) { + return PolyBus::getI(busType, pins, driverPreference); } - -bool BusManager::hasParallelOutput() { - return PolyBus::isParallelI2S1Output(); -} - //do not call this method from system context (network callback) void BusManager::removeAll() { DEBUGBUS_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); busses.clear(); - PolyBus::setParallelI2S1Output(false); + #ifndef ESP8266 + // Reset channel tracking for fresh allocation + PolyBus::resetChannelTracking(); + #endif } #ifdef ESP32_DATA_IDLE_HIGH // #2478 // If enabled, RMT idle level is set to HIGH when off // to prevent leakage current when using an N-channel MOSFET to toggle LED power +// since I2S outputs are known only during config of buses, lets just assume RMT is used for digital buses +// unused RMT channels should have no effect void BusManager::esp32RMTInvertIdle() { bool idle_out; unsigned rmt = 0; unsigned u = 0; for (auto &bus : busses) { if (bus->getLength()==0 || !bus->isDigital() || bus->is2Pin()) continue; - #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM - if (u > 1) return; - rmt = u; - #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, only has 1 I2S bus, supported in NPB - if (u > 3) return; - rmt = u; - #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, has 2 I2S but NPB does not support them ATM - if (u > 3) return; - rmt = u; - #else - unsigned numI2S = !PolyBus::isParallelI2S1Output(); // if using parallel I2S, RMT is used 1st - if (numI2S > u) continue; - if (u > 7 + numI2S) return; - rmt = u - numI2S; - #endif + if (static_cast(bus.get())->isI2S()) continue; + if (u >= WLED_MAX_RMT_CHANNELS) return; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(rmt); rmt_idle_level_t lvl; @@ -1421,9 +1389,15 @@ void BusManager::applyABL() { ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; } - +#ifndef ESP8266 +// PolyBus channel tracking for dynamic allocation bool PolyBus::_useParallelI2S = false; - +uint8_t PolyBus::_rmtChannelsAssigned = 0; // number of RMT channels assigned durig getI() check +uint8_t PolyBus::_rmtChannel = 0; // number of RMT channels actually used during bus creation in create() +uint8_t PolyBus::_i2sChannelsAssigned = 0; // number of I2S channels assigned durig getI() check +uint8_t PolyBus::_parallelBusItype = 0; // type I_NONE +uint8_t PolyBus::_2PchannelsAssigned = 0; +#endif // Bus static member definition int16_t Bus::_cct = -1; uint8_t Bus::_cctBlend = 0; // 0 - 127 diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 87d39fe34b..40427be7c4 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -140,7 +140,8 @@ class Bus { virtual uint16_t getLEDCurrent() const { return 0; } virtual uint16_t getUsedCurrent() const { return 0; } virtual uint16_t getMaxCurrent() const { return 0; } - virtual size_t getBusSize() const { return sizeof(Bus); } + virtual uint8_t getDriverType() const { return 0; } // Default to RMT (0) for non-digital buses + virtual size_t getBusSize() const { return sizeof(Bus); } // currently unused virtual const String getCustomText() const { return String(); } inline bool hasRGB() const { return _hasRgb; } @@ -242,7 +243,7 @@ class Bus { class BusDigital : public Bus { public: - BusDigital(const BusConfig &bc, uint8_t nr); + BusDigital(const BusConfig &bc); ~BusDigital() { cleanup(); } void show() override; @@ -258,10 +259,12 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + uint8_t getDriverType() const override { return _driverType; } void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; } void estimateCurrent(); // estimate used current from summed colors void applyBriLimit(uint8_t newBri); size_t getBusSize() const override; + bool isI2S(); // true if this bus uses I2S driver void begin() override; void cleanup(); @@ -272,6 +275,7 @@ class BusDigital : public Bus { uint8_t _colorOrder; uint8_t _pins[2]; uint8_t _iType; + uint8_t _driverType; // 0=RMT (default), 1=I2S uint16_t _frequencykHz; uint16_t _milliAmpsMax; uint8_t _milliAmpsPerLed; @@ -421,9 +425,11 @@ struct BusConfig { uint16_t frequency; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; + uint8_t driverType; // 0=RMT (default), 1=I2S + uint8_t iType; // internal bus type (I_*) determined during memory estimation, used for bus creation String text; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "") + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, uint8_t driver=0, String sometext = "") : count(std::max(len,(uint16_t)1)) , start(pstart) , colorOrder(pcolorOrder) @@ -433,13 +439,15 @@ struct BusConfig { , frequency(clock_kHz) , milliAmpsPerLed(maPerLed) , milliAmpsMax(maMax) + , driverType(driver) + , iType(0) // default to I_NONE , text(sometext) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) size_t nPins = Bus::getNumberOfPins(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; - DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), + DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d, driver:%s)\n"), (int)start, (int)(start+len), (int)type, (int)colorOrder, @@ -447,7 +455,8 @@ struct BusConfig { (int)skipAmount, (int)autoWhite, (int)frequency, - (int)milliAmpsPerLed, (int)milliAmpsMax + (int)milliAmpsPerLed, (int)milliAmpsMax, + driverType == 0 ? "RMT" : "I2S" ); } @@ -463,7 +472,7 @@ struct BusConfig { return true; } - size_t memUsage(unsigned nr = 0) const; + size_t memUsage() const; }; @@ -494,7 +503,6 @@ namespace BusManager { return j; } - size_t memUsage(); inline uint16_t currentMilliamps() { return _gMilliAmpsUsed + MA_FOR_ESP; } //inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; } inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL) @@ -502,8 +510,7 @@ namespace BusManager { void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized void applyABL(); // apply automatic brightness limiter, global or per bus - void useParallelOutput(); // workaround for inaccessible PolyBus - bool hasParallelOutput(); // workaround for inaccessible PolyBus + uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference); // workaround for access to PolyBus function from FX_fcn.cpp //do not call this method from system context (network callback) void removeAll(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index b2ff947418..0ecd4f986d 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -339,11 +339,17 @@ //handles pointer type conversion for all possible bus types class PolyBus { private: - static bool _useParallelI2S; - + #ifndef ESP8266 + static bool _useParallelI2S; // use parallel I2S/LCD (8 channels) + static uint8_t _rmtChannelsAssigned; // RMT channel tracking for dynamic allocation + static uint8_t _rmtChannel; // physical RMT channel to use during bus creation + static uint8_t _i2sChannelsAssigned; // I2S channel tracking for dynamic allocation + static uint8_t _parallelBusItype; // parallel output does not allow mixed LED types, track I_Type + static uint8_t _2PchannelsAssigned; // 2-Pin (SPI) channel assigned: first one gets the hardware SPI, others use bit-banged SPI + // note on 2-Pin Types: all supported types except WS2801 use start/stop or latch frames, speed is not critical. WS2801 uses a 500us timeout and is prone to flickering if bit-banged too slow. + // TODO: according to #4863 using more than one bit-banged output can cause glitches even in APA102. This needs investigation as from a hardware perspective all but WS2801 should be immune to timing issues. + #endif public: - static inline void setParallelI2S1Output(bool b = true) { _useParallelI2S = b; } - static inline bool isParallelI2S1Output(void) { return _useParallelI2S; } // initialize SPI bus speed for DotStar methods template @@ -476,21 +482,7 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { - // 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 - #endif - + static void* create(uint8_t busType, uint8_t* pins, uint16_t len) { void* busPtr = nullptr; switch (busType) { case I_NONE: break; @@ -546,18 +538,18 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses - case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; // I2S1 bus or paralell buses #ifndef CONFIG_IDF_TARGET_ESP32C3 case I_32_I2_NEO_3: if (_useParallelI2S) busPtr = new B_32_IP_NEO_3(len, pins[0]); else busPtr = new B_32_I2_NEO_3(len, pins[0]); break; @@ -1118,6 +1110,9 @@ class PolyBus { static unsigned getDataSize(void* busPtr, uint8_t busType) { unsigned size = 0; + #ifdef ARDUINO_ARCH_ESP32 + size = 100; // ~100bytes for NPB internal structures (measured for both I2S and RMT, much smaller and more variable on ESP8266) + #endif switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -1172,32 +1167,32 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses (front + back + small system managed RMT) - case I_32_RN_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) + case I_32_RN_NEO_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_NEO_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_400_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM2_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_APA106_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_FW6_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_2805_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1914_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_SM16825_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) not: for parallel I2S only the largest bus counts for DMA memory, this is not done correctly here, also assumes 3-step cadence #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_NEO_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_400_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM1_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM2_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_UCS_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_UCS_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_APA106_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_FW6_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_2805_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM1914_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_SM16825_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_400_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM2_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_APA106_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_FW6_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_2805_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1914_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_SM16825_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; #endif #endif case I_HS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; @@ -1255,6 +1250,7 @@ class PolyBus { 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; #else + // note: RMT and I2S buses use ~100 bytes of internal NPB memory each, not included here for simplicity // 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 @@ -1263,7 +1259,7 @@ class PolyBus { 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) + // I2S bus or paralell I2S 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 : // fallthrough case I_32_I2_400_3 : // fallthrough @@ -1282,30 +1278,37 @@ class PolyBus { } return size; } - - //gives back the internal type index (I_XX_XXX_X above) for the input - static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t num = 0) { +#ifndef ESP8266 + // Reset channel tracking (call before adding buses) + static void resetChannelTracking() { + _useParallelI2S = false; + _rmtChannelsAssigned = 0; + _rmtChannel = 0; + _i2sChannelsAssigned = 0; + _parallelBusItype = I_NONE; + _2PchannelsAssigned = 0; + } +#endif + // reserves and gives back the internal type index (I_XX_XXX_X above) for the input based on bus type and pins + static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference) { if (!Bus::isDigital(busType)) return I_NONE; + uint8_t t = I_NONE; if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; #ifdef ESP8266 if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; #else - // temporary hack to limit use of hardware SPI to a single SPI peripheral (HSPI): only allow ESP32 hardware serial on segment 0 - // SPI global variable is normally linked to VSPI on ESP32 (or FSPI C3, S3) - if (!num) isHSPI = true; + if (_2PchannelsAssigned == 0) isHSPI = true; // first 2-pin channel uses hardware SPI + _2PchannelsAssigned++; #endif - uint8_t t = I_NONE; switch (busType) { case TYPE_APA102: t = I_SS_DOT_3; break; case TYPE_LPD8806: t = I_SS_LPD_3; break; case TYPE_LPD6803: t = I_SS_LPO_3; break; case TYPE_WS2801: t = I_SS_WS1_3; break; case TYPE_P9813: t = I_SS_P98_3; break; - default: t=I_NONE; } if (t > I_NONE && isHSPI) t--; //hardware SPI has one smaller ID than software - return t; } else { #ifdef ESP8266 uint8_t offset = pins[0] -1; //for driver: 0 = uart0, 1 = uart1, 2 = dma, 3 = bitbang @@ -1315,96 +1318,87 @@ class PolyBus { case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: - return I_8266_U0_NEO_3 + offset; + t = I_8266_U0_NEO_3 + offset; break; case TYPE_SK6812_RGBW: - return I_8266_U0_NEO_4 + offset; + t = I_8266_U0_NEO_4 + offset; break; case TYPE_WS2811_400KHZ: - return I_8266_U0_400_3 + offset; + t = I_8266_U0_400_3 + offset; break; case TYPE_TM1814: - return I_8266_U0_TM1_4 + offset; + t = I_8266_U0_TM1_4 + offset; break; case TYPE_TM1829: - return I_8266_U0_TM2_3 + offset; + t = I_8266_U0_TM2_3 + offset; break; case TYPE_UCS8903: - return I_8266_U0_UCS_3 + offset; + t = I_8266_U0_UCS_3 + offset; break; case TYPE_UCS8904: - return I_8266_U0_UCS_4 + offset; + t = I_8266_U0_UCS_4 + offset; break; case TYPE_APA106: - return I_8266_U0_APA106_3 + offset; + t = I_8266_U0_APA106_3 + offset; break; case TYPE_FW1906: - return I_8266_U0_FW6_5 + offset; + t = I_8266_U0_FW6_5 + offset; break; case TYPE_WS2805: - return I_8266_U0_2805_5 + offset; + t = I_8266_U0_2805_5 + offset; break; case TYPE_TM1914: - return I_8266_U0_TM1914_3 + offset; + t = I_8266_U0_TM1914_3 + offset; break; case TYPE_SM16825: - return I_8266_U0_SM16825_5 + offset; + t = I_8266_U0_SM16825_5 + offset; break; } #else //ESP32 - uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive] - #if defined(CONFIG_IDF_TARGET_ESP32S2) - // ESP32-S2 only has 4 RMT channels - if (_useParallelI2S) { - if (num > 11) return I_NONE; - if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT - // Note: conflicts with AudioReactive if enabled - } else { - if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive) - } - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - // On ESP32-C3 only the first 2 RMT channels are usable for transmitting - if (num > 1) return I_NONE; - //if (num > 1) offset = 1; // I2S not supported yet (only 1 I2S) - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - // On ESP32-S3 only the first 4 RMT channels are usable for transmitting - if (_useParallelI2S) { - if (num > 11) return I_NONE; - if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT - } else { - if (num > 3) return I_NONE; // do not use single I2S (as it is not supported) - } - #else - // standard ESP32 has 8 RMT and x1/x8 I2S1 channels - if (_useParallelI2S) { - if (num > 15) return I_NONE; - if (num < 8) offset = 1; // 8 I2S followed by 8 RMT + // dynamic channel allocation based on driver preference + // determine which driver to use based on preference and availability. First I2S bus locks the I2S type, all subsequent I2S buses are assigned the same type (hardware restriction) + uint8_t offset = 0; // 0 = RMT, 1 = I2S/LCD + if (driverPreference == 0 && _rmtChannelsAssigned < WLED_MAX_RMT_CHANNELS) { + _rmtChannelsAssigned++; + } else if (_i2sChannelsAssigned < WLED_MAX_I2S_CHANNELS) { + offset = 1; // I2S requested or RMT full + _i2sChannelsAssigned++; } else { - if (num > 9) return I_NONE; - if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + return I_NONE; // No channels available } - #endif + + // Now determine actual bus type with the chosen offset switch (busType) { case TYPE_WS2812_1CH_X3: case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: - return I_32_RN_NEO_3 + offset; + t = I_32_RN_NEO_3 + offset; break; case TYPE_SK6812_RGBW: - return I_32_RN_NEO_4 + offset; + t = I_32_RN_NEO_4 + offset; break; case TYPE_WS2811_400KHZ: - return I_32_RN_400_3 + offset; + t = I_32_RN_400_3 + offset; break; case TYPE_TM1814: - return I_32_RN_TM1_4 + offset; + t = I_32_RN_TM1_4 + offset; break; case TYPE_TM1829: - return I_32_RN_TM2_3 + offset; + t = I_32_RN_TM2_3 + offset; break; case TYPE_UCS8903: - return I_32_RN_UCS_3 + offset; + t = I_32_RN_UCS_3 + offset; break; case TYPE_UCS8904: - return I_32_RN_UCS_4 + offset; + t = I_32_RN_UCS_4 + offset; break; case TYPE_APA106: - return I_32_RN_APA106_3 + offset; + t = I_32_RN_APA106_3 + offset; break; case TYPE_FW1906: - return I_32_RN_FW6_5 + offset; + t = I_32_RN_FW6_5 + offset; break; case TYPE_WS2805: - return I_32_RN_2805_5 + offset; + t = I_32_RN_2805_5 + offset; break; case TYPE_TM1914: - return I_32_RN_TM1914_3 + offset; + t = I_32_RN_TM1914_3 + offset; break; case TYPE_SM16825: - return I_32_RN_SM16825_5 + offset; + t = I_32_RN_SM16825_5 + offset; break; + } + // If using parallel I2S, set the type accordingly + if (_i2sChannelsAssigned == 1 && offset == 1) { // first I2S channel request, lock the type + _parallelBusItype = t; + #ifdef CONFIG_IDF_TARGET_ESP32S3 + _useParallelI2S = true; // ESP32-S3 always uses parallel I2S (LCD method) + #endif + } + else if (offset == 1) { // not first I2S channel, use locked type and enable parallel flag + _useParallelI2S = true; + t = _parallelBusItype; } #endif } - return I_NONE; + return t; } }; #endif diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index ff491faffd..9e4766589b 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -166,9 +166,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); Bus::setCCTBlend(cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - CJSON(useParallelI2S, hw_led[F("prl")]); - #endif #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -235,9 +232,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { maMax = 0; } ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh + uint8_t driverType = elm[F("drv")] | 0; // 0=RMT (default), 1=I2S note: polybus may override this if driver is not available String host = elm[F("text")] | String(); - busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host); + busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, driverType, host); doInitBusses = true; // finalization done in beginStrip() if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want } @@ -320,7 +318,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { unsigned start = 0; // analog always has length 1 if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; - busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); + busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, LED_MILLIAMPS_DEFAULT, ABL_MILLIAMPS_DEFAULT, 0); // driver=0 (RMT default) doInitBusses = true; // finalization done in beginStrip() } } @@ -929,9 +927,6 @@ void serializeConfig(JsonObject root) { hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - hw_led[F("prl")] = BusManager::hasParallelOutput(); - #endif #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -985,6 +980,7 @@ void serializeConfig(JsonObject root) { ins[F("freq")] = bus->getFrequency(); ins[F("maxpwr")] = bus->getMaxCurrent(); ins[F("ledma")] = bus->getLEDCurrent(); + ins[F("drv")] = bus->getDriverType(); ins[F("text")] = bus->getCustomText(); } diff --git a/wled00/const.h b/wled00/const.h index 6d1825d574..6133ece198 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -55,32 +55,37 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C #ifdef ESP8266 #define WLED_MAX_DIGITAL_CHANNELS 3 + #define WLED_MAX_RMT_CHANNELS 0 // ESP8266 does not have RMT nor I2S + #define WLED_MAX_I2S_CHANNELS 0 #define WLED_MAX_ANALOG_CHANNELS 5 - #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_MIN_VIRTUAL_BUSSES 0 // no longer used for bus creation but used to distinguish ESP type in UI #else #if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX) #include "driver/ledc.h" // needed for analog/LEDC channel counts #endif #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_DIGITAL_CHANNELS 2 + #define WLED_MAX_RMT_CHANNELS 2 // ESP32-C3 has 2 RMT output channels + #define WLED_MAX_I2S_CHANNELS 0 // I2S not supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 6 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_MIN_VIRTUAL_BUSSES 1 // no longer used for bus creation but used to distinguish ESP type in UI #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB - // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0 + #define WLED_MAX_RMT_CHANNELS 4 // ESP32-S2 has 4 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_MIN_VIRTUAL_BUSSES 2 // no longer used for bus creation but used to distinguish ESP type in UI #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1 - #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD + #define WLED_MAX_RMT_CHANNELS 4 // ESP32-S3 has 4 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // uses LCD parallel output not I2S //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish ESP type in UI #else - // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT + #define WLED_MAX_RMT_CHANNELS 8 // ESP32 has 8 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 16 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish ESP type in UI #endif + #define WLED_MAX_DIGITAL_CHANNELS (WLED_MAX_RMT_CHANNELS + WLED_MAX_I2S_CHANNELS) #endif // WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed // instead it will help determine max number of buses that can be defined at compile time @@ -299,7 +304,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define TYPE_UCS8903 26 #define TYPE_APA106 27 #define TYPE_FW1906 28 //RGB + CW + WW + unused channel (6 channels per IC) -#define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) +#define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 #define TYPE_WS2805 32 //RGB + WW + CW @@ -474,23 +479,28 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs #elif defined(CONFIG_IDF_TARGET_ESP32S2) #define MAX_LEDS 2048 //due to memory constraints S2 - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - #define MAX_LEDS 4096 #else #define MAX_LEDS 16384 #endif #endif +// maximum total memory that can be used for bus-buffers and pixel buffers #ifndef MAX_LED_MEMORY #ifdef ESP8266 - #define MAX_LED_MEMORY 4096 + #define MAX_LED_MEMORY (8*1024) #else - #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_LED_MEMORY 16384 - #elif defined(ARDUINO_ARCH_ESP32C3) - #define MAX_LED_MEMORY 32768 + #if defined(CONFIG_IDF_TARGET_ESP32S2) + #ifndef BOARD_HAS_PSRAM + #define MAX_LED_MEMORY (28*1024) // S2 has ~170k of free heap after boot, using 28k is the absolute limit to keep WLED functional + #else + #define MAX_LED_MEMORY (48*1024) // with PSRAM there is more wiggle room as buffers get moved to PSRAM when needed (prioritize functionality over speed) + #endif + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_LED_MEMORY (192*1024) // S3 has ~330k of free heap after boot + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + #define MAX_LED_MEMORY (100*1024) // C3 has ~240k of free heap after boot, even with 8000 LEDs configured (2D) there is 30k of contiguous heap left #else - #define MAX_LED_MEMORY 65536 + #define MAX_LED_MEMORY (85*1024) // ESP32 has ~160k of free heap after boot and an additional 64k of 32bit access memory that is used for pixel buffers #endif #endif #endif @@ -555,7 +565,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #ifdef ESP8266 #define JSON_BUFFER_SIZE 10240 #else - #if defined(ARDUINO_ARCH_ESP32S2) + #if defined(CONFIG_IDF_TARGET_ESP32S2) #define JSON_BUFFER_SIZE 24576 #else #define JSON_BUFFER_SIZE 32767 diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 8de233ca77..69e716c934 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -6,7 +6,7 @@ LED Settings @@ -915,8 +1065,10 @@

Hardware setup

⚠ You might run into stability or lag issues.
Use less than 800 LEDs per output for the best experience!
+
-
Use parallel I2S:
Make a segment for each output:
Custom bus start indices:

diff --git a/wled00/file.cpp b/wled00/file.cpp index ee107c6664..dcc2d57c41 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -396,6 +396,7 @@ static const uint8_t *getPresetCache(size_t &size) { if (presetsCached) { p_free(presetsCached); presetsCached = nullptr; + presetsCachedSize = 0; } } diff --git a/wled00/network.cpp b/wled00/network.cpp index 68770a716b..105fdf6b27 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -301,7 +301,7 @@ void fillStr2MAC(uint8_t *mac, const char *str) { // returns configured WiFi ID with the strongest signal (or default if no configured networks available) int findWiFi(bool doScan) { if (multiWiFi.size() <= 1) { - DEBUG_PRINTF_P(PSTR("WiFi: Defaulf SSID (%s) used.\n"), multiWiFi[0].clientSSID); + DEBUG_PRINTF_P(PSTR("WiFi: Default SSID (%s) used.\n"), multiWiFi[0].clientSSID); return 0; } diff --git a/wled00/set.cpp b/wled00/set.cpp index db8b30bac8..702f78b499 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -138,7 +138,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } } - unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed; + unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed, driverType; unsigned length, start, maMax; uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255}; String text; @@ -155,9 +155,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) Bus::setCCTBlend(cctBlending); Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt()); - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - useParallelI2S = request->hasArg(F("PR")); - #endif bool busesChanged = false; for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -175,6 +172,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM) char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA + char ld[4] = "LD"; ld[2] = offset+s; ld[3] = 0; //driver type (RMT=0, I2S=1) char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others) if (!request->hasArg(lp)) { DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1); @@ -226,10 +224,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0 } type |= request->hasArg(rf) << 7; // off refresh override + driverType = request->arg(ld).toInt(); // 0=RMT (default), 1=I2S text = request->arg(hs).substring(0,31); // actual finalization is done in WLED::loop() (removing old busses and adding new) // this may happen even before this loop is finished so we do "doInitBusses" after the loop - busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text); + busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, driverType, text); busesChanged = true; } //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed diff --git a/wled00/util.cpp b/wled00/util.cpp index 861f1ce4ff..b07f5b34be 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -692,15 +692,21 @@ static void *validateFreeHeap(void *buffer) { return buffer; } +#ifdef BOARD_HAS_PSRAM +#define RTC_RAM_THRESHOLD 1024 // use RTC RAM for allocations smaller than this size +#else +#define RTC_RAM_THRESHOLD 65535 // without PSRAM, allow any size into RTC RAM (useful especially on S2 without PSRAM) +#endif + void *d_malloc(size_t size) { - void *buffer; + void *buffer = nullptr; #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) // the newer ESP32 variants have byte-accessible fast RTC memory that can be used as heap, access speed is on-par with DRAM // the system does prefer normal DRAM until full, since free RTC memory is ~7.5k only, its below the minimum heap threshold and needs to be allocated explicitly - // use RTC RAM for small allocations to improve fragmentation or if DRAM is running low - if (size < 256 || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size) + // use RTC RAM for small allocations or if DRAM is running low to improve fragmentation + if (size <= RTC_RAM_THRESHOLD || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size) buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - else + if (buffer == nullptr) // no RTC RAM allocation: use DRAM #endif buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory buffer = validateFreeHeap(buffer); // make sure there is enough free heap left diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c0ec92a916..4e6361b6d5 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -230,11 +230,11 @@ void WLED::loop() // DEBUG serial logging (every 30s) #ifdef WLED_DEBUG loopMillis = millis() - loopMillis; - if (loopMillis > 30) { - DEBUG_PRINTF_P(PSTR("Loop took %lums.\n"), loopMillis); - DEBUG_PRINTF_P(PSTR("Usermods took %lums.\n"), usermodMillis); - DEBUG_PRINTF_P(PSTR("Strip took %lums.\n"), stripMillis); - } + //if (loopMillis > 30) { + // DEBUG_PRINTF_P(PSTR("Loop took %lums.\n"), loopMillis); + // DEBUG_PRINTF_P(PSTR("Usermods took %lums.\n"), usermodMillis); + // DEBUG_PRINTF_P(PSTR("Strip took %lums.\n"), stripMillis); + //} avgLoopMillis += loopMillis; if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis; if (millis() - debugTime > 29999) { diff --git a/wled00/wled.h b/wled00/wled.h index 66b33740d6..02d75ca3d2 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -400,9 +400,6 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load WLED_GLOBAL bool useGlobalLedBuffer _INIT(false); // double buffering disabled on ESP8266 #else WLED_GLOBAL bool useGlobalLedBuffer _INIT(true); // double buffering enabled on ESP32 - #ifndef CONFIG_IDF_TARGET_ESP32C3 -WLED_GLOBAL bool useParallelI2S _INIT(false); // parallel I2S for ESP32 - #endif #endif #ifdef WLED_USE_IC_CCT WLED_GLOBAL bool cctICused _INIT(true); // CCT IC used (Athom 15W bulbs) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 194256d82e..5b99da9f9e 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -291,14 +291,16 @@ void getSettingsJS(byte subPage, Print& settingsScript) settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str()); // set limits - settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d);"), + settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);"), WLED_MAX_BUSSES, - WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI + WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish ESP types in UI MAX_LEDS_PER_BUS, MAX_LED_MEMORY, MAX_LEDS, WLED_MAX_COLOR_ORDER_MAPPINGS, WLED_MAX_DIGITAL_CHANNELS, + WLED_MAX_RMT_CHANNELS, + WLED_MAX_I2S_CHANNELS, WLED_MAX_ANALOG_CHANNELS, WLED_MAX_BUTTONS ); @@ -310,7 +312,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend()); printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps()); printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode()); - printSetFormCheckbox(settingsScript,PSTR("PR"),BusManager::hasParallelOutput()); // get it from bus manager not global variable unsigned sumMa = 0; for (size_t s = 0; s < BusManager::getNumBusses(); s++) { @@ -321,6 +322,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order char lt[4] = "LT"; lt[2] = offset+s; lt[3] = 0; //strip type + char ld[4] = "LD"; ld[2] = offset+s; ld[3] = 0; //driver type (RMT=0, I2S=1) char ls[4] = "LS"; ls[2] = offset+s; ls[3] = 0; //strip start LED char cv[4] = "CV"; cv[2] = offset+s; cv[3] = 0; //strip reverse char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip 1st LED @@ -340,6 +342,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) } printSetFormValue(settingsScript,lc,bus->getLength()); printSetFormValue(settingsScript,lt,bus->getType()); + printSetFormValue(settingsScript,ld,bus->getDriverType()); printSetFormValue(settingsScript,co,bus->getColorOrder() & 0x0F); printSetFormValue(settingsScript,ls,bus->getStart()); printSetFormCheckbox(settingsScript,cv,bus->isReversed());