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.
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
gpiozeroorRPi.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
- Insert 35 LEDs into the breadboard in a 5×7 grid
- Connect all anodes in each row together (with a 820 Ω resistor between the GPIO pin and the row bus)
- Connect all cathodes in each column together to the column GPIO pins
- 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
- Turn all rows off
- Activate row 0, set columns for row 0's pattern, wait 2ms
- Deactivate row 0
- Activate row 1, set columns for row 1's pattern, wait 2ms
- Repeat for all rows
- 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 |