diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 91a7e1ce..9399bead 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -23,6 +23,27 @@ extern bool cctICused; +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; + if (value) + byteArray[byteIndex] |= (1 << bitIndex); + else + byteArray[byteIndex] &= ~(1 << bitIndex); +} + +size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits + return (num_bits + 7) / 8; +} + +void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value + if (byteArray == nullptr) return; + size_t len = getBitArrayBytes(numBits); + if (value) memset(byteArray, 0xFF, len); + else memset(byteArray, 0x00, len); +} + //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); @@ -790,14 +811,45 @@ void BusNetwork::cleanup(void) { BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { - mxconfig.double_buff = true; // <------------- Turn on double buffer + _valid = false; + mxconfig.double_buff = false; // default to off, known to cause issue with some effects but needs more memory + // mxconfig.double_buff = true; // <------------- Turn on double buffer + // mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver + //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; - switch(bc.type) { - case TYPE_HUB75MATRIX_HS: + fourScanPanel = nullptr; + + if(bc.type == TYPE_HUB75MATRIX_HS) { mxconfig.mx_width = bc.pins[0]; mxconfig.mx_height = bc.pins[1]; - break; + } + else if(bc.type == TYPE_HUB75MATRIX_QS) { + fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]); + fourScanPanel->setRotation(0); + switch(bc.pins[1]) { + case 16: + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH); + break; + case 32: + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); + break; + case 64: + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); + break; + default: + DEBUG_PRINTLN("Unsupported height"); + return; + } + } + else { + DEBUG_PRINTLN("Unknown type"); + return; + } + mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[2], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory @@ -882,29 +934,159 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh // let's adjust default brightness display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% + delay(24); // experimental // Allocate memory and start DMA display if( not display->begin() ) { DEBUG_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); return; } else { + DEBUG_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle _valid = true; + display->clearScreen(); // initially clear the screen buffer + DEBUG_PRINTLN("MatrixPanel_I2S_DMA clear ok"); + + if (_ledBuffer) free(_ledBuffer); // should not happen + if (_ledsDirty) free(_ledsDirty); // should not happen + DEBUG_PRINTLN("MatrixPanel_I2S_DMA allocate memory"); + _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + DEBUG_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok"); + + if (_ledsDirty == nullptr) { + display->stopDMAoutput(); + delete display; display = nullptr; + _valid = false; + DEBUG_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); + return; // fail is we cannot get memory for the buffer + } + setBitArray(_ledsDirty, _len, false); // reset dirty bits + + if (mxconfig.double_buff == false) { + _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } } - DEBUG_PRINTLN("MatrixPanel_I2S_DMA started"); + + if (_valid) { + _panelWidth = fourScanPanel ? fourScanPanel->width() : display->width(); // cache width - it will never change + } + + DEBUG_PRINT(F("MatrixPanel_I2S_DMA ")); + DEBUG_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); + + if (mxconfig.double_buff == true) DEBUG_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); + if (_ledBuffer != nullptr) DEBUG_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); + if (_ledsDirty != nullptr) DEBUG_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); + if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { + DEBUG_PRINT(F("MatrixPanel_I2S_DMA LEDS buffer uses ")); + DEBUG_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); + DEBUG_PRINTLN(F(" bytes.")); + } } -void BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { - r = R(c); - g = G(c); - b = B(c); - x = pix % display->width(); - y = floor(pix / display->width()); - display->drawPixelRGB888(x, y, r, g, b); +void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { + if (!_valid || pix >= _len) return; + // if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + + if (_ledBuffer) { + CRGB fastled_col = CRGB(c); + if (_ledBuffer[pix] != fastled_col) { + _ledBuffer[pix] = fastled_col; + setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty" + } + } + else { + if ((c == BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black + setBitInArray(_ledsDirty, pix, c != BLACK); // dirty = true means "color is not BLACK" + + #ifndef NO_CIE1931 + c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction + #endif + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + + if(fourScanPanel != nullptr) { + int width = _panelWidth; + int x = pix % width; + int y = pix / width; + fourScanPanel->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } else { + int width = _panelWidth; + int x = pix % width; + int y = pix / width; + display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } + } +} + +uint32_t BusHub75Matrix::getPixelColor(uint16_t pix) const { + if (!_valid || pix >= _len) return BLACK; + if (_ledBuffer) + return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours + else + return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not } void BusHub75Matrix::setBrightness(uint8_t b, bool immediate) { - this->display->setBrightness(b); + _bri = b; + if (_bri > 238) _bri=238; + display->setBrightness(_bri); +} + +void __attribute__((hot)) BusHub75Matrix::show(void) { + if (!_valid) return; + display->setBrightness(_bri); + + if (_ledBuffer) { + // write out buffered LEDs + bool isFourScan = (fourScanPanel != nullptr); + //if (isFourScan) fourScanPanel->setRotation(0); + unsigned height = isFourScan ? fourScanPanel->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); + } + pix ++; + } + setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits + } + + if(mxconfig.double_buff) { + display->flipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) + // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. + display->clearScreen(); // Now clear the back-buffer + setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits + } +} + +void BusHub75Matrix::cleanup() { + if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) + _valid = false; + _panelWidth = 0; + deallocatePins(); + DEBUG_PRINTLN("HUB75 output ended."); + + //if (fourScanPanel != nullptr) delete fourScanPanel; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior + delete display; + display = nullptr; + fourScanPanel = nullptr; + if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; + if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; } void BusHub75Matrix::deallocatePins() { @@ -931,10 +1113,16 @@ void BusHub75Matrix::deallocatePins() { std::vector BusHub75Matrix::getLEDTypes() { return { {TYPE_HUB75MATRIX_HS, "H", PSTR("HUB75 (Half Scan)")}, - // {TYPE_HUB75MATRIX_QS, "H", PSTR("HUB75 (Quarter Scan)")}, + {TYPE_HUB75MATRIX_QS, "H", PSTR("HUB75 (Quarter Scan)")}, }; } +uint8_t BusHub75Matrix::getPins(uint8_t* pinArray) const { + pinArray[0] = mxconfig.mx_width; + pinArray[1] = mxconfig.mx_height; + pinArray[2] = mxconfig.chain_length; + return 3; +} #endif // *************************************************************************** diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 998b7b57..37bfc25b 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -2,7 +2,11 @@ #define BusManager_h #ifdef WLED_ENABLE_HUB75MATRIX + #include +#include +#include + #endif /* * Class for addressing various light types @@ -323,30 +327,17 @@ class BusHub75Matrix : public Bus { bool hasWhite() { return false; } void setPixelColor(uint16_t pix, uint32_t c); + uint32_t getPixelColor(uint16_t pix) const override; - void show() { - if(mxconfig.double_buff) { - display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels) - display->clearScreen(); // Now clear the back-buffer - } - } + void show() override; void setBrightness(uint8_t b, bool immediate); - uint8_t getPins(uint8_t* pinArray) const override { - pinArray[0] = mxconfig.mx_height; - pinArray[1] = mxconfig.mx_width; - pinArray[2] = mxconfig.chain_length; - return 3; - } + uint8_t getPins(uint8_t* pinArray) const override; void deallocatePins(); - void cleanup() { - deallocatePins(); - delete display; - _valid = false; - } + void cleanup(); ~BusHub75Matrix() { cleanup(); @@ -356,9 +347,11 @@ class BusHub75Matrix : public Bus { private: MatrixPanel_I2S_DMA *display = nullptr; + VirtualMatrixPanel *fourScanPanel = nullptr; HUB75_I2S_CFG mxconfig; - uint8_t r, g, b, x, y; - + unsigned _panelWidth = 0; + CRGB *_ledBuffer = nullptr; + byte *_ledsDirty = nullptr; }; #endif