diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index d897a494de..38f02d1097 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -529,6 +529,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 @@ -561,15 +562,18 @@ build_flags = ${common.build_flags} [env:adafruit_matrixportal_esp32s3] ; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +; If board file is missing, copy it manually into the boards folder: .platformio\platforms\espressif32@src-xxxsomehashnumberxxx\boards +; https://github.com/platformio/platform-espressif32/blob/master/boards/adafruit_matrixportal_esp32s3.json board = adafruit_matrixportal_esp32s3 platform = ${esp32s3.platform} platform_packages = upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" - -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DARDUINO_USB_CDC_ON_BOOT=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DARDUINO_USB_MODE=1 ; if board does not boot after reset/powerup, try setting this to 0 (or open serial monitor) -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 @@ -595,7 +599,7 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"esp32S3_16MB_PSRAM_HUB75\" -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 diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 4fa5c40a57..2e71845c1b 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -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 +#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.min_refresh_rate = 90; + // mxconfig.min_refresh_rate = 120; + 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)); - virtualDisp = nullptr; + 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(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,34 @@ 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); } 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; ydrawPixelRGB888(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 } @@ -1083,17 +1062,19 @@ void BusHub75Matrix::show(void) { void BusHub75Matrix::cleanup() { if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) + delay(30); // give some time to finish DMA _valid = false; _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 +1095,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 diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 95772a443f..87d39fe34b 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -166,7 +166,7 @@ class Bus { inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } static inline std::vector 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 diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 6ada4f1f6b..bea7e61728 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -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 diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 8a3330e473..b566400321 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -112,6 +112,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= 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."; @@ -246,17 +260,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 = "
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; @@ -366,10 +382,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 } @@ -450,13 +467,14 @@ let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); } var f = gId("mLC"); - let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; + let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0, hub75B = 0; f.querySelectorAll("select[name^=LT]").forEach((s)=>{ let t = s.value; if (isDig(t) && !isD2P(t)) digitalB++; if (isD2P(t)) twopinB++; if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel if (isVir(t)) virtB++; + if (isHub75(t)) hub75B++; }); if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") @@ -528,6 +546,7 @@ let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig() if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses) + if (hub75B >= 1) disable(sel,'option[data-type="H"]'); // NOTE: see isHub75() (we will only allow 1 HUB75 bus) disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM() sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; }