Rewrite of VirtualMatrixPanel

Fix chaining issue that was mentioned in #402

Compatibility breaking change to now explicitly define chaining type:  CHAIN_TOP_LEFT_DOWN, CHAIN_TOP_RIGHT_DOWN, CHAIN_BOTTOM_LEFT_UP, CHAIN_BOTTOM_RIGHT_UP
This commit is contained in:
mrfaptastic 2023-03-12 09:13:55 +00:00
parent 1bb96e0175
commit edfadc7650
9 changed files with 608 additions and 240 deletions

Binary file not shown.

BIN
doc/VirtualMatrixPanel.odp Normal file

Binary file not shown.

BIN
doc/VirtualMatrixPanel.pdf Normal file

Binary file not shown.

View file

@ -1,158 +1,67 @@
/****************************************************************************** /******************************************************************************
----------- -------------------------------------------------------------------------
Steps to use Steps to create a virtual display made up of a chain of panels in a grid
----------- -------------------------------------------------------------------------
1) In the sketch (i.e. this example): Read the documentation!
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/examples/ChainedPanels
tl/dr:
- Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN_TYPE.
- Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN.
There are comments beside them explaining what they are in more detail.
- Other than where the matrix is defined and matrix.begin in the setup, you - Other than where the matrix is defined and matrix.begin in the setup, you
should now be using the virtual display for everything (drawing pixels, writing text etc). should now be using the virtual display for everything (drawing pixels, writing text etc).
You can do a find and replace of all calls if it's an existing sketch You can do a find and replace of all calls if it's an existing sketch
(just make sure you don't replace the definition and the matrix.begin) (just make sure you don't replace the definition and the matrix.begin)
- If the sketch makes use of MATRIX_HEIGHT or MATRIX_WIDTH, these will need to be - If the sketch makes use of MATRIX_HEIGHT or MATRIX_WIDTH, these will need to be
replaced with the width and height of your virtual screen. replaced with the width and height of your virtual screen.
Either make new defines and use that, or you can use virtualDisp.width() or .height() Either make new defines and use that, or you can use virtualDisp.width() or .height()
Thanks to:
* Brian Lough for the original example as raised in this issue:
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26
YouTube: https://www.youtube.com/brianlough
Tindie: https://www.tindie.com/stores/brianlough/
Twitter: https://twitter.com/witnessmenow
* Galaxy-Man for the kind donation of panels make/test that this is possible:
https://github.com/Galaxy-Man
*****************************************************************************/ *****************************************************************************/
// 1) Include key virtual display library
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
// 2) Set configuration
/******************************************************************************
* VIRTUAL DISPLAY / MATRIX PANEL CHAINING CONFIGURATION
*
* Note 1: If chaining from the top right to the left, and then S curving down
* then serpentine_chain = true and top_down_chain = true
* (these being the last two parameters of the virtualDisp(...) constructor.
*
* Note 2: If chaining starts from the bottom up, then top_down_chain = false.
*
* Note 3: By default, this library has serpentine_chain = true, that is, every
* second row has the panels 'upside down' (rotated 180), so the output
* pin of the row above is right above the input connector of the next
* row.
Example 1 panel chaining:
+-----------------+-----------------+-------------------+
| 64x32px PANEL 3 | 64x32px PANEL 2 | 64x32px PANEL 1 |
| ------------ <-------- | ------------xx |
| [OUT] | [IN] | [OUT] [IN] | [OUT] [ESP IN] |
+--------|--------+-----------------+-------------------+
| 64x32px|PANEL 4 | 64x32px PANEL 5 | 64x32px PANEL 6 |
| \|/ ----------> | -----> |
| [IN] [OUT] | [IN] [OUT] | [IN] [OUT] |
+-----------------+-----------------+-------------------+
Example 1 configuration:
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS 3 // Number of INDIVIDUAL PANELS per ROW
virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true);
= 192x64 px virtual display, with the top left of panel 3 being pixel co-ord (0,0)
==========================================================
Example 2 panel chaining:
+-------------------+
| 64x32px PANEL 1 |
| ----------------- |
| [OUT] [ESP IN] |
+-------------------+
| 64x32px PANEL 2 |
| |
| [IN] [OUT] |
+-------------------+
| 64x32px PANEL 3 |
| |
| [OUT] [IN] |
+-------------------+
| 64x32px PANEL 4 |
| |
| [IN] [OUT] |
+-------------------+
Example 2 configuration:
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define NUM_ROWS 4 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS 1 // Number of INDIVIDUAL PANELS per ROW
virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true);
virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true);
= 128x64 px virtual display, with the top left of panel 1 being pixel co-ord (0,0)
==========================================================
Example 3 panel chaining (bottom up):
+-----------------+-----------------+
| 64x32px PANEL 4 | 64x32px PANEL 3 |
| <---------- |
| [OUT] [IN] | [OUT] [in] |
+-----------------+-----------------+
| 64x32px PANEL 1 | 64x32px PANEL 2 |
| ----------> |
| [ESP IN] [OUT] | [IN] [OUT] |
+-----------------+-----------------+
Example 1 configuration:
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, false); /* Configure the serpetine chaining approach. Options are:
CHAIN_TOP_LEFT_DOWN
CHAIN_TOP_RIGHT_DOWN
CHAIN_BOTTOM_LEFT_UP
CHAIN_BOTTOM_RIGHT_UP
= 128x64 px virtual display, with the top left of panel 4 being pixel co-ord (0,0) The location (i.e. 'TOP_LEFT', 'BOTTOM_RIGHT') etc. refers to the starting point where
the ESP32 is located, and how the chain of panels will 'roll out' from there.
*/ In this example we're using 'CHAIN_BOTTOM_LEFT_UP' which would look like this in the real world:
Chain of 4 x 64x32 panels with the ESP at the BOTTOM_LEFT:
+---------+---------+
| 4 | 3 |
| | |
+---------+---------+
| 1 | 2 |
| (ESP) | |
+---------+---------+
*/
#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_BOTTOM_LEFT_UP
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. // 3) Create the runtime objects to use
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS // placeholder for the matrix object
#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW MatrixPanel_I2S_DMA *dma_display = nullptr;
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
// Change this to your needs, for details on VirtualPanel pls read the PDF! // placeholder for the virtual display object
#define SERPENT true VirtualMatrixPanel *virtualDisp = nullptr;
#define TOPDOWN false
// library includes
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
// placeholder for the virtual display object
VirtualMatrixPanel *virtualDisp = nullptr;
/****************************************************************************** /******************************************************************************
@ -203,37 +112,58 @@ void setup() {
Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
// create VirtualDisplay object based on our newly created dma_display object // create VirtualDisplay object based on our newly created dma_display object
virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN); virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);
// So far so good, so continue // So far so good, so continue
virtualDisp->fillScreen(virtualDisp->color444(0, 0, 0)); virtualDisp->fillScreen(virtualDisp->color444(0, 0, 0));
virtualDisp->drawDisplayTest(); // draw text numbering on each screen to check connectivity virtualDisp->drawDisplayTest(); // draw text numbering on each screen to check connectivity
delay(3000); // delay(1000);
Serial.println("Chain of 64x32 panels for this example:"); Serial.println("Chain of 4x 64x32 panels for this example:");
Serial.println("+--------+---------+"); Serial.println("+---------+---------+");
Serial.println("| 4 | 3 |"); Serial.println("| 4 | 3 |");
Serial.println("| | |"); Serial.println("| | |");
Serial.println("+--------+---------+"); Serial.println("+---------+---------+");
Serial.println("| 1 | 2 |"); Serial.println("| 1 | 2 |");
Serial.println("| (ESP) | |"); Serial.println("| (ESP32) | |");
Serial.println("+--------+---------+"); Serial.println("+---------+---------+");
// draw blue text
virtualDisp->setFont(&FreeSansBold12pt7b); virtualDisp->setFont(&FreeSansBold12pt7b);
virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255)); virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255));
virtualDisp->setTextSize(2); virtualDisp->setTextSize(3);
virtualDisp->setCursor(10, virtualDisp->height()-20); virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2));
virtualDisp->print("ABCD");
// Red text inside red rect (2 pix in from edge) // Red text inside red rect (2 pix in from edge)
virtualDisp->print("1234");
virtualDisp->drawRect(1,1, virtualDisp->width()-2, virtualDisp->height()-2, virtualDisp->color565(255,0,0)); virtualDisp->drawRect(1,1, virtualDisp->width()-2, virtualDisp->height()-2, virtualDisp->color565(255,0,0));
// White line from top left to bottom right // White line from top left to bottom right
virtualDisp->drawLine(0,0, virtualDisp->width()-1, virtualDisp->height()-1, virtualDisp->color565(255,255,255)); virtualDisp->drawLine(0,0, virtualDisp->width()-1, virtualDisp->height()-1, virtualDisp->color565(255,255,255));
virtualDisp->drawDisplayTest(); // re draw text numbering on each screen to check connectivity
} }
void loop() { void loop() {
} // end loop } // end loop
/*****************************************************************************
Thanks to:
* Brian Lough for the original example as raised in this issue:
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26
YouTube: https://www.youtube.com/brianlough
Tindie: https://www.tindie.com/stores/brianlough/
Twitter: https://twitter.com/witnessmenow
* Galaxy-Man for the kind donation of panels make/test that this is possible:
https://github.com/Galaxy-Man
*****************************************************************************/

View file

@ -10,12 +10,12 @@ Non-standard order is essentially the creation of a non-horizontal-only display
For example: You bought four (4) 64x32px panels, and wanted to use them to create a 128x64pixel display. You would use the VirtualMatrixPanel class. For example: You bought four (4) 64x32px panels, and wanted to use them to create a 128x64pixel display. You would use the VirtualMatrixPanel class.
[Refer to this document](VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. [Refer to this document](../doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use.
### Steps to Use ### ### Steps to Use ###
1. [Refer to this document](VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. 1. [Refer to this document](../doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use.
2. In your Arduino sketch, configure these defines accordingly: 2. In your Arduino sketch, configure these defines accordingly:
``` ```
@ -24,6 +24,11 @@ For example: You bought four (4) 64x32px panels, and wanted to use them to creat
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
#define VIRTUAL_MATRIX_CHAIN_TYPE <INSERT CHAINING TYPE HERE - Refer to documentation or example>
``` ```
3. In your Arduino sketch, use the 'VirtualMatrixPanel' class instance (virtualDisp) to draw to the display (i.e. drawPixel), instead of the underling MatrixPanel_I2S_DMA class instance (dma_display). 3. In your Arduino sketch, use the 'VirtualMatrixPanel' class instance (virtualDisp) to draw to the display (i.e. drawPixel), instead of the underling MatrixPanel_I2S_DMA class instance (dma_display).

View file

@ -30,6 +30,8 @@
#include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSansBold12pt7b.h>
#endif #endif
#include <iostream>
struct VirtualCoords struct VirtualCoords
{ {
int16_t x; int16_t x;
@ -49,6 +51,15 @@ enum PANEL_SCAN_RATE
FOUR_SCAN_16PX_HIGH FOUR_SCAN_16PX_HIGH
}; };
// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels.
enum PANEL_CHAIN_TYPE
{
CHAIN_TOP_LEFT_DOWN,
CHAIN_TOP_RIGHT_DOWN,
CHAIN_BOTTOM_LEFT_UP,
CHAIN_BOTTOM_RIGHT_UP
};
#ifdef USE_GFX_ROOT #ifdef USE_GFX_ROOT
class VirtualMatrixPanel : public GFX class VirtualMatrixPanel : public GFX
#elif !defined NO_GFX #elif !defined NO_GFX
@ -59,20 +70,8 @@ class VirtualMatrixPanel
{ {
public: public:
int16_t virtualResX;
int16_t virtualResY;
int16_t vmodule_rows; VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, PANEL_CHAIN_TYPE _panel_chain_type = CHAIN_TOP_RIGHT_DOWN)
int16_t vmodule_cols;
int16_t panelResX;
int16_t panelResY;
int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
MatrixPanel_I2S_DMA *display;
VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, bool serpentine_chain = true, bool top_down_chain = false)
#ifdef USE_GFX_ROOT #ifdef USE_GFX_ROOT
: GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) : GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY)
#elif !defined NO_GFX #elif !defined NO_GFX
@ -81,6 +80,8 @@ public:
{ {
this->display = &disp; this->display = &disp;
panel_chain_type = _panel_chain_type;
panelResX = _panelResX; panelResX = _panelResX;
panelResY = _panelResY; panelResY = _panelResY;
@ -90,7 +91,7 @@ public:
virtualResX = vmodule_cols * _panelResX; virtualResX = vmodule_cols * _panelResX;
virtualResY = vmodule_rows * _panelResY; virtualResY = vmodule_rows * _panelResY;
dmaResX = panelResX * vmodule_rows * vmodule_cols; dmaResX = panelResX * vmodule_rows * vmodule_cols - 1;
/* Virtual Display width() and height() will return a real-world value. For example: /* Virtual Display width() and height() will return a real-world value. For example:
* Virtual Display width: 128 * Virtual Display width: 128
@ -99,9 +100,6 @@ public:
* So, not values that at 0 to X-1 * So, not values that at 0 to X-1
*/ */
_s_chain_party = serpentine_chain; // serpentine, or 'S' chain?
_chain_top_down = top_down_chain;
coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
} }
@ -129,15 +127,27 @@ public:
void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate); void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate);
protected: private:
MatrixPanel_I2S_DMA *display;
PANEL_CHAIN_TYPE panel_chain_type;
PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN;
virtual VirtualCoords getCoords(int16_t &x, int16_t &y); virtual VirtualCoords getCoords(int16_t &x, int16_t &y);
VirtualCoords coords; VirtualCoords coords;
bool _s_chain_party = true; // Are we chained? Ain't no party like a... int16_t virtualResX;
bool _chain_top_down = false; // is the ESP at the top or bottom of the matrix of devices? int16_t virtualResY;
bool _rotate = false;
PANEL_SCAN_RATE _panelScanRate = NORMAL_TWO_SCAN; int16_t vmodule_rows;
int16_t vmodule_cols;
int16_t panelResX;
int16_t panelResY;
int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
bool _rotate = false;
}; // end Class header }; // end Class header
@ -146,71 +156,141 @@ protected:
* Updates the private class member variable 'coords', so no need to use the return value. * Updates the private class member variable 'coords', so no need to use the return value.
* Not thread safe, but not a concern for ESP32 sketch anyway... I think. * Not thread safe, but not a concern for ESP32 sketch anyway... I think.
*/ */
inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y) inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &virt_x, int16_t &virt_y)
{ {
// Serial.println("Called Base."); coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
// Do we want to rotate? if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY)
if (_rotate) { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range!
{ return coords;
int16_t temp_x = x; }
x = y;
y = virtualResY - 1 - temp_x;
}
if (x < 0 || x >= virtualResX || y < 0 || y >= virtualResY) // Do we want to rotate?
{ // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! if (_rotate)
// Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
// Stupidity check
if ((vmodule_rows == 1) && (vmodule_cols == 1)) // single panel...
{
coords.x = x;
coords.y = y;
}
else
{
uint8_t row = (y / panelResY) + 1; // a non indexed 0 row number
if ((_s_chain_party && !_chain_top_down && (row % 2 == 0)) // serpentine vertically stacked chain starting from bottom row (i.e. ESP closest to ground), upwards
||
(_s_chain_party && _chain_top_down && (row % 2 != 0)) // serpentine vertically stacked chain starting from the sky downwards
)
{ {
// First portion gets you to the correct offset for the row you need int16_t temp_x = virt_x;
// Second portion inverts the x on the row virt_x = virt_y;
coords.x = ((y / panelResY) * (virtualResX)) + (virtualResX - x) - 1; virt_y = virtualResY - 1 - temp_x;
// inverts the y the row
coords.y = panelResY - 1 - (y % panelResY);
} }
else
{
// Normal chain pixel co-ordinate
coords.x = x + ((y / panelResY) * (virtualResX));
coords.y = y % panelResY;
}
}
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT int row = (virt_y / panelResY); // 0 indexed
if (_chain_top_down) switch(panel_chain_type)
{ {
/*
const HUB75_I2S_CFG _cfg = this->display->getCfg(); case (CHAIN_TOP_RIGHT_DOWN):
coords.x = (_cfg.mx_width * _cfg.chain_length - 1) - coords.x; {
coords.y = (_cfg.mx_height-1) - coords.y; if ( (row % 2) == 1 )
*/ { // upside down panel
coords.x = (dmaResX - 1) - coords.x;
coords.y = (panelResY - 1) - coords.y; //Serial.printf("Condition 1, row %d ", row);
}
// refersed for the row
coords.x = dmaResX - virt_x - (row*virtualResX);
// y co-ord inverted within the panel
coords.y = panelResY - 1 - (virt_y % panelResY);
}
else
{
//Serial.printf("Condition 2, row %d ", row);
coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x;
coords.y = virt_y % panelResY;
}
}
break;
case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN
{
if ( (row % 2) == 0 )
{ // refersed panel
//Serial.printf("Condition 1, row %d ", row);
coords.x = dmaResX - virt_x - (row*virtualResX);
// y co-ord inverted within the panel
coords.y = panelResY - 1 - (virt_y % panelResY);
}
else
{
//Serial.printf("Condition 2, row %d ", row);
coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x;
coords.y = virt_y % panelResY;
}
}
break;
case (CHAIN_BOTTOM_LEFT_UP): //
{
row = vmodule_rows - row - 1;
if ( (row % 2) == 1 )
{
// Serial.printf("Condition 1, row %d ", row);
coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x;
coords.y = virt_y % panelResY;
}
else
{ // inverted panel
// Serial.printf("Condition 2, row %d ", row);
coords.x = dmaResX - (row*virtualResX) - virt_x;
coords.y = panelResY - 1 - (virt_y % panelResY);
}
}
break;
case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP
{
row = vmodule_rows - row - 1;
if ( (row % 2) == 0 )
{ // right side up
// Serial.printf("Condition 1, row %d ", row);
// refersed for the row
coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x;
coords.y = virt_y % panelResY;
}
else
{ // inverted panel
// Serial.printf("Condition 2, row %d ", row);
coords.x = dmaResX - (row*virtualResX) - virt_x;
coords.y = panelResY - 1 - (virt_y % panelResY);
}
}
break;
default:
return coords;
break;
} // end switch
/* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the
* the underlying hardware library is designed for (because * the underlying hardware library is designed for (because
* there's only 2 x RGB pins... and convert this to 1/4 or something * there's only 2 x RGB pins... and convert this to 1/4 or something
*/ */
if (_panelScanRate == FOUR_SCAN_32PX_HIGH) if (panel_scan_rate == FOUR_SCAN_32PX_HIGH)
{ {
/* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at
on the panel or chain of panels, per the chaining configuration) to a 1/8 panels on the panel or chain of panels, per the chaining configuration) to a 1/8 panels
@ -221,12 +301,7 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
as if the panel is 2 * W and 0.5 * H ! as if the panel is 2 * W and 0.5 * H !
*/ */
/* if ((virt_y & 8) == 0)
Serial.print("VirtualMatrixPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") ");
// to
Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") ");
*/
if ((y & 8) == 0)
{ {
coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
} }
@ -238,17 +313,12 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
// http://cpp.sh/4ak5u // http://cpp.sh/4ak5u
// Real number of DMA y rows is half reality // Real number of DMA y rows is half reality
// coords.y = (y / 16)*8 + (y & 0b00000111); // coords.y = (y / 16)*8 + (y & 0b00000111);
coords.y = (y >> 4) * 8 + (y & 0b00000111); coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111);
/*
Serial.print("OneEightScanPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") ");
// to
Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") ");
*/
} }
else if (_panelScanRate == FOUR_SCAN_16PX_HIGH) else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH)
{ {
if ((y & 8) == 0) if ((virt_y & 8) == 0)
{ {
coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
} }
@ -257,22 +327,22 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
} }
if (y < 32) if (virt_y < 32)
coords.y = (y >> 4) * 8 + (y & 0b00000111); coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111);
else else
{ {
coords.y = ((y - 32) >> 4) * 8 + (y & 0b00000111); coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111);
coords.x += 256; coords.x += 256;
} }
} }
// Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC);
return coords; return coords;
} }
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{ // adafruit virtual void override { // adafruit virtual void override
getCoords(x, y); getCoords(x, y);
// Serial.printf("Requested virtual x,y coord (%d, %d), got phyical chain coord of (%d,%d)\n", x,y, coords.x, coords.y);
this->display->drawPixel(coords.x, coords.y, color); this->display->drawPixel(coords.x, coords.y, color);
} }
@ -325,7 +395,7 @@ inline void VirtualMatrixPanel::setRotate(bool rotate)
inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate) inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate)
{ {
_panelScanRate = rate; panel_scan_rate = rate;
} }
#ifndef NO_GFX #ifndef NO_GFX
@ -339,7 +409,7 @@ inline void VirtualMatrixPanel::drawDisplayTest()
{ {
int top_left_x = (panel == 0) ? 0 : (panel * panelResX); int top_left_x = (panel == 0) ? 0 : (panel * panelResX);
this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0)); this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0));
this->display->setCursor(panel * panelResX, panelResY - 3); this->display->setCursor( (panel * panelResX)+2, panelResY - 4);
this->display->print((vmodule_cols * vmodule_rows) - panel); this->display->print((vmodule_cols * vmodule_rows) - panel);
} }
} }

View file

@ -0,0 +1,363 @@
#ifndef _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA
#define _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA
/*******************************************************************
Class contributed by Brian Lough, and expanded by Faptastic.
Originally designed to allow CHAINING of panels together to create
a 'bigger' display of panels. i.e. Chaining 4 panels into a 2x2
grid.
However, the function of this class has expanded now to also manage
the output for
1) TWO scan panels = Two rows updated in parallel.
* 64px high panel = sometimes referred to as 1/32 scan
* 32px high panel = sometimes referred to as 1/16 scan
* 16px high panel = sometimes referred to as 1/8 scan
2) FOUR scan panels = Four rows updated in parallel
* 32px high panel = sometimes referred to as 1/8 scan
* 16px high panel = sometimes referred to as 1/4 scan
YouTube: https://www.youtube.com/brianlough
Tindie: https://www.tindie.com/stores/brianlough/
Twitter: https://twitter.com/witnessmenow
*******************************************************************/
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
#ifndef NO_GFX
#include <Fonts/FreeSansBold12pt7b.h>
#endif
struct VirtualCoords
{
int16_t x;
int16_t y;
int16_t virt_row; // chain of panels row
int16_t virt_col; // chain of panels col
VirtualCoords() : x(0), y(0)
{
}
};
enum PANEL_SCAN_RATE
{
NORMAL_TWO_SCAN, NORMAL_ONE_SIXTEEN, // treated as the same
FOUR_SCAN_32PX_HIGH,
FOUR_SCAN_16PX_HIGH
};
#ifdef USE_GFX_ROOT
class VirtualMatrixPanel : public GFX
#elif !defined NO_GFX
class VirtualMatrixPanel : public Adafruit_GFX
#else
class VirtualMatrixPanel
#endif
{
public:
int16_t virtualResX;
int16_t virtualResY;
int16_t vmodule_rows;
int16_t vmodule_cols;
int16_t panelResX;
int16_t panelResY;
int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
MatrixPanel_I2S_DMA *display;
VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, bool serpentine_chain = true, bool top_down_chain = false)
#ifdef USE_GFX_ROOT
: GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY)
#elif !defined NO_GFX
: Adafruit_GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY)
#endif
{
this->display = &disp;
panelResX = _panelResX;
panelResY = _panelResY;
vmodule_rows = _vmodule_rows;
vmodule_cols = _vmodule_cols;
virtualResX = vmodule_cols * _panelResX;
virtualResY = vmodule_rows * _panelResY;
dmaResX = panelResX * vmodule_rows * vmodule_cols;
/* Virtual Display width() and height() will return a real-world value. For example:
* Virtual Display width: 128
* Virtual Display height: 64
*
* So, not values that at 0 to X-1
*/
_s_chain_party = serpentine_chain; // serpentine, or 'S' chain?
_chain_top_down = top_down_chain;
coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
}
// equivalent methods of the matrix library so it can be just swapped out.
virtual void drawPixel(int16_t x, int16_t y, uint16_t color);
virtual void fillScreen(uint16_t color); // overwrite adafruit implementation
virtual void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b);
void clearScreen() { display->clearScreen(); }
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
#ifdef USE_GFX_ROOT
// 24bpp FASTLED CRGB colour struct support
void fillScreen(CRGB color);
void drawPixel(int16_t x, int16_t y, CRGB color);
#endif
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return display->color444(r, g, b); }
uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); }
uint16_t color333(uint8_t r, uint8_t g, uint8_t b) { return display->color333(r, g, b); }
void flipDMABuffer() { display->flipDMABuffer(); }
void drawDisplayTest();
void setRotate(bool rotate);
void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate);
protected:
virtual VirtualCoords getCoords(int16_t &x, int16_t &y);
VirtualCoords coords;
bool _s_chain_party = true; // Are we chained? Ain't no party like a...
bool _chain_top_down = false; // is the ESP at the top or bottom of the matrix of devices?
bool _rotate = false;
PANEL_SCAN_RATE _panelScanRate = NORMAL_TWO_SCAN;
}; // end Class header
/**
* Calculate virtual->real co-ordinate mapping to underlying single chain of panels connected to ESP32.
* Updates the private class member variable 'coords', so no need to use the return value.
* Not thread safe, but not a concern for ESP32 sketch anyway... I think.
*/
inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
{
// Serial.println("Called Base.");
coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
// Do we want to rotate?
if (_rotate)
{
int16_t temp_x = x;
x = y;
y = virtualResY - 1 - temp_x;
}
if (x < 0 || x >= virtualResX || y < 0 || y >= virtualResY)
{ // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range!
// Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
// Stupidity check
if ((vmodule_rows == 1) && (vmodule_cols == 1)) // single panel...
{
coords.x = x;
coords.y = y;
}
else
{
uint8_t row = (y / panelResY) + 1; // a non indexed 0 row number
if ((_s_chain_party && !_chain_top_down && (row % 2 == 0)) // serpentine vertically stacked chain starting from bottom row (i.e. ESP closest to ground), upwards
||
(_s_chain_party && _chain_top_down && (row % 2 != 0)) // serpentine vertically stacked chain starting from the sky downwards
)
{
// First portion gets you to the correct offset for the row you need
// Second portion inverts the x on the row
coords.x = ((y / panelResY) * (virtualResX)) + (virtualResX - x) - 1;
// inverts the y the row
coords.y = panelResY - 1 - (y % panelResY);
}
else
{
// Normal chain pixel co-ordinate
coords.x = x + ((y / panelResY) * (virtualResX));
coords.y = y % panelResY;
}
}
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT
if (_chain_top_down)
{
/*
const HUB75_I2S_CFG _cfg = this->display->getCfg();
coords.x = (_cfg.mx_width * _cfg.chain_length - 1) - coords.x;
coords.y = (_cfg.mx_height-1) - coords.y;
*/
coords.x = (dmaResX - 1) - coords.x;
coords.y = (panelResY - 1) - coords.y;
}
/* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the
* the underlying hardware library is designed for (because
* there's only 2 x RGB pins... and convert this to 1/4 or something
*/
if (_panelScanRate == FOUR_SCAN_32PX_HIGH)
{
/* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at
on the panel or chain of panels, per the chaining configuration) to a 1/8 panels
double 'stretched' and 'squished' coordinates which is what needs to be sent from the
DMA buffer.
Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup
as if the panel is 2 * W and 0.5 * H !
*/
/*
Serial.print("VirtualMatrixPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") ");
// to
Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") ");
*/
if ((y & 8) == 0)
{
coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
}
else
{
coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
}
// http://cpp.sh/4ak5u
// Real number of DMA y rows is half reality
// coords.y = (y / 16)*8 + (y & 0b00000111);
coords.y = (y >> 4) * 8 + (y & 0b00000111);
/*
Serial.print("OneEightScanPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") ");
// to
Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") ");
*/
}
else if (_panelScanRate == FOUR_SCAN_16PX_HIGH)
{
if ((y & 8) == 0)
{
coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
}
else
{
coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
}
if (y < 32)
coords.y = (y >> 4) * 8 + (y & 0b00000111);
else
{
coords.y = ((y - 32) >> 4) * 8 + (y & 0b00000111);
coords.x += 256;
}
}
// Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC);
return coords;
}
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{ // adafruit virtual void override
getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color);
}
inline void VirtualMatrixPanel::fillScreen(uint16_t color)
{ // adafruit virtual void override
this->display->fillScreen(color);
}
inline void VirtualMatrixPanel::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b)
{
this->display->fillScreenRGB888(r, g, b);
}
inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b)
{
getCoords(x, y);
this->display->drawPixelRGB888(coords.x, coords.y, r, g, b);
}
#ifdef USE_GFX_ROOT
// Support for CRGB values provided via FastLED
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, CRGB color)
{
getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color);
}
inline void VirtualMatrixPanel::fillScreen(CRGB color)
{
this->display->fillScreen(color);
}
#endif
inline void VirtualMatrixPanel::setRotate(bool rotate)
{
_rotate = rotate;
#ifndef NO_GFX
// We don't support rotation by degrees.
if (rotate)
{
setRotation(1);
}
else
{
setRotation(0);
}
#endif
}
inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate)
{
_panelScanRate = rate;
}
#ifndef NO_GFX
inline void VirtualMatrixPanel::drawDisplayTest()
{
this->display->setFont(&FreeSansBold12pt7b);
this->display->setTextColor(this->display->color565(255, 255, 0));
this->display->setTextSize(1);
for (int panel = 0; panel < vmodule_cols * vmodule_rows; panel++)
{
int top_left_x = (panel == 0) ? 0 : (panel * panelResX);
this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0));
this->display->setCursor(panel * panelResX, panelResY - 3);
this->display->print((vmodule_cols * vmodule_rows) - panel);
}
}
#endif
/*
// need to recreate this one, as it wouldn't work to just map where it starts.
inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows) {
int i, j;
for (i = 0; i < icon_rows; i++) {
for (j = 0; j < icon_cols; j++) {
// This is a call to this libraries version of drawPixel
// which will map each pixel, which is what we want.
//drawPixelRGB565 (x + j, y + i, ico[i * module_cols + j]);
drawPixel (x + j, y + i, ico[i * icon_cols + j]);
}
}
}
*/
#endif