clockwise icon indicating copy to clipboard operation
clockwise copied to clipboard

Matrix Grid

Open krzimmer opened this issue 1 month ago • 10 comments

Hi. I'm trying to make a clock that consists of 2x2+ grid. I see that chaining is available (1x*), but for a 2x2+ grid, VirtualMatrixPanel_T is required. I was able to get the VirtualMatrixPanel example code to work, but I'm struggling to figure out how to draw the clockface to the Virtual Matrix. Could this option be enable by default but set to 1x1. This would allow users to expand if desired.

krzimmer avatar Oct 20 '25 18:10 krzimmer

This is what I've tried in main.cpp

#include <Arduino.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <ESP32-HUB75-VirtualMatrixPanel_T.hpp>

// Clockface
#include <Clockface.h>
// Commons
#include <WiFiController.h>
#include <CWDateTime.h>
#include <CWPreferences.h>
#include <CWWebServer.h>
#include <StatusController.h>

#define MIN_BRIGHT_DISPLAY_ON 4
#define MIN_BRIGHT_DISPLAY_OFF 0

/**
* Configuration of the LED matrix panels number and individual pixel resolution.
**/
#define PANEL_RES_X     32     // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y     16     // Number of pixels tall of each INDIVIDUAL panel module.

#define VDISP_NUM_ROWS      4 // 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_LEFT_DOWN_ZZ

/**
* 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
 
#define ESP32_LED_BUILTIN 2

/**
* 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.
**/
VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>* virtualDisp = nullptr;

Clockface *clockface;

WiFiController wifi;
CWDateTime cwDateTime;

bool autoBrightEnabled;
long autoBrightMillis = 0;
uint8_t currentBrightSlot = -1;

void displaySetup(bool swapBlueGreen, uint8_t displayBright, uint8_t displayRotation)
{
  HUB75_I2S_CFG mxconfig(
       PANEL_RES_X,   
       PANEL_RES_Y,  
       PANEL_CHAIN_LEN
     );
  if (swapBlueGreen)
  {
    // Swap Blue and Green pins because the panel is RBG instead of RGB.
    mxconfig.gpio.b1 = 26;
    mxconfig.gpio.b2 = 12;
    mxconfig.gpio.g1 = 27;
    mxconfig.gpio.g2 = 13;
  }
  mxconfig.gpio.r1 = 25;
  mxconfig.gpio.r2 = 14;
  mxconfig.gpio.g1 = 26;
  mxconfig.gpio.g2 = 12;
  mxconfig.gpio.b1 = 27;
  mxconfig.gpio.b2 = 13;
  mxconfig.gpio.a = 23;
  mxconfig.gpio.b = 19;
  mxconfig.gpio.c = 5;
  mxconfig.gpio.d = -1;
  mxconfig.gpio.e = -1;
  mxconfig.gpio.clk = 16;
  mxconfig.gpio.lat = 4;
  mxconfig.gpio.oe = 15;
  mxconfig.clkphase = false;
  //mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;
  //mxconfig.driver = HUB75_I2S_CFG::FM6126A;

  // Physical Display Setup
  dma_display = new MatrixPanel_I2S_DMA(mxconfig);
  dma_display->begin();
  dma_display->setBrightness8(displayBright);
  dma_display->clearScreen();
  dma_display->setRotation(displayRotation);

  //Setup the VirtualMatrixPanel_T class to map the virtual pixels to the physical pixels.
  virtualDisp = new VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>(VDISP_NUM_ROWS, VDISP_NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
}

void automaticBrightControl()
{
  if (autoBrightEnabled) {
    if (millis() - autoBrightMillis > 3000)
    {
      int16_t currentValue = analogRead(ClockwiseParams::getInstance()->ldrPin);

      uint16_t ldrMin = ClockwiseParams::getInstance()->autoBrightMin;
      uint16_t ldrMax = ClockwiseParams::getInstance()->autoBrightMax;

      const uint8_t minBright = (currentValue < ldrMin ? MIN_BRIGHT_DISPLAY_OFF : MIN_BRIGHT_DISPLAY_ON);
      uint8_t maxBright = ClockwiseParams::getInstance()->displayBright;

      uint8_t slots = 10; //10 slots
      uint8_t mapLDR = map(currentValue > ldrMax ? ldrMax : currentValue, ldrMin, ldrMax, 1, slots);
      uint8_t mapBright = map(mapLDR, 1, slots, minBright, maxBright);

      // Serial.printf("LDR: %d, mapLDR: %d, Bright: %d\n", currentValue, mapLDR, mapBright);
      if(abs(currentBrightSlot - mapLDR ) >= 2 || mapBright == 0){
           dma_display->setBrightness8(mapBright);
           currentBrightSlot=mapLDR;
          //  Serial.printf("setBrightness: %d , Update currentBrightSlot to %d\n", mapBright, mapLDR);
      }
      autoBrightMillis = millis();
    }
  }
}

void setup()
{
  Serial.begin(115200);
  pinMode(ESP32_LED_BUILTIN, OUTPUT);

  StatusController::getInstance()->blink_led(5, 100);

  ClockwiseParams::getInstance()->load();

  pinMode(ClockwiseParams::getInstance()->ldrPin, INPUT);

  displaySetup(ClockwiseParams::getInstance()->swapBlueGreen, ClockwiseParams::getInstance()->displayBright, ClockwiseParams::getInstance()->displayRotation);
  
  clockface = new Clockface(dma_display);

  // Pass a reference to the DMA display to the VirtualMatrixPanel_T class
   virtualDisp->setDisplay(*dma_display);
  
  autoBrightEnabled = (ClockwiseParams::getInstance()->autoBrightMax > 0);

  StatusController::getInstance()->clockwiseLogo();
  delay(1000);

  StatusController::getInstance()->wifiConnecting();
  if (wifi.begin())
  {
    StatusController::getInstance()->ntpConnecting();
    cwDateTime.begin(ClockwiseParams::getInstance()->timeZone.c_str(), 
        ClockwiseParams::getInstance()->use24hFormat, 
        ClockwiseParams::getInstance()->ntpServer.c_str(),
        ClockwiseParams::getInstance()->manualPosix.c_str());
    //clockface->setup(&cwDateTime);
    clockface->setup(&cwDateTime);
  }
}

void loop()
{
  wifi.handleImprovWiFi();

  if (wifi.isConnected())
  {
    ClockwiseWebServer::getInstance()->handleHttpRequest();
    ezt::events();
  }

  if (wifi.connectionSucessfulOnce)
  {
    //clockface->update();
    clockface->update();
  }

  automaticBrightControl();
}

krzimmer avatar Oct 20 '25 18:10 krzimmer

Hmm. I wouldn't change that because the clockfaces have a fixed size, the sprites won't scale.

jnthas avatar Oct 25 '25 20:10 jnthas

I’m still planning on using a total of 64x64 pixels so there shouldn’t be any scaling.

Image

krzimmer avatar Oct 26 '25 01:10 krzimmer

I figured it out with a 1x2 of P5 modules!! Again, no scaling because the P5s are 64x32. 64x64 total pixels.

The above code work with one small modification. clockface = new Clockface(dma_display); to clockface = new Clockface(virtualDisp);

Image

krzimmer avatar Nov 06 '25 06:11 krzimmer

0 and 180deg rotation works. 90 and 270deg do not. It must be a conflict between how the virtualDisplay and rotation code moves around the pixels.

krzimmer avatar Nov 06 '25 06:11 krzimmer

ESP32-VirtualMatrixPanel-I2S-DMA.h has its own rotate function.

    switch (_rotate) {
      case 0: //no rotation, do nothing
      break;
      
      case (1): //90 degree rotation
      {
        int16_t temp_x = virt_x;
        virt_x = virt_y;
        virt_y = virtualResY - 1 - temp_x;
        break;
      }

      case (2): //180 rotation
      {
        virt_x = virtualResX - 1 - virt_x;
        virt_y = virtualResY - 1 - virt_y;
        break;
      }

      case (3): //270 rotation
      {
        int16_t temp_x = virt_x;
        virt_x = virtualResX - 1 - virt_y;
        virt_y = temp_x;
        break;
      }
    }

krzimmer avatar Nov 06 '25 07:11 krzimmer

Work with P10 modules too. This is a 2x4 with 32x16px modules.

Image

krzimmer avatar Nov 07 '25 18:11 krzimmer

That's good to know. Thank you @krzimmer! Do you mind to open a pull request with that change of the virtual display? As long as it doesnt break the default one, I would like to have this feature included.

jnthas avatar Nov 09 '25 18:11 jnthas

Sure. I’ve never done a pull request, but there is a first for everything. I’ll give it a try.

Like I said, the only issue I’ve found so far is with screen rotation. Would you like for me to figure that out and include it in the same pull request or proceed with just the addition of virtualDisplay?

krzimmer avatar Nov 09 '25 19:11 krzimmer

Cool! That would be a great first PR then. Fixing the screen rotation is a "nice to have", if you can fix it that would be great but open the PR even if you can't, we can figure this out later.

jnthas avatar Nov 10 '25 00:11 jnthas