1/8 Scan Panel Example

#154 #204

Also supports chaining.
This commit is contained in:
mrfaptastic 2021-11-22 07:43:07 +00:00
parent d952839a0d
commit b810dca9d1
8 changed files with 283 additions and 476 deletions

View file

@ -258,8 +258,9 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
// Do a final check to see if we have enough space for the additional DMA linked list descriptors that will be required to link it all up! // Do a final check to see if we have enough space for the additional DMA linked list descriptors that will be required to link it all up!
if(_dma_linked_list_memory_required > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) { if(_dma_linked_list_memory_required > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) {
#if SERIAL_DEBUG
Serial.println(F("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n")); Serial.println(F("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n"));
#endif
return false; return false;
} // linked list descriptors memory check } // linked list descriptors memory check
@ -270,7 +271,9 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA); dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Can't allocate descriptor framebuffer a"); assert("Can't allocate descriptor framebuffer a");
if(!dmadesc_a) { if(!dmadesc_a) {
#if SERIAL_DEBUG
Serial.println(F("ERROR: Could not malloc descriptor framebuffer a.")); Serial.println(F("ERROR: Could not malloc descriptor framebuffer a."));
#endif
return false; return false;
} }
@ -280,18 +283,21 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA); dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Could not malloc descriptor framebuffer b."); assert("Could not malloc descriptor framebuffer b.");
if(!dmadesc_b) { if(!dmadesc_b) {
#if SERIAL_DEBUG
Serial.println(F("ERROR: Could not malloc descriptor framebuffer b.")); Serial.println(F("ERROR: Could not malloc descriptor framebuffer b."));
#endif
return false; return false;
} }
} }
#if SERIAL_DEBUG
Serial.println(F("*** ESP32-HUB75-MatrixPanel-I2S-DMA: Memory Allocations Complete ***")); Serial.println(F("*** ESP32-HUB75-MatrixPanel-I2S-DMA: Memory Allocations Complete ***"));
Serial.printf_P(PSTR("Total memory that was reserved: %d kB.\r\n"), _total_dma_capable_memory_reserved/1024); Serial.printf_P(PSTR("Total memory that was reserved: %d kB.\r\n"), _total_dma_capable_memory_reserved/1024);
Serial.printf_P(PSTR("... of which was used for the DMA Linked List(s): %d kB.\r\n"), _dma_linked_list_memory_required/1024); Serial.printf_P(PSTR("... of which was used for the DMA Linked List(s): %d kB.\r\n"), _dma_linked_list_memory_required/1024);
Serial.printf_P(PSTR("Heap Memory Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0)); Serial.printf_P(PSTR("Heap Memory Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
Serial.printf_P(PSTR("General RAM Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(MALLOC_CAP_DEFAULT), heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)); Serial.printf_P(PSTR("General RAM Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(MALLOC_CAP_DEFAULT), heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
#endif
// Just os we know // Just os we know
initialized = true; initialized = true;

View file

@ -315,7 +315,7 @@ struct HUB75_I2S_CFG {
LAT_PIN_DEFAULT, OE_PIN_DEFAULT, CLK_PIN_DEFAULT }, LAT_PIN_DEFAULT, OE_PIN_DEFAULT, CLK_PIN_DEFAULT },
shift_driver _drv = SHIFTREG, shift_driver _drv = SHIFTREG,
bool _dbuff = false, bool _dbuff = false,
clk_speed _i2sspeed = HZ_10M, clk_speed _i2sspeed = HZ_20M,
uint8_t _latblk = 1, // Anything > 1 seems to cause artefacts on ICS panels uint8_t _latblk = 1, // Anything > 1 seems to cause artefacts on ICS panels
bool _clockphase = true, bool _clockphase = true,
uint8_t _min_refresh_rate = 85 uint8_t _min_refresh_rate = 85

View file

@ -15,8 +15,15 @@
#endif #endif
struct VirtualCoords { struct VirtualCoords {
int16_t x; int16_t x;
int16_t y; int16_t y;
int16_t virt_row; // chain of panels row
int16_t virt_col; // chain of panels col
VirtualCoords() : x(0), y(0)
{ }
}; };
@ -38,6 +45,8 @@ class VirtualMatrixPanel
int16_t panelResX; int16_t panelResX;
int16_t panelResY; int16_t panelResY;
int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
MatrixPanel_I2S_DMA *display; MatrixPanel_I2S_DMA *display;
@ -57,7 +66,11 @@ class VirtualMatrixPanel
vmodule_cols = _vmodule_cols; vmodule_cols = _vmodule_cols;
virtualResX = vmodule_cols*_panelResX; virtualResX = vmodule_cols*_panelResX;
virtualResY = vmodule_rows*_panelResY; 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() and height() will return a real-world value. For example:
* Virtual Display width: 128 * Virtual Display width: 128
@ -66,7 +79,6 @@ class VirtualMatrixPanel
* So, not values that at 0 to X-1 * So, not values that at 0 to X-1
*/ */
_s_chain_party = serpentine_chain; // serpentine, or 'S' chain? _s_chain_party = serpentine_chain; // serpentine, or 'S' chain?
_chain_top_down= top_down_chain; _chain_top_down= top_down_chain;
@ -74,39 +86,25 @@ class VirtualMatrixPanel
} }
VirtualCoords getCoords(int16_t x, int16_t y);
// equivalent methods of the matrix library so it can be just swapped out. // 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 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() { void clearScreen() { display->clearScreen(); }
display->clearScreen();
}
//void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
//void drawPixelRGB24(int16_t x, int16_t y, RGB24 color);
void drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows);
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return display->color444(r, g, 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); }
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 flipDMABuffer() { display->flipDMABuffer(); }
//void showDMABuffer() { display->showDMABuffer(); } void drawDisplayTest();
void setRotate(bool rotate);
void drawDisplayTest(); protected:
// Rotate display virtual VirtualCoords getCoords(int16_t &x, int16_t &y);
inline void setRotate(bool rotate);
private:
VirtualCoords coords; VirtualCoords coords;
bool _s_chain_party = true; // Are we chained? Ain't no party like a... 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 _chain_top_down = false; // is the ESP at the top or bottom of the matrix of devices?
bool _rotate = false; bool _rotate = false;
@ -118,21 +116,29 @@ class VirtualMatrixPanel
* Updates the private class member variable 'coords', so no need to use the return value. * Updates the private class member variable 'coords', so no need to use the return value.
* Not thread safe, but not a concern for ESP32 sketch anyway... I think. * Not thread safe, but not a concern for ESP32 sketch anyway... I think.
*/ */
inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t x, int16_t y) { inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &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
coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer if ( x < 0 || x >= width() || y < 0 || y >= height() ) { // Co-ordinates go from 0 to X-1 remember! width() and height() are out of range!
//Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
if (x < 0 || x >= width() || y < 0 || y >= height() ) { // Co-ordinates go from 0 to X-1 remember! width() and height() are out of range!
//Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
// We want to rotate? // We want to rotate?
if (_rotate){ if (_rotate){
uint16_t temp_x=x; uint16_t temp_x=x;
x=y; x=y;
y=virtualResY-1-temp_x; y=virtualResY-1-temp_x;
} }
// Stupidity check
if ( vmodule_rows == vmodule_cols == 1) // single panel...
{
coords.x = x;
coords.y = y;
return coords;
}
uint8_t row = (y / panelResY) + 1; //a non indexed 0 row number 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 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
@ -157,34 +163,29 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t x, int16_t y) {
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT // Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT
if (_chain_top_down) if (_chain_top_down)
{ {
const HUB75_I2S_CFG _cfg = this->display->getCfg(); /*
coords.x = (_cfg.mx_width * _cfg.chain_length - 1) - coords.x; const HUB75_I2S_CFG _cfg = this->display->getCfg();
coords.y = (_cfg.mx_height-1) - coords.y; 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;
} }
//Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC); //Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC);
return coords; return coords;
} }
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { // adafruit virtual void override
{
//VirtualCoords coords = getCoords(x, y);
getCoords(x, y); getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color); this->display->drawPixel(coords.x, coords.y, color);
} }
inline void VirtualMatrixPanel::fillScreen(uint16_t color) // adafruit virtual void override inline void VirtualMatrixPanel::fillScreen(uint16_t color) { // adafruit virtual void override
{
// No need to map this.
this->display->fillScreen(color); this->display->fillScreen(color);
} }
inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) {
{
//VirtualCoords coords = getCoords(x, y);
getCoords(x, y); getCoords(x, y);
this->display->drawPixelRGB888( coords.x, coords.y, r, g, b); this->display->drawPixelRGB888( coords.x, coords.y, r, g, b);
} }
@ -213,7 +214,7 @@ inline void VirtualMatrixPanel::drawDisplayTest()
} }
#endif #endif
/*
// need to recreate this one, as it wouldn't work to just map where it starts. // 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) { inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows) {
int i, j; int i, j;
@ -226,5 +227,6 @@ inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_
} }
} }
} }
*/
#endif #endif

View file

@ -0,0 +1,69 @@
#ifndef _ESP32_ONE_EIGHT_SCAN_PANEL
#define _ESP32_ONE_EIGHT_SCAN_PANEL
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
/* This class inherits all the goodness of the VirtualMatrixPanel class used to created
large displays of chained panels, but does trickery to convert from 1/16 to 1/8
DMA output.
Some guidance given by looking at hzeller's code, in relation to the 'stretch factor'
concept where these panels are really double width and half the height, from the perspective
of the digital input required to get the 'real world' resolution and output.
https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/lib/multiplex-mappers.cc
*/
class OneEightScanPanel : public VirtualMatrixPanel
{
public:
using VirtualMatrixPanel::VirtualMatrixPanel; // inherit VirtualMatrixPanel's constructor(s)
protected:
/* 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 One_Eight_1_8_ScanPanel code and you'll see that the DMA buffer is setup
as if the panel is 2 * W and 0.5 * H !
*/
VirtualCoords getCoords(int16_t &x, int16_t &y);
}; // end class header note the ;
inline VirtualCoords OneEightScanPanel::getCoords(int16_t &x, int16_t &y) {
VirtualMatrixPanel::getCoords(x, y); // call to base to update coords for chaining approach
if ( coords.x == -1 || coords.y == -1 ) { // Co-ordinates go from 0 to X-1 remember! width() and height() are out of range!
return coords;
}
/*
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(") ");
*/
return coords;
}
#endif

View file

@ -0,0 +1,147 @@
/*************************************************************************
* Description:
*
* The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
* supports output to 1/16 or 1/32 scan panels - which means outputting
* two lines at the same time, 16 or 32 rows apart. This cannot be changed
* at the DMA layer as it would require a messy and complex rebuild of the
* library's DMA internals.
*
* However, it is possible to connect 1/8 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"
// Virtual Display to re-map co-ordinates such that they draw correctly on a 32x16 1/4 Scan panel
#include "1_8_ScanPanel.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
OneEightScanPanel *OneEightMatrixDisplay = 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 OneEightMatrixDisplaylay object based on our newly created dma_display object
OneEightMatrixDisplay = new OneEightScanPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN);
}
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++)
{
OneEightMatrixDisplay->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++)
{
OneEightMatrixDisplay->drawLine(i+offset, 0, i+offset, 7, dma_display->color565(0, 0, 255)); // blue
OneEightMatrixDisplay->drawLine(i+offset, 8, i+offset, 15, dma_display->color565(0, 128,0)); // g
OneEightMatrixDisplay->drawLine(i+offset, 16, i+offset, 23, dma_display->color565(128, 0,0)); // red
OneEightMatrixDisplay->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++)
{
OneEightMatrixDisplay->setTextColor(OneEightMatrixDisplay->color565(255, 255, 255));
OneEightMatrixDisplay->setCursor(i*PANEL_RES_X+7, OneEightMatrixDisplay->height()/3);
// Red text inside red rect (2 pix in from edge)
OneEightMatrixDisplay->print("Panel " + String(i+1));
OneEightMatrixDisplay->drawRect(1,1, OneEightMatrixDisplay->width()-2, OneEightMatrixDisplay->height()-2, OneEightMatrixDisplay->color565(255,0,0));
// White line from top left to bottom right
OneEightMatrixDisplay->drawLine(0,0, OneEightMatrixDisplay->width()-1, OneEightMatrixDisplay->height()-1, OneEightMatrixDisplay->color565(255,255,255));
}
delay(2000);
dma_display->clearScreen();
} // end loop

View file

@ -1,4 +1,4 @@
# Using this library with 32x16 1/4 Scan Panels # Using this library with 32x16 1/8 Scan Panels
## Problem ## Problem
ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 1/8 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default. ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 1/8 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default.
@ -7,3 +7,7 @@ ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 1
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 ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class). 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 ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class).
Creation of a 'OneEighthScanMatrixPanel.h' class which sends an adjusted drawPixel() x,y co-ordinates to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library's drawPixel routine, to trick the output to look pixel perfect. Creation of a 'OneEighthScanMatrixPanel.h' class which sends an adjusted drawPixel() x,y co-ordinates to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library's drawPixel routine, to trick the output to look pixel perfect.
Refer to the '1_8_ScanPanel.h' logic which builds upon the library's core 'Virtual Display' (ESP32-VirtualMatrixPanel-I2S-DMA.h) to also support chaining of 1/8 Scan Panels. Refer to 'ChainedPanels' example on how to configure panel chaining to create bigger displays.

View file

@ -1,296 +0,0 @@
#ifndef _ESP32_ONE_EIGTH_MATRIX_PANEL_I2S_DMA
#define _ESP32_ONE_EIGTH_MATRIX_PANEL_I2S_DMA
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
struct VirtualCoords {
int16_t x;
int16_t y;
};
#ifdef USE_GFX_ROOT
class OneEighthMatrixPanel : public GFX
#elif !defined NO_GFX
class OneEighthMatrixPanel : public Adafruit_GFX
#else
class OneEighthMatrixPanel
#endif
{
public:
int16_t virtualResX;
int16_t virtualResY;
int16_t vmodule_rows;
int16_t vmodule_cols;
int16_t panelResX;
int16_t panelResY;
MatrixPanel_I2S_DMA *display;
OneEighthMatrixPanel(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;
/* 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
}
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
void clearScreen() {
display->clearScreen();
}
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
void drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows);
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(); }
// Rotate display
inline void setRotate(bool rotate);
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?
bool _rotate = false;
}; // end Class header
/**
* Calculate virtual->real co-ordinate mapping to underlying single chain of panels connected to ESP32.
* Then do further calculations for 1/8 scan panel.
* 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 OneEighthMatrixPanel::getCoords(int16_t x, int16_t y) {
coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer
// Check if virtual work co-ordinates are outside the virtual display resolution space. This does NOT check
// against the physical real-world DMA matrix resolution / setup configured, that is used to actually output
// the electrical pulse to the panel.
if (x < 0 || x >= width() || y < 0 || y >= height() ) { // Co-ordinates go from 0 to X-1 remember! width() and height() are out of range!
//Serial.printf("OneEighthMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
// We want to rotate?
if (_rotate){
uint16_t temp_x=x;
x=y;
y=virtualResY-1-temp_x;
}
uint8_t row = (y / panelResY) + 1; //a non indexed 0 row number
//uint8_t col = (x / panelResX) + 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;
}
/* *******
* START: 1/8 Scan Panel Pixel Re-Mapping
*
* We have calculated the x, y co-ordinates as if we have a chain of standard panels this library is designed
* for, this being 1/8 or 1/16 scan panels. We have to do some further hacking to convert co-ords to the
* double length and 1/2 physical dma output length that is required for these panels to work electronically.
*/
// https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154
// 1/8 Scan Panel - Is the final x-coord on the 1st or 3rd, 1/4ths (8 pixel 'blocks') of the panel (i.e. Row 0-7 or 17-24) ?
// Double the length of the x-coord if required
if ( ((coords.y /8) % 2) == 0) { // returns true/1 for the 1st and 3rd 8-pixel 1/4th of a 32px high panel
coords.x += (panelResX);
}
// If virtual x-coord 'x' is on a panel n, then we need to start sending data from (panelResX)*2*n, given
// at the underlying DMA level these panels are actually 1/2 the height and double the length
coords.x += (panelResX)*2 * (panelResX)/x;
// Half the y coord.
coords.y = (y % 8);
if ( y >= panelResY/2 ) coords.y +=8;
// Push all the pixels across a bit more if we're on another column or row
/*
uint8_t module_num = (row*col)-1;
if (module_num > 1)
{
//coords.x += ((panelResX)*2*(col*row))-1;
}
*/
/*
* END: 1/8 Scan Panel Pixel Re-Mapping
* *******
*/
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT
/*
if (_chain_top_down)
{
coords.x = (panelResX * vmodule_rows * vmodule_cols - 1) - coords.x;
coords.y = (panelResY-1) - coords.y;
}
*/
return coords;
}
inline void OneEighthMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{
//VirtualCoords coords = getCoords(x, y);
getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color);
}
inline void OneEighthMatrixPanel::fillScreen(uint16_t color) // adafruit virtual void override
{
// No need to map this.
this->display->fillScreen(color);
}
inline void OneEighthMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b)
{
//VirtualCoords coords = getCoords(x, y);
getCoords(x, y);
this->display->drawPixelRGB888( coords.x, coords.y, r, g, b);
}
inline void OneEighthMatrixPanel::setRotate(bool rotate) {
_rotate=rotate;
// We don't support rotation by degrees.
if (rotate) { setRotation(1); } else { setRotation(0); }
}
// need to recreate this one, as it wouldn't work to just map where it starts.
inline void OneEighthMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows) {
}
#endif
/*
// http://cpp.sh/6skpy
// Example program
#include <iostream>
#include <string>
int main()
{
for (int i = 0; i < 32; i++)
{
int x = 0;
int y = i;
if ( ((y /8) % 2) == 0) { // returns true/1 for the 1st and 3rd 8-pixel 1/4th of a 32px high panel
x += 64;
}
y = (i % 8);
if ( i >= 32/2 ) y +=8;
std::cout << "For input y = " << i << " mapping to y: " << y << " and x " << x << " \n";
}
}
For input y = 0 mapping to y: 0 and x 64
For input y = 1 mapping to y: 1 and x 64
For input y = 2 mapping to y: 2 and x 64
For input y = 3 mapping to y: 3 and x 64
For input y = 4 mapping to y: 4 and x 64
For input y = 5 mapping to y: 5 and x 64
For input y = 6 mapping to y: 6 and x 64
For input y = 7 mapping to y: 7 and x 64
For input y = 8 mapping to y: 0 and x 0
For input y = 9 mapping to y: 1 and x 0
For input y = 10 mapping to y: 2 and x 0
For input y = 11 mapping to y: 3 and x 0
For input y = 12 mapping to y: 4 and x 0
For input y = 13 mapping to y: 5 and x 0
For input y = 14 mapping to y: 6 and x 0
For input y = 15 mapping to y: 7 and x 0
For input y = 16 mapping to y: 8 and x 64
For input y = 17 mapping to y: 9 and x 64
For input y = 18 mapping to y: 10 and x 64
For input y = 19 mapping to y: 11 and x 64
For input y = 20 mapping to y: 12 and x 64
For input y = 21 mapping to y: 13 and x 64
For input y = 22 mapping to y: 14 and x 64
For input y = 23 mapping to y: 15 and x 64
For input y = 24 mapping to y: 8 and x 0
For input y = 25 mapping to y: 9 and x 0
For input y = 26 mapping to y: 10 and x 0
For input y = 27 mapping to y: 11 and x 0
For input y = 28 mapping to y: 12 and x 0
For input y = 29 mapping to y: 13 and x 0
For input y = 30 mapping to y: 14 and x 0
For input y = 31 mapping to y: 15 and x 0
*/

View file

@ -1,125 +0,0 @@
/*************************************************************************
* Description:
*
* The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
* supports output to 1/16 or 1/32 scan panels - which means outputting
* two lines at the same time, 16 or 32 rows apart. This cannot be changed
* at the DMA layer as it would require a messy and complex rebuild of the
* library's DMA internals.
*
* However, it is possible to connect 1/8 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 (in this example, it is the 'dmaOutput' class).
*
* Supports chaining of multiple 1/8 panels...
*
**************************************************************************/
// 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 1 // Number of INDIVIDUAL PANELS per ROW
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
// Change this to your needs, for details on VirtualPanel pls read the PDF!
#define SERPENT true
#define TOPDOWN false
// GPIO Configuration
#define R1_PIN 2
#define G1_PIN 15
#define B1_PIN 4
#define R2_PIN 16
#define G2_PIN 27
#define B2_PIN 17
#define A_PIN 5
#define B_PIN 18
#define C_PIN 19
#define D_PIN -1 // Connected to GND on panel (21 if exist)
#define E_PIN -1 // Connected to GND on panel
#define LAT_PIN 26
#define OE_PIN 25
#define CLK_PIN 22
#include "OneEighthScanMatrixPanel.h" // Virtual Display to re-map co-ordinates such that they draw correctly on a 32x16 1/4 Scan panel
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
// placeholder for the virtual display object
OneEighthMatrixPanel *virtualDisp = nullptr;
/******************************************************************************
* Setup!
******************************************************************************/
void setup() {
delay(2000);
Serial.begin(115200);
Serial.println(""); Serial.println(""); Serial.println("");
Serial.println("*****************************************************");
Serial.println("* 1/8 Scan Panel Demonstration *");
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::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
PANEL_CHAIN // DO NOT CHANGE THIS
//,_pins // Uncomment to enable custom pins
);
//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(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 OneEighthMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN);
virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255));
virtualDisp->setCursor(0, virtualDisp->height()/2);
// 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));
}
void loop() {
} // end loop