diff --git a/doc/Panel_Chaining_Types.ods b/doc/Panel_Chaining_Types.ods new file mode 100644 index 0000000..74748c5 Binary files /dev/null and b/doc/Panel_Chaining_Types.ods differ diff --git a/examples/ChainedPanels/VirtualMatrixPanel.odp b/doc/VirtualMatrixPanel (old).odp similarity index 100% rename from examples/ChainedPanels/VirtualMatrixPanel.odp rename to doc/VirtualMatrixPanel (old).odp diff --git a/doc/VirtualMatrixPanel.odp b/doc/VirtualMatrixPanel.odp new file mode 100644 index 0000000..748a211 Binary files /dev/null and b/doc/VirtualMatrixPanel.odp differ diff --git a/doc/VirtualMatrixPanel.pdf b/doc/VirtualMatrixPanel.pdf new file mode 100644 index 0000000..f6e7157 Binary files /dev/null and b/doc/VirtualMatrixPanel.pdf differ diff --git a/examples/ChainedPanels/ChainedPanels.ino b/examples/ChainedPanels/ChainedPanels.ino index 33379c6..1343d9b 100644 --- a/examples/ChainedPanels/ChainedPanels.ino +++ b/examples/ChainedPanels/ChainedPanels.ino @@ -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. - There are comments beside them explaining what they are in more detail. + - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN_TYPE. + - 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 - - /****************************************************************************** - * 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 - -// 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("| 4 | 3 |"); - Serial.println("| | |"); - Serial.println("+--------+---------+"); - Serial.println("| 1 | 2 |"); - Serial.println("| (ESP) | |"); - Serial.println("+--------+---------+"); + Serial.println("Chain of 4x 64x32 panels for this example:"); + Serial.println("+---------+---------+"); + Serial.println("| 4 | 3 |"); + Serial.println("| | |"); + Serial.println("+---------+---------+"); + Serial.println("| 1 | 2 |"); + 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 + +*****************************************************************************/ \ No newline at end of file diff --git a/examples/ChainedPanels/README.md b/examples/ChainedPanels/README.md index ca55860..bf71095 100644 --- a/examples/ChainedPanels/README.md +++ b/examples/ChainedPanels/README.md @@ -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 + ``` 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). diff --git a/examples/ChainedPanels/VirtualMatrixPanel.pdf b/examples/ChainedPanels/VirtualMatrixPanel.pdf deleted file mode 100644 index db63112..0000000 Binary files a/examples/ChainedPanels/VirtualMatrixPanel.pdf and /dev/null differ diff --git a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h index cf5324a..1f29a30 100644 --- a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h +++ b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -30,6 +30,8 @@ #include #endif +#include + 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 + 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 (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; + } - 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 - ) + // Do we want to rotate? + if (_rotate) { - // 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); + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; } - 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; - } + 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; + + } // end switch + + /* 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); } } diff --git a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h-old b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h-old new file mode 100644 index 0000000..cf5324a --- /dev/null +++ b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h-old @@ -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 +#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