diff --git a/boards/lilygo-t7-s3.json b/boards/lilygo-t7-s3.json new file mode 100644 index 0000000000..4bf071fc7e --- /dev/null +++ b/boards/lilygo-t7-s3.json @@ -0,0 +1,47 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_TTGO_T7_S3", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "LILYGO T3-S3", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.aliexpress.us/item/3256804591247074.html", + "vendor": "LILYGO" +} \ No newline at end of file diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index d11d1f50b8..597f487383 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -180,7 +180,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; enable IR by setting remote type ; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote -; +; ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) ; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1 ; @@ -525,6 +525,7 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU -D USER_SETUP_LOADED monitor_filters = esp32_exception_decoder + # ------------------------------------------------------------------------------ # Usermod examples # ------------------------------------------------------------------------------ @@ -535,3 +536,86 @@ extends = env:esp32dev build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 lib_deps = ${env:esp32dev.lib_deps} sui77/rc-switch @ 2.6.4 + +# ------------------------------------------------------------------------------ +# Hub75 examples +# ------------------------------------------------------------------------------ + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +platform_packages = +build_unflags = ${common.build_unflags} +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 + +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = dio + +[env:esp32dev_hub75_forum_pinout] +extends = env:esp32dev_hub75 +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins +; -D WLED_DEBUG + + +[env:adafruit_matrixportal_esp32s3] +; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +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") + -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 + +[env:esp32S3_PSRAM_HUB75] +;; MOONHUB HUB75 adapter board +board = lilygo-t7-s3 +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=\"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 + -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 MOONHUB_S3_PINOUT ;; HUB75 pinout + +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 diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp index 1514e2307a..6be3a92640 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp @@ -54,7 +54,11 @@ class RgbRotaryEncoderUsermod : public Usermod void initLedBus() { - byte _pins[5] = {(byte)ledIo, 255, 255, 255, 255}; + // Initialize all pins to the sentinel value first… + byte _pins[OUTPUT_MAX_PINS]; + std::fill(std::begin(_pins), std::end(_pins), 255); + // …then set only the LED pin + _pins[0] = static_cast(ledIo); 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 99523bba9f..8cc2b8353a 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -32,6 +32,36 @@ extern char cmDNS[]; extern bool cctICused; 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 +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; +} + +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); @@ -760,6 +790,352 @@ void BusNetwork::cleanup() { _valid = false; } +// *************************************************************************** + +#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) { + size_t lastHeap = ESP.getFreeHeap(); + _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: + DEBUGBUS_PRINTLN("Unsupported height"); + return; + } + } else { + DEBUGBUS_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)) { + 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}; + +#if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 + + // https://www.adafruit.com/product/5778 + DEBUGBUS_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(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM + +#if defined(MOONHUB_S3_PINOUT) + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - T7 S3 with PSRAM, MOONHUB pinout"); + + // 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}; + mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 }; + +#else + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM"); + // 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}; + mxconfig.gpio = {1, 2, 42, 41, 40, 39, 45, 48, 47, 21, 38, 8, 3, 18}; +#endif +#elif defined(ESP32_FORUM_PINOUT) // Common format for boards designed for SmartMatrix + + DEBUGBUS_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 + DEBUGBUS_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, 19, 5, 17, 18, 4, 15, 16 }; + +#endif + + int8_t pins[PIN_COUNT]; + memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio)); + if (!PinManager::allocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75, true)) { + DEBUGBUS_PRINTLN("Failed to allocate pins for HUB75"); + return; + } + + if(bc.colorOrder == COL_ORDER_RGB) { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = Default color order (RGB)"); + } else if(bc.colorOrder == COL_ORDER_BGR) { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = color order BGR"); + int8_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 { + DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA = unsupported color order %u\n", bc.colorOrder); + } + + DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + DEBUGBUS_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) { + DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + + this->_len = (display->width() * display->height()); + DEBUGBUS_PRINTF("Length: %u\n", _len); + 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% + + delay(24); // experimental + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + // Allocate memory and start DMA display + if( not display->begin() ) { + DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + else { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_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 + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok"); + + if (_ledBuffer) free(_ledBuffer); // should not happen + if (_ledsDirty) free(_ledsDirty); // should not happen + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory"); + _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok"); + + if (_ledsDirty == nullptr) { + display->stopDMAoutput(); + delete display; display = nullptr; + _valid = false; + DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_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 + } + + DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA ")); + DEBUGBUS_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); + + if (mxconfig.double_buff == true) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering 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 ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { + DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA LEDS buffer uses ")); + DEBUGBUS_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); + DEBUGBUS_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; + 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 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(); + 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; + 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)")}, + }; +} + +size_t BusHub75Matrix::getPins(uint8_t* pinArray) const { + if (pinArray) { + 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 size_t BusConfig::memUsage(unsigned nr) const { @@ -816,6 +1192,10 @@ int BusManager::add(const BusConfig &bc) { if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1; if (Bus::isVirtual(bc.type)) { busses.push_back(make_unique(bc)); +#ifdef WLED_ENABLE_HUB75MATRIX + } else if (Bus::isHub75(bc.type)) { + busses.push_back(make_unique(bc)); +#endif } else if (Bus::isDigital(bc.type)) { busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); } else if (Bus::isOnOff(bc.type)) { @@ -847,6 +1227,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 fe70a05170..31df680ae4 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -2,6 +2,13 @@ #ifndef BusManager_h #define BusManager_h +#ifdef WLED_ENABLE_HUB75MATRIX + +#include +#include +#include + +#endif /* * Class for addressing various light types */ @@ -182,6 +189,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); } @@ -363,6 +371,37 @@ class BusNetwork : public Bus { #endif }; +#ifdef WLED_ENABLE_HUB75MATRIX +class BusHub75Matrix : public Bus { + public: + BusHub75Matrix(const BusConfig &bc); + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; + [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; + void show() override; + void setBrightness(uint8_t b) override; + size_t getPins(uint8_t* pinArray = nullptr) 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 { @@ -374,7 +413,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; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index eac6ea25a2..1d299848bb 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -207,7 +207,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { int s = 0; // bus iterator for (JsonObject elm : ins) { if (s >= WLED_MAX_BUSSES) break; // only counts physical buses - 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 1abf245396..f6867056a5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -316,6 +316,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #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 928da11753..3ad468ce15 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -17,6 +17,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 @@ -64,6 +65,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; @@ -237,12 +241,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; @@ -272,13 +279,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) @@ -325,7 +332,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") { @@ -339,6 +352,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 0c9f32e84a..709263e1a3 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -139,6 +139,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; i 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 40bd992443..dd3b4b1b95 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -331,7 +331,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others) 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] = '0'+i; @@ -457,8 +457,8 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); #ifdef WLED_ENABLE_DMX - settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message -#endif + settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message +#endif #ifndef WLED_ENABLE_DMX_INPUT settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings #else