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
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
(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
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()
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>
/******************************************************************************
* 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:
// 2) Set 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 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.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
// 3) Create the runtime objects to use
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
#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
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
// Change this to your needs, for details on VirtualPanel pls read the PDF!
#define SERPENT true
#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;
// placeholder for the virtual display object
VirtualMatrixPanel *virtualDisp = nullptr;
/******************************************************************************
@ -203,37 +112,58 @@ void setup() {
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
// 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
virtualDisp->fillScreen(virtualDisp->color444(0, 0, 0));
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("+--------+---------+");
Serial.println("Chain of 4x 64x32 panels for this example:");
Serial.println("+---------+---------+");
Serial.println("| 4 | 3 |");
Serial.println("| | |");
Serial.println("+--------+---------+");
Serial.println("+---------+---------+");
Serial.println("| 1 | 2 |");
Serial.println("| (ESP) | |");
Serial.println("+--------+---------+");
Serial.println("| (ESP32) | |");
Serial.println("+---------+---------+");
// draw blue text
virtualDisp->setFont(&FreeSansBold12pt7b);
virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255));
virtualDisp->setTextSize(2);
virtualDisp->setCursor(10, virtualDisp->height()-20);
virtualDisp->setTextSize(3);
virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2));
virtualDisp->print("ABCD");
// 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));
// White line from top left to bottom right
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() {
} // 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.
[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 ###
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:
```
@ -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_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).

View file

@ -30,6 +30,8 @@
#include <Fonts/FreeSansBold12pt7b.h>
#endif
#include <iostream>
struct VirtualCoords
{
int16_t x;
@ -49,6 +51,15 @@ enum PANEL_SCAN_RATE
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
class VirtualMatrixPanel : public GFX
#elif !defined NO_GFX
@ -59,20 +70,8 @@ class VirtualMatrixPanel
{
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)
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)
#ifdef USE_GFX_ROOT
: GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY)
#elif !defined NO_GFX
@ -81,6 +80,8 @@ public:
{
this->display = &disp;
panel_chain_type = _panel_chain_type;
panelResX = _panelResX;
panelResY = _panelResY;
@ -90,7 +91,7 @@ public:
virtualResX = vmodule_cols * _panelResX;
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: 128
@ -99,9 +100,6 @@ public:
* 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
}
@ -129,15 +127,27 @@ public:
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);
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;
int16_t virtualResX;
int16_t virtualResY;
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
@ -146,71 +156,141 @@ protected:
* 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)
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
if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY)
{ // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range!
return coords;
}
// Do we want to rotate?
if (_rotate)
{
int16_t temp_x = x;
x = y;
y = virtualResY - 1 - temp_x;
int16_t temp_x = virt_x;
virt_x = virt_y;
virt_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);
int row = (virt_y / panelResY); // 0 indexed
switch(panel_chain_type)
{
case (CHAIN_TOP_RIGHT_DOWN):
{
if ( (row % 2) == 1 )
{ // upside down panel
//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;
// 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;
} // end switch
// 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)
if (panel_scan_rate == 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
@ -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 !
*/
/*
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)
if ((virt_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
}
@ -238,17 +313,12 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
// 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);
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
}
@ -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
}
if (y < 32)
coords.y = (y >> 4) * 8 + (y & 0b00000111);
if (virt_y < 32)
coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111);
else
{
coords.y = ((y - 32) >> 4) * 8 + (y & 0b00000111);
coords.y = ((virt_y - 32) >> 4) * 8 + (virt_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);
// 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);
}
@ -325,7 +395,7 @@ inline void VirtualMatrixPanel::setRotate(bool rotate)
inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate)
{
_panelScanRate = rate;
panel_scan_rate = rate;
}
#ifndef NO_GFX
@ -339,7 +409,7 @@ inline void VirtualMatrixPanel::drawDisplayTest()
{
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->setCursor( (panel * panelResX)+2, panelResY - 4);
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