Skip to content

Commit

Permalink
HUB75 on S3: workaround for not working runtime reconfiguration
Browse files Browse the repository at this point in the history
* instead of deleting the driver object, we keep a "secret" reference and re-use it
* prevent having more than one HUB75 object
  • Loading branch information
softhack007 committed Oct 14, 2024
1 parent db983d8 commit 3fa5d0c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 38 deletions.
156 changes: 122 additions & 34 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,19 +522,35 @@ void BusNetwork::cleanup() {
#ifdef WLED_ENABLE_HUB75MATRIX
#warning "HUB75 driver enabled (experimental)"

// BusHub75Matrix "global" variables (static members)
MatrixPanel_I2S_DMA* BusHub75Matrix::activeDisplay = nullptr;
VirtualMatrixPanel* BusHub75Matrix::activeFourScanPanel = nullptr;
HUB75_I2S_CFG BusHub75Matrix::activeMXconfig = HUB75_I2S_CFG();
uint8_t BusHub75Matrix::activeType = 0;
uint8_t BusHub75Matrix::instanceCount = 0;

BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
MatrixPanel_I2S_DMA* display = nullptr;
VirtualMatrixPanel* fourScanPanel = nullptr;
HUB75_I2S_CFG mxconfig;
size_t lastHeap = ESP.getFreeHeap();

_valid = false;
fourScanPanel = nullptr;
_len = 0;

// allow exactly one instance
if (instanceCount > 0) {
USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! already active - preventing attempt to create more than one driver instance.");
return;
}

mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer

// mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver
// mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel

// mxconfig.latch_blanking = 3;
// mxconfig.latch_blanking = 1; // needed for some ICS panels
// mxconfig.latch_blanking = 3; // use in case you see gost images
// 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.clkphase = false; // can help in case that the leftmost column is invisible, or pixels on the right side "bleeds out" to the left.
Expand Down Expand Up @@ -745,11 +761,50 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh
USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u (type %u) length: %u, %u bits/pixel.\n", mxconfig.mx_width, mxconfig.mx_height, bc.type, mxconfig.chain_length, mxconfig.getPixelColorDepthBits() * 3);
DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); lastHeap = ESP.getFreeHeap();

// check if we can re-use the existing display driver
if (activeDisplay) {
if ( (memcmp(&(activeMXconfig.gpio), &(mxconfig.gpio), sizeof(mxconfig.gpio)) != 0) // other pins?
|| (activeMXconfig.chain_length != mxconfig.chain_length) // other chain length?
|| (activeMXconfig.mx_width != mxconfig.mx_width) || (activeMXconfig.mx_height != mxconfig.mx_height) // other size?
|| (bc.type != activeType) // different panel type ?
|| (activeMXconfig.clkphase != mxconfig.clkphase) // different driver options ?
|| (activeMXconfig.latch_blanking != mxconfig.latch_blanking)
|| (activeMXconfig.i2sspeed != mxconfig.i2sspeed)
|| (activeMXconfig.driver != mxconfig.driver)
|| (activeMXconfig.min_refresh_rate != mxconfig.min_refresh_rate)
|| (activeMXconfig.getPixelColorDepthBits() != mxconfig.getPixelColorDepthBits()) )
{
// not the same as before - delete old driver
DEBUG_PRINTLN("MatrixPanel_I2S_DMA deleting old driver!");
activeDisplay->stopDMAoutput();
delay(28);
//#if !defined(CONFIG_IDF_TARGET_ESP32S3) // prevent crash
delete activeDisplay;
//#endif
activeDisplay = nullptr;
activeFourScanPanel = nullptr;
#if defined(CONFIG_IDF_TARGET_ESP32S3) // runtime reconfiguration is not working on -S3
USER_PRINTLN("\n\n****** MatrixPanel_I2S_DMA !KABOOM WARNING! Reboot needed to change driver options ***********\n");
errorFlag = ERR_REBOOT_NEEDED;
#endif
}
}

// OK, now we can create our matrix object
display = new MatrixPanel_I2S_DMA(mxconfig);
bool newDisplay = false; // true when the previous display object wasn't re-used
if (!activeDisplay) {
display = new MatrixPanel_I2S_DMA(mxconfig); // create new matrix object
newDisplay = true;
} else {
display = activeDisplay; // continue with existing matrix object
fourScanPanel = activeFourScanPanel;
}

if (display == nullptr) {
USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********");
USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap());
activeDisplay = nullptr;
activeFourScanPanel = nullptr;
USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap()));
return;
}

Expand All @@ -776,21 +831,23 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh

USER_PRINTLN("MatrixPanel_I2S_DMA created");
// let's adjust default brightness
display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100%
//display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin()
_bri = 25;

delay(24); // experimental
DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap());
DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(int(lastHeap - ESP.getFreeHeap()));
// Allocate memory and start DMA display
if (display->begin() == false) {
if (newDisplay && (display->begin() == false)) {
USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********");
USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap());
USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap()));
_valid = false;
return;
}
else {
USER_PRINTLN("MatrixPanel_I2S_DMA begin ok");
USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap());
if (newDisplay) { USER_PRINTLN("MatrixPanel_I2S_DMA begin, started ok"); }
else { USER_PRINTLN("MatrixPanel_I2S_DMA begin, using existing display."); }

USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap()));
delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle
_valid = true;
display->clearScreen(); // initially clear the screen buffer
Expand Down Expand Up @@ -826,19 +883,19 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh
switch(bc.type) {
case 105:
USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 32x32");
fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 32, 32);
if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 32, 32);
fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
fourScanPanel->setRotation(0);
break;
case 106:
USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 64x32");
fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 32);
if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 32);
fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
fourScanPanel->setRotation(0);
break;
case 107:
USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_64PX_HIGH");
fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 64);
if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 64);
fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH);
fourScanPanel->setRotation(0);
break;
Expand All @@ -858,6 +915,15 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh
USER_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0));
USER_PRINTLN(F(" bytes."));
}

if (_valid) {
// config is active, copy to global
activeType = bc.type;
activeDisplay = display;
activeFourScanPanel = fourScanPanel;
if (newDisplay) memcpy(&activeMXconfig, &mxconfig, sizeof(mxconfig));
}
instanceCount++;
USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap()));
}

Expand All @@ -874,6 +940,8 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c
}
else {
// no double buffer allocated --> directly draw pixel
MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay;
VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel;
#ifndef NO_CIE1931
c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction
#endif
Expand Down Expand Up @@ -914,16 +982,21 @@ uint32_t __attribute__((hot)) BusHub75Matrix::getPixelColorRestored(uint16_t pix
void BusHub75Matrix::setBrightness(uint8_t b, bool immediate) {
_bri = b;
if (!_valid) return;
MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay;
// if (_bri > 238) _bri=238; // not strictly needed. Enable this line if you see glitches at highest brightness.
display->setBrightness(_bri);
if ((_bri > 253) && (activeMXconfig.latch_blanking < 2)) _bri=253; // prevent glitches at highest brightness.
if (display) display->setBrightness(_bri);
}

void __attribute__((hot)) BusHub75Matrix::show(void) {
if (!_valid) return;
MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay;
if (!display) return;
display->setBrightness(_bri);

if (_ledBuffer) {
// write out buffered LEDs
VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel;
bool isFourScan = (fourScanPanel != nullptr);
//if (isFourScan) fourScanPanel->setRotation(0);
unsigned height = isFourScan ? fourScanPanel->height() : display->height();
Expand Down Expand Up @@ -951,38 +1024,53 @@ void __attribute__((hot)) BusHub75Matrix::show(void) {
}

void BusHub75Matrix::cleanup() {
MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay;
VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel;
if (display) display->clearScreen();

#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't stop, as we want to re-use the driver later
if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black)
_valid = false;
_panelWidth = 0;
deallocatePins();
USER_PRINTLN("HUB75 output ended.");
#else
USER_PRINTLN("HUB75 output paused.");
#endif

_valid = false;
deallocatePins();
//if (fourScanPanel != nullptr) delete fourScanPanel; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior
#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't delete, as we want to re-use the driver later
if (display) delete display;
display = nullptr;
fourScanPanel = nullptr;
activeDisplay = nullptr;
activeFourScanPanel = nullptr;
USER_PRINTLN("HUB75 deleted.");
#else
USER_PRINTLN("HUB75 cleanup done.");
#endif

if (instanceCount > 0) instanceCount--;
if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr;
if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr;
}

void BusHub75Matrix::deallocatePins() {

pinManager.deallocatePin(mxconfig.gpio.r1, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.g1, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.b1, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.r2, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.g2, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.b2, PinOwner::HUB75);

pinManager.deallocatePin(mxconfig.gpio.lat, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.oe, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.clk, PinOwner::HUB75);

pinManager.deallocatePin(mxconfig.gpio.a, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.b, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.c, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.d, PinOwner::HUB75);
pinManager.deallocatePin(mxconfig.gpio.e, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.r1, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.g1, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.b1, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.r2, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.g2, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.b2, PinOwner::HUB75);

pinManager.deallocatePin(activeMXconfig.gpio.lat, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.oe, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.clk, PinOwner::HUB75);

pinManager.deallocatePin(activeMXconfig.gpio.a, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.b, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.c, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.d, PinOwner::HUB75);
pinManager.deallocatePin(activeMXconfig.gpio.e, PinOwner::HUB75);

}
#endif
Expand Down
12 changes: 8 additions & 4 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ class BusHub75Matrix : public Bus {
void setBrightness(uint8_t b, bool immediate) override;

uint8_t getPins(uint8_t* pinArray) const override {
pinArray[0] = mxconfig.chain_length;
pinArray[0] = activeMXconfig.chain_length;
return 1;
} // Fake value due to keep finaliseInit happy

Expand All @@ -401,12 +401,16 @@ class BusHub75Matrix : public Bus {
}

private:
MatrixPanel_I2S_DMA *display = nullptr;
VirtualMatrixPanel *fourScanPanel = nullptr;
HUB75_I2S_CFG mxconfig;
unsigned _panelWidth = 0;
CRGB *_ledBuffer = nullptr;
byte *_ledsDirty = nullptr;
// C++ dirty trick: private static variables are actually _not_ part of the class (however only visibile to class instances).
// These variables persist when BusHub75Matrix gets deleted.
static MatrixPanel_I2S_DMA *activeDisplay; // active display object
static VirtualMatrixPanel *activeFourScanPanel; // active fourScan object
static HUB75_I2S_CFG activeMXconfig; // last used mxconfig
static uint8_t activeType; // last used type
static uint8_t instanceCount; // active instances - 0 or 1
};
#endif

Expand Down

0 comments on commit 3fa5d0c

Please sign in to comment.