I've created a custom mapper for GKGD P10-3216-4s-x1

I’ve created a custom mapper for GKGD P10-3216-4s-x1. These panels were giving me headaches. I’ve almost pulled my hair out, but then it suddenly worked :slight_smile: . I’m not really familiar with linux or c++, so any feedback is appreciated. I now want to make it pluggable into the library so that i can use it in all examples and so on. How do i do that? Is there a way to reduce flickering?

Thanks for this library and your help is much appreciated! Here is my code so far:

#include “led-matrix.h”
#include “graphics.h”
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

using namespace rgb_matrix;

volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}

// P10 transformation for a SINGLE panel (GKGD P10-3216-4s-x1)
void TransformSingleP10(int x, int y, int *new_x, int *new_y) {
if (x < 0 || x >= 32 || y < 0 || y >= 16) {
*new_x = 0;
*new_y = 0;
return;
}

// Shift right by 8 pixels (1 block)
int shifted_x = (x + 8) % 32;

// Flip vertically (panel mounted upside down)
int flipped_y = 15 - y;

// Panel divided into 4 horizontal sections of 4 rows each
int section = flipped_y / 4;

// Each section divided into 4 blocks of 8 columns
int block = shifted_x / 8;
int x_in_block = shifted_x % 8;

if (section == 0 || section == 2) {
// Scrambled block order: 1,4,3,2
int block_map[4] = {0, 3, 2, 1};
int physical_block = block_map[block];
*new_x = (physical_block * 8) + x_in_block;
} else {
// Reversed horizontally with 8-pixel shift
int reversed_x = 31 - shifted_x;
*new_x = (reversed_x + 8) % 32;
}

*new_y = flipped_y;

// Safety clamp
if (*new_x < 0) *new_x = 0;
if (*new_x >= 32) *new_x = 31;
if (*new_y < 0) *new_y = 0;
if (*new_y >= 16) *new_y = 15;
}

// Transform for 2x2 matrix (64x32) - FINAL WORKING VERSION
// Physical wiring: Top-left → Top-right → Bottom-right (upside down) → Bottom-left (upside down)
// Actual chain discovered: 0=Bottom-left, 1=Bottom-right, 2=Top-right, 3=Top-left
void Transform2x2P10(int x, int y, int *new_x, int *new_y) {
// Strict bounds checking
if (x < 0 || x >= 64 || y < 0 || y >= 32) {
*new_x = -1; // Signal invalid
*new_y = -1;
return;
}

// Which quadrant in logical 2x2 layout?
int panel_col = x / 32; // 0 = left, 1 = right
int panel_row = y / 16; // 0 = top, 1 = bottom

int local_x = x % 32;
int local_y = y % 16;

// Safety check on local coords
if (local_x < 0 || local_x >= 32 || local_y < 0 || local_y >= 16) {
*new_x = -1;
*new_y = -1;
return;
}

int chain_panel;
int panel_local_x, panel_local_y;

if (panel_row == 0 && panel_col == 0) {
// Top-left → Chain position 3 (right-side up)
chain_panel = 3;
panel_local_x = local_x;
panel_local_y = local_y;

} else if (panel_row == 0 && panel_col == 1) {
// Top-right → Chain position 2 (right-side up)
chain_panel = 2;
panel_local_x = local_x;
panel_local_y = local_y;

} else if (panel_row == 1 && panel_col == 0) {
// Bottom-left → Chain position 0 (upside down)
chain_panel = 0;
panel_local_x = 31 - local_x;
panel_local_y = 15 - local_y;

} else { // panel_row == 1 && panel_col == 1
// Bottom-right → Chain position 1 (upside down)
chain_panel = 1;
panel_local_x = 31 - local_x;
panel_local_y = 15 - local_y;
}

// Apply single-panel P10 transformation
int transformed_x, transformed_y;
TransformSingleP10(panel_local_x, panel_local_y, &transformed_x, &transformed_y);

// Final bounds check
if (transformed_x < 0 || transformed_x >= 32 || transformed_y < 0 || transformed_y >= 16) {
*new_x = -1;
*new_y = -1;
return;
}

*new_x = (chain_panel * 32) + transformed_x;
*new_y = transformed_y;
}

// Wrapper canvas for 2x2 P10 matrix
class P10Canvas2x2 : public Canvas {
private:
Canvas *delegate_;

public:
P10Canvas2x2(Canvas *delegate) : delegate_(delegate) {}

virtual int width() const { return 64; }
virtual int height() const { return 32; }

virtual void SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
int new_x, new_y;
Transform2x2P10(x, y, &new_x, &new_y);

// Skip if transformation returned invalid coordinates
if (new_x < 0 || new_y < 0) return;

delegate_->SetPixel(new_x, new_y, r, g, b);

}

virtual void Clear() { delegate_->Clear(); }

virtual void Fill(uint8_t r, uint8_t g, uint8_t b) {
for (int y = 0; y < 32; y++) {
for (int x = 0; x < 64; x++) {
SetPixel(x, y, r, g, b);
}
}
}
};

int main(int argc, char *argv) {
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;

// Configuration for 2x2 matrix (4 panels = 64x32)
matrix_options.rows = 16;
matrix_options.cols = 32;
matrix_options.chain_length = 4; // 4 panels chained
matrix_options.parallel = 1;
matrix_options.brightness = 80; // Reduce brightness to minimize flickering
matrix_options.pwm_lsb_nanoseconds = 130; // Better PWM timing
runtime_opt.gpio_slowdown = 2; // Increase for stability
runtime_opt.drop_privileges = 0;

rgb_matrix::ParseOptionsFromFlags(&argc, &argv, &matrix_options, &runtime_opt);

RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (!matrix) {
fprintf(stderr, “Failed to create matrix\n”);
return 1;
}

signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);

Font font;
if (!font.LoadFont(“../fonts/9x15.bdf”)) {
fprintf(stderr, “Failed to load font, trying smaller font…\n”);
if (!font.LoadFont(“../fonts/6x9.bdf”)) {
fprintf(stderr, “Failed to load any font\n”);
delete matrix;
return 1;
}
}

// Create 2x2 P10 wrapper
P10Canvas2x2 wrapper(matrix);

printf(“P10 2x2 Matrix (64x32) - GKGD P10-3216-4s-x1\n”);
printf(“Press Ctrl+C to exit\n\n”);

int pos = 64;

while (!interrupt_received) {
wrapper.Clear();

// Draw scrolling text across full width
Color cyan(0, 255, 255);
rgb_matrix::DrawText(&wrapper, font, pos, 20, cyan, "HELLO 2x2 P10 MATRIX!");

// Draw borders around entire display
for (int x = 0; x < 64; x++) {
  wrapper.SetPixel(x, 0, 255, 0, 0);    // Red top
  wrapper.SetPixel(x, 31, 0, 0, 255);   // Blue bottom
}

for (int y = 0; y < 32; y++) {
  wrapper.SetPixel(0, y, 0, 255, 0);    // Green left
  wrapper.SetPixel(63, y, 255, 255, 0); // Yellow right
}

// Draw dividing lines to show panel boundaries
for (int x = 0; x < 64; x++) {
  wrapper.SetPixel(x, 16, 64, 64, 64); // Horizontal middle (dim)
}
for (int y = 0; y < 32; y++) {
  wrapper.SetPixel(32, y, 64, 64, 64); // Vertical middle (dim)
}

pos--;
if (pos < -130) pos = 64;
usleep(50000);  // 50ms = ~20fps

}

wrapper.Clear();
delete matrix;

printf(“\nExiting…\n”);
return 0;
}

/*

  • ============================================================================
  • GKGD P10-3216-4s-x1 2x2 MATRIX CONFIGURATION
  • ============================================================================
  • PHYSICAL LAYOUT:
  • [Top-Left] [Top-Right] (both right-side up, connectors down)
  • [Bottom-Left] [Bottom-Right] (both upside down, connectors up)
  • WIRING (Chain Order):
  • Pi GPIO → Panel 1 (Bottom-Left, upside down)
  •      → Panel 2 (Bottom-Right, upside down)  
    
  •      → Panel 3 (Top-Right, right-side up)
    
  •      → Panel 4 (Top-Left, right-side up)
    
  • Each panel connects via HUB75 ribbon cable from OUTPUT to next INPUT.
  • Bottom panels are mounted upside down for easier cable routing.
  • ============================================================================
  • COMPILATION:
  • g++ -I../include p10_2x2.cc -o p10_2x2 ../lib/librgbmatrix.a -lpthread -std=c++11
  • USAGE:
  • sudo ./p10_2x2 --led-no-hardware-pulse --led-multiplexing=4 --led-slowdown-gpio=2
  • NOTES:
    • Sound module must be disabled: dtparam=audio=off in /boot/config.txt
    • GPIO slowdown set to 2 for stability (reduce flickering)
    • Brightness set to 80% to minimize ghosting
    • All panels use the same P10 transformation (internal wiring is identical)
    • Bottom panels are flipped in software to account for upside-down mounting
  • ============================================================================
    */

Hi @kstr
It’s great that you were able to create the mapper yourself. But there’s one problem. To integrate a mapper into the library, it must be written so that it’s independent of the number of panels. If I understand your code correctly, you have a separate mapper for a single panel and another for 2x2 panels. This is the wrong approach.

In fact, your data flows sequentially from panel to panel. And, regardless of the mapper, data flows into the next matrix ONLY after the previous one is filled. Therefore, a mapper that works for a single panel should work without any modifications for both 4 and 44 panels.