-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
base: 0_15
Are you sure you want to change the base?
Add HUB75 support #3777
Changes from all commits
7ef84cf
7603b5a
755f91f
2bd1e81
07a1588
9282cf0
de9aeec
421c470
ffe25a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is also an There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -280,6 +280,9 @@ | |
#define TYPE_LPD8806 52 | ||
#define TYPE_P9813 53 | ||
#define TYPE_LPD6803 54 | ||
|
||
#define TYPE_HUB75MATRIX 100 // 100 - 110 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain this comment further? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
//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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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"); | ||
|
@@ -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 @@ | |
<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();"> | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.