mirror of
https://github.com/wled/WLED.git
synced 2026-01-14 11:17:51 +00:00
Compare commits
50 Commits
copilot/fi
...
copilot/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
371aac2cf4 | ||
|
|
09600dbb2b | ||
|
|
5e7283c698 | ||
|
|
643846c9c3 | ||
|
|
16458c40f0 | ||
|
|
6c508a747d | ||
|
|
89d073d02c | ||
|
|
1988199ce0 | ||
|
|
8da802a569 | ||
|
|
0c0a4d0c7a | ||
|
|
423e1f51d3 | ||
|
|
a1487bb3ec | ||
|
|
acc32f2ef3 | ||
|
|
4f59e53f30 | ||
|
|
da0e985160 | ||
|
|
51a6868468 | ||
|
|
28db9cbbd4 | ||
|
|
4fe3e9fcdc | ||
|
|
7fb0dca286 | ||
|
|
9c7cc22022 | ||
|
|
27c04c8743 | ||
|
|
b7170bbbaa | ||
|
|
ea9d53c22a | ||
|
|
4733e4f708 | ||
|
|
04088655e7 | ||
|
|
7a8874fff9 | ||
|
|
96710630ce | ||
|
|
5589bfd371 | ||
|
|
07c4305217 | ||
|
|
efec2b4c38 | ||
|
|
8f6f24a665 | ||
|
|
54edff85c6 | ||
|
|
87fe97b5f6 | ||
|
|
6acf3ef003 | ||
|
|
ffd29de610 | ||
|
|
0e34591eea | ||
|
|
afdd0e3f4b | ||
|
|
2726a91aa6 | ||
|
|
a69e04f7ef | ||
|
|
b556da8b36 | ||
|
|
5cfb6f984b | ||
|
|
60e1698ed2 | ||
|
|
979e3fd1f7 | ||
|
|
600b9b4d8e | ||
|
|
787d8a7342 | ||
|
|
46e77ea203 | ||
|
|
fa868568af | ||
|
|
1c2cacf185 | ||
|
|
f1f067e93a | ||
|
|
8a2a7054ab |
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
@@ -1,3 +1,2 @@
|
||||
github: [Aircoookie,blazoncek,DedeHai,lost-hope,willmmiles]
|
||||
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
|
||||
thanks_dev: u/gh/netmindz
|
||||
github: [DedeHai,lost-hope,willmmiles,netmindz]
|
||||
custom: ['https://paypal.me/Aircoookie']
|
||||
|
||||
@@ -545,6 +545,7 @@ lib_deps = ${env:esp32dev.lib_deps}
|
||||
# ------------------------------------------------------------------------------
|
||||
# Hub75 examples
|
||||
# ------------------------------------------------------------------------------
|
||||
# Note: some panels may experience ghosting with default full brightness. use -D WLED_HUB75_MAX_BRIGHTNESS=239 or lower to fix it.
|
||||
|
||||
[env:esp32dev_hub75]
|
||||
board = esp32dev
|
||||
@@ -591,7 +592,7 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8M_qspi\"
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||
-DBOARD_HAS_PSRAM
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
||||
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
||||
@@ -622,7 +623,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
|
||||
${Speed_Flags.build_flags} ;; optional: -O2 -> optimize for speed instead of size
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||
-DBOARD_HAS_PSRAM
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
||||
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
||||
|
||||
@@ -20,17 +20,19 @@ float UsermodTemperature::readDallas() {
|
||||
}
|
||||
#endif
|
||||
switch(sensorFound) {
|
||||
case 0x10: // DS18S20 has 9-bit precision
|
||||
case 0x10: // DS18S20 has 9-bit precision - 1-bit fraction part
|
||||
result = (data[1] << 8) | data[0];
|
||||
retVal = float(result) * 0.5f;
|
||||
break;
|
||||
case 0x22: // DS18B20
|
||||
case 0x28: // DS1822
|
||||
case 0x22: // DS1822
|
||||
case 0x28: // DS18B20
|
||||
case 0x3B: // DS1825
|
||||
case 0x42: // DS28EA00
|
||||
result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
|
||||
if (data[1] & 0x80) result |= 0xF000; // fix negative value
|
||||
retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f);
|
||||
// 12-bit precision - 4-bit fraction part
|
||||
result = (data[1] << 8) | data[0];
|
||||
// Clear LSBs to match desired precision (9/10/11-bit) rounding towards negative infinity
|
||||
result &= 0xFFFF << (3 - (resolution & 3));
|
||||
retVal = float(result) * 0.0625f; // 2^(-4)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -69,8 +71,8 @@ bool UsermodTemperature::findSensor() {
|
||||
if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) {
|
||||
switch (deviceAddress[0]) {
|
||||
case 0x10: // DS18S20
|
||||
case 0x22: // DS18B20
|
||||
case 0x28: // DS1822
|
||||
case 0x22: // DS1822
|
||||
case 0x28: // DS18B20
|
||||
case 0x3B: // DS1825
|
||||
case 0x42: // DS28EA00
|
||||
DEBUG_PRINTLN(F("Sensor found."));
|
||||
@@ -277,6 +279,7 @@ void UsermodTemperature::addToConfig(JsonObject &root) {
|
||||
top[FPSTR(_parasite)] = parasite;
|
||||
top[FPSTR(_parasitePin)] = parasitePin;
|
||||
top[FPSTR(_domoticzIDX)] = idx;
|
||||
top[FPSTR(_resolution)] = resolution;
|
||||
DEBUG_PRINTLN(F("Temperature config saved."));
|
||||
}
|
||||
|
||||
@@ -304,6 +307,7 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) {
|
||||
parasite = top[FPSTR(_parasite)] | parasite;
|
||||
parasitePin = top[FPSTR(_parasitePin)] | parasitePin;
|
||||
idx = top[FPSTR(_domoticzIDX)] | idx;
|
||||
resolution = top[FPSTR(_resolution)] | resolution;
|
||||
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
@@ -324,7 +328,7 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) {
|
||||
}
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_domoticzIDX)].isNull();
|
||||
return !top[FPSTR(_resolution)].isNull();
|
||||
}
|
||||
|
||||
void UsermodTemperature::appendConfigData() {
|
||||
@@ -332,6 +336,14 @@ void UsermodTemperature::appendConfigData() {
|
||||
oappend(F("',1,'<i>(if no Vcc connected)</i>');")); // 0 is field type, 1 is actual field
|
||||
oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasitePin)).c_str());
|
||||
oappend(F("',1,'<i>(for external MOSFET)</i>');")); // 0 is field type, 1 is actual field
|
||||
oappend(F("dd=addDD('")); oappend(String(FPSTR(_name)).c_str());
|
||||
oappend(F("','")); oappend(String(FPSTR(_resolution)).c_str()); oappend(F("');"));
|
||||
oappend(F("addO(dd,'0.5 °C (9-bit)',0);"));
|
||||
oappend(F("addO(dd,'0.25°C (10-bit)',1);"));
|
||||
oappend(F("addO(dd,'0.125°C (11-bit)',2);"));
|
||||
oappend(F("addO(dd,'0.0625°C (12-bit)',3);"));
|
||||
oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_resolution)).c_str());
|
||||
oappend(F("',1,'<i>(ignored on DS18S20)</i>');")); // 0 is field type, 1 is actual field
|
||||
}
|
||||
|
||||
float UsermodTemperature::getTemperature() {
|
||||
@@ -351,6 +363,7 @@ const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
|
||||
const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr";
|
||||
const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin";
|
||||
const char UsermodTemperature::_domoticzIDX[] PROGMEM = "domoticz-idx";
|
||||
const char UsermodTemperature::_resolution[] PROGMEM = "resolution";
|
||||
const char UsermodTemperature::_sensor[] PROGMEM = "sensor";
|
||||
const char UsermodTemperature::_temperature[] PROGMEM = "temperature";
|
||||
const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature";
|
||||
|
||||
@@ -48,6 +48,7 @@ class UsermodTemperature : public Usermod {
|
||||
|
||||
bool HApublished = false;
|
||||
int16_t idx = -1; // Domoticz virtual sensor idx
|
||||
uint8_t resolution = 0; // 9bits=0, 10bits=1, 11bits=2, 12bits=3
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
@@ -56,6 +57,7 @@ class UsermodTemperature : public Usermod {
|
||||
static const char _parasite[];
|
||||
static const char _parasitePin[];
|
||||
static const char _domoticzIDX[];
|
||||
static const char _resolution[];
|
||||
static const char _sensor[];
|
||||
static const char _temperature[];
|
||||
static const char _Temperature[];
|
||||
|
||||
@@ -284,7 +284,6 @@ void HttpPullLightControl::handleResponse(String& responseStr) {
|
||||
if (!requestJSONBufferLock(myLockId)) {
|
||||
DEBUG_PRINT(F("ERROR: Can not request JSON Buffer Lock, number: "));
|
||||
DEBUG_PRINTLN(myLockId);
|
||||
releaseJSONBufferLock(); // Just release in any case, maybe there was already a buffer lock
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -401,19 +401,19 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() {
|
||||
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
|
||||
|
||||
DEBUG_PRINT(F("Sorting palettes: ")); DEBUG_PRINT(getPaletteCount()); DEBUG_PRINT('/'); DEBUG_PRINTLN(customPalettes.size());
|
||||
palettes_qstrings = re_findModeStrings(JSON_palette_names, getPaletteCount() + 1); // +1 for default palette
|
||||
palettes_alpha_indexes = re_initIndexArray(getPaletteCount());
|
||||
palettes_qstrings = re_findModeStrings(JSON_palette_names, getPaletteCount()); // allocates memory for all palette names
|
||||
palettes_alpha_indexes = re_initIndexArray(getPaletteCount()); // allocates memory for all palette indexes
|
||||
if (customPalettes.size()) {
|
||||
for (int i=0; i<customPalettes.size(); i++) {
|
||||
palettes_alpha_indexes[getPaletteCount()-customPalettes.size()+i] = 255-i;
|
||||
palettes_qstrings[getPaletteCount()-customPalettes.size()+i] = PSTR("~Custom~");
|
||||
palettes_alpha_indexes[FIXED_PALETTE_COUNT+i] = 255-i;
|
||||
palettes_qstrings[FIXED_PALETTE_COUNT+i] = PSTR("~Custom~");
|
||||
}
|
||||
}
|
||||
// How many palette names start with '*' and should not be sorted?
|
||||
// (Also skipping the first one, 'Default').
|
||||
int skipPaletteCount = 1;
|
||||
while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') skipPaletteCount++;
|
||||
re_sortModes(palettes_qstrings, palettes_alpha_indexes, getPaletteCount()-customPalettes.size(), skipPaletteCount);
|
||||
int skipPaletteCount = 1; // could use DYNAMIC_PALETTE_COUNT instead
|
||||
while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') skipPaletteCount++; // legacy code
|
||||
re_sortModes(palettes_qstrings, palettes_alpha_indexes, FIXED_PALETTE_COUNT, skipPaletteCount); // only sort fixed palettes (skip dynamic)
|
||||
}
|
||||
|
||||
byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
|
||||
|
||||
@@ -230,7 +230,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
|
||||
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
|
||||
// instructed in color_from_palette()
|
||||
if (pal > FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
|
||||
if (pal >= FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
|
||||
//default palette. Differs depending on effect
|
||||
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
|
||||
switch (pal) {
|
||||
@@ -268,11 +268,11 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
default: //progmem palettes
|
||||
if (pal > 255 - customPalettes.size()) {
|
||||
targetPalette = customPalettes[255-pal]; // we checked bounds above
|
||||
} else if (pal < DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT+1) { // palette 6 - 12, fastled palettes
|
||||
targetPalette = *fastledPalettes[pal-DYNAMIC_PALETTE_COUNT-1];
|
||||
} else if (pal < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) { // palette 6 - 12, fastled palettes
|
||||
targetPalette = *fastledPalettes[pal - DYNAMIC_PALETTE_COUNT];
|
||||
} else {
|
||||
byte tcp[72];
|
||||
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-(DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT)-1])), 72);
|
||||
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp));
|
||||
targetPalette.loadDynamicGradientPalette(tcp);
|
||||
}
|
||||
break;
|
||||
@@ -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;
|
||||
}
|
||||
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
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -156,7 +156,7 @@ void ParticleSystem2D::setParticleSize(uint8_t size) {
|
||||
particleHardRadius = PS_P_MINHARDRADIUS + ((particlesize * 52) >> 6); // use 1 pixel + 80% of size for hard radius (slight overlap with boarders so they do not "float" and nicer stacking)
|
||||
}
|
||||
else if (particlesize == 0)
|
||||
particleHardRadius = particleHardRadius >> 1; // single pixel particles have half the radius (i.e. 1/2 pixel)
|
||||
particleHardRadius = PS_P_MINHARDRADIUS >> 1; // single pixel particles have half the radius (i.e. 1/2 pixel)
|
||||
}
|
||||
|
||||
// enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 0 is disable
|
||||
@@ -595,7 +595,7 @@ void ParticleSystem2D::render() {
|
||||
if (fireIntesity) { // fire mode
|
||||
brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 5;
|
||||
brightness = min(brightness, (uint32_t)255);
|
||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP);
|
||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP); // map hue to brightness for fire effect
|
||||
}
|
||||
else {
|
||||
brightness = min((particles[i].ttl << 1), (int)255);
|
||||
@@ -842,7 +842,7 @@ void ParticleSystem2D::handleCollisions() {
|
||||
for (uint32_t bin = 0; bin < numBins; bin++) {
|
||||
binParticleCount = 0; // reset for this bin
|
||||
int32_t binStart = bin * binWidth - overlap; // note: first bin will extend to negative, but that is ok as out of bounds particles are ignored
|
||||
int32_t binEnd = binStart + binWidth + overlap; // note: last bin can be out of bounds, see above;
|
||||
int32_t binEnd = binStart + binWidth + (overlap << 1); // add twice the overlap as start is start-overlap, note: last bin can be out of bounds, see above;
|
||||
|
||||
// fill the binIndices array for this bin
|
||||
for (uint32_t i = 0; i < usedParticles; i++) {
|
||||
@@ -879,7 +879,7 @@ void ParticleSystem2D::handleCollisions() {
|
||||
massratio1 = (mass2 << 8) / totalmass; // massratio 1 depends on mass of particle 2, i.e. if 2 is heavier -> higher velocity impact on 1
|
||||
massratio2 = (mass1 << 8) / totalmass;
|
||||
}
|
||||
// note: using the same logic as in 1D is much slower though it would be more accurate but it is not really needed in 2D
|
||||
// note: using the same logic as in 1D is much slower though it would be more accurate but it is not really needed in 2D: particles slipping through each other is much less visible
|
||||
int32_t dx = (particles[idx_j].x + particles[idx_j].vx) - (particles[idx_i].x + particles[idx_i].vx); // distance with lookahead
|
||||
if (dx * dx < collDistSq) { // check x direction, if close, check y direction (squaring is faster than abs() or dual compare)
|
||||
int32_t dy = (particles[idx_j].y + particles[idx_j].vy) - (particles[idx_i].y + particles[idx_i].vy); // distance with lookahead
|
||||
@@ -1247,7 +1247,7 @@ void ParticleSystem1D::setParticleSize(const uint8_t size) {
|
||||
particleHardRadius = PS_P_MINHARDRADIUS_1D + ((particlesize * 52) >> 6); // use 1 pixel + 80% of size for hard radius (slight overlap with boarders so they do not "float" and nicer stacking)
|
||||
}
|
||||
else if (particlesize == 0)
|
||||
particleHardRadius = particleHardRadius >> 1; // single pixel particles have half the radius (i.e. 1/2 pixel)
|
||||
particleHardRadius = PS_P_MINHARDRADIUS_1D >> 1; // single pixel particles have half the radius (i.e. 1/2 pixel)
|
||||
}
|
||||
|
||||
// enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 0 is disable
|
||||
@@ -1632,7 +1632,7 @@ void ParticleSystem1D::handleCollisions() {
|
||||
for (uint32_t bin = 0; bin < numBins; bin++) {
|
||||
binParticleCount = 0; // reset for this bin
|
||||
int32_t binStart = bin * binWidth - overlap; // note: first bin will extend to negative, but that is ok as out of bounds particles are ignored
|
||||
int32_t binEnd = binStart + binWidth + overlap; // note: last bin can be out of bounds, see above
|
||||
int32_t binEnd = binStart + binWidth + (overlap << 1); // add twice the overlap as start is start-overlap, note: last bin can be out of bounds, see above
|
||||
|
||||
// fill the binIndices array for this bin
|
||||
for (uint32_t i = 0; i < usedParticles; i++) {
|
||||
|
||||
@@ -21,30 +21,28 @@
|
||||
#ifdef ESP8266
|
||||
#include "core_esp8266_waveform.h"
|
||||
#endif
|
||||
#include "const.h"
|
||||
#include "colors.h"
|
||||
#include "pin_manager.h"
|
||||
#include "bus_manager.h"
|
||||
#include "bus_wrapper.h"
|
||||
#include <bits/unique_ptr.h>
|
||||
#include "wled.h"
|
||||
|
||||
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
|
||||
// note: these functions are automatically inline by the compiler
|
||||
bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value
|
||||
size_t byteIndex = position / 8;
|
||||
unsigned bitIndex = position % 8;
|
||||
size_t byteIndex = position >> 3; // divide by 8
|
||||
unsigned bitIndex = position & 0x07; // modulo 8
|
||||
uint8_t byteValue = byteArray[byteIndex];
|
||||
return (byteValue >> bitIndex) & 1;
|
||||
}
|
||||
|
||||
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
|
||||
//if (byteArray == nullptr) return;
|
||||
size_t byteIndex = position / 8;
|
||||
unsigned bitIndex = position % 8;
|
||||
size_t byteIndex = position >> 3; // divide by 8
|
||||
unsigned bitIndex = position & 0x07; // modulo 8
|
||||
if (value)
|
||||
byteArray[byteIndex] |= (1 << bitIndex);
|
||||
else
|
||||
@@ -52,7 +50,7 @@ void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bi
|
||||
}
|
||||
|
||||
size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits
|
||||
return (num_bits + 7) / 8;
|
||||
return (num_bits + 7) >> 3;
|
||||
}
|
||||
|
||||
void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value
|
||||
@@ -62,45 +60,6 @@ void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all b
|
||||
else memset(byteArray, 0x00, len);
|
||||
}
|
||||
|
||||
//colors.cpp
|
||||
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
||||
|
||||
//udp.cpp
|
||||
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false);
|
||||
|
||||
//util.cpp
|
||||
// memory allocation wrappers
|
||||
extern "C" {
|
||||
// prefer DRAM over PSRAM (if available) in d_ alloc functions
|
||||
void *d_malloc(size_t);
|
||||
void *d_calloc(size_t, size_t);
|
||||
void *d_realloc_malloc(void *ptr, size_t size);
|
||||
#ifndef ESP8266
|
||||
inline void d_free(void *ptr) { heap_caps_free(ptr); }
|
||||
#else
|
||||
inline void d_free(void *ptr) { free(ptr); }
|
||||
#endif
|
||||
#if defined(BOARD_HAS_PSRAM)
|
||||
// prefer PSRAM over DRAM in p_ alloc functions
|
||||
void *p_malloc(size_t);
|
||||
void *p_calloc(size_t, size_t);
|
||||
void *p_realloc_malloc(void *ptr, size_t size);
|
||||
inline void p_free(void *ptr) { heap_caps_free(ptr); }
|
||||
#else
|
||||
#define p_malloc d_malloc
|
||||
#define p_calloc d_calloc
|
||||
#define p_free d_free
|
||||
#endif
|
||||
}
|
||||
|
||||
//color mangling macros
|
||||
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
|
||||
#define R(c) (byte((c) >> 16))
|
||||
#define G(c) (byte((c) >> 8))
|
||||
#define B(c) (byte(c))
|
||||
#define W(c) (byte((c) >> 24))
|
||||
|
||||
|
||||
static ColorOrderMap _colorOrderMap = {};
|
||||
|
||||
bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
|
||||
@@ -164,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; }
|
||||
@@ -180,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);
|
||||
@@ -800,6 +762,13 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
_valid = false;
|
||||
_hasRgb = true;
|
||||
_hasWhite = false;
|
||||
virtualDisp = nullptr; // todo: this should be solved properly, can cause memory leak (if omitted here, nothing seems to work)
|
||||
// aliases for easier reading
|
||||
uint8_t panelWidth = bc.pins[0];
|
||||
uint8_t panelHeight = bc.pins[1];
|
||||
uint8_t chainLength = bc.pins[2];
|
||||
_rows = bc.pins[3];
|
||||
_cols = bc.pins[4];
|
||||
|
||||
mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer
|
||||
|
||||
@@ -808,38 +777,25 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
|
||||
// mxconfig.latch_blanking = 3;
|
||||
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz
|
||||
//mxconfig.min_refresh_rate = 90;
|
||||
//mxconfig.min_refresh_rate = 120;
|
||||
mxconfig.clkphase = bc.reversed;
|
||||
// mxconfig.min_refresh_rate = 90;
|
||||
// mxconfig.min_refresh_rate = 120;
|
||||
|
||||
virtualDisp = nullptr;
|
||||
mxconfig.clkphase = bc.reversed;
|
||||
// allow chain length up to 4, limit to prevent bad data from preventing boot due to low memory
|
||||
mxconfig.chain_length = max((uint8_t) 1, min(chainLength, (uint8_t) 4));
|
||||
|
||||
if (mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) {
|
||||
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
|
||||
mxconfig.chain_length = 1;
|
||||
}
|
||||
|
||||
if (bc.type == TYPE_HUB75MATRIX_HS) {
|
||||
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]);
|
||||
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]);
|
||||
// Disable chains of panels for now, incomplete UI changes
|
||||
// if(bc.pins[2] > 1 && bc.pins[3] != 0 && bc.pins[4] != 0 && bc.pins[3] != 255 && bc.pins[4] != 255) {
|
||||
// virtualDisp = new VirtualMatrixPanel((*display), bc.pins[3], bc.pins[4], mxconfig.mx_width, mxconfig.mx_height, CHAIN_BOTTOM_LEFT_UP);
|
||||
// }
|
||||
mxconfig.mx_width = min((uint8_t) 64, panelWidth); // TODO: UI limit is 128, this limits to 64
|
||||
mxconfig.mx_height = min((uint8_t) 64, panelHeight);
|
||||
} else if (bc.type == TYPE_HUB75MATRIX_QS) {
|
||||
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]) * 2;
|
||||
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]) / 2;
|
||||
virtualDisp = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]);
|
||||
virtualDisp->setRotation(0);
|
||||
switch(bc.pins[1]) {
|
||||
case 16:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH);
|
||||
break;
|
||||
case 32:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
|
||||
break;
|
||||
case 64:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH);
|
||||
break;
|
||||
default:
|
||||
DEBUGBUS_PRINTLN("Unsupported height");
|
||||
return;
|
||||
}
|
||||
_isVirtual = true;
|
||||
mxconfig.mx_width = min((uint8_t) 64, panelWidth) * 2;
|
||||
mxconfig.mx_height = min((uint8_t) 64, panelHeight) / 2;
|
||||
} else {
|
||||
DEBUGBUS_PRINTLN("Unknown type");
|
||||
return;
|
||||
@@ -853,12 +809,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
} else mxconfig.setPixelColorDepthBits(8);
|
||||
#endif
|
||||
|
||||
mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[2], (uint8_t) 4)); // prevent bad data preventing boot due to low memory
|
||||
|
||||
if(mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) {
|
||||
DEBUGBUS_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory");
|
||||
mxconfig.chain_length = 1;
|
||||
}
|
||||
|
||||
|
||||
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
|
||||
@@ -915,9 +865,9 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
return;
|
||||
}
|
||||
|
||||
if(bc.colorOrder == COL_ORDER_RGB) {
|
||||
if (bc.colorOrder == COL_ORDER_RGB) {
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = Default color order (RGB)");
|
||||
} else if(bc.colorOrder == COL_ORDER_BGR) {
|
||||
} else if (bc.colorOrder == COL_ORDER_BGR) {
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = color order BGR");
|
||||
int8_t tmpPin;
|
||||
tmpPin = mxconfig.gpio.r1;
|
||||
@@ -944,21 +894,27 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
return;
|
||||
}
|
||||
|
||||
this->_len = (display->width() * display->height());
|
||||
this->_len = (display->width() * display->height()); // note: this returns correct number of pixels but incorrect dimensions if using virtual display (updated below)
|
||||
|
||||
DEBUGBUS_PRINTF("Length: %u\n", _len);
|
||||
if(this->_len >= MAX_LEDS) {
|
||||
if (this->_len >= MAX_LEDS) {
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA Too many LEDS - playing safe");
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA created");
|
||||
// let's adjust default brightness
|
||||
display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100%
|
||||
|
||||
// as noted in HUB75_I2S_DMA library, some panels can show ghosting if set higher than 239, so let users override at compile time
|
||||
#ifndef WLED_HUB75_MAX_BRIGHTNESS
|
||||
#define WLED_HUB75_MAX_BRIGHTNESS 255
|
||||
#endif
|
||||
// let's adjust default brightness (128), brightness scaling is handled by WLED
|
||||
//display->setBrightness8(WLED_HUB75_MAX_BRIGHTNESS); // range is 0-255, 0 - 0%, 255 - 100%
|
||||
|
||||
delay(24); // experimental
|
||||
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
||||
// Allocate memory and start DMA display
|
||||
if( not display->begin() ) {
|
||||
if (!display->begin() ) {
|
||||
DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********");
|
||||
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
||||
return;
|
||||
@@ -971,10 +927,10 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
display->clearScreen(); // initially clear the screen buffer
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok");
|
||||
|
||||
if (_ledBuffer) free(_ledBuffer); // should not happen
|
||||
if (_ledsDirty) free(_ledsDirty); // should not happen
|
||||
if (_ledBuffer) d_free(_ledBuffer); // should not happen
|
||||
if (_ledsDirty) d_free(_ledsDirty); // should not happen
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory");
|
||||
_ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits
|
||||
_ledsDirty = (byte*) d_malloc(getBitArrayBytes(_len)); // create LEDs dirty bits
|
||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok");
|
||||
|
||||
if (_ledsDirty == nullptr) {
|
||||
@@ -988,17 +944,50 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
setBitArray(_ledsDirty, _len, false); // reset dirty bits
|
||||
|
||||
if (mxconfig.double_buff == false) {
|
||||
_ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK)
|
||||
// create LEDs buffer (initialized to BLACK), prefer DRAM if enough heap is available (faster in case global _pixels buffer is in PSRAM as not both will fit the cache)
|
||||
_ledBuffer = static_cast<CRGB*>(allocate_buffer(_len * sizeof(CRGB), BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR));
|
||||
}
|
||||
}
|
||||
|
||||
PANEL_CHAIN_TYPE chainType = CHAIN_NONE; // default for quarter-scan panels that do not use chaining
|
||||
// chained panels with cols and rows define need the virtual display driver, so do quarter-scan panels
|
||||
if (chainLength > 1 && (_rows > 1 || _cols > 1) || bc.type == TYPE_HUB75MATRIX_QS) {
|
||||
_isVirtual = true;
|
||||
chainType = CHAIN_BOTTOM_LEFT_UP; // TODO: is there any need to support other chaining types?
|
||||
DEBUGBUS_PRINTF_P(PSTR("Using virtual matrix: %ux%u panels of %ux%u pixels\n"), _cols, _rows, mxconfig.mx_width, mxconfig.mx_height);
|
||||
}
|
||||
else {
|
||||
_isVirtual = false;
|
||||
}
|
||||
|
||||
if (_isVirtual) {
|
||||
virtualDisp = new VirtualMatrixPanel((*display), _rows, _cols, mxconfig.mx_width, mxconfig.mx_height, chainType);
|
||||
virtualDisp->setRotation(0);
|
||||
if (bc.type == TYPE_HUB75MATRIX_QS) {
|
||||
switch(panelHeight) {
|
||||
case 16:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH);
|
||||
break;
|
||||
case 32:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
|
||||
break;
|
||||
case 64:
|
||||
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH);
|
||||
break;
|
||||
default:
|
||||
DEBUGBUS_PRINTLN("Unsupported height");
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_valid) {
|
||||
_panelWidth = virtualDisp ? virtualDisp->width() : display->width(); // cache width - it will never change
|
||||
}
|
||||
|
||||
DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA "));
|
||||
DEBUGBUS_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len);
|
||||
DEBUGBUS_PRINTF_P(PSTR("%sstarted, width=%u, %u pixels.\n"), _valid? "":"not ", _panelWidth, _len);
|
||||
|
||||
if (_ledBuffer != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled."));
|
||||
if (_ledsDirty != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled."));
|
||||
@@ -1009,8 +998,8 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
}
|
||||
}
|
||||
|
||||
void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
|
||||
if (!_valid || pix >= _len) return;
|
||||
void IRAM_ATTR BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
|
||||
if (!_valid) return; // note: no need to check pix >= _len as that is checked in containsPixel()
|
||||
// if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
|
||||
|
||||
if (_ledBuffer) {
|
||||
@@ -1028,8 +1017,8 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c
|
||||
uint8_t g = G(c);
|
||||
uint8_t b = B(c);
|
||||
|
||||
if(virtualDisp != nullptr) {
|
||||
int x = pix % _panelWidth;
|
||||
if (virtualDisp != nullptr) {
|
||||
int x = pix % _panelWidth; // TODO: check if using & and shift would be faster here, it limits to power-of-2 widths though
|
||||
int y = pix / _panelWidth;
|
||||
virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
|
||||
} else {
|
||||
@@ -1041,41 +1030,35 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c
|
||||
}
|
||||
|
||||
uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
|
||||
if (!_valid || pix >= _len) return IS_BLACK;
|
||||
if (!_valid) return IS_BLACK; // note: no need to check pix >= _len as that is checked in containsPixel()
|
||||
if (_ledBuffer)
|
||||
return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours
|
||||
return uint32_t(_ledBuffer[pix]);
|
||||
else
|
||||
return getBitFromArray(_ledsDirty, pix) ? IS_DARKGREY: IS_BLACK; // just a hack - we only know if the pixel is black or not
|
||||
}
|
||||
|
||||
void BusHub75Matrix::setBrightness(uint8_t b) {
|
||||
_bri = b;
|
||||
if (display) display->setBrightness(_bri);
|
||||
display->setBrightness(_bri);
|
||||
}
|
||||
|
||||
void BusHub75Matrix::show(void) {
|
||||
if (!_valid) return;
|
||||
display->setBrightness(_bri);
|
||||
|
||||
if (_ledBuffer) {
|
||||
// write out buffered LEDs
|
||||
bool isVirtualDisp = (virtualDisp != nullptr);
|
||||
unsigned height = isVirtualDisp ? virtualDisp->height() : display->height();
|
||||
unsigned height = _isVirtual ? virtualDisp->height() : display->height();
|
||||
unsigned width = _panelWidth;
|
||||
|
||||
//while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker.
|
||||
|
||||
size_t pix = 0; // running pixel index
|
||||
for (int y=0; y<height; y++) for (int x=0; x<width; x++) {
|
||||
if (getBitFromArray(_ledsDirty, pix) == true) { // only repaint the "dirty" pixels
|
||||
uint32_t c = uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; // get RGB color, removing FastLED "alpha" component
|
||||
uint8_t r = R(c);
|
||||
uint8_t g = G(c);
|
||||
uint8_t b = B(c);
|
||||
if (isVirtualDisp) virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
|
||||
else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
|
||||
CRGB c = _ledBuffer[pix];
|
||||
//c.nscale8_video(_bri); // apply brightness
|
||||
if (_isVirtual) virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), c.r, c.g, c.b);
|
||||
else display->drawPixelRGB888(int16_t(x), int16_t(y), c.r, c.g, c.b);
|
||||
}
|
||||
pix ++;
|
||||
pix++;
|
||||
}
|
||||
setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits
|
||||
}
|
||||
@@ -1084,16 +1067,18 @@ void BusHub75Matrix::show(void) {
|
||||
void BusHub75Matrix::cleanup() {
|
||||
if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black)
|
||||
_valid = false;
|
||||
delay(30); // give some time to finish DMA
|
||||
_panelWidth = 0;
|
||||
deallocatePins();
|
||||
DEBUGBUS_PRINTLN("HUB75 output ended.");
|
||||
|
||||
//if (virtualDisp != nullptr) delete virtualDisp; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior
|
||||
delete display;
|
||||
DEBUGBUS_PRINTLN(F("HUB75 output ended."));
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32S3 // on ESP32-S3 deleting display/virtualDisp does not work and leads to crash (DMA issues), request reboot from user instead
|
||||
if (virtualDisp != nullptr) delete virtualDisp; // note: in MM there is a warning to not do this but if using "NO_GFX" this is safe
|
||||
if (display != nullptr) delete display;
|
||||
display = nullptr;
|
||||
virtualDisp = nullptr;
|
||||
if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr;
|
||||
if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr;
|
||||
virtualDisp = nullptr; // note: when not using "NO_GFX" this causes a memory leak
|
||||
#endif
|
||||
if (_ledBuffer != nullptr) d_free(_ledBuffer); _ledBuffer = nullptr;
|
||||
if (_ledsDirty != nullptr) d_free(_ledsDirty); _ledsDirty = nullptr;
|
||||
}
|
||||
|
||||
void BusHub75Matrix::deallocatePins() {
|
||||
@@ -1114,8 +1099,10 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
|
||||
pinArray[0] = mxconfig.mx_width;
|
||||
pinArray[1] = mxconfig.mx_height;
|
||||
pinArray[2] = mxconfig.chain_length;
|
||||
pinArray[3] = _rows;
|
||||
pinArray[4] = _cols;
|
||||
}
|
||||
return 3;
|
||||
return 5;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1127,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 {
|
||||
@@ -1229,6 +1219,14 @@ 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."));
|
||||
@@ -1236,6 +1234,8 @@ void BusManager::removeAll() {
|
||||
while (!canAllShow()) yield();
|
||||
busses.clear();
|
||||
PolyBus::setParallelI2S1Output(false);
|
||||
// Reset channel tracking for fresh allocation
|
||||
PolyBus::resetChannelTracking();
|
||||
}
|
||||
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
@@ -1439,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;
|
||||
@@ -1447,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;
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -166,7 +167,7 @@ class Bus {
|
||||
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
|
||||
|
||||
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
|
||||
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 3 : is2Pin(type) + 1; } // credit @PaoloTK
|
||||
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 5 : is2Pin(type) + 1; } // credit @PaoloTK
|
||||
static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
|
||||
static constexpr bool hasRGB(uint8_t type) {
|
||||
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -395,12 +398,15 @@ class BusHub75Matrix : public Bus {
|
||||
VirtualMatrixPanel *virtualDisp = nullptr;
|
||||
HUB75_I2S_CFG mxconfig;
|
||||
unsigned _panelWidth = 0;
|
||||
CRGB *_ledBuffer = nullptr;
|
||||
uint8_t _rows = 1; // panels per row
|
||||
uint8_t _cols = 1; // panels per column
|
||||
bool _isVirtual = false; // note: this is not strictly needed but there are padding bytes here anyway
|
||||
CRGB *_ledBuffer = nullptr; // note: using uint32_t buffer is only 2% faster and not worth the extra RAM
|
||||
byte *_ledsDirty = nullptr;
|
||||
// workaround for missing constants on include path for non-MM
|
||||
uint32_t IS_BLACK = 0x000000;
|
||||
uint32_t IS_DARKGREY = 0x333333;
|
||||
const int PIN_COUNT = 14;
|
||||
static constexpr uint32_t IS_BLACK = 0x000000u;
|
||||
static constexpr uint32_t IS_DARKGREY = 0x333333u;
|
||||
static constexpr int PIN_COUNT = 14;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -418,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)
|
||||
@@ -430,13 +438,15 @@ 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,
|
||||
@@ -444,7 +454,8 @@ struct BusConfig {
|
||||
(int)skipAmount,
|
||||
(int)autoWhite,
|
||||
(int)frequency,
|
||||
(int)milliAmpsPerLed, (int)milliAmpsMax
|
||||
(int)milliAmpsPerLed, (int)milliAmpsMax,
|
||||
(int)driverType
|
||||
);
|
||||
}
|
||||
|
||||
@@ -501,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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
} else {
|
||||
if (num > 9) return I_NONE;
|
||||
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
|
||||
}
|
||||
// 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 {
|
||||
// No channels available
|
||||
return I_NONE;
|
||||
}
|
||||
|
||||
// Now determine actual bus type with the chosen offset
|
||||
switch (busType) {
|
||||
case TYPE_WS2812_1CH_X3:
|
||||
case TYPE_WS2812_2CH_X3:
|
||||
|
||||
@@ -166,7 +166,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
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")]);
|
||||
// useI2S no longer loaded from config - determined automatically based on bus configuration
|
||||
// CJSON(useI2S, hw_led[F("prl")]); // Removed - PR checkbox eliminated
|
||||
#endif
|
||||
|
||||
#ifndef WLED_DISABLE_2D
|
||||
@@ -234,9 +235,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
|
||||
|
||||
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
|
||||
}
|
||||
@@ -319,7 +321,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()
|
||||
}
|
||||
}
|
||||
@@ -984,6 +986,8 @@ void serializeConfig(JsonObject root) {
|
||||
ins[F("freq")] = bus->getFrequency();
|
||||
ins[F("maxpwr")] = bus->getMaxCurrent();
|
||||
ins[F("ledma")] = bus->getLEDCurrent();
|
||||
// Save driver preference directly from bus object (busConfigs is cleared after bus creation)
|
||||
ins[F("drv")] = bus->getDriverType();
|
||||
ins[F("text")] = bus->getCustomText();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //121
|
||||
* if using "video" method the resulting color will never become black unless it is already black
|
||||
*/
|
||||
uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
|
||||
if (c1 == 0 || amount == 0) return 0; // black or no change
|
||||
if (amount == 255) return c1;
|
||||
if (c1 == BLACK || amount == 0) return 0; // black or full fade
|
||||
if (amount == 255) return c1; // no change
|
||||
uint32_t addRemains = 0;
|
||||
|
||||
if (!video) amount++; // add one for correct scaling using bitshifts
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* Readability defines and their associated numerical values + compile-time constants
|
||||
*/
|
||||
|
||||
constexpr size_t FASTLED_PALETTE_COUNT = 7; // = sizeof(fastledPalettes) / sizeof(fastledPalettes[0]);
|
||||
constexpr size_t GRADIENT_PALETTE_COUNT = 59; // = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]);
|
||||
constexpr size_t DYNAMIC_PALETTE_COUNT = 5; // 1-5 are dynamic palettes (1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black)
|
||||
constexpr size_t FASTLED_PALETTE_COUNT = 7; // 6-12 = sizeof(fastledPalettes) / sizeof(fastledPalettes[0]);
|
||||
constexpr size_t GRADIENT_PALETTE_COUNT = 59; // 13-72 = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]);
|
||||
constexpr size_t DYNAMIC_PALETTE_COUNT = 6; // 0- 5 = dynamic palettes (0=default(virtual),1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black)
|
||||
constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes
|
||||
#ifndef ESP8266
|
||||
#define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10
|
||||
@@ -63,21 +63,22 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
||||
#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_DIGITAL_CHANNELS 2 // x2 RMT only (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
|
||||
#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
|
||||
// I2S is only used when explicitly enabled by user (Enable I2S checkbox)
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT (default), or x4 RMT + x8 I2S0 (when I2S enabled)
|
||||
//#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
|
||||
#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
|
||||
// LCD driver is only used when explicitly enabled by user (Enable I2S checkbox)
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT (default), or x4 RMT + x8 LCD (when I2S enabled)
|
||||
//#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
|
||||
#else
|
||||
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
|
||||
// RMT is used by default; I2S is only used when explicitly enabled by user (Enable I2S checkbox)
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 16 // x8 RMT (default), or x8 RMT + x8 I2S1 (when I2S enabled)
|
||||
//#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
|
||||
#endif
|
||||
|
||||
@@ -397,11 +397,19 @@ button, .btn {
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="ed active">
|
||||
<div>
|
||||
<h3>Video Lab</h3>
|
||||
<div><small>Stream video and generate animated GIFs (beta)</small></div>
|
||||
<button class="btn" id="t2" style="display:none"></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="ed active">
|
||||
<div>
|
||||
<h3>PIXEL MAGIC Tool</h3>
|
||||
<div><small>Legacy pixel art editor</small></div>
|
||||
<button class="btn" id="t2" style="display:none"></button>
|
||||
<button class="btn" id="t3" style="display:none"></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@@ -439,7 +447,8 @@ let fL; // file list
|
||||
await segLoad(); // load available segments
|
||||
await flU(); // update file list
|
||||
toolChk('pixelpaint.htm','t1'); // update buttons of additional tools
|
||||
toolChk('pxmagic.htm','t2');
|
||||
toolChk('videolab.htm','t2');
|
||||
toolChk('pxmagic.htm','t3');
|
||||
await fsMem(); // show file system memory info
|
||||
})();
|
||||
|
||||
|
||||
@@ -49,10 +49,11 @@
|
||||
d.Sf.addEventListener("submit", trySubmit);
|
||||
if (d.um_p[0]==-1) d.um_p.shift();
|
||||
pinDropdowns();
|
||||
fixLegacyDriverConfig(); // Handle legacy configs without driver selection
|
||||
}); // 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) {
|
||||
function bLimits(b,v,p,m,l,o=5,d=2,a=6,n=4,rmt=0,i2s=0) {
|
||||
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
|
||||
@@ -62,6 +63,8 @@
|
||||
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
|
||||
maxRMT = rmt; // maxRMT - max RMT channels: 8 - ESP32, 4 - S2/S3, 2 - C3, 0 - 8266
|
||||
maxI2S = i2s; // maxI2S - max I2S/LCD channels: 8 - ESP32/S2/S3, 0 - C3/8266
|
||||
}
|
||||
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
|
||||
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
|
||||
@@ -122,6 +125,20 @@
|
||||
d.Sf.data.value = '';
|
||||
e.preventDefault();
|
||||
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
|
||||
// validate HUB75 panel config
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
for (let i=0; i<LTs.length; i++) {
|
||||
let n = chrID(i);
|
||||
let t = parseInt(LTs[i].value);
|
||||
if (isHub75(t)) {
|
||||
let p = parseInt(d.Sf["L2"+n].value)||1, r = parseInt(d.Sf["L3"+n].value)||1, c = parseInt(d.Sf["L4"+n].value)||1, h = parseInt(d.Sf["L1"+n].value)||1;
|
||||
if (r*c !== p) {alert(`HUB75 error: panels≠rows×cols`); e.stopPropagation(); return false;}
|
||||
if (h >= 64 && p > 1) {alert(`HUB75 error: height >= 64, only single panel allowed`); e.stopPropagation(); return false;}
|
||||
if(isS3()) {
|
||||
alert("HUB75 changes require a reboot"); // TODO: only throw this if panel config changed?
|
||||
}
|
||||
}
|
||||
};
|
||||
if (bquot > 200) {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.";
|
||||
@@ -219,13 +236,13 @@
|
||||
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)
|
||||
let enabledI2S = (is32() || isS2() || isS3()) && !isD2P(t); // I2S always available (not 2-pin LED)
|
||||
if (isC3() || !enabledI2S) {
|
||||
mul = 2; // RMT uses double buffer
|
||||
} else if (enabledI2S && toNum(n) < 8) { // I2S/LCD uses extra DMA buffer
|
||||
dbl = len * ch * 3; // DMA buffer for I2S/LCD (TODO: only the bus with largest LED count should be used)
|
||||
// Parallel I2S uses 8 channels, requiring 8x the DMA buffer size
|
||||
if (d.Sf.PI && d.Sf.PI.checked) dbl *= 8;
|
||||
}
|
||||
}
|
||||
return len * ch * mul + dbl + pbfr;
|
||||
@@ -256,17 +273,19 @@
|
||||
p0d = "IP address:";
|
||||
break;
|
||||
case 'V': // virtual/non-GPIO based
|
||||
p0d = "Config:"
|
||||
p0d = "Config:";
|
||||
break;
|
||||
case 'H': // HUB75
|
||||
p0d = "Panel size (width x height), Panel count:"
|
||||
p0d = "Panel (width x height):";
|
||||
gId("p2d"+n).innerHTML = "<br>No. of Panels:";
|
||||
gId("p3d"+n).innerText = "rows x cols:";
|
||||
break;
|
||||
}
|
||||
gId("p0d"+n).innerText = p0d;
|
||||
gId("p1d"+n).innerText = p1d;
|
||||
gId("off"+n).innerText = off;
|
||||
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
|
||||
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4
|
||||
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 4*isHub75(t); // fixes network pins to 4
|
||||
for (let p=1; p<5; p++) {
|
||||
var LK = d.Sf["L"+p+n];
|
||||
if (!LK) continue;
|
||||
@@ -278,14 +297,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?
|
||||
@@ -324,9 +341,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") {
|
||||
@@ -376,10 +392,11 @@
|
||||
LC.style.color="#fff";
|
||||
return; // do not check conflicts
|
||||
}
|
||||
else if (isHub75(t) && nm=="L2") {
|
||||
// Chain length aka Panel Count
|
||||
else if (isHub75(t) && (nm=="L2" || nm=="L3" || nm=="L4")) {
|
||||
// chain length aka panel count (L2), cols(L3), rows(L4)
|
||||
LC.max = 4;
|
||||
LC.min = 1;
|
||||
if (LC.value === "") LC.value = 1; // default to 1
|
||||
LC.style.color="#fff";
|
||||
return; // do not check conflicts
|
||||
}
|
||||
@@ -404,12 +421,126 @@
|
||||
} 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;
|
||||
// Always show show I2S checkbox on ESP32/S2/S3
|
||||
gId("prl").classList.remove("hide");
|
||||
} else {
|
||||
d.Sf["PR"].checked = false;
|
||||
}
|
||||
// Calculate max single-pin digital bus count based on configuration:
|
||||
// - If I2S is enabled and ≤600 LEDs/bus: parallel I2S (8 I2S + RMT)
|
||||
// - If I2S is enabled but >600 LEDs/bus: single I2S (RMT + 1 I2S)
|
||||
// - If I2S is disabled: RMT only
|
||||
let maxDigitalBusCount;
|
||||
if (d.Sf["PR"].checked) {
|
||||
if (maxLC <= 600) {
|
||||
// Parallel I2S mode possible
|
||||
maxDigitalBusCount = maxD; // ESP32: 16, S2: 12, S3: 12
|
||||
} else {
|
||||
// Single I2S mode
|
||||
// Uses maxD-7 to match bus_wrapper.h logic (ESP32: 16-7=9, S2: 12-7=5)
|
||||
if (isS3()) maxDigitalBusCount = 4; // S3 doesn't support single I2S, only RMT
|
||||
else maxDigitalBusCount = maxD - 7;
|
||||
}
|
||||
} else {
|
||||
// RMT only
|
||||
maxDigitalBusCount = (isC3() ? 2 : (isS2() || isS3()) ? 4 : is32() ? 8 : maxD);
|
||||
}
|
||||
|
||||
// Mark invalid buses and update driver info + track channel usage
|
||||
let invalidBusCount = 0;
|
||||
let digitalBusIndex = 0;
|
||||
let maxRMT = (is32() ? 8 : (isS2() || isS3()) ? 4 : isC3() ? 2 : 0);
|
||||
let maxI2S = (is32() ? 8 : (isS2() || isS3()) ? 8 : isS3() ? 8 : 0);
|
||||
|
||||
// Use helper function to calculate channel usage
|
||||
let usage = calculateChannelUsage();
|
||||
let rmtUsed = usage.rmtUsed;
|
||||
let i2sUsed = usage.i2sUsed;
|
||||
let firstI2SType = usage.firstI2SType;
|
||||
let maxLEDsOnI2SBus = usage.maxLEDsOnI2SBus;
|
||||
let i2sBusesOver600 = usage.i2sBusesOver600;
|
||||
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
|
||||
let n = s.name.substring(2,3);
|
||||
let t = parseInt(s.value);
|
||||
let drv = gId("drv"+n); // bus driver info (I2S/RMT)
|
||||
let drvsel = gId("drvsel"+n); // driver selection dropdown
|
||||
if (drv) drv.textContent = ""; // reset
|
||||
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)) {
|
||||
digitalBusIndex++;
|
||||
let driverPref = d.Sf["LD"+n] ? parseInt(d.Sf["LD"+n].value || 0) : 0;
|
||||
let ledCount = parseInt(d.Sf["LC"+n].value) || 0;
|
||||
|
||||
// Update I2S/RMT driver info/dropdown for ESP32 digital buses
|
||||
if (!is8266()) {
|
||||
let useI2S = (is32() || isS2() || isS3()); // I2S always available on ESP32 variants
|
||||
|
||||
// Show driver selection dropdown when I2S is available
|
||||
if (useI2S && drvsel) {
|
||||
drvsel.style.display = "inline";
|
||||
// Set default value if not already set (backward compatibility)
|
||||
if (!d.Sf["LD"+n].value) {
|
||||
d.Sf["LD"+n].value = "0"; // default to RMT
|
||||
}
|
||||
|
||||
// Mark driver selection red if this I2S bus violates parallel I2S rules
|
||||
if (driverPref === 1) {
|
||||
// This bus uses I2S - check if it violates rules
|
||||
if (i2sBusesOver600 > 0 && i2sUsed > 1) {
|
||||
// Multiple I2S buses and at least one is >600 LEDs - invalid
|
||||
drvsel.style.color = "red";
|
||||
} else if (firstI2SType !== null && t !== firstI2SType) {
|
||||
// I2S buses must all be same type
|
||||
drvsel.style.color = "red";
|
||||
}
|
||||
}
|
||||
} else if (drv) {
|
||||
// Show info text when I2S is disabled
|
||||
drv.textContent = " (RMT)";
|
||||
}
|
||||
|
||||
// Lock PR checkbox if disabling would result in invalid config
|
||||
if (useI2S) {
|
||||
let rmtCount = is32() ? 8 : (isS2() || isS3()) ? 4 : 0;
|
||||
if (maxLEDsOnI2SBus <= 600) {
|
||||
// Parallel I2S mode would be used
|
||||
if (digitalBusIndex > rmtCount) d.Sf.PR.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
updateTypeDropdowns(); // update type dropdowns to disable unavailable digital/analog types (I2S/RMT bus count may have changed)
|
||||
|
||||
// Show channel usage warning
|
||||
let channelWarning = gId('channelwarning');
|
||||
let channelMsg = gId('channelmsg');
|
||||
if (channelWarning && channelMsg && !is8266()) {
|
||||
if (rmtUsed > maxRMT || i2sUsed > maxI2S) {
|
||||
channelWarning.style.display = 'inline';
|
||||
channelWarning.style.color = 'red';
|
||||
let msg = '';
|
||||
if (rmtUsed > maxRMT) msg += `RMT: ${rmtUsed}/${maxRMT}`;
|
||||
if (i2sUsed > maxI2S) {
|
||||
if (msg) msg += ', ';
|
||||
msg += `I2S: ${i2sUsed}/${maxI2S}`;
|
||||
}
|
||||
channelMsg.textContent = 'Channel limit exceeded! ' + msg;
|
||||
} else if ((is32() || isS2() || isS3()) && (rmtUsed >= maxRMT - 1 || i2sUsed >= maxI2S - 1)) {
|
||||
channelWarning.style.display = 'inline';
|
||||
channelWarning.style.color = 'orange';
|
||||
channelMsg.textContent = `Channel usage: RMT ${rmtUsed}/${maxRMT}, I2S ${i2sUsed}/${maxI2S}`;
|
||||
} else {
|
||||
channelWarning.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// distribute ABL current if not using PPL
|
||||
enPPL(sDI);
|
||||
|
||||
@@ -445,6 +576,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);
|
||||
@@ -453,6 +585,7 @@
|
||||
if (isPWM(t)) v = 1; //PWM busses
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
|
||||
function addLEDs(n,init=true)
|
||||
{
|
||||
var o = gEBCN("iST");
|
||||
@@ -494,13 +627,20 @@ 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();updateTypeDropdowns()" /></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);"/>
|
||||
<span id="drv${s}" style="color:#999"></span>
|
||||
<div id="drvsel${s}" style="display:none">
|
||||
Driver: <select name="LD${s}" onchange="updateTypeDropdowns();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>
|
||||
@@ -523,11 +663,38 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
||||
}
|
||||
});
|
||||
enLA(d.Sf["LAsel"+s],s); // update LED mA
|
||||
// Check channel availability before selecting default type
|
||||
let usage = calculateChannelUsage();
|
||||
let maxRMT = (is32() ? 8 : (isS2() || isS3()) ? 4 : isC3() ? 2 : 0);
|
||||
let maxI2S = (is32() ? 8 : (isS2() || isS3()) ? 8 : 0);
|
||||
let rmtFull = (usage.rmtUsed >= maxRMT);
|
||||
let i2sFull = (usage.i2sUsed >= maxI2S);
|
||||
|
||||
// Set driver preference based on channel availability
|
||||
let drvSelect = d.Sf["LD"+s];
|
||||
if (drvSelect) {
|
||||
if (rmtFull && !i2sFull && (is32() || isS2() || isS3())) {
|
||||
// RMT is full but I2S available - default to I2S
|
||||
drvSelect.value = "1";
|
||||
} else {
|
||||
// Default to RMT (backward compatible)
|
||||
drvSelect.value = "0";
|
||||
}
|
||||
}
|
||||
|
||||
// temporarily set to virtual (network) type to avoid "same type" exception during dropdown update
|
||||
let sel = d.getElementsByName("LT"+s)[0];
|
||||
sel.value = sel.querySelector('option[data-type="N"]').value;
|
||||
updateTypeDropdowns(); // update valid bus options including this new one
|
||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
||||
|
||||
// Select first non-disabled option
|
||||
let firstEnabled = sel.querySelector('option:not(:disabled)');
|
||||
if (firstEnabled) {
|
||||
sel.value = firstEnabled.value;
|
||||
} else {
|
||||
// All digital types disabled - keep as network type
|
||||
sel.value = sel.querySelector('option[data-type="N"]').value;
|
||||
}
|
||||
updateTypeDropdowns(); // update again for the newly selected type
|
||||
}
|
||||
if (n==-1) {
|
||||
@@ -648,6 +815,67 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
gId("si").checked = cs;
|
||||
tglSi(cs);
|
||||
}
|
||||
function fixLegacyDriverConfig() { //on load, handle legacy configs without driver type (LD) field
|
||||
// Check if this is a legacy config by seeing if all LD fields are unset or all RMT
|
||||
if (!is32() && !isS2() && !isS3()) return; // Only applies to ESP32 variants
|
||||
|
||||
let drvSelects = d.Sf.querySelectorAll("select[name^=LD]");
|
||||
if (drvSelects.length === 0) return; // No driver selects found
|
||||
|
||||
// Check if any LD field has a non-default value (indicating it was set by backend)
|
||||
let hasDriverConfig = false;
|
||||
let allRMT = true;
|
||||
let digitalBusCount = 0;
|
||||
|
||||
drvSelects.forEach((sel) => {
|
||||
let n = sel.name.substring(2, 3);
|
||||
let t = parseInt(d.Sf["LT"+n].value);
|
||||
|
||||
// Only check digital single-pin buses
|
||||
if (isDig(t) && !isD2P(t)) {
|
||||
digitalBusCount++;
|
||||
if (sel.value && sel.value !== "" && sel.value !== "0") {
|
||||
hasDriverConfig = true;
|
||||
allRMT = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If all drivers are RMT and we have digital buses, this might be a legacy config
|
||||
// Apply intelligent driver assignment: fill RMT first, then fallback to I2S
|
||||
if (allRMT && digitalBusCount > 0) {
|
||||
let maxRMT = (is32() ? 8 : (isS2() || isS3()) ? 4 : 0);
|
||||
let maxI2S = (is32() ? 8 : (isS2() || isS3()) ? 8 : 0);
|
||||
let rmtAssigned = 0;
|
||||
let i2sAssigned = 0;
|
||||
|
||||
// First pass: assign drivers intelligently
|
||||
drvSelects.forEach((sel) => {
|
||||
let n = sel.name.substring(2, 3);
|
||||
let t = parseInt(d.Sf["LT"+n].value);
|
||||
|
||||
// Only process digital single-pin buses
|
||||
if (isDig(t) && !isD2P(t)) {
|
||||
if (rmtAssigned < maxRMT) {
|
||||
// RMT channel available - use it
|
||||
sel.value = "0";
|
||||
rmtAssigned++;
|
||||
} else if (i2sAssigned < maxI2S) {
|
||||
// RMT full, but I2S available - use I2S
|
||||
sel.value = "1";
|
||||
i2sAssigned++;
|
||||
} else {
|
||||
// Both full - leave as RMT (will show validation error)
|
||||
sel.value = "0";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update UI to reflect the changes
|
||||
updateTypeDropdowns();
|
||||
UI();
|
||||
}
|
||||
}
|
||||
// https://stackoverflow.com/questions/7346563/loading-local-json-file
|
||||
function loadCfg(o) {
|
||||
var f, fr;
|
||||
@@ -692,6 +920,10 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
d.getElementsByName("SP"+i)[0].value = v.freq;
|
||||
d.getElementsByName("LA"+i)[0].value = v.ledma;
|
||||
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
|
||||
// Handle driver type (LD field) - load from JSON if present
|
||||
if (v.drv !== undefined && d.getElementsByName("LD"+i)[0]) {
|
||||
d.getElementsByName("LD"+i)[0].value = v.drv;
|
||||
}
|
||||
});
|
||||
d.getElementsByName("PR")[0].checked = l.prl | 0;
|
||||
d.getElementsByName("MA")[0].value = l.maxpwr;
|
||||
@@ -726,6 +958,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
if (li) {
|
||||
d.getElementsByName("MS")[0].checked = li.aseg;
|
||||
}
|
||||
fixLegacyDriverConfig(); // Handle legacy configs without driver selection
|
||||
UI();
|
||||
}
|
||||
}
|
||||
@@ -826,11 +1059,50 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
// Helper function to calculate channel usage across all buses
|
||||
function calculateChannelUsage() {
|
||||
let rmtUsed = 0, i2sUsed = 0;
|
||||
let firstI2SType = null;
|
||||
let maxLEDsOnI2SBus = 0;
|
||||
let i2sBusesOver600 = 0;
|
||||
|
||||
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] ? parseInt(d.Sf["LD"+n].value || 0) : 0;
|
||||
let ledCount = parseInt(d.Sf["LC"+n].value) || 0;
|
||||
|
||||
if (isDig(t) && !isD2P(t)) {
|
||||
if (driverPref === 1) {
|
||||
i2sUsed++;
|
||||
if (!firstI2SType) firstI2SType = t;
|
||||
if (ledCount > maxLEDsOnI2SBus) maxLEDsOnI2SBus = ledCount;
|
||||
if (ledCount > 600) i2sBusesOver600++;
|
||||
} else {
|
||||
rmtUsed++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let parallelI2SAllowed = (maxLEDsOnI2SBus <= 600);
|
||||
return { rmtUsed, i2sUsed, firstI2SType, maxLEDsOnI2SBus, i2sBusesOver600, parallelI2SAllowed };
|
||||
}
|
||||
|
||||
// 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
|
||||
let maxRMT = (is32() ? 8 : (isS2() || isS3()) ? 4 : isC3() ? 2 : 0);
|
||||
let maxI2S = (is32() ? 8 : (isS2() || isS3()) ? 8 : isS3() ? 8 : 0);
|
||||
|
||||
// Use helper function to calculate channel usage
|
||||
let usage = calculateChannelUsage();
|
||||
let rmtUsed = usage.rmtUsed;
|
||||
let i2sUsed = usage.i2sUsed;
|
||||
let firstI2SType = usage.firstI2SType;
|
||||
let maxLEDsOnI2SBus = usage.maxLEDsOnI2SBus;
|
||||
|
||||
// Count all bus types
|
||||
LTs.forEach(sel => {
|
||||
let t = parseInt(sel.value);
|
||||
if (isDig(t) && !isD2P(t)) digitalB++;
|
||||
@@ -838,21 +1110,90 @@ 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
|
||||
|
||||
// Determine if parallel I2S is allowed (all I2S buses ≤600 LEDs)
|
||||
let parallelI2SOK = (maxLEDsOnI2SBus <= 600);
|
||||
let rmtFull = (rmtUsed >= maxRMT);
|
||||
let i2sFull = (i2sUsed >= maxI2S);
|
||||
|
||||
// Second pass: update each dropdown with appropriate constraints
|
||||
LTs.forEach(sel => {
|
||||
let n = sel.name.substring(2,3);
|
||||
const curType = parseInt(sel.value);
|
||||
const curDriver = d.Sf["LD"+n] ? parseInt(d.Sf["LD"+n].value || 0) : 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
|
||||
// First I2S bus can select any type, subsequent I2S buses restricted to first type
|
||||
let isFirstI2SBus = (curDriver === 1 && firstI2SType === null);
|
||||
if (curDriver === 1 && firstI2SType !== null && !isFirstI2SBus) {
|
||||
sel.querySelectorAll('option[data-type="D"]').forEach(o => {
|
||||
if (parseInt(o.value) !== firstI2SType) o.disabled = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// For non-digital current types, check if we can add digital buses
|
||||
let canAddRMT = (rmtUsed < maxRMT || (isDig(curType) && !isD2P(curType) && curDriver === 0));
|
||||
let canAddI2S = ((is32() || isS2() || isS3()) && i2sUsed < maxI2S) || (isDig(curType) && !isD2P(curType) && curDriver === 1);
|
||||
|
||||
// If both RMT and I2S are full, disable all digital types
|
||||
if (!canAddRMT && !canAddI2S) {
|
||||
disable('option[data-type="D"]');
|
||||
}
|
||||
}
|
||||
|
||||
// 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)}"]`);
|
||||
});
|
||||
|
||||
// Third pass: update driver selection dropdowns
|
||||
if (is32() || isS2() || isS3()) {
|
||||
LTs.forEach(sel => {
|
||||
let n = sel.name.substring(2,3);
|
||||
let t = parseInt(sel.value);
|
||||
let drvsel = gId("drvsel"+n);
|
||||
|
||||
if (drvsel && isDig(t) && !isD2P(t)) {
|
||||
let rmtOpt = drvsel.querySelector('option[value="0"]');
|
||||
let i2sOpt = drvsel.querySelector('option[value="1"]');
|
||||
let curDriver = parseInt(d.Sf["LD"+n].value || 0);
|
||||
|
||||
// Enable both options by default
|
||||
if (rmtOpt) rmtOpt.disabled = false;
|
||||
if (i2sOpt) i2sOpt.disabled = false;
|
||||
|
||||
// Disable RMT if full (unless this bus already uses RMT)
|
||||
if (rmtFull && curDriver !== 0) {
|
||||
if (rmtOpt) rmtOpt.disabled = true;
|
||||
}
|
||||
|
||||
// Disable I2S if full (unless this bus already uses I2S)
|
||||
if (i2sFull && curDriver !== 1) {
|
||||
if (i2sOpt) i2sOpt.disabled = true;
|
||||
}
|
||||
|
||||
// For I2S buses >600 LEDs or when mixing with other I2S buses, validate
|
||||
let ledCount = parseInt(d.Sf["LC"+n].value) || 0;
|
||||
if (curDriver === 1 && !parallelI2SOK && ledCount > 600) {
|
||||
// This I2S bus has >600 LEDs - mark as invalid if other I2S buses exist
|
||||
if (i2sUsed > 1) {
|
||||
drvsel.style.color = "red";
|
||||
} else {
|
||||
drvsel.style.color = "#fff";
|
||||
}
|
||||
} else {
|
||||
drvsel.style.color = "#fff";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
@@ -898,8 +1239,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="channelwarning" class="warn" style="display: none; color: orange;">
|
||||
⚠ <span id="channelmsg"></span><br>
|
||||
</div>
|
||||
<hr class="sml">
|
||||
<div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div>
|
||||
|
||||
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">
|
||||
|
||||
@@ -945,26 +945,25 @@ void serializePalettes(JsonObject root, int page)
|
||||
{
|
||||
byte tcp[72];
|
||||
#ifdef ESP8266
|
||||
int itemPerPage = 5;
|
||||
constexpr int itemPerPage = 5;
|
||||
#else
|
||||
int itemPerPage = 8;
|
||||
constexpr int itemPerPage = 8;
|
||||
#endif
|
||||
|
||||
int customPalettesCount = customPalettes.size();
|
||||
int palettesCount = getPaletteCount() - customPalettesCount; // palettesCount is number of palettes, not palette index
|
||||
const int customPalettesCount = customPalettes.size();
|
||||
const int palettesCount = FIXED_PALETTE_COUNT; // palettesCount is number of palettes, not palette index
|
||||
|
||||
int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage;
|
||||
const int maxPage = (palettesCount + customPalettesCount) / itemPerPage;
|
||||
if (page > maxPage) page = maxPage;
|
||||
|
||||
int start = itemPerPage * page;
|
||||
int end = start + itemPerPage;
|
||||
if (end > palettesCount + customPalettesCount) end = palettesCount + customPalettesCount;
|
||||
const int start = itemPerPage * page;
|
||||
int end = min(start + itemPerPage, palettesCount + customPalettesCount);
|
||||
|
||||
root[F("m")] = maxPage; // inform caller how many pages there are
|
||||
JsonObject palettes = root.createNestedObject("p");
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
JsonArray curPalette = palettes.createNestedArray(String(i<=palettesCount ? i : 255 - (i - (palettesCount + 1))));
|
||||
for (int i = start; i < end; i++) {
|
||||
JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i));
|
||||
switch (i) {
|
||||
case 0: //default palette
|
||||
setPaletteColors(curPalette, PartyColors_p);
|
||||
@@ -993,12 +992,12 @@ void serializePalettes(JsonObject root, int page)
|
||||
curPalette.add("c1");
|
||||
break;
|
||||
default:
|
||||
if (i > palettesCount)
|
||||
setPaletteColors(curPalette, customPalettes[i - (palettesCount + 1)]);
|
||||
else if (i < 13) // palette 6 - 12, fastled palettes
|
||||
setPaletteColors(curPalette, *fastledPalettes[i-6]);
|
||||
if (i >= palettesCount) // custom palettes
|
||||
setPaletteColors(curPalette, customPalettes[i - palettesCount]);
|
||||
else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes
|
||||
setPaletteColors(curPalette, *fastledPalettes[i - DYNAMIC_PALETTE_COUNT]);
|
||||
else {
|
||||
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72);
|
||||
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp));
|
||||
setPaletteColors(curPalette, tcp);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -89,7 +89,8 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
for (int i = it; i < playlistLen; i++) playlistEntries[i].dur = playlistEntries[it -1].dur;
|
||||
if (it > 0) // should never happen but just in case
|
||||
for (int i = it; i < playlistLen; i++) playlistEntries[i].dur = playlistEntries[it -1].dur;
|
||||
|
||||
it = 0;
|
||||
JsonArray tr = playlistObj[F("transition")];
|
||||
|
||||
@@ -138,7 +138,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
}
|
||||
}
|
||||
|
||||
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
|
||||
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed, driverType;
|
||||
unsigned length, start, maMax;
|
||||
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
|
||||
String text;
|
||||
@@ -156,7 +156,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
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"));
|
||||
// useI2S is now always determined based on actual bus configuration, no longer user-controlled via PR checkbox
|
||||
#endif
|
||||
|
||||
bool busesChanged = false;
|
||||
@@ -175,6 +175,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
|
||||
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
|
||||
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
|
||||
char ld[4] = "LD"; ld[2] = offset+s; ld[3] = 0; //driver type (RMT=0, I2S=1)
|
||||
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
|
||||
if (!request->hasArg(lp)) {
|
||||
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
|
||||
@@ -226,10 +227,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
|
||||
@@ -279,7 +281,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
int offset = i < 10 ? '0' : 'A' - 10;
|
||||
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
|
||||
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
|
||||
int hw_btn_pin = request->arg(bt).toInt();
|
||||
int hw_btn_pin = request->hasArg(bt) ? request->arg(bt).toInt() : -1;
|
||||
if (i >= buttons.size()) buttons.emplace_back(hw_btn_pin, request->arg(be).toInt()); // add button to vector
|
||||
else {
|
||||
buttons[i].pin = hw_btn_pin;
|
||||
|
||||
@@ -334,8 +334,8 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
|
||||
selseg.custom2 = udpIn[30+ofs];
|
||||
selseg.custom3 = udpIn[31+ofs] & 0x1F;
|
||||
selseg.check1 = (udpIn[31+ofs]>>5) & 0x1;
|
||||
selseg.check1 = (udpIn[31+ofs]>>6) & 0x1;
|
||||
selseg.check1 = (udpIn[31+ofs]>>7) & 0x1;
|
||||
selseg.check2 = (udpIn[31+ofs]>>6) & 0x1;
|
||||
selseg.check3 = (udpIn[31+ofs]>>7) & 0x1;
|
||||
}
|
||||
}
|
||||
if (receiveSegmentBounds) {
|
||||
|
||||
@@ -213,7 +213,7 @@ void releaseJSONBufferLock()
|
||||
|
||||
|
||||
// extracts effect mode (or palette) name from names serialized string
|
||||
// caller must provide large enough buffer for name (including SR extensions)!
|
||||
// caller must provide large enough buffer for name (including SR extensions)! maxLen is (buffersize - 1)
|
||||
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
|
||||
{
|
||||
if (src == JSON_mode_names || src == nullptr) {
|
||||
@@ -235,7 +235,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
|
||||
|
||||
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
|
||||
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
|
||||
dest[maxLen-1] = '\0';
|
||||
dest[maxLen] = '\0';
|
||||
return strlen(dest);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
|
||||
case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
|
||||
case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
|
||||
}
|
||||
dest[maxLen] = '\0'; // strncpy does not necessarily null terminate string
|
||||
dest[maxLen-1] = '\0'; // strncpy does not necessarily null terminate string
|
||||
}
|
||||
}
|
||||
return strlen(dest);
|
||||
|
||||
@@ -401,7 +401,7 @@ WLED_GLOBAL bool useGlobalLedBuffer _INIT(false); // double buffering disabled o
|
||||
#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
|
||||
WLED_GLOBAL bool useI2S _INIT(false); // I2S/LCD for ESP32 (parallel or single)
|
||||
#endif
|
||||
#endif
|
||||
#ifdef WLED_USE_IC_CCT
|
||||
|
||||
@@ -291,7 +291,23 @@ 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);"),
|
||||
// Calculate max RMT and I2S channels based on platform
|
||||
uint8_t maxRMT = 0, maxI2S = 0;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
maxRMT = 8; // ESP32 has 8 RMT channels
|
||||
maxI2S = 8; // Can use 8 parallel I2S or 1 single I2S
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
maxRMT = 4; // ESP32-S2 has 4 RMT channels
|
||||
maxI2S = 8; // Can use 8 parallel I2S or 1 single I2S
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
maxRMT = 4; // ESP32-S3 has 4 RMT channels
|
||||
maxI2S = 8; // Can use 8 parallel LCD (no single I2S support)
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
maxRMT = 2; // ESP32-C3 has 2 RMT channels
|
||||
maxI2S = 0; // No I2S support
|
||||
#endif
|
||||
|
||||
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);"),
|
||||
WLED_MAX_BUSSES,
|
||||
WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI
|
||||
MAX_LEDS_PER_BUS,
|
||||
@@ -300,7 +316,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
||||
WLED_MAX_COLOR_ORDER_MAPPINGS,
|
||||
WLED_MAX_DIGITAL_CHANNELS,
|
||||
WLED_MAX_ANALOG_CHANNELS,
|
||||
WLED_MAX_BUTTONS
|
||||
WLED_MAX_BUTTONS,
|
||||
maxRMT,
|
||||
maxI2S
|
||||
);
|
||||
|
||||
printSetFormCheckbox(settingsScript,PSTR("MS"),strip.autoSegments);
|
||||
@@ -330,6 +348,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
||||
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
|
||||
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
|
||||
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
|
||||
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)
|
||||
settingsScript.print(F("addLEDs(1);"));
|
||||
uint8_t pins[OUTPUT_MAX_PINS];
|
||||
@@ -370,6 +389,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
||||
printSetFormValue(settingsScript,sp,speed);
|
||||
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
|
||||
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
|
||||
printSetFormValue(settingsScript,ld,bus->getDriverType());
|
||||
printSetFormValue(settingsScript,hs,bus->getCustomText().c_str());
|
||||
sumMa += bus->getMaxCurrent();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user