Community project

Animated LED Matrix Display

Photo of Animated LED Matrix Display
Generated with AI

Julius Grau

Published November 5, 2026

Build a 64x64 RGB LED matrix display that animates colorful orbs bouncing across a grid backdrop. This project combines an ESP32 microcontroller with a HUB75 LED panel, powered by a rechargeable LiPo battery and USB-C charging module for portable operation.

This guide provides a complete wiring diagram, parts list, and pre-written firmware using the Adafruit Protomatter library. Assembly takes just minutes—mount the MatrixPortal adapter to the LED panel, connect the battery to the charging module, wire the panel to power, and connect the controller to the battery. The included code demonstrates smooth animation with collision detection and customizable orb behavior, ready to modify for different effects.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

HUB75 RGB LED Matrix Panel

64 × 64, HUB75E, 5 V

HUB75 standard 2mm-pitch 16-pin IDC parallel-interface RGB LED matrix panel. Driven by two rows of RGB shift-register pixels simultaneously (upper: R1/G1/B1, lower: R2/G2/B2). Row addressing via A/B/C/D (and E for 64-row variants). Clocked serial data with CLK, latched with LAT/STB, output-enabled active-low with OE. Panel supply 5 V; data signals typically 3.3 V logic compatible when driven via level shifter or ESP32 DMA adapter. Common sizes: 32×16, 64×32, 64×64. Panels can be chained. Driven on ESP32 via I2S/DMA peripheral for high refresh rates.

1

Electrolytic Capacitor

1000 µF, 10 V minimum

Radial aluminium electrolytic capacitor bought by value and voltage rating, such as 10 µF, 100 µF or 1000 µF 10 V. It is polarised: the + lead goes to the higher voltage and the - lead, marked by the stripe on the sleeve, to the lower voltage or ground. It is mostly used for bulk filtering across a power rail, with a voltage rating comfortably above the rail.

1

1S 3.7 V 5000 mAh LiPo battery pack, 10C minimum

A protected rechargeable lithium-polymer battery pack that stores energy for the portable display.

1

IP5328P 18 W USB-C power-bank module

A preassembled USB-C battery charge and 5 V boost module that safely charges one LiPo pack and powers the display.

Assemble it in 5 steps

1. MatrixPortal auf das Panel setzen

Lege das 64×64-Panel mit der Anschlusseite nach oben hin. Stecke den 2×8-HUB75-Stecker des MatrixPortal S3 gerade auf den HUB75-Eingang des Panels, oder verwende das beiliegende 16-polige Flachbandkabel. Die rote Kante des Kabels muss an beiden Enden bei Pin 1 liegen.

  • Der Stecker darf nicht um eine Reihe versetzt sitzen; der MatrixPortal-Stecker ist dafür breiter als die Panelbuchse.
  • Ein verdrehtes oder um eine Reihe versetztes Flachbandkabel zeigt kein richtiges Bild und kann das Panel beschädigen.

2. Akku an das Lademodul anschließen

Verbinde BAT+ des 1S-LiPo-Akkus mit B+ am IP5328P-Modul und BAT− mit B−. Nutze den passenden Akkuanschluss oder isoliere jede Lötstelle einzeln mit Schrumpfschlauch.

  • Vertauschte Akkuadern können Akku und Lademodul beschädigen. Niemals kurzzeitig Plus und Minus berühren lassen.

3. Panel direkt mit 5 V versorgen

Verbinde 5V OUT des IP5328P mit VCC des Panels und GND OUT mit GND des Panels. Verwende für diese zwei Leitungen kurze, dicke Kabel. Stecke den 1000-µF-Kondensator parallel nahe am Panel an: sein längeres Bein mit Plus an VCC, die Seite mit dem Minusstreifen an GND.

  • Der Kondensator hilft gegen Flackern, wenn viele LEDs zugleich aufleuchten.
  • Der Minusstreifen am Kondensator muss an GND liegen; falsch herum kann der Kondensator heiß werden oder platzen. Das Panel darf nie am 3V3-Anschluss des Controllers hängen.

4. Controller vom Akku speisen

Verbinde 5 V vom USB-C-Ausgang des IP5328P mit einem kurzen USB-C-Kabel mit der USB-C-Buchse des MatrixPortal S3. Das Panel bleibt trotzdem an seinen eigenen dicken 5-V- und GND-Kabeln aus Schritt 3.

  • Wenn der Akku das MatrixPortal über USB-C versorgt, darf nicht gleichzeitig ein Computer-USB-Kabel angeschlossen sein; zwei 5-V-Quellen gegeneinander können Geräte beschädigen.

5. Zum Programmieren umstecken

Ziehe für das Programmieren das USB-C-Kabel vom Akkumodul zum MatrixPortal ab. Schließe danach nur den Computer per USB-C am MatrixPortal an. Nach dem Übertragen ziehst du das Computerkabel wieder ab und steckst das Akkukabel erneut ein.

  • Das Panel darf beim Programmieren weiter aus seinem eigenen 5-V-Stromweg versorgt bleiben.

Review all connections

1. Connections between "battery_1s_5000" and "ESP32"

Functionbattery_1s_5000ESP32
powerBAT+ → IP5328P 18 W USB-C power-bank module B+EXT
groundBAT- → IP5328P 18 W USB-C power-bank module B-EXT

2. Connections between "powerbank_module" and "ESP32"

Functionpowerbank_moduleESP32
power5V OUT → HUB75 RGB LED Matrix Panel VCCEXT
groundGND OUT → HUB75 RGB LED Matrix Panel GNDEXT
powerUSB-C → USB-C power input on the controller boardEXT

3. Connections between "supply_capacitor" and "ESP32"

Functionsupply_capacitorESP32
power+ → HUB75 RGB LED Matrix Panel VCCEXT
ground- → HUB75 RGB LED Matrix Panel GNDEXT

4. Connections between "matrix_64x64" and "ESP32"

Functionmatrix_64x64ESP32
digitalR1 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalG1 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalB1 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalR2 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalG2 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalB2 → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalA → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalB → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalC → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalD → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalE → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalCLK → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalLAT → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT
digitalOE → MatrixPortal S3 HUB75-Stecker über das 16-polige FlachbandkabelEXT

Deploy the firmware

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_Protomatter.h>

constexpr uint16_t PANEL_WIDTH = 64;
constexpr uint16_t PANEL_HEIGHT = 64;
// Four-bit color depth reduces current compared with a maximum-brightness panel mode.
constexpr uint32_t FRAME_INTERVAL_MS = 50;

// Fixed MatrixPortal S3 HUB75 wiring; do not connect these pins manually.
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
constexpr uint8_t CLOCK_PIN = 2;
constexpr uint8_t LATCH_PIN = 47;
constexpr uint8_t OUTPUT_ENABLE_PIN = 14;

Adafruit_Protomatter matrix(
  PANEL_WIDTH, 4, 1, rgbPins, 5, addrPins,
  CLOCK_PIN, LATCH_PIN, OUTPUT_ENABLE_PIN, false);

struct Orb {
  float x;
  float y;
  float dx;
  float dy;
  int16_t radius;
  uint16_t color;
};

Orb orbs[] = {
  {10.0f, 12.0f,  0.95f,  0.70f, 6, 0xF800},
  {50.0f, 14.0f, -0.80f,  0.92f, 5, 0x07E0},
  {18.0f, 48.0f,  0.72f, -0.86f, 7, 0x001F},
  {48.0f, 46.0f, -0.90f, -0.63f, 4, 0xFFE0}
};
constexpr size_t ORB_COUNT = sizeof(orbs) / sizeof(orbs[0]);
constexpr uint16_t GRID_COLOR = 0x0001;
uint32_t lastFrameMs = 0;

void drawGrid() {
  for (int16_t y = 0; y < PANEL_HEIGHT; y += 8) {
    matrix.drawFastHLine(0, y, PANEL_WIDTH, GRID_COLOR);
  }
  for (int16_t x = 0; x < PANEL_WIDTH; x += 8) {
    matrix.drawFastVLine(x, 0, PANEL_HEIGHT, GRID_COLOR);
  }
}

void restoreGridUnder(const Orb &orb) {
  const int16_t left = max<int16_t>(0, static_cast<int16_t>(orb.x) - orb.radius);
  const int16_t right = min<int16_t>(PANEL_WIDTH - 1, static_cast<int16_t>(orb.x) + orb.radius);
  const int16_t top = max<int16_t>(0, static_cast<int16_t>(orb.y) - orb.radius);
  const int16_t bottom = min<int16_t>(PANEL_HEIGHT - 1, static_cast<int16_t>(orb.y) + orb.radius);

  for (int16_t y = ((top + 7) / 8) * 8; y <= bottom; y += 8) {
    matrix.drawFastHLine(left, y, right - left + 1, GRID_COLOR);
  }
  for (int16_t x = ((left + 7) / 8) * 8; x <= right; x += 8) {
    matrix.drawFastVLine(x, top, bottom - top + 1, GRID_COLOR);
  }
}

void eraseOldOrbs() {
  for (size_t i = 0; i < ORB_COUNT; ++i) {
    matrix.fillCircle(static_cast<int16_t>(orbs[i].x), static_cast<int16_t>(orbs[i].y), orbs[i].radius, 0);
    restoreGridUnder(orbs[i]);
  }
}

void moveOrb(Orb &orb) {
  orb.x += orb.dx;
  orb.y += orb.dy;
  if (orb.x - orb.radius < 0 || orb.x + orb.radius >= PANEL_WIDTH) {
    orb.dx = -orb.dx;
    orb.x = constrain(orb.x, static_cast<float>(orb.radius), static_cast<float>(PANEL_WIDTH - 1 - orb.radius));
  }
  if (orb.y - orb.radius < 0 || orb.y + orb.radius >= PANEL_HEIGHT) {
    orb.dy = -orb.dy;
    orb.y = constrain(orb.y, static_cast<float>(orb.radius), static_cast<float>(PANEL_HEIGHT - 1 - orb.radius));
  }
}

void drawOrbs() {
  for (size_t i = 0; i < ORB_COUNT; ++i) {
    matrix.fillCircle(static_cast<int16_t>(orbs[i].x), static_cast<int16_t>(orbs[i].y), orbs[i].radius, orbs[i].color);
  }
}

void setup() {
  if (matrix.begin() != PROTOMATTER_OK) {
    while (true) delay(1000);
  }
  matrix.fillScreen(0);
  drawGrid();
  drawOrbs();
}

void loop() {
  const uint32_t now = millis();
  const uint32_t elapsed = now - lastFrameMs;
  if (elapsed < FRAME_INTERVAL_MS) {
    delay(FRAME_INTERVAL_MS - elapsed);
    return;
  }

  lastFrameMs = now;
  eraseOldOrbs();
  for (size_t i = 0; i < ORB_COUNT; ++i) moveOrb(orbs[i]);
  drawOrbs();
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik