diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 29f5c6b573..7696eaa495 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -493,3 +493,40 @@ lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 board_build.partitions = ${esp32.default_partitions} + +# ------------------------------------------------------------------------------ +# HUB75 examples - esp32 +# ------------------------------------------------------------------------------ + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32_V4} -D WLED_RELEASE_NAME=ESP32_hub75 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX + ; -D ESP32_FORUM_PINOUT +lib_deps = ${esp32_idf_V4.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git @ 3.0.10 + +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} + +# ------------------------------------------------------------------------------ +# HUB75 examples - Adafruit MAtrix Portal S3 +# ------------------------------------------------------------------------------ + +[env:adafruit_matrixportal_s3_hub75] +board = adafruit_matrixportal_esp32s3 +upload_speed = 921600 +platform = espressif32 ; latest +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32_V4} -D WLED_RELEASE_NAME=ESP32_adafruit_matrixportal_s3_hub75 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX + -D DEFAULT_LED_TYPE=101 +lib_deps = ${esp32s3.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git @ 3.0.10 + +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index d78ed60d0c..fc1272cf8d 100755 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -633,6 +633,198 @@ void BusNetwork::cleanup() { freeData(); } +// *************************************************************************** + +#ifdef WLED_ENABLE_HUB75MATRIX + +BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + + mxconfig.double_buff = false; // <------------- Turn on double buffer + + switch(bc.type) { + case 101: + mxconfig.mx_width = 32; + mxconfig.mx_height = 32; + break; + case 102: + mxconfig.mx_width = 64; + mxconfig.mx_height = 32; + break; + case 103: + mxconfig.mx_width = 64; + mxconfig.mx_height = 64; + break; + } + + mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[0], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory + + if(mxconfig.mx_width >= 64 && (bc.pins[0] > 1)) { + DEBUG_PRINTF("WARNING, only single panel can be used of 64 pixel boards due to memory") + mxconfig.chain_length = 1; + } + + // mxconfig.driver = HUB75_I2S_CFG::SHIFTREG; + +#if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 + + // https://www.adafruit.com/product/5778 + + DEBUG_PRINTF("MatrixPanel_I2S_DMA - Matrix Portal S3 config"); + + mxconfig.gpio.r1 = 42; + mxconfig.gpio.g1 = 41; + mxconfig.gpio.b1 = 40; + mxconfig.gpio.r2 = 38; + mxconfig.gpio.g2 = 39; + mxconfig.gpio.b2 = 37; + + mxconfig.gpio.lat = 47; + mxconfig.gpio.oe = 14; + mxconfig.gpio.clk = 2; + + mxconfig.gpio.a = 45; + mxconfig.gpio.b = 36; + mxconfig.gpio.c = 48; + mxconfig.gpio.d = 35; + mxconfig.gpio.e = 21; + +#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.r1 = 2; + mxconfig.gpio.g1 = 15; + mxconfig.gpio.b1 = 4; + mxconfig.gpio.r2 = 16; + mxconfig.gpio.g2 = 27; + mxconfig.gpio.b2 = 17; + + mxconfig.gpio.lat = 26; + mxconfig.gpio.oe = 25; + mxconfig.gpio.clk = 22; + + mxconfig.gpio.a = 5; + mxconfig.gpio.b = 18; + mxconfig.gpio.c = 19; + mxconfig.gpio.d = 21; + mxconfig.gpio.e = 12; + +#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.r1 = 25; + mxconfig.gpio.g1 = 26; + mxconfig.gpio.b1 = 27; + mxconfig.gpio.r2 = 14; + mxconfig.gpio.g2 = 12; + mxconfig.gpio.b2 = 13; + + mxconfig.gpio.lat = 4; + mxconfig.gpio.oe = 15; + mxconfig.gpio.clk = 16; + + mxconfig.gpio.a = 23; + mxconfig.gpio.b = 19; + mxconfig.gpio.c = 5; + mxconfig.gpio.d = 17; + mxconfig.gpio.e = 18; + +#endif + + + DEBUG_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + + // OK, now we can create our matrix object + display = new MatrixPanel_I2S_DMA(mxconfig); + + this->_len = (display->width() * display->height()); + + pinManager.allocatePin(mxconfig.gpio.r1, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.g1, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.b1, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.r2, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.g2, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.b2, true, PinOwner::HUB75); + + pinManager.allocatePin(mxconfig.gpio.lat, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.oe, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.clk, true, PinOwner::HUB75); + + pinManager.allocatePin(mxconfig.gpio.a, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.b, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.c, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.d, true, PinOwner::HUB75); + pinManager.allocatePin(mxconfig.gpio.e, true, PinOwner::HUB75); + + // display->setLatBlanking(4); + + DEBUG_PRINTF("MatrixPanel_I2S_DMA created"); + // let's adjust default brightness + display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA display + if( not display->begin() ) { + DEBUG_PRINTF("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + return; + } + else { + _valid = true; + } + + DEBUG_PRINTF("MatrixPanel_I2S_DMA started"); +} + +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 BusHub75Matrix::setBrightness(uint8_t b, bool immediate) { + this->display->setBrightness(b); +} + +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); + +} +#endif +// *************************************************************************** //utility to get the approx. memory usage of a given BusConfig uint32_t BusManager::memUsage(BusConfig &bc) { @@ -665,6 +857,10 @@ int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; if (IS_VIRTUAL(bc.type)) { busses[numBusses] = new BusNetwork(bc); +#ifdef WLED_ENABLE_HUB75MATRIX + } else if (bc.type >= TYPE_HUB75MATRIX && bc.type <= (TYPE_HUB75MATRIX + 10)) { + busses[numBusses] = new BusHub75Matrix(bc); +#endif } else if (IS_DIGITAL(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); } else if (bc.type == TYPE_ONOFF) { diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index fd7b647e1c..36527a42e7 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -1,6 +1,9 @@ #ifndef BusManager_h #define BusManager_h +#ifdef WLED_ENABLE_HUB75MATRIX +#include +#endif /* * Class for addressing various light types */ @@ -56,6 +59,7 @@ struct BusConfig { if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address else if (type > 47) nPins = 2; else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); + else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 0; for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } @@ -316,6 +320,50 @@ class BusNetwork : public Bus { bool _broadcastLock; }; +#ifdef WLED_ENABLE_HUB75MATRIX +class BusHub75Matrix : public Bus { + public: + BusHub75Matrix(BusConfig &bc); + + bool hasRGB() { return true; } + bool hasWhite() { return false; } + + void setPixelColor(uint16_t pix, uint32_t c); + + 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 setBrightness(uint8_t b, bool immediate); + + uint8_t getPins(uint8_t* pinArray) { + pinArray[0] = mxconfig.chain_length; + return 1; + } // Fake value due to keep finaliseInit happy + + void deallocatePins(); + + void cleanup() { + deallocatePins(); + delete display; + _valid = false; + } + + ~BusHub75Matrix() { + cleanup(); + } + + private: + MatrixPanel_I2S_DMA *display = nullptr; + HUB75_I2S_CFG mxconfig; + uint_fast8_t r, g, b; + uint_fast16_t x, y; + +}; +#endif class BusManager { public: diff --git a/wled00/const.h b/wled00/const.h index 30a045319a..18bdb259c1 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -280,6 +280,9 @@ #define TYPE_LPD8806 52 #define TYPE_P9813 53 #define TYPE_LPD6803 54 + +#define TYPE_HUB75MATRIX 100 // 100 - 110 + //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 611653a64c..237bb3d43a 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -23,6 +23,20 @@ function isD2P(t) { return t > 47 && t < 64; } // is digital 2 pin type function is16b(t) { return t == 26 || t == 29 } // is digital 16 bit type function isVir(t) { return t >= 80 && t < 96; } // is virtual type + function isHub75(t) { return t >= 100 && t <= 110; } // is Hub75 type + function hideHub75() { + var s = d.getElementsByTagName("select"); + for (i=0; i 0; j--) { + var t = parseInt(selectobject.options[j].value); + if(t >= 100 && t <= 110) selectobject.remove(j); + } + } + } + } // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript function loadJS(FILE_URL, async = true) { let scE = d.createElement("script"); @@ -212,9 +226,9 @@ // is the field a LED type? var n = s.name.substring(2); var t = parseInt(s.value); - gId("p0d"+n).innerHTML = isVir(t) ? "IP address:" : isD2P(t) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; + gId("p0d"+n).innerHTML = isHub75(t) ? "Chain Length:" : isVir(t) ? "IP address:" : isD2P(t) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; gId("p1d"+n).innerHTML = isD2P(t) ? "Clk GPIO:" : ""; - gId("abl"+n).style.display = (!ablEN || isVir(t) || isAna(t)) ? "none" : "inline"; + gId("abl"+n).style.display = (!ablEN || isVir(t) || isAna(t) || isHub75(t)) ? "none" : "inline"; //var LK = d.getElementsByName("L1"+n)[0]; // clock pin memu += getMem(t, n); // calc memory @@ -243,13 +257,13 @@ } gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h - 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) && isRGBW) ? "inline":"none"; // show swap channels dropdown if (!(isDig(t) && isRGBW)) 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+"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+"f").style.display = (isDig(t)) ? "inline":"none"; // hide refresh + gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog + gId("dig"+n+"r").style.display = (isVir(t) || isHub75(t)) ? "none":"inline"; // hide reversed for virtual + 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) ) ? "inline":"none"; // hide refresh gId("dig"+n+"a").style.display = (isRGBW) ? "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) gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog @@ -402,6 +416,9 @@ + + +
mA/LED: