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

**URL:** <https://rpi-rgb-led-matrix.discourse.group/t/ive-created-a-custom-mapper-for-gkgd-p10-3216-4s-x1/1151>\
**Category:** Uncategorized\
**Created:** [December 1, 2025, 7:53pm UTC](https://rpi-rgb-led-matrix.discourse.group/t/ive-created-a-custom-mapper-for-gkgd-p10-3216-4s-x1/1151 "2025-12-01T19:53:48Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![kstr](https://avatars.discourse-cdn.com/v4/letter/k/df705f/32.png) [@kstr](https://rpi-rgb-led-matrix.discourse.group/u/kstr)\
**Post date:** [December 1, 2025, 7:53pm UTC](https://rpi-rgb-led-matrix.discourse.group/t/ive-created-a-custom-mapper-for-gkgd-p10-3216-4s-x1/1151/1 "2025-12-01T19:53:48Z")

</div>

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 🙂 . 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);

```auto
// 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();

```auto
// 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)
- 

```auto
     → Panel 2 (Bottom-Right, upside down)  

```

- 

```auto
     → Panel 3 (Top-Right, right-side up)

```

- 

```auto
     → 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

- ============================================================================  
\*/

---

<div class="post-metadata">

**Author:** ![board707](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@board707](https://rpi-rgb-led-matrix.discourse.group/u/board707)\
**Post date:** [December 2, 2025, 7:41am UTC](https://rpi-rgb-led-matrix.discourse.group/t/ive-created-a-custom-mapper-for-gkgd-p10-3216-4s-x1/1151/2 "2025-12-02T07:41:38Z")

</div>

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.
