Tag Archives: RGB LED Matrix Panel

Build a Pixel-Art LED Matrix Clock, 3d printed diffuser and a web app

Four LED panels behind a 3D-printed diffuser, driven by a Raspberry Pi. Bright white digits on a shifting field of color — or flip it and the numbers go negative.

Clock using four OzzMaker RGFB LED matrices
Clock using four OzzMaker RGFB LED matrices

Underneath it’s just 1,024 LEDs, but in front of them sits a 3D-printed diffuser that gives every LED its own little cell — so the whole thing reads as crisp pixel art instead of a scatter of bright dots. The default look leans right into that: bright white digits on a background color that slowly wanders through a pastel palette. The diffuser’s hard grid between pixels is exactly what makes the contrast pop — white numeral, colored field, a clean edge around every square. And when you want a different mood, one toggle inverts it: the digit LEDs switch off and read as dark cut-outs in the lit field  

The idea: two ways to read the same grid The spark was a blank RGB panel glowing a flat color on the bench. A 64×16 grid of individually LEDs is really just a tiny screen — so what should the time actually look like on it? We landed on two answers and kept both. The headline look lights the digits up in bright white and lets the background carry the color: a soft pastel field that drifts, with clean white numerals on top. Behind the diffuser those white pixels sit in their own crisp cells, and the border between a lit-white digit and the colored surroundings is razor sharp — that contrast is the whole charm. The second look is the inverse: leave the field lit and switch the digit LEDs off, so the time reads as dark holes punched out of the color. That’s the negative mode — quieter, but just as striking. One toggle swaps between them.

Clock using four 16x16 RGB LED panels and a diffuser
Clock using four 16×16 RGB LED panels and a diffuser, powered by a Raspberry Pi
   

The idea: two ways to read the same grid

The headline look lights the digits up in bright white and lets the background carry the color: a soft pastel field that drifts, with clean white numerals on top. Behind the diffuser those white pixels sit in their own crisp cells, and the border between a lit-white digit and the colored surroundings is razor sharp — that contrast is the whole charm. The second look is the inverse: leave the field lit and switch the digit LEDs off, so the time reads as dark holes punched out of the color. That’s the negative mode — quieter, but just as striking. One toggle swaps between them.

The hardware

The canvas is four OzzMaker 16×16 RGB LED panels chained left-to-right into a single 64×16 display — 1,024 pixels. Driving them is a Raspberry Pi through OzzMaker’s LED connector , using Henner Zeller’s excellent rpi-rgb-led-matrix library to actually push bits to the panels at speed. Chain the panels OUT → IN, give them proper power, and the Pi sees them as one wide strip.
OzzMaker RGB LED Clock
Four RGB LED 16×16 panels, Raspberry Pi and diffuser
There’s one more physical layer that makes the whole thing work: a 3D-printed diffuser sits directly in front of the panels — a grid that gives every LED its own little walled-off cell. It softens the raw, glary point-sources into clean, evenly-lit squares, so the display reads as crisp pixel art rather than a scatter of bright dots. Those hard walls between pixels are also what sell the white-digit look: a lit-white numeral and the colored field beside it meet at a sharp, high-contrast edge instead of bleeding into each other. It’s the single cheapest part of the build and the one that does the most work — the difference between “a circuit board with LEDs on it” and “a screen.”  The diffuser also acts as a stand.  
Led diffuser and stand
Led diffuser and stand
  The back of the diffuser is shown below, as you can see, the inner walls are printed with black PLA, these stops light leaking between the LEDs.  This was printed on a Babu Lab  P2S multicolour printer
Led diffuser
LED diffuser
 

Drawing the digits

Everything is rendered into a plain in-memory buffer first — a 64×16 list of RGB tuples — and only pushed to the hardware at the very end. The digits themselves are an old-school seven-segment renderer. Each numeral is just a set of little rectangles; each digit says which segments it needs: # which of the 7 segments each digit lights (a=top, g=middle, …) SEGMENTS = { “0”: “abcdef”, “1”: “bc”, “2”: “abged”, “3”: “abgcd”, “4”: “fgbc”, “8”: “abcdefg”, # … } By default we draw those segments in lit white on top of the colored background, so the time The digits themselves are an old-school seven-segment renderer. Each numeral is just a set of little rectangles; each digit says which segments it needs:
# which of the 7 segments each digit lights (a=top, g=middle, ...)
SEGMENTS = {
  "0": "abcdef",  "1": "bc",     "2": "abged",
  "3": "abgcd",  "4": "fgbc",   "8": "abcdefg",  # ...
}

An effects gallery got out of hand

This is the part where a “simple clock” quietly became a hobby. The background doesn’t have to be a flat color — it just has to be something, with the time (white or negative) laid on top. So we kept adding “somethings.” By default the field cycles through an editable pastel palette once an hour, easing between colors with a fade, a diagonal swipe, or a top-to-bottom wipe. Then came the animated backgrounds:
  • Plasma — the classic rolling sine-wave field, colored by your palette.
  • Aurora — soft bands drifting sideways like northern lights.
  • Lava lamp — a few blobs floating and merging (metaballs, if you’re fancy).
  • Clouds — slow, morphing pastel noise.
  • Twinkle — a calm wash with sparse stars blinking in and out.
  • Ripples — two emitters of concentric waves, interfering.
  • Spiral — arms winding out from the center and rotating.
  • Scanner — a single comet sweeping edge to edge on black
And because that still wasn’t enough, there are flourishes you can stack on top of any of them: a slow breathing brightness pulse, a sparkle that shimmers for a second whenever the minute rolls over, and a time-of-day tint that warms the colors in the evening and cools them in the morning. Each effect precomputes its geometry once and then just does cheap math per pixel, so the whole thing holds a steady frame rate on a Raspberry Pi.  

A little web panel to tune it live

Fiddling with settings by editing files and restarting is miserable, so the same program also runs a tiny Flask web panel. Open http://<pi-ip>:8080 from any phone or laptop on the network and you get sliders and dropdowns for brightness, palette, effect, transitions, the lot — with a live preview of the actual frame the panel is drawing. Every change is saved to a settings.json and survives a reboot. Under the hood it’s one process with two threads that never touch each other’s stuff: a render thread that solely owns the matrix and runs at ~20 fps, and the web server. They talk only through a small set of thread-safe objects — settings in, status frames out. Keeping that boundary clean is what made it safe to keep piling on features without the display ever stuttering.    

Software setup

1. Install libraries

Follow this guide to get hzeller’s library installed, which will be used to drive the RGB LED panels.

2. Download and run the code

You can download the cross demo from our GitHub repository:

pberrypi ~ $ git clone https://github.com/ozzmaker/ozzMaker-LED-matrix

cd into the clock folder and run the script

pi@raspberrypi ~ $ cd ozzMaker-LED-matrix
pi@raspberrypi ~ $ cd clock
pi@raspberrypi ~ $ sudo python clock.py

Built with four OzzMaker 16×16 panels, a Raspberry Pi, and hzeller’s rpi-rgb-led-matrix. Digits by seven segments, color by pastels, time by NTP.

LED matrix in cross formation and how to pixel map using AI

In this guide you’ll build a cross-shaped display using five OzzMaker 16×16 RGB LED Matrix Panels — three panels across the middle, one on top and one on the bottom — driven by a Raspberry Pi.
You’ll then run a set of old-school demo effects (plasma, rainbow, expanding rings and more) that flow seamlessly across the whole cross, as if it were one display. 

A straight strip of panels is easy — the hzeller library treats a chain as one long display out of the box. A cross is not a simple rectangle, two of the panels are mounted upside down, and the chain order doesn’t match the visual layout. Solving that is where this guide gets interesting, and it’s also where AI (Claude) did most of the heavy lifting — more on that below.

What you’ll build

A 48×48 pixel cross made from five 16×16 panels:

OzzMaker LED matrix
LED Matrix cross orientation

The above image was provided by Claude The top and bottom panels are deliberately rotated 180 degrees. This lets the FFC cables reach their neighbouring panels and keeps the cables hidden behind the display when viewed from the front. The code corrects for the rotation, so the patterns still appear the right way up.

OzzMaker RGB LED matrix cross back
Rear view showing how panels are connected

 

Bill of materials

Assemble hardware

1. Fit the connector to the Pi

Power off the Pi. Sit the OzzMaker LED connector down onto the 40-pin GPIO header. The connector breaks out the unused GPIO and includes a QWIIC socket for I²C.

2. Lay out the panels

Place the three middle panels (LEFT, CENTRE, RIGHT) face down in a row, all the same way up. Now place the TOP and BOTTOM panels face down above and below the centre panel, but rotated 180 degrees relative to the middle row. Why the rotation? Each panel’s IN and OUT FFC ports sit near opposite edges of the PCB. With the top and bottom panels flipped, their ports end up facing the centre panel, so the short FFC cables can reach — and the whole loom stays tucked behind the display.

3. Chain the panels

Each panel has an IN and OUT FFC port, and the arrow on the back of the PCB shows the direction data flows. The chain order for the cross is: Pi → TOP → LEFT → CENTRE → RIGHT → BOTTOM. Plug an FFC cable from the connector on the Pi into the IN port of the TOP panel, then OUT of TOP into IN of LEFT, and continue through CENTRE and RIGHT, finishing at BOTTOM. Note that the chain order is not the same as the visual order — the software takes care of putting the right pixels on the right panel. A common mistake is getting one of the FFC cables in backwards or 180° rotated. The contacts on the cable should always face the same side of the connector latch. If one panel shows garbage, that’s almost always the cause.

4. Power

The FFC cable carries 5 V and GND alongside the data lines, and five panels is within what the FFC can deliver for typical animations. However, at 100% brightness with lots of LEDs lit, five panels can pull around 3.5 A — far more than the Pi’s 5 V rail can supply. The demo script defaults to 50% brightness for this reason. If you see flickering or garbage on panels at the end of the chain, feed 5 V and GND directly to the 5V and GND pads on the back of one of the later panels in the chain, using the same supply that powers the Pi (or a second 5 V supply with a common ground — never two supplies without a common ground). If you’re using a second supply, cut the trace between the green-highlighted pads on the back of the panel where you have connected the second power supply. See the LED matrix overview guide for the pad locations.

5. Soldering panels together

First place some solder on the pads near the edge of the panels, then align the panels together and add more solder to each pair of pads until the solder flows across both pads, bridging them. For the cross you’ll solder along four joints: TOP-to-CENTRE, LEFT-to-CENTRE, RIGHT-to-CENTRE and BOTTOM-to-CENTRE. Take extra care aligning the top and bottom panels — remember they are rotated 180°, so double-check the LED side lines up square before the solder goes on.

OzzMaker 16x16 RGB LED panel
Example of soldering two panels together using Solder-joinable tabs

Software setup

1. Install libraries

Follow this guide to get hzeller’s library installed, which will be used to drive the RGB LED panels.

2. Download and run the code

You can download the cross demo from our GitHub repository:

pberrypi ~ $ git clone https://github.com/ozzmaker/ozzMaker-LED-matrix

cd into the pacman folder and run the script

pi@raspberrypi ~ $ cd ozzMaker-LED-matrix
pi@raspberrypi ~ $ cd cross
pi@raspberrypi ~ $ sudo python cross.py

sudo is required by the rpi-rgb-led-matrix library so it can use realtime scheduling on the GPIO. If you see flicker or ghosting, the two knobs to turn are –gpio-slowdown (a Pi 4 likes 2–4, a Pi 3 likes 1–2, a Pi Zero usually 0–1) and the hardware mapping setting — see the getting-started guide above.

OzzMake LED matrix
4 LED panels connected in a cross

How the pixel mapping works

This is the clever part of the project. As far as hzeller’s library is concerned, the five panels are just one long 80×16 strip — it has no idea they’re arranged in a cross, and no idea two of them are upside down. The solution is a logical canvas. All of the animation code draws onto a simple 48×48 buffer using normal (x, y) coordinates, exactly as if the cross were one square display. A small mapping layer then translates each logical pixel to its physical location in the chain on every frame. Each panel is described by one line in a table: where it sits in the FFC chain, where its top-left corner lands on the 48×48 canvas, and whether it’s rotated:

Don’t worry if below appears complex, we don’t have to worry about it as Claude did all the work for us.

# ---------------------------------------------------------------------------
# Cross canvas
# ---------------------------------------------------------------------------
# Each panel's top-left corner in "cross-space" coordinates.
# Centre panel sits at (0, 0).
PANEL_OFFSETS = {
    "centre": ( 0,          0),
    "top":    ( 0,         -PANEL_SIZE),
    "bottom": ( 0,          PANEL_SIZE),
    "left":   (-PANEL_SIZE, 0),
    "right":  ( PANEL_SIZE, 0),
}
CROSS_MIN_X = -PANEL_SIZE
CROSS_MAX_X =  PANEL_SIZE * 2
CROSS_MIN_Y = -PANEL_SIZE
CROSS_MAX_Y =  PANEL_SIZE * 2
CROSS_W     = CROSS_MAX_X - CROSS_MIN_X   # 48
CROSS_H     = CROSS_MAX_Y - CROSS_MIN_Y   # 48

def _make_cross_mask():
    mask = np.zeros((CROSS_H, CROSS_W), dtype=bool)
    for ox, oy in PANEL_OFFSETS.values():
        px = ox - CROSS_MIN_X
        py = oy - CROSS_MIN_Y
        mask[py:py + PANEL_SIZE, px:px + PANEL_SIZE] = True
    return mask

CROSS_MASK = _make_cross_mask()

class CrossCanvas:
    """
    A 48x48 numpy RGB buffer representing the full cross.
    Only pixels inside CROSS_MASK are physically illuminated.
    """
    def __init__(self):
        self.buf = np.zeros((CROSS_H, CROSS_W, 3), dtype=np.uint8)
    def clear(self):
        self.buf[:] = 0
    def set(self, x, y, r, g, b):
        bx = int(x) - CROSS_MIN_X
        by = int(y) - CROSS_MIN_Y
        if 0 &amp;amp;lt;= bx &amp;amp;lt; CROSS_W and 0 &amp;amp;lt;= by &amp;amp;lt; CROSS_H and CROSS_MASK[by, bx]:
            self.buf[by, bx] = (r, g, b)
    def fill(self, r, g, b):
        self.buf[CROSS_MASK] = (r, g, b)
    def fill_panel(self, panel, r, g, b):
        ox, oy = PANEL_OFFSETS[panel]
        px = ox - CROSS_MIN_X
        py = oy - CROSS_MIN_Y
        self.buf[py:py + PANEL_SIZE, px:px + PANEL_SIZE] = (r, g, b)
    def apply_mask(self):
        """Zero out pixels outside the cross shape, then apply global brightness."""
        self.buf[~CROSS_MASK] = 0
        if BRIGHTNESS &amp;amp;lt; 1.0:
            self.buf[CROSS_MASK] = (
                self.buf[CROSS_MASK].astype(np.float32) * max(0.0, min(1.0, BRIGHTNESS))
            ).astype(np.uint8)

For an upright panel, a logical pixel maps straight through to the same position on the physical panel. For a rotated panel, the coordinates are mirrored in both axes — (x, y) becomes (15 – x, 15 – y) within the panel — which is exactly what a 180° rotation looks like in code. The physical x position is then the panel’s chain index × 16 plus the local x.

The corners of the 48×48 canvas don’t exist physically (that’s what makes it a cross), so the mapping layer also keeps a mask of which logical pixels fall on a real LED, and simply skips the rest. The animations don’t need to care — they can draw to the full square and only the cross lights up.

The payoff is that every effect in the script — plasma, rainbow, expanding rings, the lot — is written in plain, readable code against a square canvas, with not a single mention of chains, panels or rotations.

 

Using AI (Claude) to create the patterns

This project was built with the help of Claude. I didn’t write the mapping layer or the animations by hand — I described the physical setup and let Claude work out the geometry.

This was the actual prompt:

I am using the RGB LED matrix in the attached URL. I have 5 setup in a cross formation. The panels would be setup in this order. Top is first, left is second, centre is third, right is fourth, bottom is fifth, and the top panel and bottom panel are upside down. Create some old school animation effects to show off the panels, like rainbow, plasma, etc.. Add a setting for brightness. Power for these panels will come from the FFC cable connected to the raspberry Pi. Show an image of how the panels should be connected.

A few things worth noting about how that went:

  • Claude read the product page first. By including the URL, Claude picked up the panel specs and even checked the FFC power limits against the page before writing any code.
  • It flagged the power problem unprompted. Claude pointed out that five panels at full white can draw around 4 A through the FFC — more than the Pi can supply — and made the brightness default conservative (50%) on its own.
  • The chain-order and upside-down mapping came out right first go. The sentence “the top panel and bottom panel are upside down” was enough for Claude to build the coordinate remapping table above, including the 180° flip and the cross mask.
  • It drew the wiring diagram. The connection diagram showing the Pi and the five panels in chain order was generated by Claude as part of the same conversation.

 

Build a scrolling Pac-Man on a 8-panel OzzMaker LED strip

In this guide you’ll build the Pac-Man chase animation shown on the 16×16 RGB LED matrix panel product page: Pac-Man and the four classic ghosts running across a long horizontal strip of 8 panels chained end-to-end (128 × 16 pixels), driven by a Raspberry Pi.

It’s a great first project for the panels because it covers everything you need for bigger displays later — chaining, managing power, the driver library, and sprite-based animation in Python.

https://youtu.be/mdd0pqUy4kg

 

What you’ll build

A 8-panel horizontal strip — 128 pixels wide, 16 pixels tall — showing Pac-Man chasing the four ghosts (Blinky, Pinky, Inky and Clyde) across the display. Animation runs at around 25 FPS and loops forever.

 

Bill of materials

  • 8 × OzzMaker 16×16 RGB LED Matrix Panel (each ships with one FFC cable)
  • 1 × OzzMaker RGB LED Matrix Connector for Raspberry Pi
  • 1 × Raspberry Pi 4, Pi 3, Pi Zero 2 W, or Pi Zero
  • 1 × 5 V 3 A power supply
  • A microSD card with Raspberry Pi OS

Assemble hardware

1. Fit the connector to the Pi

Power off the Pi. Sit the OzzMaker LED connector down onto the 40-pin GPIO header so it covers all 40 pins. The connector breaks out the unused GPIO and includes a QWIIC socket for I²C.

2. Chain the panels

Each panel has an IN and OUT FFC port. The arrow on the back of the PCB shows the direction data flows. Plug an FFC cable from the connector on the Pi into the IN port of panel 1, then from OUT of panel 1 to IN of panel 2, and so on, all the way to panel 8.

A common mistake: getting one of the FFC cables in backwards or 180° rotated. The contacts on the cable should always face the same side of the connector latch. If a panel half-way down the chain shows garbage, that’s almost always the cause.

3. Power

The FFC cable carries 5 V and GND alongside the data lines and you should be able to run this animation without needing a separate power supply for some of the LED panels as most of the LEDs are off during the animation. If you see flickering or garbage towards the end of the chain, this would most likely mean the power supply powering your Pi and LED panels isn’t reliable or powerful enough. From the panel before you see the problem, feed 5 V and GND directly to the 5V and GND pads on the back of panel 7. Run those wires from the same supply that’s powering the Pi (or a second 5 V supply with a common ground — never two supplies without a common ground).

If you’re using a second supply, cut the trace between the green-highlighted pads on the back of panel where you have connected the second power supply. See the LED matrix overview guide for the pad locations.

4. Soldering panels together

OzzMaker RGB LED matrix
Back of panels

Before permanently soldering the panels together for a rigid display, you should run a test to make sure everything is connect properly. Once it is confirmed working, you can now begin soldering. First place some solder on the pads near there edge of the panels, then aligned them together and place more solder on the two pads until the solder flows across both pads, as show in the image below.

Software setup

1. Install libraries

Follow this guide to get hzeller’s library installed which will be used to run the RGB LED panels.

2.  Download code

You can download the pacman demo from our github repositry

pi@raspberrypi ~ $ git clone https://github.com/ozzmaker/ozzMaker-LED-matrix

cd into the pacman folder and run the script

pi@raspberrypi ~ $ cd ozzMaker-LED-matrix
pi@raspberrypi ~ $ cd pacman
pi@raspberrypi ~ $ sudo python pacman.py

Below is the main python script, which is well document and easy to follow.

#!/usr/bin/env python
from rgbmatrix import RGBMatrix, RGBMatrixOptions, graphics

#from samplebase import SampleBase
import time
import math
import colorsys
from PIL import Image



options = RGBMatrixOptions()
options.rows = 16
options.cols = 16
options.chain_length = 10
options.gpio_slowdown = 2
options.brightness = 30
matrix = RGBMatrix(options = options)
def clearMatrix():
    for x in range(matrix.width):
        for y in range( matrix.height):
            double_buffer.SetPixel(x, y, 0,0,0)
from PIL import Image
import time
# Define constants
MATRIX_WIDTH = 160
MATRIX_HEIGHT = 16
SPRITE_WIDTH = 16  # Assuming each sprite is 16 pixels wide
PACMAN_WIDTH = 16  # Assuming Pacman is also 16 pixels wide
GAP = 8  # Gap between ghosts
INITIAL_PACMAN_GAP = 80  # Initial gap between Pacman and the first ghost
FINAL_PACMAN_GAP = -SPRITE_WIDTH  # Final gap where ghosts overlap Pacman
ANIMATION_DELAY = 0.03  # Delay between frames in seconds
PACMAN_DIES_LOCATION = 130 
PACMAN_DIES_DELAY = 0.07  # Delay between frames in the Pacman death animation
# Load sprite images
ghostRed1 = Image.open('ghostRed1.ppm').convert('RGB')
ghostRed2 = Image.open('ghostRed2.ppm').convert('RGB')
ghostPink1 = Image.open('ghostPink1.ppm').convert('RGB')
ghostPink2 = Image.open('ghostPink2.ppm').convert('RGB')
ghostLightBlue1 = Image.open('ghostLightBlue1.ppm').convert('RGB')
ghostLightBlue2 = Image.open('ghostLightBlue2.ppm').convert('RGB')
ghostOrange1 = Image.open('ghostOrange1.ppm').convert('RGB')
ghostOrange2 = Image.open('ghostOrange2.ppm').convert('RGB')
# Load Pacman images
pacman1 = Image.open('pacman1.ppm').convert('RGB')
pacman2 = Image.open('pacman2.ppm').convert('RGB')
pacman3 = Image.open('pacman3.ppm').convert('RGB')
# Load PacmanDies images
pacmanDies = [
    Image.open(f'pacmanDies{i+1}.ppm').convert('RGB') for i in range(11)
]
# Initialize double buffer and matrix
double_buffer = matrix.CreateFrameCanvas()
# List of sprites, their positions, and individual frame counters
sprites = [
    {'images': [pacman1, pacman2, pacman3], 'x': -PACMAN_WIDTH, 'frame': 0, 'switch_rate': 4},
    {'images': [ghostRed1, ghostRed2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - SPRITE_WIDTH, 'frame': 0, 'switch_rate': 5},
    {'images': [ghostPink1, ghostPink2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 2*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 7},
    {'images': [ghostLightBlue1, ghostLightBlue2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 3*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 9},
    {'images': [ghostOrange1, ghostOrange2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 4*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 11}
]
# Function to play the Pacman death animation
def play_pacman_dies(x_position):
    for image in pacmanDies:
        double_buffer.Clear()
        double_buffer.SetImage(image, x_position)
        matrix.SwapOnVSync(double_buffer)
        time.sleep(PACMAN_DIES_DELAY)
# Main animation loop
while True:
    double_buffer.Clear()
    # Calculate the current gap between Pacman and the first ghost based on Pacman's position
    pacman_position = sprites[0]['x']
    if pacman_position &lt; PACMAN_DIES_LOCATION:
        # Linearly decrease the gap as Pacman approaches 2/3 of the way across the matrix
        current_gap = INITIAL_PACMAN_GAP - (INITIAL_PACMAN_GAP - FINAL_PACMAN_GAP) * (pacman_position / PACMAN_DIES_LOCATION)
    else:
        # Once Pacman is beyond 2/3, the gap is at its minimum (overlap)
        current_gap = FINAL_PACMAN_GAP
    # Update positions of the ghosts based on the current gap
    sprites[1]['x'] = sprites[0]['x'] - current_gap - SPRITE_WIDTH
    sprites[2]['x'] = sprites[1]['x'] - (SPRITE_WIDTH + GAP)
    sprites[3]['x'] = sprites[2]['x'] - (SPRITE_WIDTH + GAP)
    sprites[4]['x'] = sprites[3]['x'] - (SPRITE_WIDTH + GAP)
    # Check if the red ghost catches up to Pacman
    if sprites[1]['x'] &gt;= pacman_position - 2:
        play_pacman_dies(pacman_position)
        break
    for sprite in sprites:
        # Update the frame counter for each sprite
        sprite['frame'] += 1
        
        # Switch between the two images for ghosts or three images for Pacman based on the individual sprite's frame counter
        image = sprite['images'][(sprite['frame'] // sprite['switch_rate']) % len(sprite['images'])]
        
        # Draw the sprite at the current position
        double_buffer.SetImage(image, sprite['x'])
        
        # Move sprite to the right
        sprite['x'] += 1
        
        # Reset sprite position when it moves out of the right side of the matrix
        if sprite['x'] &gt; MATRIX_WIDTH:
            # Reset positions, with Pacman leading and ghosts following with the initial gap
            sprite['x'] = -PACMAN_WIDTH - INITIAL_PACMAN_GAP - (SPRITE_WIDTH + GAP) * (sprites.index(sprite) - 1)
        
    # Swap the buffers to display the new frame
    matrix.SwapOnVSync(double_buffer)
    
    # Wait before the next frame
    time.sleep(ANIMATION_DELAY)

New Products – OzzMaker 16×16 RGB LED Matrix Panel

We’ve just added three new products to the OzzMaker shop: a 16×16 RGB LED Matrix Panel, and small Raspberry Pi connector boards for our matrix, one which supports a LiPo battery built specifically to drive it.

 

OzzMaker LED matrix

The panel

The 16×16 RGB LED Matrix Panel uses the HUB75 signalling protocol, but with one important twist — instead of the awkward 16-pin IDC ribbon you’d normally wrestle with, our panels use a flat FFC cable for connections. That makes chaining panels together a lot less fiddly.

Below you can see 5 setup in a cross formation, being driven buy a Raspberry Pi with a OzzMaker RGB LED Matrix Connector with Lipo/Ii-Ion support.

OzzMake LED matrix
4 LED panels connected in a cross

Back of cross

OzzMaker RGB LED matrix cross back
Back of cross

 

You can chain 50+ panels in a long strip for scrolling text, or tile them into bigger square displays — 64×64 and beyond. Each panel has solder pads along its edges that act as anchor points: line two panels up side by side, match the pads, and flow solder across them to join the panels together — a bit like spot-welding. The result is a single rigid display rather than a loose array of separate boards

OzzMaker RGB LED Matrix
50 panels

The panels are driven with hzeller’s open-source library, which is well-documented, actively maintained, and works with Python or C. We also have plenty of examples available for scrolling text, animations,  (yes, including a Pac-Man animation), clocks,  patterns, etc..

 

Raspberry Pi Connectors

We have two type of connectors which are used to connect the 16×16 RGB LED Matrix Panels to a Raspberry Pi.

 

OzzMaker Raspberry Pi LED connector
OzzMaker RGB LED Matrix Connector for Raspberry Pi

OzzMaker RGB LED connector with Lipo/Li-Ion
OzzMaker RGB LED Matrix Connector for Raspberry Pi with Lipo/Ii-Ion support

Each connector is a small board that sits on the 40-pin GPIO header and gives you a clean, solder-free way to plug a panel straight into a Pi. No jumper wires, no IDC ribbon. It also breaks out the unused GPIO pins and adds a QWIIC connector for I²C accessories, so you can keep building on the same Pi. The second connector above has support for a Lipo/Li-Ion battery which allows you to power your Raspberry Pi and OzzMaker RGB LED matrix from a single 3.7V LiPo or Li-Ion battery.  It also includes a charger!

OzzMaker RGB LED matrix eyes
Raspberry Pi connector which supports Lipo battery

They work with the Pi 3, Pi 4, Pi Zero and Pi Zero 2. Pi 5 support is in progress.

 

10 Panels chained

Click on the below image to see the video

OzzMaker RGB LED matrix
10 panels chained

 

Create a LED cube

Use 6 OzzMaker 16×16 RGB LED matric panels to create a LED cube.
The cube on the left below is using the OzzMaker RGB LED Matrix Connector for Raspberry Pi with Lipo/Ii-Ion support, connected to a Raspberry Pi Zero and a small 3.7V LiPo battery, all which is sitting inside the cube.

LED cube
Two RGB LED cubes

Get them