diff --git a/ESP32-VirtualMatrixPanel-I2S-DMA.h b/ESP32-VirtualMatrixPanel-I2S-DMA.h index c42b962..151a485 100644 --- a/ESP32-VirtualMatrixPanel-I2S-DMA.h +++ b/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -2,13 +2,16 @@ #define _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA /******************************************************************* - Contributed by Brian Lough + Class contributed by Brian Lough, and expanded by Faptastic. + YouTube: https://www.youtube.com/brianlough Tindie: https://www.tindie.com/stores/brianlough/ Twitter: https://twitter.com/witnessmenow *******************************************************************/ #include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h" +#include + struct VirtualCoords { int16_t x; @@ -30,34 +33,40 @@ class VirtualMatrixPanel : public Adafruit_GFX int16_t module_rows; int16_t module_cols; - int16_t screenResX; - int16_t screenResY; + int16_t panelResX; + int16_t panelResY; RGB64x32MatrixPanel_I2S_DMA *display; #ifdef USE_GFX_ROOT - VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int screenX, int screenY) - : GFX(vmodule_cols*screenX, vmodule_rows*screenY) + VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int panelX, int panelY, bool serpentine_chain = true, bool top_down_chain = false) + : GFX(vmodule_cols*panelX, vmodule_rows*panelY) #else - VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int screenX, int screenY) - : Adafruit_GFX(vmodule_cols*screenX, vmodule_rows*screenY) + VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int panelX, int panelY, bool serpentine_chain = true, bool top_down_chain = false ) + : Adafruit_GFX(vmodule_cols*panelX, vmodule_rows*panelY) #endif { this->display = &disp; + + panelResX = panelX; + panelResY = panelY; + module_rows = vmodule_rows; module_cols = vmodule_cols; - screenResX = screenX; - screenResY = screenY; - virtualResX = vmodule_rows*screenY; - virtualResY = vmodule_cols*screenX; + virtualResY = vmodule_rows*panelY; + virtualResX = vmodule_cols*panelX; + + _s_chain_party = serpentine_chain; // serpentine, or 'S' chain? + _chain_top_down= top_down_chain; + } VirtualCoords getCoords(int16_t x, int16_t y); // 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 fillScreen(uint16_t color); // overwrite adafruit implementation void clearScreen() { fillScreen(0); } @@ -76,16 +85,54 @@ class VirtualMatrixPanel : public Adafruit_GFX return display->Color333(r, g, b); } + void drawDisplayTest(); + private: 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? }; // end Class header inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t x, int16_t y) { - int16_t xOffset = (y / screenResY) * (module_cols * screenResX); - coords.x = x + xOffset; - coords.y = y % screenResY; - return coords; + + coords.x = coords.y = 0; + + if (x < 0 || x > module_cols*panelResX || y < 0 || y > module_rows*panelResY ) { + //Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y); + return coords; + } + + 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) * (module_cols * panelResX) + (virtualResX - 1 - x); + + // inverts the y the row + coords.y = panelResY - 1 - (y % panelResY); + } + else + { + coords.x = x + (y / panelResY) * (module_cols * panelResX) ; + coords.y = y % panelResY; + } + + // Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT + if (_chain_top_down) + { + coords.x = (this->display->width()-1) - coords.x; + coords.y = (this->display->height()-1) - coords.y; + } + + //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) @@ -119,6 +166,24 @@ inline void VirtualMatrixPanel::drawPixelRGB24(int16_t x, int16_t y, rgb_24 colo this->display->drawPixelRGB24(coords.x, coords.y, color); } +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 < module_cols*module_rows; panel++ ) { + Serial.print("LOOP for module: "); Serial.println(panel, DEC); + int top_left_x = (panel == 0) ? 0:(panel*panelResX); + Serial.print("Stop start x: "); Serial.println(top_left_x, DEC); + + 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((module_cols*module_rows)-panel); + } + +} + // need to recreate this one, as it wouldnt work to just map where it starts. inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows) { int i, j; diff --git a/examples/ChainedPanels/ChainedPanels.ino b/examples/ChainedPanels/ChainedPanels.ino index ffdb5e2..3f90042 100644 --- a/examples/ChainedPanels/ChainedPanels.ino +++ b/examples/ChainedPanels/ChainedPanels.ino @@ -1,59 +1,40 @@ -/******************************************************************* - This is the PatternPlasma Demo adopted for use with multiple - displays arranged in a non standard order - - What is a non standard order? - - When you connect multiple panels together, the library treats the - multiple panels as one big panel arranged horizontally. Arranging - the displays like this would be a standard order. - - [ 4 ][ 3 ][ 2 ][ 1 ] (ESP32 is connected to 1) - - If you wanted to arrange the displays vertically, or in rows and - columns this example might be able to help. - - [ 4 ][ 3 ] - [ 2 ][ 1 ] - - It creates a virtual screen that you draw to in the same way you would - the matrix, but it will look after mapping it back to the displays. - +/****************************************************************************** ----------- Steps to use ----------- 1) In ESP32-RGB64x32MatrixPanel-I2S-DMA.h: - - Set the MATRIX_HEIGHT to be the y resolution of the physical chained panels in a line (if the panels are 32 x 16, set it to be 16) - - Set the MATRIX_WIDTH to be the sum of the x resolution of all the physical chained panels (i.e. If you have 4 x (32px w x 16px h) panels, 32x4 = 128) + - Set the MATRIX_HEIGHT to be the y resolution of the physical chained + panels in a line (if the panels are 32 x 16, set it to be 16) + - Set the MATRIX_WIDTH to be the sum of the x resolution of all the physical + chained panels (i.e. If you have 4 x (32px w x 16px h) panels, 32x4 = 128) - 2) In the sketch: + 2) In the sketch (i.e. this example): - - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y. 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() + - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y. + 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() - Parts: - ESP32 D1 Mini * - https://s.click.aliexpress.com/e/_dSi824B - ESP32 I2S Matrix Shield (From my Tindie) = https://www.tindie.com/products/brianlough/esp32-i2s-matrix-shield/ + Thanks to: - * * = Affilate + * Brian Lough for the original example as raised in this issue: + https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA/issues/26 - If you find what I do useful and would like to support me, - please consider becoming a sponsor on Github - https://github.com/sponsors/witnessmenow/ + 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 - Written by Brian Lough - YouTube: https://www.youtube.com/brianlough - Tindie: https://www.tindie.com/stores/brianlough/ - Twitter: https://twitter.com/witnessmenow - *******************************************************************/ +*****************************************************************************/ //#define USE_CUSTOM_PINS // uncomment to use custom pins, then provide below @@ -75,36 +56,132 @@ #define OE_PIN 13 -/* Include FastLED, DMA Display (for module driving), and VirtualMatrixPanel - * to enable easy painting & graphics for a chain of individual LED modules. - */ + /****************************************************************************** + * 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_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 + + virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, false); + + = 128x64 px virtual display, with the top left of panel 4 being pixel co-ord (0,0) + +*/ + + -#define NUM_ROWS 2 // Number of rows panels in your overall display -#define NUM_COLS 1 // number of panels in each row #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. -/* Create physical module driver class AND virtual (chained) display class. */ +#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS +#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + + +/****************************************************************************** + * Create physical DMA panel class AND virtual (chained) display class. + ******************************************************************************/ #include RGB64x32MatrixPanel_I2S_DMA dma_display; -VirtualMatrixPanel virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y); +VirtualMatrixPanel virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true); -/* FastLED Global Variables for Pattern */ -#include -int time_counter = 0; -int cycles = 0; -CRGBPalette16 currentPalette; -CRGB currentColor; - -CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { - return ColorFromPalette(currentPalette, index, brightness, blendType); -} +/****************************************************************************** + * Setup! + ******************************************************************************/ void setup() { + delay(2000); Serial.begin(115200); - Serial.println(""); Serial.println(""); Serial.println(""); Serial.println("*****************************************************"); Serial.println(" HELLO !"); @@ -115,44 +192,53 @@ void setup() { #else dma_display.begin(); #endif - - // fill the screen with 'black' - //dma_display.fillScreen(dma_display.color444(0, 0, 0)); + + + + // Sanity checks + if (NUM_ROWS <= 1) { + Serial.println(F("There is no reason to use the VirtualDisplay class for a single horizontal chain and row!")); + } + + if (dma_display.width() != NUM_ROWS*NUM_COLS*PANEL_RES_X ) + { + Serial.println(F("\r\nERROR: MATRIX_WIDTH and/or MATRIX_HEIGHT in 'ESP32-RGB64x32MatrixPanel-I2S-DMA.h'\r\nis not configured correctly for the requested VirtualMatrixPanel dimensions!\r\n")); + Serial.printf("WIDTH according dma_display is %d, but should be %d. Is your NUM_ROWS and NUM_COLS correct?\r\n", dma_display.width(), NUM_ROWS*NUM_COLS*PANEL_RES_X); + return; + } + + // 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); + + Serial.println("Chain of 64x32 panels for this example:"); + Serial.println("+--------+---------+"); + Serial.println("| 4 | 3 |"); + Serial.println("| | |"); + Serial.println("+--------+---------+"); + Serial.println("| 1 | 2 |"); + Serial.println("| (ESP) | |"); + Serial.println("+--------+---------+"); + + virtualDisp.setFont(&FreeSansBold12pt7b); + virtualDisp.setTextColor(virtualDisp.color565(0, 0, 255)); + virtualDisp.setTextSize(2); + virtualDisp.setCursor(10, virtualDisp.height()-20); + + // 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)); - // Set current FastLED palette - currentPalette = RainbowColors_p; - virtualDisp.drawRect(4, 4, PANEL_RES_X * NUM_COLS - 8, PANEL_RES_Y * NUM_ROWS - 8, virtualDisp.color565(255, 255, 255)); - delay(5000); } - void loop() { - for (int x = 0; x < virtualDisp.width(); x++) { - for (int y = 0; y < virtualDisp.height(); y++) { - int16_t v = 0; - uint8_t wibble = sin8(time_counter); - v += sin16(x * wibble * 3 + time_counter); - v += cos16(y * (128 - wibble) + time_counter); - v += sin16(y * x * cos8(-time_counter) / 8); - - currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType); - virtualDisp.drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b); - - } - } - - time_counter += 1; - cycles++; - - if (cycles >= 2048) { - time_counter = 0; - cycles = 0; - } - - } // end loop \ No newline at end of file diff --git a/examples/ChainedPanels/VirtualDisplayGraphic.png b/examples/ChainedPanels/VirtualDisplayGraphic.png index 3573895..449e802 100644 Binary files a/examples/ChainedPanels/VirtualDisplayGraphic.png and b/examples/ChainedPanels/VirtualDisplayGraphic.png differ