diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 19b8c273a0..c94d592ba8 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -530,6 +530,48 @@ lib_deps = ${esp32.lib_deps} TFT_eSPI @ 2.5.33 ;; this is the last version that compiles with the WLED default framework - newer versions require platform = espressif32 @ ^6.3.2 + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +extends = esp32dev_V4 +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + ; -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + ; -D WLED_DEBUG +lib_deps = ${esp32_idf_V4.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11 +board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = dio + + +[env:adafruit_matrixportal_esp32s3] +; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +board = lolin_s3_mini +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.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") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder # ------------------------------------------------------------------------------ # Usermod examples # ------------------------------------------------------------------------------ diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index 00fc227252..b06037f50b 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -56,7 +56,7 @@ class RgbRotaryEncoderUsermod : public Usermod void initLedBus() { - byte _pins[5] = {(byte)ledIo, 255, 255, 255, 255}; + byte _pins[OUTPUT_MAX_PINS] = {(byte)ledIo, 255, 255, 255, 255}; BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0); ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 6e159a82b2..18f99987bc 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -21,8 +21,37 @@ #include "bus_wrapper.h" #include "bus_manager.h" +// 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 +bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value + size_t byteIndex = position / 8; + unsigned bitIndex = position % 8; + uint8_t byteValue = byteArray[byteIndex]; + return (byteValue >> bitIndex) & 1; +} 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); @@ -776,6 +805,334 @@ void BusNetwork::cleanup() { freeData(); } +// *************************************************************************** + +#ifdef WLED_ENABLE_HUB75MATRIX +#warning "HUB75 driver enabled (experimental)" +#ifdef ESP8266 +#error ESP8266 does not support HUB75 +#endif + +BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + + _valid = false; + _hasRgb = true; + _hasWhite = false; + + 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.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; + + virtualDisp = nullptr; + + if (bc.type == TYPE_HUB75MATRIX_HS) { + mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]); + mxconfig.mx_height = min((u_int8_t) 64, bc.pins[1]); + if(bc.pins[2] > 1 && bc.pins[3] > 0 && bc.pins[4]) { + 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) { + mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]) * 2; + mxconfig.mx_height = min((u_int8_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: + DEBUG_PRINTLN("Unsupported height"); + return; + } + } else { + DEBUG_PRINTLN("Unknown type"); + return; + } + +#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)// classic esp32, or esp32-s2: reduce bitdepth for large panels + if (mxconfig.mx_height >= 64) { + if (mxconfig.chain_length * mxconfig.mx_width > 192) mxconfig.setPixelColorDepthBits(3); + else if (mxconfig.chain_length * mxconfig.mx_width > 64) mxconfig.setPixelColorDepthBits(4); + else mxconfig.setPixelColorDepthBits(8); + } else mxconfig.setPixelColorDepthBits(8); +#endif + + 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 + + if(mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) { + DEBUG_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}; + +#if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 + + // https://www.adafruit.com/product/5778 + DEBUG_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config"); + mxconfig.gpio = { 42, 41, 40, 38, 39, 37, 45, 36, 48, 35, 21, 47, 14, 2 }; + +#elif defined(ESP32_FORUM_PINOUT) // Common format for boards designed for SmartMatrix + + DEBUG_PRINTLN("MatrixPanel_I2S_DMA - ESP32_FORUM_PINOUT"); +/* + ESP32 with SmartMatrix's default pinout - ESP32_FORUM_PINOUT + https://github.com/pixelmatix/SmartMatrix/blob/teensylc/src/MatrixHardware_ESP32_V0.h + Can use a board like https://github.com/rorosaurus/esp32-hub75-driver +*/ + + mxconfig.gpio = { 2, 15, 4, 16, 27, 17, 5, 18, 19, 21, 12, 26, 25, 22 }; + +#else + DEBUG_PRINTLN("MatrixPanel_I2S_DMA - Default pins"); + /* + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA?tab=readme-ov-file + + Boards + + https://esp32trinity.com/ + https://www.electrodragon.com/product/rgb-matrix-panel-drive-interface-board-for-esp32-dma/ + + */ + mxconfig.gpio = { 25, 26, 27, 14, 12, 13, 23, 9, 5, 17, 18, 4, 15, 16 }; + +#endif + + int8_t pins[PIN_COUNT]; + memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio)); + PinManager::allocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75, true); + + if(bc.colorOrder == COL_ORDER_RGB) { + DEBUG_PRINTLN("MatrixPanel_I2S_DMA = Default color order (RGB)"); + } else if(bc.colorOrder == COL_ORDER_BGR) { + DEBUG_PRINTLN("MatrixPanel_I2S_DMA = color order BGR"); + uint8_t tmpPin; + tmpPin = mxconfig.gpio.r1; + mxconfig.gpio.r1 = mxconfig.gpio.b1; + mxconfig.gpio.b1 = tmpPin; + tmpPin = mxconfig.gpio.r2; + mxconfig.gpio.r2 = mxconfig.gpio.b2; + mxconfig.gpio.b2 = tmpPin; + } + else { + DEBUG_PRINTF("MatrixPanel_I2S_DMA = unsupported color order %u\n", bc.colorOrder); + } + + DEBUG_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + DEBUG_PRINTF("R1_PIN=%u, G1_PIN=%u, B1_PIN=%u, R2_PIN=%u, G2_PIN=%u, B2_PIN=%u, A_PIN=%u, B_PIN=%u, C_PIN=%u, D_PIN=%u, E_PIN=%u, LAT_PIN=%u, OE_PIN=%u, CLK_PIN=%u\n", + mxconfig.gpio.r1, mxconfig.gpio.g1, mxconfig.gpio.b1, mxconfig.gpio.r2, mxconfig.gpio.g2, mxconfig.gpio.b2, + mxconfig.gpio.a, mxconfig.gpio.b, mxconfig.gpio.c, mxconfig.gpio.d, mxconfig.gpio.e, mxconfig.gpio.lat, mxconfig.gpio.oe, mxconfig.gpio.clk); + + // OK, now we can create our matrix object + display = new MatrixPanel_I2S_DMA(mxconfig); + if (display == nullptr) { + DEBUG_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + + this->_len = (display->width() * display->height()); + DEBUG_PRINTF("Length: %u\n", _len); + if(this->_len >= MAX_LEDS) { + DEBUG_PRINTLN("MatrixPanel_I2S_DMA Too many LEDS - playing safe"); + return; + } + + DEBUG_PRINTLN("MatrixPanel_I2S_DMA created"); + // let's adjust default brightness + display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% + + delay(24); // experimental + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); + // Allocate memory and start DMA display + if( not display->begin() ) { + DEBUG_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + else { + DEBUG_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(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 + 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!")); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); + 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) + } + } + + + if (_valid) { + _panelWidth = virtualDisp ? virtualDisp->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 __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned 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 == IS_BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black + setBitInArray(_ledsDirty, pix, c != IS_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(virtualDisp != nullptr) { + int x = pix % _panelWidth; + int y = pix / _panelWidth; + virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } else { + int x = pix % _panelWidth; + int y = pix / _panelWidth; + display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } + } +} + +uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const { + if (!_valid || pix >= _len) return IS_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) ? 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; + 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 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 (virtualDisp != nullptr) delete virtualDisp; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior + delete display; + display = nullptr; + virtualDisp = nullptr; + if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; + if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; +} + +void BusHub75Matrix::deallocatePins() { + uint8_t pins[PIN_COUNT]; + memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio)); + PinManager::deallocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75); +} + +std::vector BusHub75Matrix::getLEDTypes() { + return { + {TYPE_HUB75MATRIX_HS, "H", PSTR("HUB75 (Half 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 +// *************************************************************************** //utility to get the approx. memory usage of a given BusConfig uint32_t BusManager::memUsage(const BusConfig &bc) { @@ -807,6 +1164,10 @@ int BusManager::add(const BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; if (Bus::isVirtual(bc.type)) { busses[numBusses] = new BusNetwork(bc); +#ifdef WLED_ENABLE_HUB75MATRIX + } else if (Bus::isHub75(bc.type)) { + busses[numBusses] = new BusHub75Matrix(bc); +#endif } else if (Bus::isDigital(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); } else if (Bus::isOnOff(bc.type)) { @@ -838,6 +1199,10 @@ String BusManager::getLEDTypesJSONString() { json += LEDTypesToJson(BusPwm::getLEDTypes()); json += LEDTypesToJson(BusNetwork::getLEDTypes()); //json += LEDTypesToJson(BusVirtual::getLEDTypes()); + #ifdef WLED_ENABLE_HUB75MATRIX + json += LEDTypesToJson(BusHub75Matrix::getLEDTypes()); + #endif + json.setCharAt(json.length()-1, ']'); // replace last comma with bracket return json; } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 9aed013089..41cc377ac5 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -1,6 +1,13 @@ #ifndef BusManager_h #define BusManager_h +#ifdef WLED_ENABLE_HUB75MATRIX + +#include +#include +#include + +#endif /* * Class for addressing various light types */ @@ -105,6 +112,7 @@ class Bus { inline bool isOnOff() const { return isOnOff(_type); } inline bool isPWM() const { return isPWM(_type); } inline bool isVirtual() const { return isVirtual(_type); } + inline bool isHub75() const { return isHub75(_type); } inline bool is16bit() const { return is16bit(_type); } inline bool mustRefresh() const { return mustRefresh(_type); } inline void setReversed(bool reversed) { _reversed = reversed; } @@ -144,6 +152,7 @@ class Bus { static constexpr bool isOnOff(uint8_t type) { return (type == TYPE_ONOFF); } static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } + static constexpr bool isHub75(uint8_t type) { return (type >= TYPE_HUB75MATRIX_MIN && type <= TYPE_HUB75MATRIX_MAX); } static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; } static constexpr int numPWMPins(uint8_t type) { return (type - 40); } @@ -315,6 +324,37 @@ class BusNetwork : public Bus { bool _broadcastLock; }; +#ifdef WLED_ENABLE_HUB75MATRIX +class BusHub75Matrix : public Bus { + public: + BusHub75Matrix(const BusConfig &bc); + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; + void show() override; + void setBrightness(uint8_t b) override; + uint8_t getPins(uint8_t* pinArray) const override; + void deallocatePins(); + void cleanup(); + + ~BusHub75Matrix() { + cleanup(); + } + + static std::vector getLEDTypes(void); + + private: + MatrixPanel_I2S_DMA *display = nullptr; + VirtualMatrixPanel *virtualDisp = nullptr; + HUB75_I2S_CFG mxconfig; + unsigned _panelWidth = 0; + CRGB *_ledBuffer = nullptr; + 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; +}; +#endif //temporary struct for passing bus configuration to bus struct BusConfig { @@ -326,7 +366,7 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {255, 255, 255, 255, 255}; + uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255}; uint16_t frequency; bool doubleBuffer; uint8_t milliAmpsPerLed; @@ -346,7 +386,7 @@ struct BusConfig { { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - size_t nPins = Bus::getNumberOfPins(type); + size_t nPins = OUTPUT_MAX_PINS; for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 00cfc60d7e..69aef528c0 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -193,7 +193,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { for (JsonObject elm : ins) { if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break; - uint8_t pins[5] = {255, 255, 255, 255, 255}; + uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255}; JsonArray pinArr = elm["pin"]; if (pinArr.size() == 0) continue; //pins[0] = pinArr[0]; diff --git a/wled00/const.h b/wled00/const.h index 1ebcb9397d..79c51908ce 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -322,6 +322,12 @@ #define TYPE_P9813 53 #define TYPE_LPD6803 54 #define TYPE_2PIN_MAX 63 + +#define TYPE_HUB75MATRIX_MIN 64 +#define TYPE_HUB75MATRIX_HS 65 +#define TYPE_HUB75MATRIX_QS 66 +#define TYPE_HUB75MATRIX_MAX 71 + //Network types (master broadcast) (80-95) #define TYPE_VIRTUAL_MIN 80 #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 02ebb6ed0b..fd824a04b7 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -18,6 +18,7 @@ function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type function isNet(t) { return gT(t).t === "N"; } // is network type function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type + function isHub75(t){ return gT(t).t === "H"; } // is HUB75 type function hasRGB(t) { return !!(gT(t).c & 0x01); } // has RGB function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled @@ -59,6 +60,9 @@ let nm = LC.name.substring(0,2); let n = LC.name.substring(2); let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT + if(isHub75(t)) { + return; + } // ignore IP address if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { if (isNet(t)) return; @@ -234,12 +238,15 @@ case 'V': // virtual/non-GPIO based p0d = "Config:" break; + case 'H': // HUB75 + p0d = "Panel size (width x height), Panel count:" + 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); // fixes network pins to 4 + let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4 for (let p=1; p<5; p++) { var LK = d.Sf["L"+p+n]; if (!LK) continue; @@ -267,13 +274,13 @@ } gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory gRGBW |= hasW(t); // RGBW checkbox - gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM + gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping - gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog + gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual - gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog + gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32) gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) @@ -314,7 +321,13 @@ } // do we have led pins for digital leds if (nm=="L0" || nm=="L1") { - d.Sf["LC"+n].max = maxPB; // update max led count value + 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=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { @@ -328,6 +341,20 @@ LC.min = -1; } } + if (isHub75(t) && (nm=="L0" || nm=="L1")) { + // Matrix width and height + LC.max = 128; + LC.min = 16; + LC.style.color="#fff"; + return; // do not check conflicts + } + else if (isHub75(t) && nm=="L2") { + // Chain length aka Panel Count + LC.max = 4; + LC.min = 1; + LC.style.color="#fff"; + return; // do not check conflicts + } // check for pin conflicts & color fields if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4") if (LC.value!="" && LC.value!="-1") { diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 6f16523010..bb8a47246c 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -138,6 +138,12 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar return true; } +bool PinManager::allocateMultiplePins(const int8_t * mptArray, byte arrayElementCount, PinOwner tag, boolean output) { + PinManagerPinType pins[arrayElementCount]; + for (int i=0; iarg(F("MA")).toInt(); BusManager::setMilliampsMax(ablMilliampsMax); diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index e82493d117..86c70e32cf 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -96,7 +96,7 @@ void loadSettingsFromEEPROM() if (apHide > 1) apHide = 1; uint16_t length = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); //was ledCount if (length > MAX_LEDS || length == 0) length = 30; - uint8_t pins[5] = {2, 255, 255, 255, 255}; + uint8_t pins[OUTPUT_MAX_PINS] = {2, 255, 255, 255, 255}; uint8_t colorOrder = COL_ORDER_GRB; if (lastEEPROMversion > 9) colorOrder = EEPROM.read(383); if (colorOrder > COL_ORDER_GBR) colorOrder = COL_ORDER_GRB; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 78d2d7d567..9350c2ad9e 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -309,11 +309,11 @@ void getSettingsJS(byte subPage, Print& settingsScript) char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current settingsScript.print(F("addLEDs(1);")); - uint8_t pins[5]; + uint8_t pins[OUTPUT_MAX_PINS]; int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]); + if (PinManager::isPinOk(pins[i]) || bus->isVirtual() || bus->isHub75()) printSetFormValue(settingsScript,lp,pins[i]); } printSetFormValue(settingsScript,lc,bus->getLength()); printSetFormValue(settingsScript,lt,bus->getType());