MAX7219 LED Dot Matrix Display
Introduction
The MAX7219 is one of the most popular drivers for 8x8 LED matrix modules. It makes dot matrix displays much easier to use because it handles the multiplexing for you. Instead of manually driving rows and columns yourself, you send data over SPI and let the MAX7219 take care of the display updates.
With Raspberry Pi Pico and MicroPython, this makes it easy to build:
- scrolling text displays
- scoreboards
- counters and timers
- simple animations
- menu or status indicators
In this tutorial, you can cover three useful versions of the same display family:
- a single
8x8module - a chained
4 x 8x8display - a larger
8 x 8x8display for long scrolling text
Why Use This Module?
Advantages:
- Simple SPI wiring
- Works well with MicroPython
- Easy to chain multiple modules together
Components Needed
| Component | Quantity |
|---|---|
| Raspberry Pi Pico or Pico W | 1 |
| MAX7219 8x8 LED matrix module | 1 or more |
| Jumper wires | Several |
| USB cable | 1 |
Wiring

Common MAX7219 module pins:
VCCGNDDINCSorLOADCLK
Example Pico wiring:
VCC -> VBUS / 5VGND -> GNDDIN -> GP3CS -> GP5CLK -> GP2
Library
We will use this library based on - mcauser/micropython-max7219
from micropython import const
import framebuf
import time
_NOOP = const(0)
_DIGIT0 = const(1)
_DECODEMODE = const(9)
_INTENSITY = const(10)
_SCANLIMIT = const(11)
_SHUTDOWN = const(12)
_DISPLAYTEST = const(15)
class Matrix8x8:
def __init__(self, spi, cs, num):
self.spi = spi
self.cs = cs
self.cs.init(cs.OUT, True)
self.buffer = bytearray(8 * num)
self.num = num
fb = framebuf.FrameBuffer(self.buffer, 8 * num, 8, framebuf.MONO_HLSB)
self.framebuf = fb
# expose framebuffer functions
self.fill = fb.fill
self.pixel = fb.pixel
self.hline = fb.hline
self.vline = fb.vline
self.line = fb.line
self.rect = fb.rect
self.fill_rect = fb.fill_rect
self.text = fb.text
self.blit = fb.blit
self.init()
# -----------------------
# LOW LEVEL
# -----------------------
def _write(self, command, data):
self.cs(0)
for _ in range(self.num):
self.spi.write(bytearray([command, data]))
self.cs(1)
def init(self):
for command, data in (
(_SHUTDOWN, 0),
(_DISPLAYTEST, 0),
(_SCANLIMIT, 7),
(_DECODEMODE, 0),
(_SHUTDOWN, 1),
):
self._write(command, data)
def brightness(self, value):
if not 0 <= value <= 15:
raise ValueError("Brightness out of range")
self._write(_INTENSITY, value)
def show(self):
for y in range(8):
self.cs(0)
for m in range(self.num):
self.spi.write(bytearray([_DIGIT0 + y, self.buffer[(y * self.num) + m]]))
self.cs(1)
# -----------------------
# ANIMATION TOOLKIT
# -----------------------
def scroll(self, message, speed=0.08, loop=False):
width = self.num * 8
while True:
for pos in range(len(message) * 8 + width):
self.fill(0)
self.text(message, width - pos, 0, 1)
self.show()
time.sleep(speed)
if not loop:
break
def scroll_reverse(self, message, speed=0.08):
width = self.num * 8
for pos in range(len(message) * 8 + width):
self.fill(0)
self.text(message, -width + pos, 0, 1)
self.show()
time.sleep(speed)
def blink(self, message, times=5, speed=0.3):
for _ in range(times):
self.fill(0)
self.show()
time.sleep(speed)
self.fill(0)
self.text(message, 0, 0, 1)
self.show()
time.sleep(speed)
def fade_in(self, message):
for b in range(16):
self.brightness(b)
self.fill(0)
self.text(message, 0, 0, 1)
self.show()
time.sleep(0.05)
def fade_out(self, message):
for b in range(15, -1, -1):
self.brightness(b)
self.fill(0)
self.text(message, 0, 0, 1)
self.show()
time.sleep(0.05)
def bounce(self, message, speed=0.05):
width = self.num * 8
text_width = len(message) * 8
for pos in range(width - text_width):
self.fill(0)
self.text(message, pos, 0, 1)
self.show()
time.sleep(speed)
for pos in range(width - text_width, 0, -1):
self.fill(0)
self.text(message, pos, 0, 1)
self.show()
time.sleep(speed)
def wipe(self, speed=0.02):
width = self.num * 8
for x in range(width):
self.vline(x, 0, 8, 1)
self.show()
time.sleep(speed)
for x in range(width):
self.vline(x, 0, 8, 0)
self.show()
time.sleep(speed)
def draw_sprite(self, sprite, x_offset=0):
for y in range(8):
for x in range(8):
if sprite[y] & (1 << (7 - x)):
px = x + x_offset
if 0 <= px < self.num * 8:
self.pixel(px, y, 1)
Save that file as max7219.py, then import it like this:
from max7219 import Matrix8x8
Basic Setup
from machine import Pin, SPI
from max7219 import Matrix8x8
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(1)
If you chain multiple modules:
- single module ->
create_display(1) - 4 modules ->
create_display(4) - 8 modules ->
create_display(8)
If your wiring changes, you only need to edit the din, cs_pin, and clk values in one place.
Code Explanation
def create_display(modules=1, din=3, cs_pin=5, clk=2):
This helper function keeps the display setup in one place so the later examples stay short and easier to copy.
sck=Pin(clk), mosi=Pin(din)
These values define the CLK and DIN wiring for the MAX7219.
cs = Pin(cs_pin, Pin.OUT)
This sets the CS pin used by the display.
display = create_display(1)
The final 1 means one 8x8 matrix module is connected. If you chain four modules together, you would use create_display(4).
Example 1 - Single 8x8 Module
This is the simplest setup and a good first test to confirm your wiring and library are working.
from machine import Pin, SPI
from max7219 import Matrix8x8
import time
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(1)
display.brightness(5)
display.fill(0)
display.text('A', 0, 0, 1)
display.show()
This clears the display, writes the character A, and sends it to the matrix.
Example 2 - Drawing Basics
This version shows one drawing function at a time so it is easier to understand what each one does.
from machine import Pin, SPI
from max7219 import Matrix8x8
import time
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(1)
# Clear display
display.fill(0)
display.show()
# -----------------------
# Draw a horizontal line
# -----------------------
display.hline(0, 3, 8, 1)
display.show()
time.sleep(1)
# Clear
display.fill(0)
display.show()
# -----------------------
# Draw a vertical line
# -----------------------
display.vline(3, 0, 8, 1)
display.show()
time.sleep(1)
# Clear
display.fill(0)
display.show()
# -----------------------
# Draw a diagonal line
# -----------------------
display.line(0, 0, 7, 7, 1)
display.show()
time.sleep(1)
# Clear
display.fill(0)
display.show()
# -----------------------
# Draw a rectangle
# -----------------------
display.rect(1, 1, 6, 6, 1)
display.show()
time.sleep(1)
# Clear
display.fill(0)
display.show()
# -----------------------
# Draw pixels (corners)
# -----------------------
display.pixel(0, 0, 1)
display.pixel(7, 0, 1)
display.pixel(0, 7, 1)
display.pixel(7, 7, 1)
display.show()
Drawing Function Arguments
pixel(x, y, color)
display.pixel(0, 0, 1)
x-> horizontal position from0to7y-> vertical position from0to7color->1= on,0= off
hline(x, y, length, color)
display.hline(0, 3, 8, 1)
x,y-> starting pointlength-> how long the line iscolor->1or0
This draws a horizontal line.
vline(x, y, length, color)
display.vline(3, 0, 8, 1)
This draws a vertical line.
line(x1, y1, x2, y2, color)
display.line(0, 0, 7, 7, 1)
(x1, y1)-> start point(x2, y2)-> end point
This lets you draw a line in any direction.
rect(x, y, width, height, color)
display.rect(1, 1, 6, 6, 1)
x,y-> top-left cornerwidth,height-> size
This draws an outline rectangle.
fill(0 or 1)
display.fill(0)
0-> clear the screen1-> fill the whole screen
Example 3 - 4 x 8x8 Chain
If you connect four modules together, you get a 32x8 display. That is much better for short words, counters, and mini banners.
from machine import Pin, SPI
from max7219 import Matrix8x8
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(4)
display.brightness(5)
display.fill(0)
display.text('PICO', 0, 0, 1)
display.show()
Example 4 - 4 x 8x8 Short Scroll
This is a good example if you want one moving message across four chained modules.
from machine import Pin, SPI
from max7219 import Matrix8x8
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(4)
display.scroll(" PICO ", 0.08, loop=True)
Example 5 - 8 x 8x8 Long Scrolling Text
With eight modules chained together, you get a 64x8 display, which is much better for long text. This is probably the best showcase example for the tutorial.
from machine import Pin, SPI
from max7219 import Matrix8x8
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(8)
display.brightness(4)
display.scroll(" NERDCAVE - RASPBERRY PI PICO - MICROPYTHON ", 0.05, loop=True)
Example 6 - Static Text Across 8 Modules
If you do not want scrolling yet, you can still use eight modules like one wide banner.
from machine import Pin, SPI
from max7219 import Matrix8x8
def create_display(modules=1, din=3, cs_pin=5, clk=2):
spi = SPI(
0,
baudrate=10000000,
polarity=1,
phase=0,
sck=Pin(clk),
mosi=Pin(din),
)
cs = Pin(cs_pin, Pin.OUT)
return Matrix8x8(spi, cs, modules)
display = create_display(8)
display.brightness(5)
display.fill(0)
display.text('MAX7219', 0, 0, 1)
display.show()
Brightness Control
You can change the display brightness:
display.brightness(1)
or
display.brightness(10)
Lower values are often easier to read indoors and use less power.
Built-In Animation Helpers
If you use the updated library above, you also get a small animation toolkit built into Matrix8x8.
1. Scroll Text
Scroll text smoothly from right to left across the display.
display.scroll("HELLO", 0.05)
Arguments:
message-> text to displayspeed-> delay between frames, where smaller values scroll faster
Good for messages, names, or notifications.
2. Reverse Scroll
Scroll text from left to right.
display.scroll_reverse("WORLD", 0.05)
This is useful when you want a different motion direction or want to alternate animations.
3. Blink Text
Make text flash on and off.
display.blink("HI", times=6, speed=0.2)
Arguments:
times-> number of blinksspeed-> on/off delay
This works well for alerts or attention-grabbing messages.
4. Fade In / Fade Out
Use MAX7219 brightness control for a smoother effect.
display.fade_in("A")
display.fade_out("A")
This is a simple way to add visual polish without changing the wiring or setup.
5. Bounce Text
Move short text across the display and back again.
display.bounce("OK", 0.05)
This works best with short words or status indicators.
6. Wipe Effect
Fill and clear the screen column by column.
display.wipe()
This makes a good transition between other animations.
Common Problems
Nothing lights up
- check that the module is powered correctly
- confirm
DIN,CLK, andCSare connected to the expected Pico pins - verify SPI pin assignments in the code
Display is mirrored or text seems backwards
Some MAX7219 modules are chained in different physical directions. You may need to adjust your text position or use a different module orientation.
Random flickering
- lower the SPI speed if needed
- check jumper wires
- make sure the power supply is stable
Summary
The MAX7219 dot matrix display is one of the easiest ways to add visual output to a Raspberry Pi Pico project. With MicroPython and a ready-made driver, you can quickly display characters, draw patterns, and build scrolling text effects.
The key ideas are:
- use SPI
- use a MAX7219 driver such as
Matrix8x8 - call
show()after drawing - use
1,4, or8modules depending on how much text you want to display