Build a Raspberry Pi LED Matrix with Resistor Networks

Build a multiplexed LED matrix with a Raspberry Pi. Learn resistor calculations, charlieplexing basics, and current management for GPIO-driven displays.

Andreas · April 16, 2026 · 9 min read

Introduction

An LED matrix is a grid of LEDs arranged in rows and columns. By controlling which row and column are active, you can light individual LEDs to display patterns, text, or simple animations. This tutorial builds a 5×7 LED matrix driven directly from Raspberry Pi GPIO pins — no external driver chip needed for this size.

What you'll need

  • Raspberry Pi (any model with GPIO)
  • 35 LEDs (red or green 5mm, same type)
  • 12 resistors (5× for rows, 7× for columns — values calculated below)
  • Breadboard (full-size recommended)
  • Jumper wires
  • Python 3 with gpiozero or RPi.GPIO

Resistor calculation

Each LED needs a current-limiting resistor. In a matrix, the resistor goes on either the row line or the column line (not both — that would double the resistance).

The math

With 3.3V GPIO, a red LED (1.8V forward), and 10 mA target current:

R = (3.3 − 1.8) / 0.01 = 150 Ω

Nearest E24 standard: 150 Ω

With 5 LEDs per row potentially lit simultaneously:

  • Peak current per row GPIO: 5 × 10 mA = 50 mA — exceeds the 16 mA GPIO limit!

This is why we multiplex: only one row is active at a time, scanning fast enough that persistence of vision makes all rows appear lit simultaneously.

With multiplexing (one row at a time):

  • Row GPIO sources current for up to 7 LEDs: 7 × 10 mA = 70 mA — still too much for a single pin
  • Reduce per-LED current to 2 mA: R = (3.3 − 1.8) / 0.002 = 750 Ω → use 820 Ω

At 2 mA, red LEDs are still visible but dim. For brighter displays, use a transistor per row pin to source current from the 5V rail instead of GPIO.

Use the LED resistor calculator to compute values for different LED colors and supply voltages. The resistor color code decoder helps identify resistors from your parts bin.

Wiring the matrix

Matrix layout

         Col0  Col1  Col2  Col3  Col4  Col5  Col6
Row0  ──[ LED ][ LED ][ LED ][ LED ][ LED ][ LED ][ LED ]──
Row1  ──[ LED ][ LED ][ LED ][ LED ][ LED ][ LED ][ LED ]──
Row2  ──[ LED ][ LED ][ LED ][ LED ][ LED ][ LED ][ LED ]──
Row3  ──[ LED ][ LED ][ LED ][ LED ][ LED ][ LED ][ LED ]──
Row4  ──[ LED ][ LED ][ LED ][ LED ][ LED ][ LED ][ LED ]──

Each LED's anode connects to its row wire (through a resistor) and its cathode connects to its column wire. To light a specific LED, drive its row HIGH and its column LOW.

GPIO pin assignment

ROW_PINS = [17, 27, 22, 5, 6]       # 5 rows (output HIGH to select)
COL_PINS = [13, 19, 26, 16, 20, 21, 12]  # 7 columns (output LOW to select)

Total GPIO used: 12 pins for a 5×7 matrix.

Physical wiring

  1. Insert 35 LEDs into the breadboard in a 5×7 grid
  2. Connect all anodes in each row together (with a 820 Ω resistor between the GPIO pin and the row bus)
  3. Connect all cathodes in each column together to the column GPIO pins
  4. Connect grounds

The scanning code

import RPi.GPIO as GPIO
import time

ROW_PINS = [17, 27, 22, 5, 6]
COL_PINS = [13, 19, 26, 16, 20, 21, 12]

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

for pin in ROW_PINS:
    GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW)
for pin in COL_PINS:
    GPIO.setup(pin, GPIO.OUT, initial=GPIO.HIGH)

# 5x7 bitmap: 1 = LED on, 0 = LED off
# Example: letter "A"
LETTER_A = [
    [0, 0, 1, 1, 1, 0, 0],
    [0, 1, 0, 0, 0, 1, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 0],
    [0, 1, 0, 0, 0, 1, 0],
]

def display_frame(bitmap, duration=1.0):
    """Display a bitmap by scanning rows for the given duration."""
    end_time = time.time() + duration
    while time.time() < end_time:
        for row_idx, row_pin in enumerate(ROW_PINS):
            # Activate this row
            GPIO.output(row_pin, GPIO.HIGH)

            # Set columns (LOW = LED on, HIGH = LED off)
            for col_idx, col_pin in enumerate(COL_PINS):
                GPIO.output(col_pin, GPIO.LOW if bitmap[row_idx][col_idx] else GPIO.HIGH)

            # Brief delay for persistence of vision
            time.sleep(0.002)  # 2ms per row = 10ms per frame = 100 Hz

            # Deactivate row
            GPIO.output(row_pin, GPIO.LOW)

try:
    while True:
        display_frame(LETTER_A, duration=2.0)
except KeyboardInterrupt:
    GPIO.cleanup()

How it works

  1. Turn all rows off
  2. Activate row 0, set columns for row 0's pattern, wait 2ms
  3. Deactivate row 0
  4. Activate row 1, set columns for row 1's pattern, wait 2ms
  5. Repeat for all rows
  6. Loop back to row 0

At 2ms per row and 5 rows, each full frame takes 10ms (100 Hz refresh). Above ~50 Hz, the human eye perceives continuous illumination.

Scrolling text

FONT = {
    'A': [[0,0,1,1,1,0,0], [0,1,0,0,0,1,0], [0,1,1,1,1,1,0], [0,1,0,0,0,1,0], [0,1,0,0,0,1,0]],
    'B': [[0,1,1,1,1,0,0], [0,1,0,0,0,1,0], [0,1,1,1,1,0,0], [0,1,0,0,0,1,0], [0,1,1,1,1,0,0]],
    'H': [[0,1,0,0,0,1,0], [0,1,0,0,0,1,0], [0,1,1,1,1,1,0], [0,1,0,0,0,1,0], [0,1,0,0,0,1,0]],
    'I': [[0,0,1,1,1,0,0], [0,0,0,1,0,0,0], [0,0,0,1,0,0,0], [0,0,0,1,0,0,0], [0,0,1,1,1,0,0]],
    ' ': [[0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0]],
}

def scroll_text(text, scroll_speed=0.1):
    """Scroll text across the matrix."""
    # Build a wide bitmap from character bitmaps
    wide = [[] for _ in range(5)]
    for char in text:
        glyph = FONT.get(char.upper(), FONT[' '])
        for row in range(5):
            wide[row].extend(glyph[row])
            wide[row].append(0)  # 1-column gap between characters

    total_cols = len(wide[0])

    for offset in range(total_cols - 6):
        # Extract 7-column window
        frame = [row[offset:offset + 7] for row in wide]
        display_frame(frame, duration=scroll_speed)

Brightness and current budget

With multiplexing at 2 mA per LED and one row active at a time:

  • Peak current: 7 × 2 mA = 14 mA — within GPIO limits
  • Average current per LED: 2 mA / 5 rows = 0.4 mA — dim but visible

For brighter displays:

  • Use NPN transistors (e.g., 2N2222) on each row pin, powered from the 5V rail
  • This allows 20 mA per LED without stressing GPIO
  • Total peak current: 7 × 20 mA = 140 mA from the 5V supply (well within USB power)

Check total power consumption with the power calculator: at 5V and 140 mA peak, that's 0.7W — negligible compared to the Pi itself.

Scaling up: MAX7219 driver

For larger matrices (8×8 or chained panels), the MAX7219 driver IC handles multiplexing and current regulation in hardware. You communicate via SPI with just 3 GPIO pins:

pip3 install luma.led_matrix
from luma.core.interface.serial import spi, noop
from luma.led_matrix.device import max7219

serial = spi(port=0, device=0, gpio=noop())
device = max7219(serial, cascaded=4)  # 4 chained 8x8 modules = 32x8

device.contrast(16)
# Use luma.core canvas for text and graphics

The MAX7219 handles up to 64 LEDs per chip at configurable brightness, with constant-current regulation — no per-LED resistors needed.

Troubleshooting

Problem Cause Fix
All LEDs dim Current too low Lower resistor values or add transistors
Some LEDs don't light Wrong polarity Check anode/cathode orientation
Flickering Scan rate too slow Reduce delay per row (<3ms)
Random LEDs ghosting Column not fully off Add pull-up resistors on column pins
Pi undervoltage warning Too much GPIO current Use transistors for row driving

Comments