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);
// 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
- ============================================================================
*/