mrcodetastic / ESP32-HUB75-MatrixPanel-DMA

An Adafruit GFX Compatible Library for the ESP32, ESP32-S2, ESP32-S3 to drive HUB75 LED matrix panels using DMA for high refresh rates. Supports panel chaining.
MIT License
899 stars 201 forks source link

Newbe: P4 Outdoor panel 80x40 nothing works #627

Open bartoszx opened 2 months ago

bartoszx commented 2 months ago

Hi,

I've got this panel (https://a.aliexpress.com/_EJLWwpl) and I've been trying to run some demos, but no luck so far. All I'm getting are some random pixels and deformed output. I'm just trying to display some simple text. Can anyone tell me how to do it, or if it's even possible?

Attached are some images of my panel and the ESP32.

Here's an example of the code I've been trying to run (you'll find an enclosed image showing how it looks).


// Example sketch which shows how to display some patterns
// on a 64x32 LED matrix
//

#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#define R1_PIN 25
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 14
#define G2_PIN 12
#define B2_PIN 13

#define A_PIN 23
#define B_PIN 22
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1

#define CLK_PIN 16
#define LAT_PIN 4
#define OE_PIN 15

#define PANEL_RES_X 80      // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y 40     // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1      // Total number of panels chained one to another

//MatrixPanel_I2S_DMA dma_display;
MatrixPanel_I2S_DMA *dma_display = nullptr;

uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
// From: https://gist.github.com/davidegironi/3144efdc6d67e5df55438cc3cba613c8
uint16_t colorWheel(uint8_t pos) {
  if(pos < 85) {
    return dma_display->color565(pos * 3, 255 - pos * 3, 0);
  } else if(pos < 170) {
    pos -= 85;
    return dma_display->color565(255 - pos * 3, 0, pos * 3);
  } else {
    pos -= 170;
    return dma_display->color565(0, pos * 3, 255 - pos * 3);
  }
}

void drawText(int colorWheelOffset)
{

  // draw text with a rotating colour
  dma_display->setTextSize(1);     // size 1 == 8 pixels high
  dma_display->setTextWrap(false); // Don't wrap at end of line - will do ourselves

  dma_display->setCursor(5, 0);    // start at top left, with 8 pixel of spacing
  uint8_t w = 0;
  const char *str = "ESP32 DMA";
  for (w=0; w<strlen(str); w++) {
    dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
    dma_display->print(str[w]);
  }

  dma_display->println();
  dma_display->print(" ");
  for (w=9; w<18; w++) {
    dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
    dma_display->print("*");
  }

  dma_display->println();

  dma_display->setTextColor(dma_display->color444(15,15,15));
  dma_display->println("LED MATRIX!");

  // print each letter with a fixed rainbow color
  dma_display->setTextColor(dma_display->color444(0,8,15));
  dma_display->print('3');
  dma_display->setTextColor(dma_display->color444(15,4,0));
  dma_display->print('2');
  dma_display->setTextColor(dma_display->color444(15,15,0));
  dma_display->print('x');
  dma_display->setTextColor(dma_display->color444(8,15,0));
  dma_display->print('6');
  dma_display->setTextColor(dma_display->color444(8,0,15));
  dma_display->print('4');

  // Jump a half character
  dma_display->setCursor(34, 24);
  dma_display->setTextColor(dma_display->color444(0,15,15));
  dma_display->print("*");
  dma_display->setTextColor(dma_display->color444(15,0,0));
  dma_display->print('R');
  dma_display->setTextColor(dma_display->color444(0,15,0));
  dma_display->print('G');
  dma_display->setTextColor(dma_display->color444(0,0,15));
  dma_display->print("B");
  dma_display->setTextColor(dma_display->color444(15,0,8));
  dma_display->println("*");

}

void setup() {

  // Module configuration
  HUB75_I2S_CFG mxconfig(
    PANEL_RES_X,   // module width
    PANEL_RES_Y,   // module height
    PANEL_CHAIN    // Chain length
  );

  //mxconfig.gpio.e = 18;
  //mxconfig.clkphase = false;
  //mxconfig.driver = HUB75_I2S_CFG::FM6126A;

  // Display Setup
  dma_display = new MatrixPanel_I2S_DMA(mxconfig);
  dma_display->begin();
  dma_display->setBrightness8(90); //0-255
  dma_display->clearScreen();
  dma_display->fillScreen(myWHITE);

  // fix the screen with green
  dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0));
  delay(500);

  // draw a box in yellow
  dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0));
  delay(500);

  // draw an 'X' in red
  dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0));
  dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0));
  delay(500);

  // draw a blue circle
  dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15));
  delay(500);

  // fill a violet circle
  dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15));
  delay(500);

  // fill the screen with 'black'
  dma_display->fillScreen(dma_display->color444(0, 0, 0));

  //drawText(0);

}

uint8_t wheelval = 0;
void loop() {

    // animate by going through the colour wheel for the first two lines
    drawText(wheelval);
    wheelval +=1;

    delay(20); 
/*
  drawText(0);
  delay(2000);
  dma_display->clearScreen();
  dma_display->fillScreen(myBLACK);
  delay(2000);
  dma_display->fillScreen(myBLUE);
  delay(2000);
  dma_display->fillScreen(myRED);
  delay(2000);
  dma_display->fillScreen(myGREEN);
  delay(2000);
  dma_display->fillScreen(myWHITE);
  dma_display->clearScreen();
  */

}

output Zrzut ekranu 2024-05-1 o 20 52 54

Zrzut ekranu 2024-05-1 o 20 47 53

Zrzut ekranu 2024-05-1 o 20 46 42

Zrzut ekranu 2024-05-1 o 20 55 50

board707 commented 2 months ago

Hi Your panel probably could be run same way as quarter scan example. See #624 issue.

I have tested 40x20 s5 panel recently.

bartoszx commented 2 months ago

Hi Board707 Thanks for your reply! I've been trying to follow the quarter scan example you mentioned and made some progress, although it's mostly been through a trial and error approach rather than a clear strategy. Below is my code adapted from the "four scan" example:

#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" 

#define R1_PIN 25
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 14
#define G2_PIN 12
#define B2_PIN 13

#define A_PIN 23
#define B_PIN 22
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1

#define CLK_PIN 16
#define LAT_PIN 4
#define OE_PIN 15

  // Panel configuration
  #define PANEL_RES_X 80 // Number of pixels wide of each INDIVIDUAL panel module. 
  #define PANEL_RES_Y 40 // 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

  // ^^^ 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 false
  #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::ICN2038S;   // 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

also tryin to change

if (panel_scan_rate == FOUR_SCAN_32PX_HIGH) 

in ESP32-HUB75-MatrixPanel-I2S-DMA.h

here is how it look now

https://youtu.be/m4HkwAWMZqI?si=5c3woYD7t0OXuZA4

board707 commented 2 months ago

Hi In order to run your panel you need to change ESP32-HUB75-MatrixPanel-I2S-DMA.h file. Currently your code using a settings for 32 height panel.

But the better way rather than changing the library is create a custom virtual class as shown in example in #622 discussion. Sorry I can't dive in detail, I am in airport now :)

bartoszx commented 2 months ago

Try different changes and some of the tests seem to look okay (video), but it can't display the simple text "hello". Board707, thanks for your help, but I am afraid it is too complicated for my level of knowledge. We are building a smart basketball court and we need a simple display to show e.g. the score and player information. I thought that it would be an easy job, but it is not. I think I will look for something that works almost out of the box because we still need to integrate it with MQTT, etc., so I'm afraid this part could take a lot of time to learn.

Here is my latest code and results

/*************************************************************************
   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.

 **************************************************************************/

/* Use a custom Virtual Display class to re-map co-ordinates such that they draw
   correctly on a 32x16 1/4 Scan panel (or chain of such panels).
*/
#include "ESP32-VirtualMatrixPanel-I2S-DMA.h"
#include <Adafruit_GFX.h>  // Include the library for the display functions

#define R1_PIN 25
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 14
#define G2_PIN 12
#define B2_PIN 13

#define A_PIN 23
#define B_PIN 22
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1

#define CLK_PIN 16
#define LAT_PIN 4
#define OE_PIN 15

/* ================================================== */
// Define custom class derived from VirtualMatrixPanel
class EightPxBasePanel : public VirtualMatrixPanel
{
  public:
    using VirtualMatrixPanel::VirtualMatrixPanel; // inherit VirtualMatrixPanel's constructor(s)

  protected:

    VirtualCoords getCoords(int16_t x, int16_t y);  // custom getCoords() method for specific pixel mapping

};

// define custom getCoords() method for specific pixel mapping
inline VirtualCoords EightPxBasePanel ::getCoords(int16_t x, int16_t y) {

  coords = VirtualMatrixPanel::getCoords(x, y); // first call base class method 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;
  }

  uint8_t pxbase = 8;  // pixel base
  if ((coords.y & 4) == 0)
  {
    coords.x = (coords.x / pxbase) * 2 * pxbase  + pxbase  + 7 - (coords.x & 0x7); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
  }
  else
  {
    coords.x += (coords.x / pxbase) * pxbase; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
  }

  coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);

  return coords;
}
/* ================================================== */
// Panel configuration
#define PANEL_RES_X 80 // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y 40 // 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

// ^^^ 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
#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN

// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;

// placeholder for the virtual display object
EightPxBasePanel   *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::ICN2038S;  
  //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(40);    // 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 EightPxBasePanel ((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y,    VIRTUAL_MATRIX_CHAIN_TYPE);

  // THE IMPORTANT BIT BELOW!
  // FOUR_SCAN_16PX_HIGH
  // FOUR_SCAN_32PX_HIGH
// **** You don't need to set PhysicalPanelScanRate when use your own virtual panel class
  //FourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH);

  dma_display->begin();  // Start the display

  dma_display->setTextColor(dma_display->color565(255, 255, 255)); // Set text color to white
  dma_display->setTextSize(1); // Set text size. Adjust as needed for your specific panel size
  dma_display->setCursor(0, 0); // Set cursor to top left corner of the display

  dma_display->println("hello"); // Print "hello" at the current cursor position

}

// Test the pixel mapping - fill the panel pixel by pixel
void loop() {

  for (int i = 0; i < FourScanPanel->height(); i++)
  {
    for (int j = 0; j < FourScanPanel->width(); j++)
    {

      FourScanPanel->drawPixel(j, i, FourScanPanel->color565(255, 0, 0));
      delay(30);
    }
  }

  delay(2000);
  dma_display->clearScreen();

} // end loop

results

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/195576/0576647d-5049-4e54-9558-83075e8c02bb

p.s check out our family project https://www.instagram.com/gamechangair?igsh=MW5jbm9nemJ4MHl3dA==

mrcodetastic commented 2 months ago

This is just a pixel mapping issue because of the weird way this panel works.

The brute force way of fixing this is simply having a function that converts coordinates from what you want to what the panel needs.

One way to figure this out is make a simple sketch that starts at pixel x 0, y 0 and then slowly increments (lights up LEDs) line by line to x 79, y 39.

Make notes for the first couple of lines and work on a lookup function using a simple array to convert x to panelx and y to panely.

There's a pattern here in that it looks like the panel is broken into x*x sub squares of pixels.

Painful, but this library supports this panel electrically, but if the manufacturer chains physical pixels in weird ways you'll need to figure out the mapping.

Alternatively, just go buy a standard 64x64 pixel panel and it'll probably work out of the box.

bartoszx commented 2 months ago

@mrcodetastic thanks for clarification will it work out of the box https://a.aliexpress.com/_Evn9Fzt ?

mrcodetastic commented 2 months ago

I do not give any guarantees on any sellers, in particular Chinese ones as they'll send anything, regardless of the description.

Doesn't look like the right one to me, you'd want a 1/32 scan for a 64*64 panel.

board707 commented 2 months ago

Hi, @bartoszx You took my example for the 32x16 s4 matrix and used it as is. But it won't work that way. Each type of matrix requires its own code. The example needs to be edited for your 80x40 panel. Please change the getCoords() method in your class this way and show the resulting video.

// define custom getCoords() method for specific pixel mapping
inline VirtualCoords EightPxBasePanel ::getCoords(int16_t x, int16_t y) {

  coords = VirtualMatrixPanel::getCoords(x, y); // first call base class method 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;
  }

  uint8_t pxbase = 80;  // pixel base
  if ((coords.y  % 20 ) < 10)
  {
    coords.x += (coords.x / pxbase + 1) * pxbase;
  }
  else
  {
    coords.x += (coords.x / pxbase) * pxbase; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
  }

  coords.y = (coords.y /20 ) * 10 + (coords.y % 10);

  return coords;
}

(take note that it is not a final solution, but just a test)

bartoszx commented 2 months ago

@board707 Here is how it looks now

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/195576/93951efe-4eee-4838-95d5-20eb18eb8f16

board707 commented 2 months ago

Looks good. Now change the pixbase to 16 and test again.

bartoszx commented 2 months ago

@board707 looks good?

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/195576/da64f07d-26d1-49d3-bfb0-2f0cfdf5fb5d

board707 commented 2 months ago

It seems to me that the problem has solved. You can now use this virtual class in your project. The pixel mapping also should works for multiple panels.

bartoszx commented 2 months ago

It works 🍾 Thank you @board707

IMG_7332

mrcodetastic commented 2 months ago

Looks good but I suggest you shorten the length of the wires from the esp32 to no greater than 10cm to avoid interference.

There seems to be some random artefacts showing... Likely caused by electrical interference.

bartoszx commented 2 months ago

@mrcodetastic thanks for tip, didnt know that

bartoszx commented 3 weeks ago

Hi @mrcodetastic one more question :) Everything works fine with text, but when trying to show a gif, after 2 days of trying different approaches, code, and parameters, nothing seems to work. I tried using different gifs, and each one looks jumbled.

my code

#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
#include "virtualdispaly.h"
#include <LittleFS.h>
#include <AnimatedGIF.h>

#define FILESYSTEM LittleFS

// Define panel configuration for 4 panels (2x2)
//#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 PANEL_CHAIN 4    // Total number of panels chained one to another

#define PANEL_RES_X 80 // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y 40 // Number of pixels tall of each INDIVIDUAL panel module.

// MatrixPanel_I2S_DMA *dma_display = nullptr;
VirtualMatrixPanel *virtualDisp = nullptr;

uint16_t myBLACK, myWHITE, myRED, myGREEN, myBLUE;

AnimatedGIF gif;
File f;
int x_offset, y_offset;

void GIFDraw(GIFDRAW *pDraw)
{
  uint8_t *s;
  uint16_t *d, *usPalette, usTemp[320];
  int x, y, iWidth;

  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line

  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) // restore to background color
  {
    for (x = 0; x < iWidth; x++)
    {
      if (s[x] == pDraw->ucTransparent)
        s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }
  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) // if transparency used
  {
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    int x, iCount;
    pEnd = s + pDraw->iWidth;
    x = 0;
    iCount = 0; // count non-transparent pixels
    while (x < pDraw->iWidth)
    {
      c = ucTransparent - 1;
      d = usTemp;
      while (c != ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent) // done, stop
        {
          s--; // back up to treat it like transparent
        }
        else // opaque
        {
          *d++ = usPalette[c];
          iCount++;
        }
      }           // while looking for opaque pixels
      if (iCount) // any opaque pixels?
      {
        for (int xOffset = 0; xOffset < iCount; xOffset++)
        {
          dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]);
        }
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent)
          iCount++;
        else
          s--;
      }
      if (iCount)
      {
        x += iCount; // skip these
        iCount = 0;
      }
    }
  }
  else
  {
    s = pDraw->pPixels;
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    for (x = 0; x < pDraw->iWidth; x++)
    {
      dma_display->drawPixel(x, y, usPalette[*s++]);
    }
  }
} /* GIFDraw() */

void *GIFOpenFile(const char *fname, int32_t *pSize)
{
    Serial.print("Playing gif: ");
    Serial.println(fname);
    f = FILESYSTEM.open(fname);
    if (f)
    {
        *pSize = f.size();
        return (void *)&f;
    }
    return NULL;
}

void GIFCloseFile(void *pHandle)
{
    File *f = static_cast<File *>(pHandle);
    if (f != NULL)
        f->close();
}

int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
    int32_t iBytesRead;
    iBytesRead = iLen;
    File *f = static_cast<File *>(pFile->fHandle);
    // Note: If you read a file all the way to the last byte, seek() stops working
    if ((pFile->iSize - pFile->iPos) < iLen)
        iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
    if (iBytesRead <= 0)
        return 0;
    iBytesRead = (int32_t)f->read(pBuf, iBytesRead); // Poprawka: int32_t zamiast int3232
    pFile->iPos = f->position();
    return iBytesRead;
}

int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{ 
    int i = micros();
    File *f = static_cast<File *>(pFile->fHandle);
    f->seek(iPosition);
    pFile->iPos = (int32_t)f->position();
    i = micros() - i;
    return pFile->iPos;
}

unsigned long start_tick = 0;

void ShowGIF(char *name)
{
    start_tick = millis();

    if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
    {
        while (gif.playFrame(true, NULL))
        {      
            if ((millis() - start_tick) > 8000) { // we'll get bored after about 8 seconds of the same looping gif
                break;
            }
        }
        gif.close();
    }
}

void setup()
{
    Serial.begin(115200);
    delay(1000);

    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;

    dma_display = new MatrixPanel_I2S_DMA(mxconfig);
    dma_display->begin();
    dma_display->setBrightness8(40); // Ustaw jasność na 64 (możesz dostosować tę wartość)
    dma_display->clearScreen();

//    virtualDisp = new VirtualMatrixPanel((*dma_display), 2, 2, PANEL_RES_X, PANEL_RES_Y, CHAIN_TOP_RIGHT_DOWN);
    virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);

//   new EightPxBasePanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);

    myBLACK = virtualDisp->color565(0, 0, 0);
    myWHITE = virtualDisp->color565(255, 255, 255);
    myRED = virtualDisp->color565(255, 0, 0);
    myGREEN = virtualDisp->color565(0, 255, 0);
    myBLUE = virtualDisp->color565(0, 0, 255);

    Serial.println("Starting AnimatedGIFs Sketch");

    // Start filesystem
    Serial.println(" * Loading LittleFS");
    if (!LittleFS.begin()) {
        Serial.println("LittleFS Mount Failed");
    }

    virtualDisp->fillScreen(myBLACK);
    gif.begin(LITTLE_ENDIAN_PIXELS);
}

String gifDir = "/gifs"; // play all GIFs in this directory on the SD card
char filePath[256] = { 0 };
File root, gifFile;

void loop()
{
    while (1) // run forever
    {
        root = FILESYSTEM.open(gifDir);
        if (root)
        {
            gifFile = root.openNextFile();
                Serial.println("openNextFile: ");            
            while (gifFile)
            {
                if (!gifFile.isDirectory()) // play it
                {
                    // C-strings... urghh...                
                    memset(filePath, 0x0, sizeof(filePath));                
                    strcpy(filePath, gifFile.path());

                    // Show it.
                    Serial.println("Show: ");
                    Serial.println(filePath);
                    ShowGIF(filePath);
                }
                gifFile.close();
                gifFile = root.openNextFile();
            }
            root.close();
        } // root

        delay(400); // pause before restarting
    } // while
}

virtualdisplay.h

#define R1_PIN 25
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 14
#define G2_PIN 12
#define B2_PIN 13

#define A_PIN 23
#define B_PIN 19
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1

#define CLK_PIN 16
#define LAT_PIN 4
#define OE_PIN 15

/* ================================================== */
// Define custom class derived from VirtualMatrixPanel
class EightPxBasePanel : public VirtualMatrixPanel
{
  public:
    using VirtualMatrixPanel::VirtualMatrixPanel; // inherit VirtualMatrixPanel's constructor(s)

  protected:

    VirtualCoords getCoords(int16_t x, int16_t y);  // custom getCoords() method for specific pixel mapping

};

// define custom getCoords() method for specific pixel mapping
// define custom getCoords() method for specific pixel mapping
inline VirtualCoords EightPxBasePanel ::getCoords(int16_t x, int16_t y) {

  coords = VirtualMatrixPanel::getCoords(x, y); // first call base class method 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;
  }

  uint8_t pxbase = 16;  // pixel base
  if ((coords.y  % 20 ) < 10)
  {
    coords.x += (coords.x / pxbase + 1) * pxbase;
  }
  else
  {
    coords.x += (coords.x / pxbase) * pxbase; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
  }

  coords.y = (coords.y /20 ) * 10 + (coords.y % 10);

  return coords;
}
/* ================================================== */
// Panel configuration
#define PANEL_RES_X 80 // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y 40 // 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

// ^^^ 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
#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN

// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;

// placeholder for the virtual display object
EightPxBasePanel   *FourScanPanel = nullptr;

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/195576/1723e844-3362-4ee8-9968-d23a45797ede

bartoszx commented 3 weeks ago

@board707 is that problem with my 1/10 scan?

board707 commented 3 weeks ago

In the void GIFDraw(GIFDRAW *pDraw) function try to use a

virtualDisp->drawPixel(...)

anywhere instead of dma_display->

bartoszx commented 3 weeks ago

I've tried that as well. Here's another example I've checked.

void GIFDraw(GIFDRAW *pDraw)
{
    uint8_t *s;
    uint16_t *d, *usPalette, usTemp[320];
    int x, y, iWidth;

    iWidth = pDraw->iWidth;
    if (iWidth > 80) // Adjust for total width of 4 panels horizontally
        iWidth = 80;

    usPalette = pDraw->pPalette;
    y = pDraw->iY + pDraw->y + y_offset -0; // Add y_offset

    s = pDraw->pPixels;
    if (pDraw->ucDisposalMethod == 2) // restore to background color
    {
        for (x = 0; x < iWidth; x++)
        {
            if (s[x] == pDraw->ucTransparent)
                s[x] = pDraw->ucBackground;
        }
        pDraw->ucHasTransparency = 0;
    }
    // Apply the new pixels to the main image
    if (pDraw->ucHasTransparency) // if transparency used
    {
        uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
        int x, iCount;
        pEnd = s + pDraw->iWidth;
        x = 0;
        iCount = 0; // count non-transparent pixels
        while (x < pDraw->iWidth)
        {
            c = ucTransparent - 1;
            d = usTemp;
            while (c != ucTransparent && s < pEnd)
            {
                c = *s++;
                if (c == ucTransparent) // done, stop
                {
                    s--; // back up to treat it like transparent
                }
                else // opaque
                {
                    *d++ = usPalette[c];
                    iCount++;
                }
            } // while looking for opaque pixels
            if (iCount) // any opaque pixels?
            {
                for (int xOffset = 0; xOffset < iCount; xOffset++)
                {
                    virtualDisp->drawPixel(x + xOffset, y, usTemp[xOffset]); // Add x_offset
                }
                x += iCount;
                iCount = 0;
            }
            // no, look for a run of transparent pixels
            c = ucTransparent;
            while (c == ucTransparent && s < pEnd)
            {
                c = *s++;
                if (c == ucTransparent)
                    iCount++;
                else
                    s--;
            }
            if (iCount)
            {
                x += iCount; // skip these
                iCount = 0;
            }
        }
    }
    else // does not have transparency
    {
        s = pDraw->pPixels;
        // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
        for (x = 0; x < pDraw->iWidth; x++)
        {
            virtualDisp->drawPixel(x, y, usPalette[*s++]); // Add x_offset
        }
    }
}

everytime, same results In another test I ve tried to display static harts

const unsigned short RedHeart[2048] PROGMEM={
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,   // 0x0010 (16) pixels
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,   // 0x0020 (32) pixels

...

    FourScanPanel = new EightPxBasePanel((*dma_display), NUM_ROWS, NUM_COLS,PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);

...
    FourScanPanel->drawRGBBitmap(0, 0, (const uint16_t *)RedHeart, 80, 40);

but no luck

Zrzut ekranu 2024-07-4 o 16 51 22

board707 commented 3 weeks ago

It looks like the real and virtual coordinates are mixed up somewhere else - just like I indicated in the function GIFDraw(). All calls to the real dma_display object need to be replaced with a virtual one, except for the very first one where the dma_display is used to initialize the virtual.

bartoszx commented 3 weeks ago

You are right when you pointed out the dependency, it dawned on me, and I changed the code Works perfect. Thanks again

Zrzut ekranu 2024-07-4 o 17 56 58