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
This commit is contained in:
@@ -545,6 +545,7 @@ lib_deps = ${env:esp32dev.lib_deps}
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Hub75 examples
|
# 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]
|
[env:esp32dev_hub75]
|
||||||
board = esp32dev
|
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\"
|
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")
|
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||||
-DBOARD_HAS_PSRAM
|
-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_WATCHDOG_TIMEOUT=0
|
||||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
-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
|
-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
|
${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")
|
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||||
-DBOARD_HAS_PSRAM
|
-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_WATCHDOG_TIMEOUT=0
|
||||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
-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
|
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
||||||
|
|||||||
@@ -21,12 +21,9 @@
|
|||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#include "core_esp8266_waveform.h"
|
#include "core_esp8266_waveform.h"
|
||||||
#endif
|
#endif
|
||||||
#include "const.h"
|
|
||||||
#include "colors.h"
|
|
||||||
#include "pin_manager.h"
|
|
||||||
#include "bus_manager.h"
|
#include "bus_manager.h"
|
||||||
#include "bus_wrapper.h"
|
#include "bus_wrapper.h"
|
||||||
#include <bits/unique_ptr.h>
|
#include "wled.h"
|
||||||
|
|
||||||
extern char cmDNS[];
|
extern char cmDNS[];
|
||||||
extern bool cctICused;
|
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
|
// 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
|
// 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
|
bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value
|
||||||
size_t byteIndex = position / 8;
|
size_t byteIndex = position >> 3; // divide by 8
|
||||||
unsigned bitIndex = position % 8;
|
unsigned bitIndex = position & 0x07; // modulo 8
|
||||||
uint8_t byteValue = byteArray[byteIndex];
|
uint8_t byteValue = byteArray[byteIndex];
|
||||||
return (byteValue >> bitIndex) & 1;
|
return (byteValue >> bitIndex) & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
|
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
|
||||||
//if (byteArray == nullptr) return;
|
//if (byteArray == nullptr) return;
|
||||||
size_t byteIndex = position / 8;
|
size_t byteIndex = position >> 3; // divide by 8
|
||||||
unsigned bitIndex = position % 8;
|
unsigned bitIndex = position & 0x07; // modulo 8
|
||||||
if (value)
|
if (value)
|
||||||
byteArray[byteIndex] |= (1 << bitIndex);
|
byteArray[byteIndex] |= (1 << bitIndex);
|
||||||
else
|
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
|
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
|
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);
|
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 = {};
|
static ColorOrderMap _colorOrderMap = {};
|
||||||
|
|
||||||
bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
|
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;
|
_valid = false;
|
||||||
_hasRgb = true;
|
_hasRgb = true;
|
||||||
_hasWhite = false;
|
_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
|
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.latch_blanking = 3;
|
||||||
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz
|
// 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 = 90;
|
||||||
//mxconfig.min_refresh_rate = 120;
|
// mxconfig.min_refresh_rate = 120;
|
||||||
mxconfig.clkphase = bc.reversed;
|
|
||||||
|
|
||||||
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) {
|
if (bc.type == TYPE_HUB75MATRIX_HS) {
|
||||||
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]);
|
mxconfig.mx_width = min((uint8_t) 64, panelWidth); // TODO: UI limit is 128, this limits to 64
|
||||||
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]);
|
mxconfig.mx_height = min((uint8_t) 64, panelHeight);
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
} else if (bc.type == TYPE_HUB75MATRIX_QS) {
|
} else if (bc.type == TYPE_HUB75MATRIX_QS) {
|
||||||
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]) * 2;
|
_isVirtual = true;
|
||||||
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]) / 2;
|
mxconfig.mx_width = min((uint8_t) 64, panelWidth) * 2;
|
||||||
virtualDisp = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]);
|
mxconfig.mx_height = min((uint8_t) 64, panelHeight) / 2;
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
DEBUGBUS_PRINTLN("Unknown type");
|
DEBUGBUS_PRINTLN("Unknown type");
|
||||||
return;
|
return;
|
||||||
@@ -853,12 +806,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
|||||||
} else mxconfig.setPixelColorDepthBits(8);
|
} else mxconfig.setPixelColorDepthBits(8);
|
||||||
#endif
|
#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};
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bc.colorOrder == COL_ORDER_RGB) {
|
if (bc.colorOrder == COL_ORDER_RGB) {
|
||||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = Default color 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");
|
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = color order BGR");
|
||||||
int8_t tmpPin;
|
int8_t tmpPin;
|
||||||
tmpPin = mxconfig.gpio.r1;
|
tmpPin = mxconfig.gpio.r1;
|
||||||
@@ -944,21 +891,27 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
|||||||
return;
|
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);
|
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");
|
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA Too many LEDS - playing safe");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA created");
|
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
|
delay(24); // experimental
|
||||||
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
||||||
// Allocate memory and start DMA display
|
// Allocate memory and start DMA display
|
||||||
if( not display->begin() ) {
|
if (!display->begin() ) {
|
||||||
DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********");
|
DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********");
|
||||||
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
|
||||||
return;
|
return;
|
||||||
@@ -971,10 +924,10 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
|||||||
display->clearScreen(); // initially clear the screen buffer
|
display->clearScreen(); // initially clear the screen buffer
|
||||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok");
|
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok");
|
||||||
|
|
||||||
if (_ledBuffer) free(_ledBuffer); // should not happen
|
if (_ledBuffer) d_free(_ledBuffer); // should not happen
|
||||||
if (_ledsDirty) free(_ledsDirty); // should not happen
|
if (_ledsDirty) d_free(_ledsDirty); // should not happen
|
||||||
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory");
|
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");
|
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok");
|
||||||
|
|
||||||
if (_ledsDirty == nullptr) {
|
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
|
setBitArray(_ledsDirty, _len, false); // reset dirty bits
|
||||||
|
|
||||||
if (mxconfig.double_buff == false) {
|
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) {
|
if (_valid) {
|
||||||
_panelWidth = virtualDisp ? virtualDisp->width() : display->width(); // cache width - it will never change
|
_panelWidth = virtualDisp ? virtualDisp->width() : display->width(); // cache width - it will never change
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA "));
|
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 (_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."));
|
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) {
|
void IRAM_ATTR BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
|
||||||
if (!_valid || pix >= _len) return;
|
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 (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
|
||||||
|
|
||||||
if (_ledBuffer) {
|
if (_ledBuffer) {
|
||||||
@@ -1028,8 +1014,8 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c
|
|||||||
uint8_t g = G(c);
|
uint8_t g = G(c);
|
||||||
uint8_t b = B(c);
|
uint8_t b = B(c);
|
||||||
|
|
||||||
if(virtualDisp != nullptr) {
|
if (virtualDisp != nullptr) {
|
||||||
int x = pix % _panelWidth;
|
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;
|
int y = pix / _panelWidth;
|
||||||
virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
|
virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
|
||||||
} else {
|
} else {
|
||||||
@@ -1041,41 +1027,35 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
|
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)
|
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
|
else
|
||||||
return getBitFromArray(_ledsDirty, pix) ? IS_DARKGREY: IS_BLACK; // just a hack - we only know if the pixel is black or not
|
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) {
|
void BusHub75Matrix::setBrightness(uint8_t b) {
|
||||||
_bri = b;
|
_bri = b;
|
||||||
if (display) display->setBrightness(_bri);
|
display->setBrightness(_bri);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusHub75Matrix::show(void) {
|
void BusHub75Matrix::show(void) {
|
||||||
if (!_valid) return;
|
if (!_valid) return;
|
||||||
display->setBrightness(_bri);
|
|
||||||
|
|
||||||
if (_ledBuffer) {
|
if (_ledBuffer) {
|
||||||
// write out buffered LEDs
|
// write out buffered LEDs
|
||||||
bool isVirtualDisp = (virtualDisp != nullptr);
|
unsigned height = _isVirtual ? virtualDisp->height() : display->height();
|
||||||
unsigned height = isVirtualDisp ? virtualDisp->height() : display->height();
|
|
||||||
unsigned width = _panelWidth;
|
unsigned width = _panelWidth;
|
||||||
|
|
||||||
//while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker.
|
//while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker.
|
||||||
|
|
||||||
size_t pix = 0; // running pixel index
|
size_t pix = 0; // running pixel index
|
||||||
for (int y=0; y<height; y++) for (int x=0; x<width; x++) {
|
for (int y=0; y<height; y++) for (int x=0; x<width; x++) {
|
||||||
if (getBitFromArray(_ledsDirty, pix) == true) { // only repaint the "dirty" pixels
|
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
|
CRGB c = _ledBuffer[pix];
|
||||||
uint8_t r = R(c);
|
//c.nscale8_video(_bri); // apply brightness
|
||||||
uint8_t g = G(c);
|
if (_isVirtual) virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), c.r, c.g, c.b);
|
||||||
uint8_t b = B(c);
|
else display->drawPixelRGB888(int16_t(x), int16_t(y), c.r, c.g, c.b);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
pix ++;
|
pix++;
|
||||||
}
|
}
|
||||||
setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits
|
setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits
|
||||||
}
|
}
|
||||||
@@ -1084,16 +1064,18 @@ void BusHub75Matrix::show(void) {
|
|||||||
void BusHub75Matrix::cleanup() {
|
void BusHub75Matrix::cleanup() {
|
||||||
if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black)
|
if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black)
|
||||||
_valid = false;
|
_valid = false;
|
||||||
|
delay(30); // give some time to finish DMA
|
||||||
_panelWidth = 0;
|
_panelWidth = 0;
|
||||||
deallocatePins();
|
deallocatePins();
|
||||||
DEBUGBUS_PRINTLN("HUB75 output ended.");
|
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; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior
|
if (virtualDisp != nullptr) delete virtualDisp; // note: in MM there is a warning to not do this but if using "NO_GFX" this is safe
|
||||||
delete display;
|
if (display != nullptr) delete display;
|
||||||
display = nullptr;
|
display = nullptr;
|
||||||
virtualDisp = nullptr;
|
virtualDisp = nullptr; // note: when not using "NO_GFX" this causes a memory leak
|
||||||
if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr;
|
#endif
|
||||||
if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr;
|
if (_ledBuffer != nullptr) d_free(_ledBuffer); _ledBuffer = nullptr;
|
||||||
|
if (_ledsDirty != nullptr) d_free(_ledsDirty); _ledsDirty = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusHub75Matrix::deallocatePins() {
|
void BusHub75Matrix::deallocatePins() {
|
||||||
@@ -1114,8 +1096,10 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
|
|||||||
pinArray[0] = mxconfig.mx_width;
|
pinArray[0] = mxconfig.mx_width;
|
||||||
pinArray[1] = mxconfig.mx_height;
|
pinArray[1] = mxconfig.mx_height;
|
||||||
pinArray[2] = mxconfig.chain_length;
|
pinArray[2] = mxconfig.chain_length;
|
||||||
|
pinArray[3] = _rows;
|
||||||
|
pinArray[4] = _cols;
|
||||||
}
|
}
|
||||||
return 3;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class Bus {
|
|||||||
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
|
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 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 size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
|
||||||
static constexpr bool hasRGB(uint8_t 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);
|
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;
|
VirtualMatrixPanel *virtualDisp = nullptr;
|
||||||
HUB75_I2S_CFG mxconfig;
|
HUB75_I2S_CFG mxconfig;
|
||||||
unsigned _panelWidth = 0;
|
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;
|
byte *_ledsDirty = nullptr;
|
||||||
// workaround for missing constants on include path for non-MM
|
// workaround for missing constants on include path for non-MM
|
||||||
uint32_t IS_BLACK = 0x000000;
|
static constexpr uint32_t IS_BLACK = 0x000000u;
|
||||||
uint32_t IS_DARKGREY = 0x333333;
|
static constexpr uint32_t IS_DARKGREY = 0x333333u;
|
||||||
const int PIN_COUNT = 14;
|
static constexpr int PIN_COUNT = 14;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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) {
|
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 (c1 == BLACK || amount == 0) return 0; // black or full fade
|
||||||
if (amount == 255) return c1;
|
if (amount == 255) return c1; // no change
|
||||||
uint32_t addRemains = 0;
|
uint32_t addRemains = 0;
|
||||||
|
|
||||||
if (!video) amount++; // add one for correct scaling using bitshifts
|
if (!video) amount++; // add one for correct scaling using bitshifts
|
||||||
|
|||||||
@@ -122,6 +122,20 @@
|
|||||||
d.Sf.data.value = '';
|
d.Sf.data.value = '';
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
|
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;}
|
if (bquot > 200) {var msg = "Too many LEDs! Can't handle that!"; alert(msg); e.stopPropagation(); return false;}
|
||||||
else {
|
else {
|
||||||
if (bquot > 80) {var msg = "Memory usage is high, reboot recommended!\n\rSet transitions to 0 to save memory.";
|
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:";
|
p0d = "IP address:";
|
||||||
break;
|
break;
|
||||||
case 'V': // virtual/non-GPIO based
|
case 'V': // virtual/non-GPIO based
|
||||||
p0d = "Config:"
|
p0d = "Config:";
|
||||||
break;
|
break;
|
||||||
case 'H': // HUB75
|
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;
|
break;
|
||||||
}
|
}
|
||||||
gId("p0d"+n).innerText = p0d;
|
gId("p0d"+n).innerText = p0d;
|
||||||
gId("p1d"+n).innerText = p1d;
|
gId("p1d"+n).innerText = p1d;
|
||||||
gId("off"+n).innerText = off;
|
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)
|
// 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++) {
|
for (let p=1; p<5; p++) {
|
||||||
var LK = d.Sf["L"+p+n];
|
var LK = d.Sf["L"+p+n];
|
||||||
if (!LK) continue;
|
if (!LK) continue;
|
||||||
@@ -376,10 +392,11 @@
|
|||||||
LC.style.color="#fff";
|
LC.style.color="#fff";
|
||||||
return; // do not check conflicts
|
return; // do not check conflicts
|
||||||
}
|
}
|
||||||
else if (isHub75(t) && nm=="L2") {
|
else if (isHub75(t) && (nm=="L2" || nm=="L3" || nm=="L4")) {
|
||||||
// Chain length aka Panel Count
|
// chain length aka panel count (L2), cols(L3), rows(L4)
|
||||||
LC.max = 4;
|
LC.max = 4;
|
||||||
LC.min = 1;
|
LC.min = 1;
|
||||||
|
if (LC.value === "") LC.value = 1; // default to 1
|
||||||
LC.style.color="#fff";
|
LC.style.color="#fff";
|
||||||
return; // do not check conflicts
|
return; // do not check conflicts
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user