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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
<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}">
|
||||
⚠ 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">
|
||||
|
||||
@@ -396,6 +396,7 @@ static const uint8_t *getPresetCache(size_t &size) {
|
||||
if (presetsCached) {
|
||||
p_free(presetsCached);
|
||||
presetsCached = nullptr;
|
||||
presetsCachedSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user