aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormrcodetastic <12006953+mrcodetastic@users.noreply.github.com>2025-02-19 01:39:09 +0000
committerGitHub <noreply@github.com>2025-02-19 01:39:09 +0000
commit35e441e8af48faa2d351a63bad7c55dddaae3014 (patch)
treefaf8f247dbc1760875b0e1d5a4fced3b3df42629
parent3a78a9dfb68c695a4ee194cad992ef6296525d5b (diff)
parent42f5da448fc23ab47689ab67a10251c7b9799c13 (diff)
Merge pull request #757 from mrcodetastic/virtdev
Migrate new VirtualDisplayPanel_T changes to master.
-rw-r--r--README.md2
-rw-r--r--doc/VirtualMatrixPanel (old).odpbin127435 -> 0 bytes
-rw-r--r--doc/VirtualMatrixPanel.odpbin240953 -> 193860 bytes
-rw-r--r--doc/VirtualMatrixPanel.pdfbin1023141 -> 197112 bytes
-rw-r--r--examples/Four_Scan_Panel/Four_Scan_Panel.ino153
-rw-r--r--examples/Four_Scan_Panel/README.md7
-rw-r--r--examples/VirtualMatrixPanel/README.md48
-rw-r--r--examples/VirtualMatrixPanel/VirtualMatrixPanel.ino381
-rw-r--r--src/ESP32-HUB75-MatrixPanel-I2S-DMA.h85
-rw-r--r--src/ESP32-HUB75-VirtualMatrixPanel_T.hpp492
-rw-r--r--src/ESP32-VirtualMatrixPanel-I2S-DMA.h21
-rw-r--r--src/platforms/esp32s3/gdma_lcd_parallel16.cpp2
12 files changed, 758 insertions, 433 deletions
diff --git a/README.md b/README.md
index 023da2c..1c7f548 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ __[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [!
# Introduction
* This is an ESP32 Arduino/IDF library for HUB75 / HUB75E connection based RGB LED panels.
* This library 'out of the box' (mostly) supports HUB75 panels where simple TWO rows/lines are updated in parallel... referred to as 'two scan' panels within this documentation.
-* 'Four scan' panels are also supported - but please refer to the Four Scan Panel example sketch.
+* 1/4 (aka. 'Four Scan') outdoor panels are also supported - but please refer to the VirtualMatrixPanel example.
* The library uses the DMA functionality provided by the ESP32's 'LCD Mode' for fast data output.
## Features
diff --git a/doc/VirtualMatrixPanel (old).odp b/doc/VirtualMatrixPanel (old).odp
deleted file mode 100644
index 4e3a066..0000000
--- a/doc/VirtualMatrixPanel (old).odp
+++ /dev/null
Binary files differ
diff --git a/doc/VirtualMatrixPanel.odp b/doc/VirtualMatrixPanel.odp
index 748a211..ecb226f 100644
--- a/doc/VirtualMatrixPanel.odp
+++ b/doc/VirtualMatrixPanel.odp
Binary files differ
diff --git a/doc/VirtualMatrixPanel.pdf b/doc/VirtualMatrixPanel.pdf
index f6e7157..b8cd845 100644
--- a/doc/VirtualMatrixPanel.pdf
+++ b/doc/VirtualMatrixPanel.pdf
Binary files differ
diff --git a/examples/Four_Scan_Panel/Four_Scan_Panel.ino b/examples/Four_Scan_Panel/Four_Scan_Panel.ino
deleted file mode 100644
index 5086b62..0000000
--- a/examples/Four_Scan_Panel/Four_Scan_Panel.ino
+++ /dev/null
@@ -1,153 +0,0 @@
-/*************************************************************************
- * Description:
- *
- * The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
- * supports output to HALF scan panels - which means outputting
- * two lines at the same time, 16 or 32 rows apart if a 32px or 64px high panel
- * respectively.
- * This cannot be changed at the DMA layer as it would require a messy and complex
- * rebuild of the library's internals.
- *
- * However, it is possible to connect QUARTER (i.e. FOUR lines updated in parallel)
- * scan panels to this same library and
- * 'trick' the output to work correctly on these panels by way of adjusting the
- * pixel co-ordinates that are 'sent' to the ESP32-HUB75-MatrixPanel-I2S-DMA
- * library.
- *
- **************************************************************************/
-#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
-
-/* Use the Virtual Display class to re-map co-ordinates such that they draw
- * correctly on a 32x16 1/8 Scan panel (or chain of such panels).
- */
-#include "ESP32-VirtualMatrixPanel-I2S-DMA.h"
-
-
- // Panel 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 1 // Number of rows of chained INDIVIDUAL PANELS
- #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
-
- // ^^^ NOTE: DEFAULT EXAMPLE SETUP IS FOR A CHAIN OF TWO x 1/8 SCAN PANELS
-
- // Change this to your needs, for details on VirtualPanel pls read the PDF!
- #define SERPENT true
- #define TOPDOWN false
-
- // placeholder for the matrix object
- MatrixPanel_I2S_DMA *dma_display = nullptr;
-
- // placeholder for the virtual display object
- VirtualMatrixPanel *FourScanPanel = nullptr;
-
- /******************************************************************************
- * Setup!
- ******************************************************************************/
- void setup()
- {
- delay(250);
-
- Serial.begin(115200);
- Serial.println(""); Serial.println(""); Serial.println("");
- Serial.println("*****************************************************");
- Serial.println("* 1/8 Scan Panel Demonstration *");
- Serial.println("*****************************************************");
-
-/*
- // 62x32 1/8 Scan Panels don't have a D and E pin!
-
- HUB75_I2S_CFG::i2s_pins _pins = {
- R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN,
- A_PIN, B_PIN, C_PIN, D_PIN, E_PIN,
- LAT_PIN, OE_PIN, CLK_PIN
- };
-*/
- HUB75_I2S_CFG mxconfig(
- PANEL_RES_X*2, // DO NOT CHANGE THIS
- PANEL_RES_Y/2, // DO NOT CHANGE THIS
- NUM_ROWS*NUM_COLS // DO NOT CHANGE THIS
- //,_pins // Uncomment to enable custom pins
- );
-
- mxconfig.clkphase = false; // Change this if you see pixels showing up shifted wrongly by one column the left or right.
-
- //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object
-
- // OK, now we can create our matrix object
- dma_display = new MatrixPanel_I2S_DMA(mxconfig);
-
- // let's adjust default brightness to about 75%
- dma_display->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100%
-
- // Allocate memory and start DMA display
- if( not dma_display->begin() )
- Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
-
-
- dma_display->clearScreen();
- delay(500);
-
- // create FourScanPanellay object based on our newly created dma_display object
- FourScanPanel = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
-
- // THE IMPORTANT BIT BELOW!
- FourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
- }
-
-
- void loop() {
-
- // What the panel sees from the DMA engine!
- for (int i=PANEL_RES_X*2+10; i< PANEL_RES_X*(NUM_ROWS*NUM_COLS)*2; i++)
- {
- dma_display->drawLine(i, 0, i, 7, dma_display->color565(255, 0, 0)); // red
- delay(10);
- }
-
- dma_display->clearScreen();
- delay(1000);
-/*
- // Try again using the pixel / dma memory remapper
- for (int i=PANEL_RES_X+5; i< (PANEL_RES_X*2)-1; i++)
- {
- FourScanPanel->drawLine(i, 0, i, 7, dma_display->color565(0, 0, 255)); // blue
- delay(10);
- }
-*/
-
- // Try again using the pixel / dma memory remapper
- int offset = PANEL_RES_X*((NUM_ROWS*NUM_COLS)-1);
- for (int i=0; i< PANEL_RES_X; i++)
- {
- FourScanPanel->drawLine(i+offset, 0, i+offset, 7, dma_display->color565(0, 0, 255)); // blue
- FourScanPanel->drawLine(i+offset, 8, i+offset, 15, dma_display->color565(0, 128,0)); // g
- FourScanPanel->drawLine(i+offset, 16, i+offset, 23, dma_display->color565(128, 0,0)); // red
- FourScanPanel->drawLine(i+offset, 24, i+offset, 31, dma_display->color565(0, 128, 128)); // blue
- delay(10);
- }
-
- delay(1000);
-
-
- // Print on each chained panel 1/8 module!
- // This only really works for a single horizontal chain
- for (int i = 0; i < NUM_ROWS*NUM_COLS; i++)
- {
- FourScanPanel->setTextColor(FourScanPanel->color565(255, 255, 255));
- FourScanPanel->setCursor(i*PANEL_RES_X+7, FourScanPanel->height()/3);
-
- // Red text inside red rect (2 pix in from edge)
- FourScanPanel->print("Panel " + String(i+1));
- FourScanPanel->drawRect(1,1, FourScanPanel->width()-2, FourScanPanel->height()-2, FourScanPanel->color565(255,0,0));
-
- // White line from top left to bottom right
- FourScanPanel->drawLine(0,0, FourScanPanel->width()-1, FourScanPanel->height()-1, FourScanPanel->color565(255,255,255));
- }
-
- delay(2000);
- dma_display->clearScreen();
-
- } // end loop
diff --git a/examples/Four_Scan_Panel/README.md b/examples/Four_Scan_Panel/README.md
deleted file mode 100644
index dd75176..0000000
--- a/examples/Four_Scan_Panel/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Using this library with 32x16 1/4 Scan Panels
-
-## Problem
-ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 'Four Scan' 64x32 1/8 or 32x16 1/4 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default.
-
-## Solution
-It is possible to connect 1/8 scan panels to this library and 'trick' the output to work correctly on these panels by way of adjusting the pixel co-ordinates that are 'sent' to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class).
diff --git a/examples/VirtualMatrixPanel/README.md b/examples/VirtualMatrixPanel/README.md
index c5aca88..7012f3f 100644
--- a/examples/VirtualMatrixPanel/README.md
+++ b/examples/VirtualMatrixPanel/README.md
@@ -1,4 +1,9 @@
-## Chained Panels example - Chaining individual LED matrix panels to make a larger panel ##
+# The 'VirtualMatrixPanel_T' class
+The `VirtualMatrixPanel_T` is used to perform pixel re-mapping in order to support the following use-cases that can be used together:
+1. To create a larger display based on a chain of individual physical panels connected electrically in a Serpentine or Zig-Zag manner.
+2. To provide support for physical panels with non-standard (i.e. Not a 1/2 scan panel) pixel mapping approaches. This is often seen with 1/4 scan outdoor panels.
+
+## 1. Chaining individual LED matrix panels to make a larger virtual display ##
This is the PatternPlasma Demo adopted for use with multiple LED Matrix Panel displays arranged in a non standard order (i.e. a grid) to make a bigger display.
@@ -19,33 +24,34 @@ For example: You bought four (4) 64x32px panels, and wanted to use them to creat
1. [Refer to this document](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use.
-2. In your Arduino sketch, configure these defines accordingly:
-```
-#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.
+2. Read the `VirtualMatrixPanel.ino` code
-#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
-#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
+## 2. Using this library with 1/4 Scan Panels (Four Scan)
-#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
+This library does not natively support 'Four Scan' 64x32 1/8 or 32x16 1/4 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default.
-#define VIRTUAL_MATRIX_CHAIN_TYPE <INSERT CHAINING TYPE HERE - Refer to documentation or example>
-
-```
-VIRTUAL_MATRIX_CHAIN_TYPE's:
-![image](https://user-images.githubusercontent.com/12006953/224537356-e3c8e87b-0bc0-4185-8f5d-d2d3b328d176.png)
+### Solution
+Read the `VirtualMatrixPanel.ino` code.
+The VirtualMatrixPanel_T class provides a way to additionally remap pixel for each individual panel by way of the `ScanTypeMapping` class.
-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).
+You can create your own custom per-panel pixel mapping class as well should you wish.
+```cpp
+// --- Example 3: Single non-standard 1/4 Scan (Four-Scan 1/8) ---
-#### Thanks to ####
-* Brian Lough for the Virtual to Real pixel co-ordinate code.
+// Use an existing library user-contributed Scan Type pixel mapping
+using MyScanTypeMapping = ScanTypeMapping<FOUR_SCAN_32PX_HIGH>;
-YouTube: https://www.youtube.com/brianlough
-
-Tindie: https://www.tindie.com/stores/brianlough/
+// Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
+VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>* virtualDisp = nullptr;
+```
-Twitter: https://twitter.com/witnessmenow
+The library has these user-contributed additions, but given the variety of panels on the market, your success with any of these may vary.
-* Galaxy-Man for the donation of hardware for testing.
+```cpp
+ FOUR_SCAN_32PX_HIGH, ///< Four-scan mode, 32-pixel high panels.
+ FOUR_SCAN_16PX_HIGH, ///< Four-scan mode, 16-pixel high panels.
+ FOUR_SCAN_64PX_HIGH, ///< Four-scan mode, 64-pixel high panels.
+ FOUR_SCAN_40PX_HIGH ///< Four-scan mode, 40-pixel high panels.
+``` \ No newline at end of file
diff --git a/examples/VirtualMatrixPanel/VirtualMatrixPanel.ino b/examples/VirtualMatrixPanel/VirtualMatrixPanel.ino
index 1343d9b..787df1f 100644
--- a/examples/VirtualMatrixPanel/VirtualMatrixPanel.ino
+++ b/examples/VirtualMatrixPanel/VirtualMatrixPanel.ino
@@ -1,169 +1,218 @@
-/******************************************************************************
- -------------------------------------------------------------------------
- Steps to create a virtual display made up of a chain of panels in a grid
- -------------------------------------------------------------------------
-
- 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.
-
- - 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()
-
-*****************************************************************************/
-// 1) Include key virtual display library
- #include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
-
-// 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
-
- /* Configure the serpetine chaining approach. Options are:
- CHAIN_TOP_LEFT_DOWN
- CHAIN_TOP_RIGHT_DOWN
- CHAIN_BOTTOM_LEFT_UP
- CHAIN_BOTTOM_RIGHT_UP
-
- 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) | |
- +---------+---------+
-
+/**
+ * @file VirtualMatrixPanel.ino
+ * @brief Example of using the VirtualMatrixPanel_T template class.
+ *
+ * The VirtualMatrixPanel_T class can be used for two purposes:
+ *
+ * 1) Create a much larger display out of a number of physical LED panels
+ * chained in a Serpentine or Zig-Zag manner;
+ *
+ * 2) Provide a way to deal with weird individual physical panels that do not have a
+ * simple linear X, Y pixel mapping. For example, 1/4 scan panels, or outdoor panels.
+ *
+ * 1) and 2) can be combined and utilsied together.
+ *
+ * There are THREE examples contained within this library. What example gets built depends
+ * on the value of the "#define EXAMPLE_NUMBER X" value. Where X = Example number.
+ *
+ * Example 1: STANDARD 1/2 Scan (i.e. 1/16, 1/32) LED matrix panels, 64x32 pixels each,
+ * in a grid of 2x2 panels, chained in a Serpentine manner.
+ *
+ * Example 2: Non-Standard 1/4 Scan (i.e. Four-Scan 1/8) outdoor LED matrix panels, 64x32 pixels each,
+ * in a grid of 2x2 panels, chained in a Serpentine manner.
+ *
+ * Example 3: A single non-standard 1/4 Scan (i.e. Four-Scan 1/8) outdoor LED matrix panel, 64x32 pixels.
+ */
+
+ #include <Arduino.h>
+ #include <ESP32-HUB75-VirtualMatrixPanel_T.hpp>
+
+ // Select example to compile!
+ #define EXAMPLE_NUMBER 1
+ //#define EXAMPLE_NUMBER 2
+ //#define EXAMPLE_NUMBER 3
+
+ /**
+ * Configuration of the LED matrix panels number and individual pixel resolution.
+ **/
+ #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 VDISP_NUM_ROWS 2 // Number of rows of individual LED panels
+ #define VDISP_NUM_COLS 2 // Number of individual LED panels per row
+
+ #define PANEL_CHAIN_LEN (VDISP_NUM_ROWS*VDISP_NUM_COLS) // Don't change
+
+
+ /**
+ * Configuration of the approach used to chain all the individual panels together.
+ * Refer to the documentation or check the enum 'PANEL_CHAIN_TYPE' in VirtualMatrixPanel_T.hpp for options.
+ **/
+ #define PANEL_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN
+
+ /**
+ * Optional config for the per-panel pixel mapping, for non-standard panels.
+ * i.e. 1/4 scan panels, or outdoor panels. They're a pain in the a-- and all
+ * have their own weird pixel mapping that is not linear.
+ *
+ * This is used for Examples 2 and 3.
+ *
+ **/
+ #define PANEL_SCAN_TYPE FOUR_SCAN_32PX_HIGH
+
+ /**
+ * Mandatory declaration of the dma_display. DO NOT CHANGE
+ **/
+ MatrixPanel_I2S_DMA *dma_display = nullptr;
+
+
+ /**
+ * Template instantiation for the VirtualMatrixPanel_T class, depending on use-case.
+ **/
+ #if EXAMPLE_NUMBER == 1
+ // --- Example 1: STANDARD 1/2 Scan ---
+
+ // Declare a pointer to the specific instantiation:
+ VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>* virtualDisp = nullptr;
+
+ #endif
+
+
+ #if EXAMPLE_NUMBER == 2
+ // --- Example 2: Non-Standard 1/4 Scan (Four-Scan 1/8) ---
+
+ // Use an existing library user-contributed Scan Type pixel mapping
+ using MyScanTypeMapping = ScanTypeMapping<PANEL_SCAN_TYPE>;
+
+ // Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
+ VirtualMatrixPanel_T<PANEL_CHAIN_TYPE, MyScanTypeMapping>* virtualDisp = nullptr;
+
+ #endif
+
+ #if EXAMPLE_NUMBER == 3
+ // --- Example 3: Single non-standard 1/4 Scan (Four-Scan 1/8) ---
+
+ // Use an existing library user-contributed Scan Type pixel mapping
+ using MyScanTypeMapping = ScanTypeMapping<PANEL_SCAN_TYPE>;
+
+ // Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
+ VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>* virtualDisp = nullptr;
+
+ #endif
+
+
+
+ // Bonus non-existnat example. Create your own per-panel custom pixel mapping!
+ #if EXAMPLE_NUMBER == 4
+
+ // --- Custom Scan–Type Pixel Mapping ---
+ // This policy adds a fixed offset to the coordinates.
+ struct CustomScanTypeMapping {
+ static constexpr VirtualCoords apply(VirtualCoords coords, int virt_y, int panel_pixel_base) {
+ // For demonstration, add a fixed offset of +5 to x and +3 to y.
+ coords.x += 5;
+ coords.y += 3;
+ return coords;
+ }
+ };
+ #endif
+
+ void setup()
+ {
+ Serial.begin(115200);
+ delay(2000);
+
+ #if EXAMPLE_NUMBER == 3
+ /**
+ * HACK ALERT!
+ * For 1/4 scan panels (namely outdoor panels), electrically the pixels are connected in a chain that is
+ * twice the physical panel's pixel width, and half the pixel height. As such, we need to configure
+ * the underlying DMA library to match the same. Then we use the VirtualMatrixPanel_T class to map the
+ * physical pixels to the virtual pixels.
+ */
+ HUB75_I2S_CFG mxconfig(
+ PANEL_RES_X*2, // DO NOT CHANGE THIS
+ PANEL_RES_Y/2, // DO NOT CHANGE THIS
+ 1 // A Single panel
+ );
+
+ #elif EXAMPLE_NUMBER == 2
+
+ /**
+ * HACK ALERT!
+ * For 1/4 scan panels (namely outdoor panels), electrically the pixels are connected in a chain that is
+ * twice the physical panel's pixel width, and half the pixel height. As such, we need to configure
+ * the underlying DMA library to match the same. Then we use the VirtualMatrixPanel_T class to map the
+ * physical pixels to the virtual pixels.
+ */
+ HUB75_I2S_CFG mxconfig(
+ PANEL_RES_X*2, // DO NOT CHANGE THIS
+ PANEL_RES_Y/2, // DO NOT CHANGE THIS
+ PANEL_CHAIN_LEN
+ );
+
+ #else
+
+ // Standard panel type natively supported by this library (Example 1)
+ HUB75_I2S_CFG mxconfig(
+ PANEL_RES_X,
+ PANEL_RES_Y,
+ PANEL_CHAIN_LEN
+ );
+
+ #endif
+
+ mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;
+ mxconfig.clkphase = false;
+ //mxconfig.driver = HUB75_I2S_CFG::FM6126A;
+
+
+ /**
+ * Setup physical DMA LED display output.
*/
- #define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_BOTTOM_LEFT_UP
-
-// 3) Create the runtime objects to use
-
- // placeholder for the matrix object
- MatrixPanel_I2S_DMA *dma_display = nullptr;
-
- // placeholder for the virtual display object
- VirtualMatrixPanel *virtualDisp = nullptr;
-
-
-/******************************************************************************
- * Setup!
- ******************************************************************************/
-void setup() {
-
- delay(2000);
- Serial.begin(115200);
- Serial.println(""); Serial.println(""); Serial.println("");
- Serial.println("*****************************************************");
- Serial.println(" HELLO !");
- Serial.println("*****************************************************");
-
-
- /******************************************************************************
- * Create physical DMA panel class AND virtual (chained) display class.
- ******************************************************************************/
-
- /*
- The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure,
- All options has it's predefined default values. So we can create a new structure and redefine only the options we need
-
- Please refer to the '2_PatternPlasma.ino' example for detailed example of how to use the MatrixPanel_I2S_DMA configuration
- */
-
- HUB75_I2S_CFG mxconfig(
- PANEL_RES_X, // module width
- PANEL_RES_Y, // module height
- PANEL_CHAIN // chain length
- );
-
- //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object
-
- // 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!"));
- }
-
- // OK, now we can create our matrix object
- dma_display = new MatrixPanel_I2S_DMA(mxconfig);
-
- // let's adjust default brightness to about 75%
- dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100%
-
- // Allocate memory and start DMA display
- if( not dma_display->begin() )
- 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, 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(1000);
-
- 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(3);
- virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2));
- virtualDisp->print("ABCD");
-
- // Red text inside red rect (2 pix in from edge)
- 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));
-
+ dma_display = new MatrixPanel_I2S_DMA(mxconfig);
+ dma_display->begin();
+ dma_display->setBrightness8(128); //0-255
+ dma_display->clearScreen();
+
+ /**
+ * Setup the VirtualMatrixPanel_T class to map the virtual pixels to the physical pixels.
+ */
+
+ #if EXAMPLE_NUMBER == 1
+ virtualDisp = new VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>(VDISP_NUM_ROWS, VDISP_NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
+ #elif EXAMPLE_NUMBER == 2
+ virtualDisp = new VirtualMatrixPanel_T<PANEL_CHAIN_TYPE, MyScanTypeMapping>(VDISP_NUM_ROWS, VDISP_NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
+ #elif EXAMPLE_NUMBER == 3
+ virtualDisp = new VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>(1, 1, PANEL_RES_X, PANEL_RES_Y); // Single 1/4 scan panel
+ #endif
+
+ // Pass a reference to the DMA display to the VirtualMatrixPanel_T class
+ virtualDisp->setDisplay(*dma_display);
+
+ for (int y = 0; y < virtualDisp->height(); y++) {
+ for (int x = 0; x < virtualDisp->width(); x++) {
+
+ uint16_t color = virtualDisp->color565(96, 0, 0); // red
+
+ if (x == 0) color = virtualDisp->color565(0, 255, 0); // g
+ if (x == (virtualDisp->width()-1)) color = virtualDisp->color565(0, 0, 255); // b
+
+ virtualDisp->drawPixel(x, y, color);
+ delay(2);
+ }
+ }
+
+ delay(3000);
+ virtualDisp->clearScreen();
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
+ }
+
+
+ void loop() {
+
+ // Do nothing here.
+ delay (100);
+
+ } \ No newline at end of file
diff --git a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h
index f0c60ec..43e6178 100644
--- a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h
+++ b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h
@@ -196,33 +196,27 @@ struct frameStruct
};
/***************************************************************************************/
-// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
-// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e
-// need to make sure this would end up in RAM for fastest access
#ifndef NO_CIE1931
-/*
-static const uint8_t DRAM_ATTR lumConvTab[]={
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255};
-*/
-// This is 16-bit version of the table,
-// the constants taken from the example in the article above, each entries subtracted from 65535:
-static const uint16_t DRAM_ATTR lumConvTab[] = {
- 0, 27, 56, 84, 113, 141, 170, 198, 227, 255, 284, 312, 340, 369, 397, 426,
- 454, 483, 511, 540, 568, 597, 626, 657, 688, 720, 754, 788, 824, 860, 898, 936,
- 976, 1017, 1059, 1102, 1146, 1191, 1238, 1286, 1335, 1385, 1436, 1489, 1543, 1598, 1655, 1713,
- 1772, 1833, 1895, 1958, 2023, 2089, 2156, 2225, 2296, 2368, 2441, 2516, 2592, 2670, 2750, 2831,
- 2914, 2998, 3084, 3171, 3260, 3351, 3443, 3537, 3633, 3731, 3830, 3931, 4034, 4138, 4245, 4353,
- 4463, 4574, 4688, 4803, 4921, 5040, 5161, 5284, 5409, 5536, 5665, 5796, 5929, 6064, 6201, 6340,
- 6482, 6625, 6770, 6917, 7067, 7219, 7372, 7528, 7687, 7847, 8010, 8174, 8341, 8511, 8682, 8856,
- 9032, 9211, 9392, 9575, 9761, 9949, 10139, 10332, 10527, 10725, 10925, 11127, 11332, 11540, 11750, 11963,
- 12178, 12395, 12616, 12839, 13064, 13292, 13523, 13757, 13993, 14231, 14473, 14717, 14964, 15214, 15466, 15722,
- 15980, 16240, 16504, 16771, 17040, 17312, 17587, 17865, 18146, 18430, 18717, 19006, 19299, 19595, 19894, 20195,
- 20500, 20808, 21119, 21433, 21750, 22070, 22393, 22720, 23049, 23382, 23718, 24057, 24400, 24745, 25094, 25446,
- 25802, 26160, 26522, 26888, 27256, 27628, 28004, 28382, 28765, 29150, 29539, 29932, 30328, 30727, 31130, 31536,
- 31946, 32360, 32777, 33197, 33622, 34049, 34481, 34916, 35354, 35797, 36243, 36692, 37146, 37603, 38064, 38528,
- 38996, 39469, 39945, 40424, 40908, 41395, 41886, 42382, 42881, 43383, 43890, 44401, 44916, 45434, 45957, 46484,
- 47014, 47549, 48088, 48630, 49177, 49728, 50283, 50842, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465,
- 56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535};
+// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
+// 16 bit resolution of https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e
+static const uint16_t lumConvTab[] = {
+0, 28, 57, 85, 114, 142, 171, 199, 228, 256, 285, 313, 342, 370, 399, 427,
+456, 484, 513, 541, 570, 598, 627, 658, 689, 721, 755, 789, 825, 861, 899, 937,
+977, 1018, 1060, 1103, 1147, 1192, 1239, 1287, 1336, 1386, 1437, 1490, 1544, 1599, 1656, 1714,
+1773, 1834, 1896, 1959, 2024, 2090, 2157, 2226, 2297, 2369, 2442, 2517, 2593, 2671, 2751, 2832,
+2914, 2999, 3085, 3172, 3261, 3352, 3444, 3538, 3634, 3732, 3831, 3932, 4035, 4139, 4245, 4354,
+4464, 4575, 4689, 4804, 4922, 5041, 5162, 5285, 5410, 5537, 5666, 5797, 5930, 6065, 6202, 6341,
+6482, 6626, 6771, 6918, 7068, 7220, 7373, 7529, 7687, 7848, 8010, 8175, 8342, 8512, 8683, 8857,
+9033, 9212, 9393, 9576, 9762, 9949, 10140, 10333, 10528, 10725, 10926, 11128, 11333, 11541, 11751, 11963,
+12179, 12396, 12617, 12840, 13065, 13293, 13524, 13757, 13993, 14232, 14474, 14718, 14965, 15215, 15467, 15722,
+15980, 16241, 16505, 16771, 17041, 17313, 17588, 17866, 18147, 18431, 18717, 19007, 19300, 19596, 19894, 20196,
+20501, 20809, 21119, 21433, 21750, 22071, 22394, 22720, 23050, 23383, 23719, 24058, 24400, 24746, 25095, 25447,
+25802, 26161, 26523, 26888, 27257, 27629, 28004, 28383, 28765, 29151, 29540, 29932, 30328, 30728, 31131, 31537,
+31947, 32360, 32777, 33198, 33622, 34050, 34481, 34916, 35355, 35797, 36243, 36693, 37146, 37603, 38064, 38529,
+38997, 39469, 39945, 40425, 40908, 41396, 41887, 42382, 42881, 43384, 43891, 44401, 44916, 45435, 45957, 46484,
+47015, 47549, 48088, 48631, 49178, 49728, 50283, 50843, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465,
+56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535,
+};
#endif
/** @brief - configuration values for HUB75_I2S driver
@@ -588,13 +582,8 @@ public:
// Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
static uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r * 17, g * 17, b * 17); }
-
- // Converts RGB888 to RGB565
static uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX!
- // Converts RGB333 to RGB565
- static uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function.
-
/**
* @brief - convert RGB565 to RGB888
* @param uint16_t colour - RGB565 input colour
@@ -953,40 +942,6 @@ inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b)
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
-// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb
-inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b)
-{
- return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1);
-}
-
-inline void MatrixPanel_I2S_DMA::drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows)
-{
- /* drawIcon draws a C style bitmap.
- // Example 10x5px bitmap of a yellow sun
- //
- int half_sun [50] = {
- 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000,
- 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000,
- 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000,
- 0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0,
- 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000,
- };
-
- MatrixPanel_I2S_DMA matrix;
-
- matrix.drawIcon (half_sun, 0,0,10,5);
- */
-
- int i, j;
- for (i = 0; i < rows; i++)
- {
- for (j = 0; j < cols; j++)
- {
- drawPixel(x + j, y + i, (uint16_t)ico[i * cols + j]);
- }
- }
-}
-
#endif
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
diff --git a/src/ESP32-HUB75-VirtualMatrixPanel_T.hpp b/src/ESP32-HUB75-VirtualMatrixPanel_T.hpp
new file mode 100644
index 0000000..089bc06
--- /dev/null
+++ b/src/ESP32-HUB75-VirtualMatrixPanel_T.hpp
@@ -0,0 +1,492 @@
+/**
+ * @file VirtualMatrixPanel_T.hpp
+ * @brief TEMPLATED Virtual Matrix Panel class for HUB75 displays. Hence the '_T'.
+ *
+ * This header defines the VirtualMatrixPanel_T template class which maps virtual pixel
+ * coordinates to physical LED coordinates. It supports compile‐time configuration for:
+ * - Panel chain type (PANEL_CHAIN_TYPE)
+ * - Scan type mapping (via a class, default is STANDARD_TWO_SCAN)
+ * - A compile‐time scale factor (each virtual pixel is drawn as a block)
+ *
+ * Runtime rotation is supported via setRotation(). Depending on the build options,
+ * the class conditionally inherits from Adafruit_GFX, GFX_Lite, or stands alone.
+ *
+ * This class is used to accomplish two objectives:
+ * 1) Create a much larger display out of a number of physical LED panels
+ * chained in a various pattern.
+ *
+ * 2) Provide a way to deal with weird individual physical panels that
+ * do not have a simple linear X, Y pixel mapping.
+ * i.e. Their DMA pixel mapping differs to the real world.
+ * i.e. Weird four-scan outdoor panels etc.
+ *
+ * @tparam ChainType Compile‐time panel chain configuration.
+ * @tparam ScanType Policy type implementing a static apply() function for mapping.
+ * Default is ScanTypeMapping<STANDARD_TWO_SCAN>.
+ * @tparam ScaleFactor Compile‐time zoom factor (each virtual pixel becomes a
+ * ScaleFactor x ScaleFactor block).
+ *
+ * @note The enum PANEL_SCAN_TYPE replaces the former PANEL_SCAN_RATE.
+ */
+
+
+
+#ifndef VIRTUAL_MATRIX_PANEL_TEMPLATE_H
+#define VIRTUAL_MATRIX_PANEL_TEMPLATE_H
+
+//#include <cstdint>
+#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
+
+#ifdef USE_GFX_LITE
+ #include "GFX_Lite.h"
+#elif !defined(NO_GFX)
+ #include "Adafruit_GFX.h"
+#endif
+
+// ----------------------------------------------------------------------
+// Data structures and enums
+
+/**
+ * @brief Structure holding virtual/physical coordinate mapping.
+ */
+struct VirtualCoords {
+ int16_t x;
+ int16_t y;
+ int16_t virt_row; // chain of panels row (optional)
+ int16_t virt_col; // chain of panels col (optional)
+ VirtualCoords() : x(0), y(0), virt_row(0), virt_col(0) {}
+};
+
+/**
+ * @brief Panel scan types.
+ *
+ * Defines the different scanning modes.
+ */
+enum PANEL_SCAN_TYPE {
+ STANDARD_TWO_SCAN,
+ FOUR_SCAN_32PX_HIGH, ///< Four-scan mode, 32-pixel high panels.
+ FOUR_SCAN_16PX_HIGH, ///< Four-scan mode, 16-pixel high panels.
+ FOUR_SCAN_64PX_HIGH, ///< Four-scan mode, 64-pixel high panels.
+ FOUR_SCAN_40PX_HIGH ///< Four-scan mode, 40-pixel high panels.
+};
+
+/**
+ * @brief Panel chain types.
+ *
+ * Defines the physical chain configuration for multiple panels.
+ */
+enum PANEL_CHAIN_TYPE {
+ CHAIN_NONE, ///< No chaining.
+ CHAIN_TOP_LEFT_DOWN, ///< Chain starting top-left, going down.
+ CHAIN_TOP_RIGHT_DOWN, ///< Chain starting top-right, going down.
+ CHAIN_BOTTOM_LEFT_UP, ///< Chain starting bottom-left, going up.
+ CHAIN_BOTTOM_RIGHT_UP, ///< Chain starting bottom-right, going up.
+ CHAIN_TOP_LEFT_DOWN_ZZ, ///< Zigzag chain starting top-left.
+ CHAIN_TOP_RIGHT_DOWN_ZZ, ///< Zigzag chain starting top-right.
+ CHAIN_BOTTOM_RIGHT_UP_ZZ, ///< Zigzag chain starting bottom-right.
+ CHAIN_BOTTOM_LEFT_UP_ZZ ///< Zigzag chain starting bottom-left.
+};
+
+// ----------------------------------------------------------------------
+// Default Scan Rate Policy
+/**
+ * @brief Default policy for scan type mapping.
+ *
+ * This templated policy implements the static function apply() to remap
+ * coordinates according to the panel scan type. It uses the panel's pixel base
+ * to calculate offsets.
+ *
+ * @tparam Type The compile-time scan type (of type PANEL_SCAN_TYPE).
+ */
+template <PANEL_SCAN_TYPE ScanType>
+struct ScanTypeMapping {
+ static constexpr VirtualCoords apply(VirtualCoords coords, int virt_y, int panel_pixel_base)
+ {
+ log_v("ScanTypeMapping: coords.x: %d, coords.y: %d, virt_y: %d, pixel_base: %d", coords.x, coords.y, virt_y, panel_pixel_base);
+
+ // FOUR_SCAN_16PX_HIGH
+ if constexpr (ScanType == FOUR_SCAN_16PX_HIGH)
+ {
+ if ((coords.y & 4) == 0) {
+ coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
+ } else {
+ coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
+ }
+
+ coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);
+ }
+ // FOUR_SCAN_40PX_HIGH
+ else if constexpr (ScanType == FOUR_SCAN_40PX_HIGH)
+ {
+
+ if (((coords.y) / 10) % 2 == 0) {
+ coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
+ } else {
+ coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
+ }
+ coords.y = (coords.y / 20) * 10 + (coords.y % 10);
+ }
+ // FOUR_SCAN_64PX_HIGH || FOUR_SCAN_32PX_HIGH
+ else if constexpr (ScanType == FOUR_SCAN_64PX_HIGH || ScanType == FOUR_SCAN_32PX_HIGH)
+ {
+ int adjusted_y = virt_y;
+ if constexpr (ScanType == FOUR_SCAN_64PX_HIGH)
+ {
+ // As in the original code (with extra remapping for 64px high panels)
+ if ((virt_y & 8) != ((virt_y & 16) >> 1))
+ adjusted_y = (((virt_y & 0b11000) ^ 0b11000) + (virt_y & 0b11100111));
+ }
+
+ if ((coords.y & 8) == 0) {
+ coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
+ } else {
+ coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
+ }
+
+ coords.y = (adjusted_y >> 4) * 8 + (adjusted_y & 0b00000111);
+ }
+
+ // For STANDARD_TWO_SCAN / NORMAL_ONE_SIXTEEN no remapping is done.
+ return coords;
+ }
+};
+
+// ----------------------------------------------------------------------
+// VirtualMatrixPanel_T Declaration
+//
+// Template parameters:
+// - ChainScanType: compile–time panel chain configuration.
+// - ScanTypeMapping: a policy type implementing a static "apply" function
+// (default is ScanTypeMapping<STANDARD_TWO_SCAN>).
+// - ScaleFactor: a compile–time zoom factor (must be >= 1).
+#ifdef USE_GFX_LITE
+template <PANEL_CHAIN_TYPE ChainScanType,
+ class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
+ int ScaleFactor = 1>
+class VirtualMatrixPanel_T : public GFX {
+public:
+#elif !defined(NO_GFX)
+template <PANEL_CHAIN_TYPE ChainScanType,
+ class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
+ int ScaleFactor = 1>
+class VirtualMatrixPanel_T : public Adafruit_GFX {
+public:
+#else
+template <PANEL_CHAIN_TYPE ChainScanType,
+ class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
+ int ScaleFactor = 1>
+class VirtualMatrixPanel_T {
+public:
+#endif
+
+ // Constructor: pass the underlying MatrixPanel_I2S_DMA display,
+ // virtual module dimensions, and physical panel resolution.
+ // (Chain type is chosen at compile time.)
+ VirtualMatrixPanel_T(uint8_t _vmodule_rows,
+ uint8_t _vmodule_cols,
+ uint8_t _panel_res_x,
+ uint8_t _panel_res_y)
+#ifdef USE_GFX_LITE
+ : GFX(_vmodule_cols * _panel_res_x, _vmodule_rows * _panel_res_y),
+#elif !defined(NO_GFX)
+ : Adafruit_GFX(_vmodule_cols * _panel_res_x, _vmodule_rows * _panel_res_y),
+#endif
+ panel_res_x(_panel_res_x),
+ panel_res_y(_panel_res_y),
+ panel_pixel_base(_panel_res_x), // default pixel base is panel_res_x
+ vmodule_rows(_vmodule_rows),
+ vmodule_cols(_vmodule_cols),
+ virtual_res_x(_vmodule_cols * _panel_res_x),
+ virtual_res_y(_vmodule_rows * _panel_res_y),
+ dma_res_x(_panel_res_x * _vmodule_rows * _vmodule_cols - 1),
+ _virtual_res_x(virtual_res_x),
+ _virtual_res_y(virtual_res_y),
+ _rotate(0)
+ {
+ // Initialize with an invalid coordinate.
+ coords.x = coords.y = -1;
+ }
+
+ // ------------------------------------------------------------------
+ // Drawing methods
+ inline void drawPixel(int16_t x, int16_t y, uint16_t color) {
+ if constexpr (ScaleFactor > 1)
+ {
+ for (int dx = 0; dx < ScaleFactor; dx++) {
+ for (int dy = 0; dy < ScaleFactor; dy++) {
+ //irtualCoords v = getCoords(x * ScaleFactor + dx, y * ScaleFactor + dy);
+ // display->drawPixel(v.x, v.y, color);
+ calcCoords(x * ScaleFactor + dx, y * ScaleFactor + dy);
+ display->drawPixel(coords.x, coords.y, color);
+
+ }
+ }
+ } else {
+ //VirtualCoords v = getCoords(x, y);
+ //display->drawPixel(v.x, v.y, color);
+
+ calcCoords(x , y);
+ display->drawPixel(coords.x, coords.y, color);
+ }
+
+ log_v("x: %d, y: %d -> coords.x: %d, coords.y: %d", x, y, coords.x, coords.y);
+ }
+
+ inline void fillScreen(uint16_t color) {
+ display->fillScreen(color);
+ }
+
+ inline void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) {
+ display->fillScreenRGB888(r, g, b);
+ }
+
+ inline void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) {
+ //VirtualCoords v = getCoords(x, y);
+ //display->drawPixelRGB888(v.x, v.y, r, g, b);
+
+ calcCoords(x , y);
+ display->drawPixelRGB888(coords.x, coords.y, r, g, b);
+ }
+
+#ifdef USE_GFX_LITE
+ inline void drawPixel(int16_t x, int16_t y, CRGB color) {
+ //VirtualCoords v = getCoords(x, y);
+ //display->drawPixel(v.x, v.y, color);
+
+ calcCoords(x , y);
+ display->drawPixel(coords.x, coords.y, color);
+
+ }
+
+ inline void fillScreen(CRGB color) {
+ display->fillScreen(color);
+ }
+#endif
+
+#ifndef NO_GFX
+ inline void drawDisplayTest() {
+
+ // Call ourself as we need to re-map pixels if we're using our own ScanTypeMapping
+ // Note: Will mean this display test will be impacted by chaining approach etc.
+ // this->setFont(&FreeSansBold12pt7b);
+ this->setTextColor(display->color565(255, 255, 0));
+ // this->setTextSize(1);
+ for (int col = 0; col < vmodule_cols; col++) {
+ for (int row = 0; row < vmodule_rows; row++) {
+
+ int start_x = col * panel_res_x;
+ int start_y = row * panel_res_y;
+
+ int panel_id = col + (row * vmodule_cols) + 1;
+ //int top_left_x = panel * panel_res_x;
+ this->drawRect(start_x, start_y, panel_res_x, panel_res_y, this->color565(0, 255, 0));
+ this->setCursor(start_x + panel_res_x/2 - 2, start_y + panel_res_y/2 - 4);
+ this->print(panel_id);
+
+ log_d("drawDisplayTest() Panel: %d, start_x: %d, start_y: %d", panel_id, start_x, start_y);
+ }
+ }
+
+ }
+
+ inline void drawDisplayTestDMA()
+ {
+ // Write to the underlying panels only via the dma_display instance.
+ // This only works on standard panels with a linear mapping (i.e. two-scan).
+ 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 * panel_res_x;
+ this->display->drawRect(top_left_x, 0, panel_res_x, panel_res_y, this->display->color565(0, 255, 0));
+ this->display->setCursor((panel * panel_res_x) + 6, panel_res_y - 12);
+ this->display->print((vmodule_cols * vmodule_rows) - panel);
+ }
+ }
+
+#endif
+
+ inline void clearScreen() { display->clearScreen(); }
+
+ inline uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return display->color444(r, g, b); }
+ inline uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); }
+
+ inline void flipDMABuffer() { display->flipDMABuffer(); }
+
+ // ------------------------------------------------------------------
+ // Rotation (runtime)
+ inline void setRotation(uint8_t rotate) {
+ if (rotate < 4)
+ _rotate = rotate;
+#ifdef NO_GFX
+ // When NO_GFX is defined, update _virtual_res_x/_virtual_res_y as needed.
+#else
+ uint8_t rotation = (rotate & 3);
+ switch (rotation) {
+ case 0:
+ case 2:
+ _virtual_res_x = virtual_res_x;
+ _virtual_res_y = virtual_res_y;
+ _width = virtual_res_x;
+ _height = virtual_res_y;
+ break;
+ case 1:
+ case 3:
+ _virtual_res_x = virtual_res_y;
+ _virtual_res_y = virtual_res_x;
+ _width = virtual_res_y;
+ _height = virtual_res_x;
+ break;
+ }
+#endif
+ }
+
+ // ------------------------------------------------------------------
+ // Panel scan–type configuration (runtime adjustment of pixel base)
+ inline void setPixelBase(uint8_t pixel_base) {
+ panel_pixel_base = pixel_base;
+ }
+
+ // ------------------------------------------------------------------
+ // calcCoords() maps a virtual (x,y) coordinate to a physical coordinate.
+ //VirtualCoords getCoords(int16_t virt_x, int16_t virt_y) {
+ void calcCoords(int16_t virt_x, int16_t virt_y) {
+
+#ifdef NO_GFX
+ if (virt_x < 0 || virt_x >= _virtual_res_x || virt_y < 0 || virt_y >= _virtual_res_y) {
+#else
+ if (virt_x < 0 || virt_x >= _width || virt_y < 0 || virt_y >= _height) {
+#endif
+ coords.x = coords.y = -1;
+ return;
+ //return coords;
+ }
+
+ //log_d("calcCoords pre-chain: virt_x: %d, virt_y: %d", virt_x, virt_y);
+
+ // --- Runtime rotation ---
+ switch (_rotate) {
+ case 1: {
+ int16_t temp = virt_x;
+ virt_x = virt_y;
+ virt_y = virtual_res_y - 1 - temp;
+ break;
+ }
+ case 2: {
+ virt_x = virtual_res_x - 1 - virt_x;
+ virt_y = virtual_res_y - 1 - virt_y;
+ break;
+ }
+ case 3: {
+ int16_t temp = virt_x;
+ virt_x = virtual_res_x - 1 - virt_y;
+ virt_y = temp;
+ break;
+ }
+ default:
+ break;
+ }
+
+ // --- Chain mapping ---
+ int row = virt_y / panel_res_y; // 0-indexed row in the virtual module
+ if constexpr (ChainScanType == CHAIN_TOP_RIGHT_DOWN) {
+ if ((row & 1) == 1) {
+ coords.x = dma_res_x - virt_x - (row * virtual_res_x);
+ coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
+ } else {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ }
+ else if constexpr (ChainScanType == CHAIN_TOP_RIGHT_DOWN_ZZ) {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ else if constexpr (ChainScanType == CHAIN_TOP_LEFT_DOWN) {
+ if ((row & 1) == 0) {
+ coords.x = dma_res_x - virt_x - (row * virtual_res_x);
+ coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
+ } else {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ }
+ else if constexpr (ChainScanType == CHAIN_TOP_LEFT_DOWN_ZZ) {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ else if constexpr (ChainScanType == CHAIN_BOTTOM_LEFT_UP) {
+ row = vmodule_rows - row - 1;
+ if ((row & 1) == 1) {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ } else {
+ coords.x = dma_res_x - (row * virtual_res_x) - virt_x;
+ coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
+ }
+ }
+ else if constexpr (ChainScanType == CHAIN_BOTTOM_LEFT_UP_ZZ) {
+ row = vmodule_rows - row - 1;
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ else if constexpr (ChainScanType == CHAIN_BOTTOM_RIGHT_UP) {
+ row = vmodule_rows - row - 1;
+ if ((row & 1) == 0) {
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ } else {
+ coords.x = dma_res_x - (row * virtual_res_x) - virt_x;
+ coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
+ }
+ }
+ else if constexpr (ChainScanType == CHAIN_BOTTOM_RIGHT_UP_ZZ) {
+ row = vmodule_rows - row - 1;
+ coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
+ coords.y = (virt_y % panel_res_y);
+ }
+ else { // CHAIN_NONE (default)
+ coords.x = virt_x;
+ coords.y = virt_y;
+ }
+
+ //log_d("calcCoords post-chain: virt_x: %d, virt_y: %d", virt_x, virt_y);
+
+ // --- Apply physical LED panel scan–type mapping / fix ---
+ coords = ScanTypeMapping::apply(coords, virt_y, panel_pixel_base);
+
+ }
+
+#ifdef NO_GFX
+ inline uint16_t width() const { return _virtual_res_x; }
+ inline uint16_t height() const { return _virtual_res_y; }
+#endif
+
+ // ------------------------------------------------------------------
+ // Data members (public for compatibility)
+ VirtualCoords coords;
+
+ uint8_t panel_res_x; // physical panel resolution X
+ uint8_t panel_res_y; // physical panel resolution Y
+ uint8_t panel_pixel_base; // used for scan–type mapping
+
+ inline void setDisplay(MatrixPanel_I2S_DMA &disp) {
+ display = &disp;
+ }
+
+private:
+ MatrixPanel_I2S_DMA *display;
+ // Note: panel_chain_type is now fixed via the compile–time template parameter 'ChainScanType'.
+ uint16_t virtual_res_x; // virtual display width (combination of panels)
+ uint16_t virtual_res_y; // virtual display height (combination of panels)
+ uint16_t _virtual_res_x; // width adjusted by current rotation
+ uint16_t _virtual_res_y; // height adjusted by current rotation
+ uint8_t vmodule_rows; // virtual module rows
+ uint8_t vmodule_cols; // virtual module columns
+ uint16_t dma_res_x; // width as seen by the DMA engine
+
+ int _rotate; // runtime rotation (0 to 3)
+};
+
+#endif // VIRTUAL_MATRIX_PANEL_TEMPLATE_H
diff --git a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h
index 0ad453b..7a01bab 100644
--- a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h
+++ b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h
@@ -74,6 +74,7 @@ enum PANEL_CHAIN_TYPE
CHAIN_BOTTOM_LEFT_UP_ZZ
};
+[[deprecated("VirtualMatrixPanel is depreciated. Please include 'HUB75-VirtualMatrixPanelT.hpp' and use VirtualMatrixPanelT instead. Refer to the documentation.")]]
#ifdef USE_GFX_LITE
class VirtualMatrixPanel : public GFX
#elif !defined NO_GFX
@@ -140,12 +141,8 @@ public:
inline uint16_t height() const { return _virtualResY; }
#endif
- uint16_t color444(uint8_t r, uint8_t g, uint8_t b)
- {
- return display->color444(r, g, b);
- }
+ 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();
@@ -558,19 +555,5 @@ inline void VirtualMatrixPanel::drawDisplayTest()
}
#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
diff --git a/src/platforms/esp32s3/gdma_lcd_parallel16.cpp b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp
index 9d15565..944db95 100644
--- a/src/platforms/esp32s3/gdma_lcd_parallel16.cpp
+++ b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp
@@ -257,7 +257,7 @@
};
gdma_apply_strategy(dma_chan, &strategy_config);
-#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
gdma_transfer_config_t transfer_config = {
#ifdef SPIRAM_DMA_BUFFER
.max_data_burst_size = 64,