Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a69e04f
Initial plan
Copilot Jan 3, 2026
2726a91
Update UI and backend to default to RMT, make I2S optional
Copilot Jan 3, 2026
afdd0e3
Implement parallel/single I2S distinction and add UI driver info
Copilot Jan 3, 2026
0e34591
Fix I2S memory check to only apply to parallel I2S
Copilot Jan 3, 2026
ffd29de
Fix critical bug: calculate sameType and maxLEDsPerBus before use
Copilot Jan 3, 2026
6acf3ef
Revert I2S memory check to include both single and parallel I2S
Copilot Jan 3, 2026
87fe97b
Optimize driver info display by using existing calculations
Copilot Jan 3, 2026
54edff8
Fix I2S memory calculation logic for parallel vs single I2S
Copilot Jan 3, 2026
8f6f24a
Fix UI: Always show Enable I2S checkbox and auto-remove excess buses
Copilot Jan 3, 2026
efec2b4
Fix I2S checkbox logic and max bus count calculation
Copilot Jan 3, 2026
07c4305
Use maxD-7 formula instead of hard-coded values for single I2S
Copilot Jan 4, 2026
5589bfd
Simplify driver info display logic
Copilot Jan 4, 2026
9671063
enable "RMT" display on C3, minor cleanup
DedeHai Jan 4, 2026
7a8874f
fix incorrect bus counting
DedeHai Jan 4, 2026
0408865
Fix bus removal to only remove single-pin digital buses
Copilot Jan 4, 2026
4733e4f
fix RMT bus count
DedeHai Jan 4, 2026
ea9d53c
Replace auto-removal with visual warning for invalid bus combinations
Copilot Jan 4, 2026
b7170bb
Consolidate duplicate loops for bus validation and driver info
Copilot Jan 4, 2026
27c04c8
consolidate code, add lock i I2S is used (maybe not the best solution…
DedeHai Jan 5, 2026
9c7cc22
Add maxRMT and maxI2S parameters to backend and UI
Copilot Jan 9, 2026
7fb0dca
Part 2: Add driver selection dropdown UI for each digital bus
Copilot Jan 9, 2026
4fe3e9f
Part 3: Configuration storage for driver preference per bus
Copilot Jan 9, 2026
28db9cb
Part 4: Dynamic bus allocation logic with user-selectable drivers
Copilot Jan 9, 2026
51a6868
Part 5: Channel tracking and validation with brief warnings
Copilot Jan 9, 2026
da0e985
Remove outdated "mixed" bus logic for flexible driver assignment
Copilot Jan 9, 2026
4f59e53
Fix critical channel tracking bugs in user-selectable driver system
Copilot Jan 9, 2026
acc32f2
Fix compilation error: remove extra #endif in bus_wrapper.h
Copilot Jan 10, 2026
a1487bb
Fix all three critical issues with driver selection system
Copilot Jan 10, 2026
423e1f5
Use determineBusType() instead of getI() in memUsage() to avoid side …
Copilot Jan 10, 2026
0c0a4d0
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
Copilot Jan 10, 2026
8da802a
Simplify bus allocation by storing iType in BusConfig
Copilot Jan 10, 2026
1988199
Fix parallel I2S DMA buffer estimation (8x multiplier)
Copilot Jan 10, 2026
89d073d
Improve UI validation logic for RMT/I2S bus management
Copilot Jan 11, 2026
6c508a7
Refactor UI validation: extract helper function and fix duplicate res…
Copilot Jan 11, 2026
16458c4
Apply I2S validation rules to finalizeInit() matching UI behavior
Copilot Jan 11, 2026
643846c
Fix new bus driver selection to respect channel availability
Copilot Jan 11, 2026
5e7283c
Remove "Enable I2S" checkbox, make I2S always available, allow first …
Copilot Jan 11, 2026
09600db
Fix driver type persistence: get driverType from bus object instead o…
Copilot Jan 11, 2026
371aac2
Fix legacy config driver assignment: intelligently fallback to I2S wh…
Copilot Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 73 additions & 17 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,24 +1162,65 @@ void WS2812FX::finalizeInit() {

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;
// Determine if I2S/LCD should be used and whether parallel mode is possible
// Count I2S buses and check if they meet requirements
unsigned i2sBusCount = 0;
unsigned firstI2SBusType = 0;
unsigned maxI2SLedsOnBus = 0;
bool mixedI2SBusTypes = false;

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
// Check if this bus will use I2S driver (driverType == 1)
if (bus.driverType == 1) {
i2sBusCount++;
if (firstI2SBusType == 0) firstI2SBusType = bus.type; // remember first I2S bus type
if (firstI2SBusType != bus.type) {
mixedI2SBusTypes = true;
}
if (bus.count > maxI2SLedsOnBus) maxI2SLedsOnBus = bus.count;
}
}
}
DEBUG_PRINTF_P(PSTR("Digital buses: %u, I2S buses: %u, Max LEDs on I2S bus: %u\n"), digitalCount, i2sBusCount, maxI2SLedsOnBus);

// Determine I2S usage automatically based on bus configuration
bool useI2S = (i2sBusCount > 0); // Use I2S if any buses have driverType == 1

// Determine parallel vs single I2S usage
bool useParallelI2S = false;
if (useI2S && i2sBusCount > 0) {
// Parallel I2S requirements:
// - All I2S buses must be same LED type
// - If multiple I2S buses OR ESP32-S3: all I2S buses must have ≤600 LEDs
// - Single I2S bus has no LED count restriction
bool ledCountValid = true;
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// S3: all I2S buses (LCD driver) must have ≤600 LEDs
if (maxI2SLedsOnBus > 600) ledCountValid = false;
#else
// ESP32/S2: only restrict if multiple I2S buses
if (i2sBusCount > 1 && maxI2SLedsOnBus > 600) ledCountValid = false;
#endif

if (!mixedI2SBusTypes && ledCountValid) {
useParallelI2S = true;
DEBUG_PRINTF_P(PSTR("Using parallel I2S/LCD output.\n"));
} else {
if (mixedI2SBusTypes) {
DEBUG_PRINTF_P(PSTR("Using single I2S output (mixed I2S bus types).\n"));
} else {
DEBUG_PRINTF_P(PSTR("Using single I2S output (I2S bus >600 LEDs).\n"));
}
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
}
}
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

// Set the flags in PolyBus via BusManager
BusManager::useI2SOutput(useI2S);
if (useParallelI2S) {
BusManager::useParallelOutput(); // This sets parallel I2S flag - must call before creating buses
}
digitalCount = 0;
#endif

Expand All @@ -1190,12 +1231,25 @@ void WS2812FX::finalizeInit() {
for (const auto &bus : busConfigs) {
unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
mem += memB;
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses)
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses when I2S is enabled)
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1));
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
const bool usesI2S = (useParallelI2S && digitalCount <= 8);
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)
bool usesI2S = false;
if (BusManager::hasI2SOutput()) {
if (BusManager::hasParallelOutput()) {
// Parallel I2S: first 8 buses use I2S
usesI2S = (digitalCount <= 8);
} else {
// Single I2S: only the last bus uses I2S
#if defined(CONFIG_IDF_TARGET_ESP32)
usesI2S = (digitalCount == 9); // bus 8 (9th bus, 0-indexed)
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
usesI2S = (digitalCount == 5); // bus 4 (5th bus, 0-indexed)
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
usesI2S = false; // S3 doesn't support single I2S
#endif
}
}
#else
const bool usesI2S = false;
#endif
Expand All @@ -1206,6 +1260,8 @@ void WS2812FX::finalizeInit() {
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);
// Parallel I2S uses 8 channels, requiring 8x the DMA buffer size
if (BusManager::hasParallelOutput()) i2sCommonSize *= 8;
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
}
#endif
Expand Down
27 changes: 24 additions & 3 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

extern char cmDNS[];
extern bool cctICused;
extern bool useParallelI2S;
extern bool useI2S;

// functions to get/set bits in an array - based on functions created by Brandon for GOL
// toDo : make this a class that's completely defined in a header file
Expand Down Expand Up @@ -123,6 +123,7 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
, _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; }
Expand All @@ -139,7 +140,9 @@ 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);
// Reuse the iType that was determined during memory estimation (memUsage)
// This avoids calling getI() twice which would double-count channels
_iType = bc.iType;
if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; }
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
Expand Down Expand Up @@ -1111,7 +1114,10 @@ size_t BusConfig::memUsage(unsigned nr) const {
return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type));
} 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));
// Call getI() to determine bus type and allocate channel (this is the single call)
// Store the result in iType for later reuse during bus creation
const_cast<BusConfig*>(this)->iType = PolyBus::getI(type, pins, nr, driverType);
return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, iType);
} else if (Bus::isOnOff(type)) {
return sizeof(BusOnOff);
} else {
Expand Down Expand Up @@ -1213,13 +1219,23 @@ bool BusManager::hasParallelOutput() {
return PolyBus::isParallelI2S1Output();
}

void BusManager::useI2SOutput(bool enable) {
PolyBus::setI2SOutput(enable);
}

bool BusManager::hasI2SOutput() {
return PolyBus::isI2SOutput();
}

//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);
// Reset channel tracking for fresh allocation
PolyBus::resetChannelTracking();
}

#ifdef ESP32_DATA_IDLE_HIGH
Expand Down Expand Up @@ -1423,6 +1439,7 @@ ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }


bool PolyBus::_useParallelI2S = false;
bool PolyBus::_useI2S = false;

// Bus static member definition
int16_t Bus::_cct = -1;
Expand All @@ -1431,6 +1448,10 @@ uint8_t Bus::_gAWM = 255;

uint16_t BusDigital::_milliAmpsTotal = 0;

// PolyBus channel tracking for dynamic allocation
uint8_t PolyBus::_rmtChannelsUsed = 0;
uint8_t PolyBus::_i2sChannelsUsed = 0;

std::vector<std::unique_ptr<Bus>> BusManager::busses;
uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
Expand Down
16 changes: 13 additions & 3 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class Bus {
virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual uint8_t getDriverType() const { return 0; } // Default to RMT (0) for non-digital buses
virtual size_t getBusSize() const { return sizeof(Bus); }
virtual const String getCustomText() const { return String(); }

Expand Down Expand Up @@ -258,6 +259,7 @@ 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);
Expand All @@ -272,6 +274,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;
Expand Down Expand Up @@ -421,9 +424,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)
Expand All @@ -433,21 +438,24 @@ struct BusConfig {
, frequency(clock_kHz)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
, driverType(driver)
, iType(I_NONE) // Initialize to I_NONE, will be set during memory estimation
, 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:%d)\n"),
(int)start, (int)(start+len),
(int)type,
(int)colorOrder,
(int)reversed,
(int)skipAmount,
(int)autoWhite,
(int)frequency,
(int)milliAmpsPerLed, (int)milliAmpsMax
(int)milliAmpsPerLed, (int)milliAmpsMax,
(int)driverType
);
}

Expand Down Expand Up @@ -504,6 +512,8 @@ namespace BusManager {

void useParallelOutput(); // workaround for inaccessible PolyBus
bool hasParallelOutput(); // workaround for inaccessible PolyBus
void useI2SOutput(bool enable); // set I2S/LCD usage flag
bool hasI2SOutput(); // check I2S/LCD usage flag

//do not call this method from system context (network callback)
void removeAll();
Expand Down
83 changes: 48 additions & 35 deletions wled00/bus_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,14 @@
//handles pointer type conversion for all possible bus types
class PolyBus {
private:
static bool _useParallelI2S;
static bool _useParallelI2S; // use parallel I2S/LCD (8 channels)
static bool _useI2S; // use I2S/LCD at all (could be parallel or single)

public:
static inline void setParallelI2S1Output(bool b = true) { _useParallelI2S = b; }
static inline bool isParallelI2S1Output(void) { return _useParallelI2S; }
static inline void setI2SOutput(bool b = true) { _useI2S = b; }
static inline bool isI2SOutput(void) { return _useI2S; }

// initialize SPI bus speed for DotStar methods
template <class T>
Expand Down Expand Up @@ -481,16 +484,11 @@ class PolyBus {

#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
// I2S/LCD 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

void* busPtr = nullptr;
switch (busType) {
case I_NONE: break;
Expand Down Expand Up @@ -1283,8 +1281,19 @@ class PolyBus {
return size;
}

// Channel tracking for dynamic allocation
static uint8_t _rmtChannelsUsed;
static uint8_t _i2sChannelsUsed;

// Reset channel tracking (call before adding buses)
static void resetChannelTracking() {
_rmtChannelsUsed = 0;
_i2sChannelsUsed = 0;
}

//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) {
// driverPreference: 0=RMT (default), 1=I2S/LCD
static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t num = 0, uint8_t driverPreference = 0) {
if (!Bus::isDigital(busType)) return I_NONE;
if (Bus::is2Pin(busType)) { //SPI LED chips
bool isHSPI = false;
Expand Down Expand Up @@ -1340,39 +1349,43 @@ class PolyBus {
return I_8266_U0_SM16825_5 + offset;
}
#else //ESP32
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive]
// Dynamic channel allocation based on driver preference
// Get platform-specific max channels
#if defined(CONFIG_IDF_TARGET_ESP32S2)
// ESP32-S2 only has 4 RMT channels
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT
// Note: conflicts with AudioReactive if enabled
} else {
if (num > 4) return I_NONE;
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
}
const uint8_t maxRMT = 4;
const uint8_t maxI2S = _useI2S ? (_useParallelI2S ? 8 : 1) : 0;
#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)
const uint8_t maxRMT = 2;
const uint8_t maxI2S = 0; // I2S not supported on C3
#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)
}
const uint8_t maxRMT = 4;
const uint8_t maxI2S = _useI2S ? (_useParallelI2S ? 8 : 0) : 0; // LCD only, no single I2S
#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
// Standard ESP32
const uint8_t maxRMT = 8;
const uint8_t maxI2S = _useI2S ? (_useParallelI2S ? 8 : 1) : 0;
#endif

// Determine which driver to use based on preference and availability
uint8_t offset = 0; // 0 = RMT, 1 = I2S/LCD

if (driverPreference == 1 && _i2sChannelsUsed < maxI2S) {
// User wants I2S and we have I2S channels available
offset = 1;
_i2sChannelsUsed++;
} else if (_rmtChannelsUsed < maxRMT) {
// Use RMT (either user wants RMT, or I2S unavailable, or fallback)
_rmtChannelsUsed++;
} else if (_i2sChannelsUsed < maxI2S) {
// RMT full, fallback to I2S if available
offset = 1;
_i2sChannelsUsed++;
} else {
if (num > 9) return I_NONE;
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
// No channels available
return I_NONE;
}
#endif

// Now determine actual bus type with the chosen offset
switch (busType) {
case TYPE_WS2812_1CH_X3:
case TYPE_WS2812_2CH_X3:
Expand Down
Loading