Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HUB75 support #3777

Open
wants to merge 9 commits into
base: 0_15
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions platformio_override.sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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}
196 changes: 196 additions & 0 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in another comment, this should in fact be a runtime option.
I know it will/may be a lot of work/selection options, but I hate the idea that such configuration is compile-time based.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised by this comment as I messaged you during the development and specifically discussed the challenges relating to the usability of configuration when so many pins are needed and that they are non configurable if you are using one of the ready made boards such as the Adafruit Matrix Portal S3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry if I forgot, I interact with several (possibly in range of 50) users and developers at any time.
So things may slip my mind and I do not heave the time to revisit all communication made. I apologise.

Nevertheless users may build clones or try their own PCBs following HW layout of the existing boards. So it would be more natural to select any Hub75 compatible board instead of compile-time choice. Similar as it is with Ethernet.

After all, the only difference I can see is in GPIO allocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ideal solution would be a total refactor whereby the front-end requests what config the bus requires, rather than the current mess of having a large number of elements that you then need to work out what to hide for that bus type. Then you can remove all the isType calls.

But this is of course a big change and should be a PR in it's own right

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is a mess. Still, a little better than it was.


// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also an allocateMultiplePins() method available.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, now I've swapped to this library for the implementation, it supports config of pins by array so I can flip to that and then use the multi call

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a struct for the gpio pins, not array so can't really swap over easily

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) {
Expand Down Expand Up @@ -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) {
Expand Down
48 changes: 48 additions & 0 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef BusManager_h
#define BusManager_h

#ifdef WLED_ENABLE_HUB75MATRIX
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#endif
/*
* Class for addressing various light types
*/
Expand Down Expand Up @@ -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];
}

Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@
#define TYPE_LPD8806 52
#define TYPE_P9813 53
#define TYPE_LPD6803 54

#define TYPE_HUB75MATRIX 100 // 100 - 110
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may need revisiting as it somehow clashes with @Aircoookie 's bit oriented approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this comment further?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to hide the fact that this was a bitmasked value in 0.15 macros (check marcos in 0.14 or previous). But I would still like it to follow the original idea of particular bit defining LED type if possible.
There is no need for you to do anything at this point. It was more of a reminder for myself.


//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)
Expand Down
31 changes: 24 additions & 7 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the other way around. Instead of hiding Hub75 types, insert them into SELECT.
It will require more effort in xml.cpp and some modified code here but should allow for enhancements in the future.

I will tackle this problem and push modifications when I'm done and the solution is viable. No need for you to do anything.

var s = d.getElementsByTagName("select");
for (i=0; i<s.length; i++) {
// is the field a LED type?
if (s[i].name.substring(0,2)=="LT") {
var selectobject = s[i];
for (var j=(selectobject.length - 1); j > 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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -402,6 +416,9 @@
<option value="82">Art-Net RGB (network)</option>
<option value="88">DDP RGBW (network)</option>
<option value="89">Art-Net RGBW (network)</option>
<option value="101">Hub75 Matrix 32x32</option>
<option value="102">Hub75 Matrix 64x32</option>
<option value="103">Hub75 Matrix 64x64</option>
</select><br>
<div id="abl${i}">
mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
Expand Down
1 change: 1 addition & 0 deletions wled00/pin_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum struct PinOwner : uint8_t {
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
HUB75 = 0x8E, // 'Hub75' == Hub75 driver
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
Expand Down