Improved bus handling: free choice of bus driver in any order and improved memory calculations (#5303)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
This commit is contained in:
Damian Schneider
2026-02-14 19:00:37 +01:00
committed by GitHub
parent f830ea498c
commit d1ed547a7c
16 changed files with 486 additions and 337 deletions

View File

@@ -60,8 +60,8 @@ class RgbRotaryEncoderUsermod : public Usermod
// …then set only the LED pin
_pins[0] = static_cast<byte>(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;

View File

@@ -120,6 +120,9 @@ void WS2812FX::setUpMatrix() {
for (unsigned i=0; i<customMappingSize; i++) {
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
#if defined(CONFIG_IDF_TARGET_ESP32S2)
delay(1); // on S2 the CDC output can crash without a delay
#endif
}
DEBUG_PRINTLN();
#endif

View File

@@ -100,12 +100,12 @@ Segment& Segment::operator= (const Segment &orig) {
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,57 +1159,64 @@ 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 (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();

View File

@@ -105,15 +105,19 @@ 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; }
_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; }
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
_frequencykHz = 0U;
_colorSum = 0;
@@ -127,28 +131,30 @@ 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);
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"
);
}
@@ -339,6 +345,10 @@ std::vector<LEDType> 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);
@@ -1093,10 +1103,11 @@ 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)
, _driverType(bc.driverType)
, _frequency(bc.frequency)
, _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax)
@@ -1113,47 +1124,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);
mem += 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
}
return size + maxI2S;
return mem;
}
int BusManager::add(const BusConfig &bc, bool placeholder) {
@@ -1178,7 +1162,7 @@ int BusManager::add(const BusConfig &bc, bool placeholder) {
busses.push_back(make_unique<BusHub75Matrix>(bc));
#endif
} else if (Bus::isDigital(bc.type)) {
busses.push_back(make_unique<BusDigital>(bc, Bus::is2Pin(bc.type) ? twoPin : digital));
busses.push_back(make_unique<BusDigital>(bc));
} else if (Bus::isOnOff(bc.type)) {
busses.push_back(make_unique<BusOnOff>(bc));
} else {
@@ -1216,49 +1200,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<BusDigital*>(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_channel_t>(rmt);
rmt_idle_level_t lvl;
@@ -1433,9 +1403,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

View File

@@ -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;
@@ -391,6 +395,7 @@ class BusPlaceholder : public Bus {
uint16_t getFrequency() const override { return _frequency; }
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
uint8_t getDriverType() const override { return _driverType; }
const String getCustomText() const override { return _text; }
bool isPlaceholder() const override { return true; }
@@ -400,6 +405,7 @@ class BusPlaceholder : public Bus {
uint8_t _colorOrder;
uint8_t _skipAmount;
uint8_t _pins[OUTPUT_MAX_PINS];
uint8_t _driverType;
uint16_t _frequency;
uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax;
@@ -455,9 +461,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 +475,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 +491,8 @@ struct BusConfig {
(int)skipAmount,
(int)autoWhite,
(int)frequency,
(int)milliAmpsPerLed, (int)milliAmpsMax
(int)milliAmpsPerLed, (int)milliAmpsMax,
driverType == 0 ? "RMT" : "I2S"
);
}
@@ -497,7 +508,7 @@ struct BusConfig {
return true;
}
size_t memUsage(unsigned nr = 0) const;
size_t memUsage() const;
};
@@ -528,7 +539,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 +546,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();

View File

@@ -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 <class T>
@@ -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<B_32_RN_NEO_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_NEO_4: size = (static_cast<B_32_RN_NEO_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_400_3: size = (static_cast<B_32_RN_400_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM1_4: size = (static_cast<B_32_RN_TM1_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM2_3: size = (static_cast<B_32_RN_TM2_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_UCS_3: size = (static_cast<B_32_RN_UCS_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_UCS_4: size = (static_cast<B_32_RN_UCS_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_APA106_3: size = (static_cast<B_32_RN_APA106_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_FW6_5: size = (static_cast<B_32_RN_FW6_5*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_2805_5: size = (static_cast<B_32_RN_2805_5*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM1914_3: size = (static_cast<B_32_RN_TM1914_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_SM16825_5: size = (static_cast<B_32_RN_SM16825_5*>(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<B_32_RN_NEO_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_NEO_4: size += (static_cast<B_32_RN_NEO_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_400_3: size += (static_cast<B_32_RN_400_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM1_4: size += (static_cast<B_32_RN_TM1_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM2_3: size += (static_cast<B_32_RN_TM2_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_UCS_3: size += (static_cast<B_32_RN_UCS_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_UCS_4: size += (static_cast<B_32_RN_UCS_4*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_APA106_3: size += (static_cast<B_32_RN_APA106_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_FW6_5: size += (static_cast<B_32_RN_FW6_5*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_2805_5: size += (static_cast<B_32_RN_2805_5*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_TM1914_3: size += (static_cast<B_32_RN_TM1914_3*>(busPtr))->PixelsSize()*2; break;
case I_32_RN_SM16825_5: size += (static_cast<B_32_RN_SM16825_5*>(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<B_32_IP_NEO_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_NEO_4: size = (_useParallelI2S) ? (static_cast<B_32_IP_NEO_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_400_3: size = (_useParallelI2S) ? (static_cast<B_32_IP_400_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_400_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM1_4: size = (_useParallelI2S) ? (static_cast<B_32_IP_TM1_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM1_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM2_3: size = (_useParallelI2S) ? (static_cast<B_32_IP_TM2_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM2_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_UCS_3: size = (_useParallelI2S) ? (static_cast<B_32_IP_UCS_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_UCS_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_UCS_4: size = (_useParallelI2S) ? (static_cast<B_32_IP_UCS_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_UCS_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_APA106_3: size = (_useParallelI2S) ? (static_cast<B_32_IP_APA106_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_APA106_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_FW6_5: size = (_useParallelI2S) ? (static_cast<B_32_IP_FW6_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_FW6_5*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_2805_5: size = (_useParallelI2S) ? (static_cast<B_32_IP_2805_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_2805_5*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM1914_3: size = (_useParallelI2S) ? (static_cast<B_32_IP_TM1914_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM1914_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_SM16825_5: size = (_useParallelI2S) ? (static_cast<B_32_IP_SM16825_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_SM16825_5*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_NEO_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_NEO_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_NEO_4: size += (_useParallelI2S) ? (static_cast<B_32_IP_NEO_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_400_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_400_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_400_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM1_4: size += (_useParallelI2S) ? (static_cast<B_32_IP_TM1_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM1_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM2_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_TM2_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM2_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_UCS_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_UCS_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_UCS_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_UCS_4: size += (_useParallelI2S) ? (static_cast<B_32_IP_UCS_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_UCS_4*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_APA106_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_APA106_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_APA106_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_FW6_5: size += (_useParallelI2S) ? (static_cast<B_32_IP_FW6_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_FW6_5*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_2805_5: size += (_useParallelI2S) ? (static_cast<B_32_IP_2805_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_2805_5*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_TM1914_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_TM1914_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_TM1914_3*>(busPtr))->PixelsSize()*4; break;
case I_32_I2_SM16825_5: size += (_useParallelI2S) ? (static_cast<B_32_IP_SM16825_5*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_SM16825_5*>(busPtr))->PixelsSize()*4; break;
#endif
#endif
case I_HS_DOT_3: size = (static_cast<B_HS_DOT_3*>(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
// 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 > 4) return I_NONE;
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
return I_NONE; // No channels available
}
#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
} else {
if (num > 9) return I_NONE;
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
}
#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

View File

@@ -177,9 +177,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
@@ -246,9 +243,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
}
@@ -331,7 +329,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()
}
}
@@ -947,9 +945,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
@@ -1003,6 +998,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();
}

View File

@@ -56,32 +56,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_PLATFORM_ID 0 // used in UI to distinguish ESP types, needs a proper fix!
#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_PLATFORM_ID 1 // used in UI to distinguish ESP types, needs a proper fix!
#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_PLATFORM_ID 2 // used in UI 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_PLATFORM_ID 3 // used in UI to distinguish ESP type in UI, needs a proper fix!
#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_PLATFORM_ID 4 // used in UI to distinguish ESP type in UI, needs a proper fix!
#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
@@ -308,7 +313,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
@@ -508,23 +513,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
@@ -589,7 +599,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

View File

@@ -6,7 +6,7 @@
<title>LED Settings</title>
<style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var maxD=1,maxI2S=0,maxRMT=0,maxA=1,chipID=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h
@@ -37,6 +37,7 @@
function S() {
getLoc();
if(localStorage.getItem('ASc')==='true') d.Sf.AS.checked=true;
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
d.ledTypes = [/*{i:22,c:1,t:"D",n:"WS2812"},{i:42,c:6,t:"AA",n:"PWM CCT"}*/]; // filled from GetV()
d.um_p = [];
@@ -52,22 +53,23 @@
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6,n=4) {
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxBT = n; // maxBT - max buttons
function bLimits(c,p,m,l,o,di,r,i,a,n) {
chipID = c; // chip/platformID - 0 = ESP8266, 1 = C3, 2 = S2, 3 = S3, 4 = ESP32
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
maxD = di; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxRMT = r; // maxRMT - max RMT channels: 8 - ESP32, 4 - S2/S3, 2 - C3, 0 - 8266
maxI2S = i; // maxI2S - max I2S/LCD channels: 8 - ESP32/S2/S3, 0 - C3/8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxBT = n; // maxBT - max buttons
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
function isC3() { return maxA == 6 && maxD == 2; } // NOTE: see const.h
function isS2() { return maxA == 8 && maxD == 12 && maxV == 4; } // NOTE: see const.h
function isS3() { return maxA == 8 && maxD == 12 && maxV == 6; } // NOTE: see const.h
function is8266() { return chipID == 0; } // NOTE: see const.h: WLED_PLATFORM_ID (TODO: use info json lookup instead)
function isC3() { return chipID == 1; }
function isS2() { return chipID == 2; }
function isS3() { return chipID == 3; }
function is32() { return chipID == 4; }
function pinsOK() {
var ok = true;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
@@ -122,6 +124,12 @@
d.Sf.data.value = '';
e.preventDefault();
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
let usage = getDuse(), invalidBus = false;
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach(s=>{
let n = s.name.substring(2,3);
if (!isBCok(n, usage)) invalidBus = true;
});
if (invalidBus) { alert("Invalid Bus-config"); e.stopPropagation(); return false; }
// validate HUB75 panel config
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
for (let i=0; i<LTs.length; i++) {
@@ -136,7 +144,7 @@
}
}
};
if (bquot > 200) {var msg = "Too many LEDs! Can't handle that!"; alert(msg); e.stopPropagation(); return false;}
if (bquot > 100) {var msg = "Too many LEDs! Can't handle that!"; alert(msg); e.stopPropagation(); return false;}
else {
if (bquot > 80) {var msg = "Memory usage is high, reboot recommended!\n\rSet transitions to 0 to save memory.";
if (bquot > 100) msg += "\n\rToo many LEDs for me to handle properly!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
@@ -219,12 +227,12 @@
enABL();
gId('m1').innerHTML = maxM;
}
//returns mem usage
//returns mem usage for buses including two pixel buffers (segment buffer + global buffer)
function getMem(t, n) {
if (isAna(t)) return 5; // analog
let len = parseInt(d.Sf["LC"+n].value);
len += parseInt(d.Sf["SL"+n].value); // skipped LEDs are allocated too
let dbl = 0;
let dma = 0; // DMA memory for this bus (only for I2S)
let pbfr = len * 8; // pixel buffers: global buffer + segment buffer (at least one segment buffer is required)
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
@@ -233,16 +241,30 @@
if (is8266() && d.Sf["L0"+n].value == 3) { //8266 DMA uses 5x the mem
mul = 5;
}
let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3()) && !isD2P(t);
if (isC3() || (isS3() && !parallelI2S)) {
mul = 2; // ESP32 RMT uses double buffer
} else if ((is32() || isS2() || isS3()) && toNum(n) > (parallelI2S ? 7 : 0)) {
mul = 2; // ESP32 RMT uses double buffer
} else if ((parallelI2S && toNum(n) < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer
dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used)
if (!is8266() && !isD2P(t)) {
mul = 2; // default to double buffer (RMT, 2-pin digital)
let driverPref = d.Sf["LD"+n]?.value | 0; // driver preference selection: 0=RMT, 1=I2S
if (driverPref == 1) { // I2S to be used
mul = 1; // NPB uses single pixel buffer for I2S, DMA buffer serves as second buffer
let usage = getDuse();
dma = usage.I2Smem; // DMA buffer for I2S/LCD, getDuse() returns the average per I2S bus so it can be distributed and summed
}
}
//console.log(`LED mem for bus ${n} (NPB buffers, DMA buffer per bus, WLED pixel buffers): ${len * ch * mul} + ${dma} + ${pbfr}`);
}
return len * ch * mul + dbl + pbfr;
return len * ch * mul + dma + pbfr;
}
// check if bus configuration is valid
function isBCok(n, usage) {
if (is8266()) return true; // no special bus limits on ESP8266 other than digital bus count, checked in updateTypeDropdowns()
let t = parseInt(d.Sf["LT"+n].value);
if (!isDig(t) || isD2P(t)) return true; // only digital non-2pin types need bus check
let drv = d.Sf["LD"+n]?.value | 0; // driver preference selection: 0=RMT, 1=I2S
if (drv==1 && usage.I2SType!==null && t!==usage.I2SType) return false; // mismatched type in parallel I2S
if (drv==0 && usage.rmtUsed > maxRMT) return false; // too many RMT buses
if (drv==1 && usage.i2sUsed > maxI2S) return false; // too many I2S buses
return true;
}
function UI(change=false)
@@ -294,14 +316,12 @@
// enable/disable LED fields
updateTypeDropdowns(); // restrict bus types in dropdowns to max allowed digital/analog buses
let dC = 0; // count of digital buses (for parallel I2S)
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
LTs.forEach((s,i)=>{
// is the field a LED type?
var n = s.name.substring(2,3); // bus number (0-Z)
var t = parseInt(s.value);
memu += getMem(t, n); // calc memory
dC += (isDig(t) && !isD2P(t));
setPinConfig(n,t);
gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings
if (change) { // did we change LED type?
@@ -340,9 +360,8 @@
let nm = LC.name.substring(0,2); // field name : /L./
let n = LC.name.substring(2,3); // bus number (0-Z)
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (isDig(t)) {
if (isDig(t) && !isD2P(t)) {
if (sameType == 0) sameType = t; // first bus type
else if (sameType != t) sameType = -1; // different bus type
}
// do we have a led count field
if (nm=="LC") {
@@ -416,13 +435,47 @@
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
} else LC.style.color = "#fff";
});
if (is32() || isS2() || isS3()) {
if (maxLC > 600 || dC < 2 || sameType <= 0) {
d.Sf["PR"].checked = false;
gId("prl").classList.add("hide");
} else
gId("prl").classList.remove("hide");
} else d.Sf["PR"].checked = false;
// Use helper function to calculate channel usage
let usage = getDuse();
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
let n = s.name.substring(2,3);
let t = parseInt(s.value);
let drvsel = gId("drvsel"+n); // driver selection dropdown
if (drvsel) {
drvsel.style.display = "none"; // hide by default
drvsel.style.color = "#fff"; // reset color
}
s.style.color = "#fff"; // reset
if (isDig(t) && !isD2P(t)) {
// Update I2S/RMT driver info/dropdown for ESP32 digital buses, C3 only supports RMT
if (!is8266() && !isC3()) {
// Show driver selection dropdown when I2S is enabled, mark red if invalid
if (drvsel) {
if (d.Sf.AS.checked) drvsel.style.display = "inline"; // only show when advanced settings enabled
d.Sf["LD"+n].value = d.Sf["LD"+n].value | 0; // default to RMT
if (!isBCok(n, usage)) drvsel.style.color = "red"; else drvsel.style.color = "#fff";
}
}
}
});
updateTypeDropdowns(); // update type dropdowns to disable unavailable digital/analog types (I2S/RMT bus count may have changed due to memory usage change)
// note: do not remvoe this second call to updateTypeDropdowns() as it also updates the available LED types based on the current bus configuration, not just the driver options
// Show channel usage warning
let chanuse = gId('chanuse');
let channelMsg = gId('chanusemsg');
if (chanuse && channelMsg && !is8266()) {
chanuse.style.display = 'inline';
chanuse.style.color = '#ccc';
channelMsg.textContent = `Hardware channels used: RMT ${usage.rmtUsed}/${maxRMT}, I2S ${usage.i2sUsed}/${maxI2S}`;
if (usage.rmtUsed > maxRMT || usage.i2sUsed > maxI2S) {
chanuse.style.color = 'red';
}
}
// distribute ABL current if not using PPL
enPPL(sDI);
@@ -458,6 +511,7 @@
gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none';
gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none';
}
function lastEnd(i) {
if (i-- < 1) return 0;
var s = chrID(i);
@@ -466,6 +520,7 @@
if (isPWM(t)) v = 1; //PWM busses
return isNaN(v) ? 0 : v;
}
function addLEDs(n,init=true)
{
var o = gEBCN("iST");
@@ -481,7 +536,7 @@
var cn = `<div class="iST">
<hr class="sml">
${i+1}:
<select name="LT${s}" onchange="updateTypeDropdowns();UI(true)"></select><br>
<select name="LT${s}" onchange="UI(true)"></select><br>
<div id="abl${s}">
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<option value="55" selected>55mA (typ. 5V WS281x)</option>
@@ -507,13 +562,19 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<div id="dig${s}l" style="display:none">Clock: <select name="SP${s}"><option value="0">Slowest</option><option value="1">Slow</option><option value="2">Normal</option><option value="3">Fast</option><option value="4">Fastest</option></select></div>
<div>
<span id="psd${s}">Start:</span> <input type="number" name="LS${s}" id="ls${s}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${s}c" style="display:inline">Length: <input type="number" name="LC${s}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
<div id="dig${s}c" style="display:inline">Length: <input type="number" name="LC${s}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI();" /></div><br>
</div>
<span id="p0d${s}">GPIO:</span><input type="number" name="L0${s}" required class="s" onchange="UI();pinUpd(this);"/>
<span id="p1d${s}"></span><input type="number" name="L1${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p2d${s}"></span><input type="number" name="L2${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${s}"></span><input type="number" name="L3${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${s}"></span><input type="number" name="L4${s}" class="s" onchange="UI();pinUpd(this);"/>
<div id="drvsel${s}" style="display:none">
Driver: <select name="LD${s}" onchange="UI()">
<option value="0">RMT</option>
<option value="1">I2S</option>
</select>
</div>
<div id="net${s}h" class="hide">Host: <input type="text" name="HS${s}" maxlength="32" pattern="[a-zA-Z0-9_\\-]*" onchange="UI()"/>.local</div>
<div id="dig${s}r" style="display:inline"><br><span id="rev${s}">Reversed</span>: <input type="checkbox" name="CV${s}"></div>
<div id="dig${s}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"></div>
@@ -694,6 +755,7 @@ Swap: <select id="xw${s}" name="XW${s}">
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LD"+i)[0].value = v.drv | 0; // output driver type (RMT or I2S, default to RMT if not set)
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order & 0x0F;
@@ -706,7 +768,6 @@ Swap: <select id="xw${s}" name="XW${s}">
d.getElementsByName("LA"+i)[0].value = v.ledma;
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
});
d.getElementsByName("PR")[0].checked = l.prl | 0;
d.getElementsByName("MA")[0].value = l.maxpwr;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
}
@@ -839,11 +900,50 @@ Swap: <select id="xw${s}" name="XW${s}">
}
return opt;
}
// calculate channel usage across all buses
function getDuse() {
let rmtUsed = 0, i2sUsed = 0;
let I2SType = null;
let I2Smem = 0; // DMA memory usage for I2S buses: 3x LED count for single I2S bus, 24x LED count for parallel I2S
let maxLEDs = 0; // max number of LEDs for DMA buffer calc
if (!is8266()) {
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach(sel => {
let n = sel.name.substring(2,3);
let t = parseInt(sel.value);
let driverPref = d.Sf["LD"+n]?.value | 0;
let ledCount = (parseInt(d.Sf["LC"+n].value) || 0) + (parseInt(d.Sf["SL"+n].value) || 0);
if (isDig(t) && !isD2P(t)) {
if (driverPref === 1) {
i2sUsed++;
maxLEDs = maxLEDs > ledCount ? maxLEDs : ledCount; // for parallel I2S the memory buffer is shared, largest bus determines total memory usage
if (!I2SType) I2SType = t; // first I2S bus determines allowed type for all subsequent I2S buses (parallel I2S limitation)
} else {
rmtUsed++;
}
}
});
// calculate I2S memory usage
if (I2SType) {
let ch = 3*hasRGB(I2SType) + hasW(I2SType) + hasCCT(I2SType); // byte channel count per LED
if (is16b(I2SType)) maxLEDs *= 2; // 16 bit LEDs use 2 bytes per channel
I2Smem = maxLEDs * ch * (i2sUsed > 1 || isS3() ? 24 : 3); // 3 bytes per LED byte for single I2S, 24 bytes per LED byte for parallel I2S (S3 always uses parallel), assumes 3-step cadence
I2Smem = Math.round(I2Smem / i2sUsed); // average memory per I2S bus (used for memory estimation), round to nearest integer to avoid float rounding errors
}
}
return { rmtUsed, i2sUsed, I2SType, I2Smem };
}
// dynamically enforce bus type availability based on current usage
function updateTypeDropdowns() {
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
// count currently used buses
// calculate channel usage
let usage = getDuse();
let firstI2SType = null;
// Count all bus types
LTs.forEach(sel => {
let t = parseInt(sel.value);
if (isDig(t) && !isD2P(t)) digitalB++;
@@ -851,20 +951,66 @@ Swap: <select id="xw${s}" name="XW${s}">
if (isD2P(t)) twopinB++;
if (isVir(t)) virtB++;
});
// enable/disable type options according to limits in dropdowns
// update each LED-type and driver dropdown with appropriate constraints
let RMTcount = 0;
let I2Scount = 0;
LTs.forEach(sel => {
let n = sel.name.substring(2,3);
const curType = parseInt(sel.value);
const curDriver = d.Sf["LD"+n]?.value | 0;
const disable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = true);
const enable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = false);
enable('option'); // reset all first
// max digital buses: ESP32 & S2 support mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
// supported outputs using parallel I2S/mono I2S: S2: 12/5, S3: 12/4, ESP32: 16/9
let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
// disallow adding more of a type that has reached its limit but allow changing the current type
if (digitalB >= maxDB && !(isDig(curType) && !isD2P(curType))) disable('option[data-type="D"]');
// Update LED type constraints for digital buses
if (isDig(curType) && !isD2P(curType)) {
// If this bus uses I2S and other I2S buses exist, restrict to same type
if (curDriver === 1) {
if (firstI2SType == null) firstI2SType = curType; // set first type, first I2S bus is allowed to change to any digital type
else {
sel.querySelectorAll('option[data-type="D"]').forEach(o => {
if (parseInt(o.value) !== firstI2SType) o.disabled = true;
});
}
}
} else {
if (digitalB >= maxD) disable('option[data-type="D"]'); // disable digital bus options if limit reached
else if (!is8266() && usage.rmtUsed >= maxRMT && (firstI2SType != null)) {
// there are still digital buses available, restrict digital bus options to I2S type if RMT is full (ESP32 only)
sel.querySelectorAll('option[data-type="D"]').forEach(o => {
if (parseInt(o.value) !== firstI2SType) o.disabled = true;
});
}
}
// 2-pin digital buses limited to 2
if (twopinB >= 2 && !isD2P(curType)) disable('option[data-type="2P"]');
// Disable PWM types that need more pins than available (accounting for current type's pins if PWM)
// PWM analog types limited by pin count
disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(curType)?numPins(curType):0) + 1)}"]`);
// update driver selection dropdowns
let drvsel = gId("drvsel"+n);
if (drvsel && isDig(curType) && !isD2P(curType)) {
let rmtOpt = drvsel.querySelector('option[value="0"]');
let i2sOpt = drvsel.querySelector('option[value="1"]');
rmtOpt.disabled = false;
i2sOpt.disabled = false;
if (curDriver === 0) {
if ((usage.i2sUsed >= maxI2S)) i2sOpt.disabled = true; // disable I2S selection on RMT buses if full
RMTcount++;
if (RMTcount > maxRMT) {
rmtOpt.disabled = true; // disable RMT if now full (other logic disables digital types if both full)
d.Sf["LD"+n].value = "1"; // switch to I2S (if no I2S available, digital type is disabled above)
}
}
else {
if ((usage.rmtUsed >= maxRMT)) rmtOpt.disabled = true; // disable RMT selection on I2S buses if full
I2Scount++;
if (I2Scount > maxI2S) {
i2sOpt.disabled = true; // disable I2S if now full (other logic disables digital types if both full)
d.Sf["LD"+n].value = "0"; // switch to RMT (if no RMT available, digital type is disabled above)
}
}
}
});
}
</script>
@@ -911,8 +1057,11 @@ Swap: <select id="xw${s}" name="XW${s}">
&#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br>
</div>
<div id="chanuse" style="display: none;">
<span id="chanusemsg"></span><br>
</div>
<hr class="sml">
<div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div>
Show Advanced Settings <input type="checkbox" name="AS" onchange="localStorage.setItem('ASc',this.checked);UI()"><br>
Make a segment for each output: <input type="checkbox" name="MS"><br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
<hr class="sml">

View File

@@ -396,6 +396,7 @@ static const uint8_t *getPresetCache(size_t &size) {
if (presetsCached) {
p_free(presetsCached);
presetsCached = nullptr;
presetsCachedSize = 0;
}
}

View File

@@ -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;
}

View File

@@ -175,7 +175,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;
@@ -192,9 +192,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"
@@ -212,6 +209,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);
@@ -263,10 +261,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

View File

@@ -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

View File

@@ -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) {

View File

@@ -403,9 +403,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)

View File

@@ -307,14 +307,15 @@ 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);"),
WLED_MAX_BUSSES,
WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);"),
WLED_PLATFORM_ID, // TODO: replace with a info json lookup
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
);
@@ -326,7 +327,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++) {
@@ -337,6 +337,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
@@ -356,6 +357,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());