Compare commits

..

9 Commits

Author SHA1 Message Date
Damian Schneider
e95450b318 replace cos8 with cos8_t correcting an oversight 2026-01-03 15:45:44 +01:00
Copilot
b556da8b36 Bugfix: GPIO0 always gets assigned to a button (#5259)
if button pin arg is missing, it defaults to 0 assigning a button to that pin. This change fixes that.

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2026-01-03 12:19:56 +01:00
Damian Schneider
5cfb6f984b Palettes fix (#5263)
* Fix for #5201
- use constants instead of magic numbers

Authored-by: Blaž Kristan <blaz@kristan-sp.si>
2026-01-03 10:07:04 +01:00
Damian Schneider
60e1698ed2 Improvements & fixes for HUB75 (#5026)
* Improvements & fixes for HUB75

- added proper config parameters to allow multiple panels
- added config checks
- fixed crashes on S3
- changed constant variables to constexpr
- added "wled.h" to bus_manager.cpp and removed local function prototypes (needed for buffer allocations)
- speed optimisations: yields about 10% higher FPS
- updated platformio_override.sample.ini
- some code cleanup
2026-01-03 09:56:10 +01:00
Will Tatam
979e3fd1f7 Update GitHub sponsors list in FUNDING.yml
Maintain PayPal link, but sponsor is not appropriate for someone who isn't actively contributing going forward
2026-01-01 11:33:26 +00:00
Will Tatam
600b9b4d8e Update funding sources in FUNDING.yml
Removed a contributor from the GitHub funding list and updated the custom funding sources.
2026-01-01 11:29:50 +00:00
Damian Schneider
787d8a7342 fix FX checkmark sync (#5239)
this fixes an ancient copy-paste bug that apparently went under the radar for years. Now FX checkmarks sync correctly.
2025-12-29 14:54:38 +01:00
Damian Schneider
46e77ea203 revert change to extractModeName, add comment for clarification 2025-12-29 14:50:58 +01:00
Damian Schneider
fa868568af minor bugfixes as suggested by the rabbit
- PulLightControl UM: dont release a lock you do not own
- off-by-one error in extractModeSlider (used only in rotary in UM)
- safety check in playlist in case something goes horribly wrong
2025-12-29 12:56:06 +01:00
16 changed files with 192 additions and 185 deletions

5
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -10276,7 +10276,7 @@ uint16_t mode_particleBalance(void) {
if (SEGMENT.check3) // random, use perlin noise
xgravity = ((int16_t)perlin8(SEGENV.aux0) - 128);
else // sinusoidal
xgravity = (int16_t)cos8(SEGENV.aux0) - 128;//((int32_t)(SEGMENT.custom3 << 2) * cos8(SEGENV.aux0)
xgravity = (int16_t)cos8_t(SEGENV.aux0) - 128;//((int32_t)(SEGMENT.custom3 << 2) * cos8(SEGENV.aux0)
// scale the force
xgravity = (xgravity * ((SEGMENT.custom3+1) << 2)) / 128; // xgravity: -127 to +127
PartSys->applyForce(xgravity);

View File

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

View File

@@ -21,12 +21,9 @@
#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;
@@ -34,17 +31,18 @@ extern bool useParallelI2S;
// 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) {
@@ -800,6 +759,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 +774,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 +806,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 +862,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 +891,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 +924,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 +941,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 +995,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 +1014,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 +1027,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 +1064,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 +1096,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

View File

@@ -166,7 +166,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);
@@ -395,12 +395,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
@@ -451,7 +454,7 @@ struct BusConfig {
//validates start and length and extends total if needed
bool adjustBounds(uint16_t& total) {
if (!count) count = 1;
if (!Bus::isVirtual(type) && count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS;
if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS;
if (start >= MAX_LEDS) return false;
//limit length of strip if it would exceed total permissible LEDs
if (start + count > MAX_LEDS) count = MAX_LEDS - start;

View File

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

View File

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

View File

@@ -122,6 +122,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.";
@@ -256,17 +270,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;
@@ -330,29 +346,33 @@
}
// do we have a led count field
if (nm=="LC") {
if (!isHub75(t)) {
LC.max = isAna(t) ? 1 : (isDig(t) ? maxPB : 16384); // set max value
} else {
LC.min = undefined;
LC.max = undefined;
}
let c = parseInt(LC.value,10); //get LED count
if (!customStarts || !startsDirty[toNum(n)]) gId("ls"+n).value = sLC; //update start value
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
if (c) {
let s = parseInt(gId("ls"+n).value); //start value
if (s+c > sLC) sLC = s+c; //update total count
if (c > maxLC) maxLC = c; //max per output
if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs
if (isDig(t)) {
if (c > maxLC) maxLC = c; //max per output
sDI += c; // summarize digital LED count
let maPL = parseInt(d.Sf["LA"+n].value);
if (maPL == 255) maPL = 12; // wacky WS2815 mode (255 == 12mA per LED)
if (maPL == 255) maPL = 12;
busMA += maPL*c; // summarize maximum bus current (calculated)
}
} // increase led count
return;
}
// do we have led pins for digital leds
if (nm=="L0" || nm=="L1") {
if (!isHub75(t)) {
d.Sf["LC"+n].max = maxPB; // update max led count value
}
else {
d.Sf["LC"+n].min = undefined;
d.Sf["LC"+n].max = undefined;
}
}
// ignore IP address (stored in pins for virtual busses)
if (nm.search(/^L[0-3]/) == 0) { // pin fields
if (isVir(t)) {
@@ -372,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
}

View File

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

View File

@@ -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")];

View File

@@ -279,7 +279,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;

View File

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

View File

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