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 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 (auto bus : busConfigs) { + 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) { bool use_placeholder = false; - unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer - // estimate maximum I2S memory usage (only relevant for digital non-2pin busses) + unsigned busMemUsage = bus.memUsage(); // does not include DMA/RMT buffer but includes pixel buffers (segment buffer + global buffer) + mem += busMemUsage; + // 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 + busMemUsage + 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) DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count); errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: not enough memory for bus use_placeholder = true; @@ -1219,7 +1226,7 @@ void WS2812FX::finalizeInit() { if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count } } - 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(); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index a73146ec0f..3411f831bc 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,7 +1114,7 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const { #endif // *************************************************************************** -BusPlaceholder::BusPlaceholder(const BusConfig &bc) +BusPlaceholder::BusPlaceholder(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, bc.refreshReq) , _colorOrder(bc.colorOrder) , _skipAmount(bc.skipAmount) @@ -1125,47 +1134,20 @@ size_t BusPlaceholder::getPins(uint8_t* pinArray) const { return nPins; } -//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, bool placeholder) { @@ -1190,7 +1172,7 @@ int BusManager::add(const BusConfig &bc, bool placeholder) { 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 { @@ -1228,49 +1210,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; @@ -1445,9 +1413,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 db49301994..29f95ccc9c 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; } @@ -243,7 +244,7 @@ class Bus { class BusDigital : public Bus { public: - BusDigital(const BusConfig &bc, uint8_t nr); + BusDigital(const BusConfig &bc); ~BusDigital() { cleanup(); } void show() override; @@ -259,10 +260,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(); @@ -273,6 +276,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; @@ -455,9 +459,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) @@ -467,13 +473,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, @@ -481,7 +489,8 @@ struct BusConfig { (int)skipAmount, (int)autoWhite, (int)frequency, - (int)milliAmpsPerLed, (int)milliAmpsMax + (int)milliAmpsPerLed, (int)milliAmpsMax, + driverType == 0 ? "RMT" : "I2S" ); } @@ -497,7 +506,7 @@ struct BusConfig { return true; } - size_t memUsage(unsigned nr = 0) const; + size_t memUsage() const; }; @@ -528,7 +537,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) @@ -536,8 +544,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 75854751ea..4dac253082 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 bb1befcdd6..22a9a8273c 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -258,11 +258,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 eebce343ea..bd6a3772f0 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());