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

@@ -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();