32×32 LED Matrix Display with Arduino Mega (MAX7219) – Complete Wokwi Tutorial

32×32 LED Matrix with Arduino Mega & MAX7219 | MakeMindz
⚡ Free Wokwi Simulation — No Hardware Required  |  ▶ Launch Simulator
🔴 Arduino Mega  ·  MAX7219  ·  Wokwi Simulator

Build a 32×32 LED Matrix
with Arduino Mega

Chain four MAX7219 modules with just 3 SPI wires, write a framebuffer, and animate smooth circular wave patterns across 1024 LEDs — all in your browser.

⏱ 20 min read 🎯 Intermediate–Advanced 🔧 Arduino C++ 📡 SPI · MAX7219 🖥 Wokwi Simulator 🖼 Framebuffer Graphics
01 — Overview

Project Overview

This project builds a 32×32 LED matrix display using an Arduino Mega 2560 and four chained MAX7219 8×8 LED matrix modules. Instead of managing 64 individual row/column wires, the MAX7219 driver handles all multiplexing internally — requiring just 3 control wires (DIN, CLK, CS) via SPI.

📡

SPI Communication

Control 16 individual 8×8 modules using just 3 daisy-chained wires

🖼

Framebuffer

Store the full 32×32 pixel state in memory before rendering to hardware

🌊

Wave Animation

Integer square root mathematics drive smooth circular wave patterns

🔗

Daisy Chaining

4 matrix rows × 4 modules each = 16 MAX7219 chips on a single SPI bus

02 — Hardware

Understanding the Hardware

Why Arduino Mega?

The Arduino Mega 2560 is ideal for large chained display projects:

54 digital I/O pins
Hardware SPI peripheral
8KB SRAM for framebuffers
Stable 5V for multiple LED modules
💡

An Arduino Uno can drive MAX7219 modules, but the Mega is the better choice for 32×32 due to its extra memory and stable power delivery to 16 daisy-chained chips.

Matrix Layout

The 32×32 display is assembled from 16 individual 8×8 LED sections arranged in a 4×4 boustrophedon (zig-zag) layout. Each section contains one MAX7219 driver that handles its own row multiplexing automatically.

32×32 LED Matrix — Boustrophedon Layout 8×8 #1 8×8 #2 8×8 #3 8×8 #4 → even row 8×8 #8 8×8 #7 8×8 #6 8×8 #5 ← odd row 8×8 #9 8×8 #10 8×8 #11 8×8 #12 Row 1 Row 2 Row 3 Even rows → MSBFIRST Odd rows → LSBFIRST

Boustrophedon (zig-zag) layout: even rows are sent MSB-first, odd rows are reversed with LSB-first to correct physical orientation.

03 — Wiring

SPI Wiring & Pin Mapping

Only 3 signal wires connect the Arduino Mega to the first MAX7219 module. All subsequent modules are daisy-chained.

Arduino Mega → First MAX7219

Arduino Mega PinMAX7219 PinWire ColourFunction
Pin 11DIN🔵 BlueSPI Data In
Pin 13CLK🟠 OrangeSPI Clock
Pin 10CS🟢 GreenChip Select
5VV+🔴 RedPower
GNDGND⚫ BlackGround

Daisy-Chain Between Modules

From ModuleConnectionTo Next Module
DOUTDIN
CLKCLK
CSCS
V+ (pass-through)V+
GND (pass-through)GND
⚠️

Only the first module connects directly to the Arduino Mega. All others receive power and signals from the previous module in the chain. The SPI data propagates through all 16 chips in sequence.

04 — MAX7219 Driver

How the MAX7219 Works

The MAX7219 is a serial-input, parallel-output display driver. Unlike direct LED matrix wiring, it takes care of everything internally:

FEATURE 01

Internal Multiplexing

Automatically scans all 8 rows at high speed — no CPU intervention needed.

FEATURE 02

SPI Row Data

You send row address + pixel data over SPI; the chip handles the rest.

FEATURE 03

Brightness Control

An intensity register (0x0A) controls LED brightness from 1/32 to 31/32.

FEATURE 04

No-Decode Mode

Register 0x09 = 0x00 enables raw bitmap mode — each bit maps to one LED.

Initialization Register Sequence

RegisterAddressValueEffect
Display Test0x0F0x00Test mode OFF
Scan Limit0x0B0x07Display all 8 rows
Shutdown0x0C0x01Normal operation
Intensity0x0A0x0FMaximum brightness
Decode Mode0x090x00No BCD decode (raw bitmap)
05 — Code Design

Key Code Concepts

CONCEPT 01

Framebuffer Array

byte fb[8 * NUM_SEGMENTS] — A 128-byte array storing the entire 32×32 display. Each bit = one LED. Modified in RAM, then pushed to hardware via show().

CONCEPT 02

shiftAll() Function

Broadcasts the same register+value to all 16 chained MAX7219s simultaneously. Pulls CS LOW, loops through all segments with shiftOut(), then releases CS.

CONCEPT 03

Circular Wave Math

A integer fixed-point CORDIC-style circle oscillator updates centre offset each frame. The radial distance to each pixel is computed with incremental square root for zero floating-point cost.

CONCEPT 04

show() & Boustrophedon

Sends framebuffer rows to all daisy-chained modules. Even segment rows use MSBFIRST byte order; odd rows flip to LSBFIRST to correct the physical zig-zag orientation.

06 — Wokwi Config

diagram.json

Paste this into the diagram.json tab in Wokwi. It places the Arduino Mega, four MAX7219 matrix strips, and all daisy-chain connections automatically.

JSON — diagram.json
{
  "version": 1,
  "author": "sutaburosu",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-arduino-mega",
      "id": "mega",
      "top": -177.58,
      "left": 252.33,
      "rotate": 90,
      "attrs": {}
    },
    {
      "type": "wokwi-max7219-matrix",
      "id": "matrix1",
      "top": -126.7, "left": -79.5,
      "attrs": { "chain": "4", "layout": "fc16" }
    },
    {
      "type": "wokwi-max7219-matrix",
      "id": "matrix2",
      "top": -52.5, "left": -79.5,
      "rotate": 180,
      "attrs": { "chain": "4", "layout": "fc16" }
    },
    {
      "type": "wokwi-max7219-matrix",
      "id": "matrix3",
      "top": 22.46, "left": -78.96,
      "attrs": { "chain": "4", "layout": "fc16" }
    },
    {
      "type": "wokwi-max7219-matrix",
      "id": "matrix4",
      "top": 96.8, "left": -79.5,
      "rotate": 180,
      "attrs": { "chain": "4", "layout": "fc16" }
    }
  ],
  "connections": [
    // ── Daisy-chain: matrix1 → matrix2 ──
    [ "matrix1:GND.2",  "matrix2:GND",  "black",  [ "h-47.8",  "v89.07"  ] ],
    [ "matrix1:DOUT",   "matrix2:DIN",  "blue",   [ "h-38.2",  "v69.87"  ] ],
    [ "matrix1:CS.2",   "matrix2:CS",   "green",  [ "h-28.6",  "v50.67"  ] ],
    [ "matrix1:CLK.2",  "matrix2:CLK",  "orange", [ "h-19",    "v31.47"  ] ],
    // ── Daisy-chain: matrix2 → matrix3 ──
    [ "matrix2:CLK.2",  "matrix3:CLK",  "orange", [ "h58.3",   "v110.3"  ] ],
    [ "matrix2:CS.2",   "matrix3:CS",   "green",  [ "h48.7",   "v91.1"   ] ],
    [ "matrix2:DOUT",   "matrix3:DIN",  "blue",   [ "h39.1",   "v71.9"   ] ],
    [ "matrix2:GND.2",  "matrix3:GND",  "black",  [ "h29.5",   "v52.7"   ] ],
    // ── Power pass-through ──
    [ "matrix3:V+",     "matrix2:V+.2", "red",    [ "h19.2",   "v-31.46" ] ],
    [ "matrix2:V+",     "matrix1:V+.2", "red",    [ "h-56.74", "v-110.5" ] ],
    [ "matrix4:V+",     "matrix3:V+.2", "red",    [ "h-56.74", "v-115.8" ] ],
    // ── Daisy-chain: matrix3 → matrix4 ──
    [ "matrix3:GND.2",  "matrix4:GND",  "black",  [ "h-47.84", "v93.34"  ] ],
    [ "matrix4:DIN",    "matrix3:DOUT", "blue",   [ "h-37.54", "v-67.8"  ] ],
    [ "matrix4:CS",     "matrix3:CS.2", "green",  [ "h-27.94", "v-58.2"  ] ],
    [ "matrix4:CLK",    "matrix3:CLK.2","orange", [ "h-18.34", "v-39"    ] ],
    // ── Arduino Mega → matrix1 ──
    [ "mega:5V",         "matrix1:V+",  "red",    [ "h-33.66", "v-77.39" ] ],
    [ "mega:GND.2",      "matrix1:GND", "black",  [ "h-42.41", "v-79.48" ] ],
    [ "mega:11",         "matrix1:DIN", "blue",   [ "h20.97",  "v249.88", "h-211.2", "v-211.2" ] ],
    [ "mega:10",         "matrix1:CS",  "green",  [ "h30.57",  "v249.98", "h-230.4", "v-220.8" ] ],
    [ "mega:13",         "matrix1:CLK", "orange", [ "h40.17",  "v288.08", "h-249.6", "v-220.8" ] ]
  ]
}
07 — Arduino Sketch

Full Arduino Code

This sketch uses bit-banged SPI to drive all 16 MAX7219 modules. A framebuffer stores the full 32×32 state, and a circular wave animation updates every frame.

💡

If you see tearing (jagged circle edges), try the hardware SPI version at wokwi.com/arduino/projects/318868939929027156.

C++ — sketch.ino (bit-banged SPI)
// Bit-banged SPI version. For hardware SPI (less tearing) see:
// https://wokwi.com/arduino/projects/318868939929027156

#define CLK          13
#define DIN          11
#define CS           10
#define X_SEGMENTS    4
#define Y_SEGMENTS    4
#define NUM_SEGMENTS (X_SEGMENTS * Y_SEGMENTS)

// Framebuffer: entire 32×32 display in raster order, (0,0) = top-left
byte fb[8 * NUM_SEGMENTS];

// Broadcast register + value to ALL chained MAX7219s
void shiftAll(byte send_to_address, byte send_this_data) {
  digitalWrite(CS, LOW);
  for (int i = 0; i < NUM_SEGMENTS; i++) {
    shiftOut(DIN, CLK, MSBFIRST, send_to_address);
    shiftOut(DIN, CLK, MSBFIRST, send_this_data);
  }
  digitalWrite(CS, HIGH);
}

void setup() {
  Serial.begin(115200);
  pinMode(CLK, OUTPUT);
  pinMode(DIN, OUTPUT);
  pinMode(CS,  OUTPUT);

  // Initialise all MAX7219s
  shiftAll(0x0f, 0x00); // Display test OFF
  shiftAll(0x0b, 0x07); // Scan limit: rows 0–7
  shiftAll(0x0c, 0x01); // Shutdown: normal operation
  shiftAll(0x0a, 0x0f); // Intensity: maximum
  shiftAll(0x09, 0x00); // Decode mode: raw bitmap
}

// ── Animation loop ──────────────────────────────────────────────────
// Two CORDIC-style integer circle oscillators control the wave centre.
// Incremental integer square root computes radial distance per pixel.
void loop() {
  static int16_t sx1 = 15 << 8, sx2 = sx1, sy1, sy2;
  sx1 = sx1 - (sy1 >> 6);
  sy1 = sy1 + (sx1 >> 6);
  sx2 = sx2 - (sy2 >> 5);
  sy2 = sy2 + (sx2 >> 5);

  static byte travel = 0;
  travel--;
  byte *dst = fb;
  byte output = 0;
  int8_t x_offset = (sx1 >> 8) - X_SEGMENTS * 4;
  int8_t y_offset = (sx2 >> 8) - Y_SEGMENTS * 4;

  uint8_t  screenx, screeny, xroot, yroot;
  uint16_t xsumsquares, ysumsquares, xnextsquare, ynextsquare;
  int8_t   x, y;

  x = x_offset;
  y = y_offset;
  ysumsquares = x_offset * x_offset + y * y;
  yroot = int(sqrtf(ysumsquares));
  ynextsquare = yroot * yroot;

  // Quadrant II – top left
  screeny = Y_SEGMENTS * 8;
  while (y < 0 && screeny) {
    x = x_offset; screenx = X_SEGMENTS * 8;
    xsumsquares = ysumsquares; xroot = yroot;
    if (x < 0) {
      xnextsquare = xroot * xroot;
      while (x < 0 && screenx) {
        screenx--;
        output <<= 1;
        output |= ((xroot + travel) & 8) >> 3;
        if (!(screenx & 7)) *dst++ = output;
        xsumsquares += 2 * x++ + 1;
        if (xsumsquares < xnextsquare) xnextsquare -= 2 * xroot-- - 1;
      }
    }
    // Quadrant I – top right
    if (screenx) {
      xnextsquare = (xroot + 1) * (xroot + 1);
      while (screenx) {
        screenx--;
        output <<= 1;
        output |= ((xroot + travel) & 8) >> 3;
        if (!(screenx & 7)) *dst++ = output;
        xsumsquares += 2 * x++ + 1;
        if (xsumsquares >= xnextsquare) xnextsquare += 2 * ++xroot + 1;
      }
    }
    ysumsquares += 2 * y++ + 1;
    if (ysumsquares < ynextsquare) ynextsquare -= 2 * yroot-- - 1;
    screeny--;
  }

  // Quadrants III & IV – bottom half
  ynextsquare = (yroot + 1) * (yroot + 1);
  while (screeny) {
    x = x_offset; screenx = X_SEGMENTS * 8;
    xsumsquares = ysumsquares; xroot = yroot;
    if (x < 0) {
      xnextsquare = xroot * xroot;
      while (x < 0 && screenx) {
        screenx--;
        output <<= 1;
        output |= ((xroot + travel) & 8) >> 3;
        if (!(screenx & 7)) *dst++ = output;
        xsumsquares += 2 * x++ + 1;
        if (xsumsquares < xnextsquare) xnextsquare -= 2 * xroot-- - 1;
      }
    }
    if (screenx) {
      xnextsquare = (xroot + 1) * (xroot + 1);
      while (screenx--) {
        output <<= 1;
        output |= ((xroot + travel) & 8) >> 3;
        if (!(screenx & 7)) *dst++ = output;
        xsumsquares += 2 * x++ + 1;
        if (xsumsquares >= xnextsquare) xnextsquare += 2 * ++xroot + 1;
      }
    }
    ysumsquares += 2 * y++ + 1;
    if (ysumsquares >= ynextsquare) ynextsquare += 2 * ++yroot + 1;
    screeny--;
  }
  show();
}

// ── Pixel helpers ─────────────────────────────────────────────────
void set_pixel(uint8_t x, uint8_t y, uint8_t mode) {
  byte *addr = &fb[x / 8 + y * X_SEGMENTS];
  byte  mask = 128 >> (x % 8);
  switch (mode) {
    case 0: *addr &= ~mask; break; // clear
    case 1: *addr |=  mask; break; // plot
    case 2: *addr ^=  mask; break; // XOR
  }
}

void safe_pixel(uint8_t x, uint8_t y, uint8_t mode) {
  if ((x >= X_SEGMENTS * 8) || (y >= Y_SEGMENTS * 8)) return;
  set_pixel(x, y, mode);
}

void clear() {
  byte *addr = fb;
  for (byte i = 0; i < 8 * NUM_SEGMENTS; i++) *addr++ = 0;
}

// ── show() — push framebuffer to all chained MAX7219s ──────────────
// Handles boustrophedon (zig-zag) layout:
//   even segment rows → MSBFIRST  (normal orientation)
//   odd  segment rows → LSBFIRST  (flipped orientation)
void show() {
  for (byte row = 0; row < 8; row++) {
    digitalWrite(CS, LOW);
    byte segment = NUM_SEGMENTS;
    while (segment--) {
      byte x    = segment % X_SEGMENTS;
      byte y    = segment / X_SEGMENTS * 8;
      byte addr = (row + y) * X_SEGMENTS;
      if (segment & X_SEGMENTS) {          // odd rows of segments
        shiftOut(DIN, CLK, MSBFIRST, 8 - row);
        shiftOut(DIN, CLK, LSBFIRST, fb[addr + x]);
      } else {                               // even rows of segments
        shiftOut(DIN, CLK, MSBFIRST, 1 + row);
        shiftOut(DIN, CLK, MSBFIRST, fb[addr - x + X_SEGMENTS - 1]);
      }
    }
    digitalWrite(CS, HIGH);
  }
}
08 — Setup Guide

Step-by-Step Instructions

1

Open Wokwi

Go to wokwi.com, create a new project and select Arduino Mega 2560 as the board.

2

Paste diagram.json

Click the diagram.json tab and replace its contents with the JSON from Section 6. This places all 4 matrix strips and wires them automatically.

3

Paste the Sketch

In the main sketch.ino file, paste the full C++ code from Section 7. No external libraries are required — just built-in Arduino functions.

4

Verify Wiring

Confirm in the diagram that Pin 11 → DIN (blue), Pin 13 → CLK (orange), Pin 10 → CS (green). Matrix strips should be chained DOUT→DIN top to bottom.

5

Click ▶ Start Simulation

Hit the green play button. The display will initialise and begin rendering an animated circular wave pattern across all 1024 LEDs.

6

Experiment

Try changing 0x0a brightness value (0x00–0x0F) in shiftAll to dim the display, or modify the oscillator shift values (>> 6, >> 5) to change animation speed.

🟥

Run It Free in Your Browser

Full simulation — Arduino Mega + all 4 MAX7219 matrix strips — pre-wired and ready to play.

▶ Open Wokwi Simulation
09 — Learning Outcomes

What You Learn from This Project

SPI communication protocol
LED matrix addressing
Framebuffer graphics programming
MAX7219 register control
Large display daisy-chaining
Real-time animation rendering
Integer square root optimisation
Boustrophedon raster mapping
10 — Use Cases

Real-World Applications

📜
Scrolling Text
📢
Digital Signage
🏆
Scoreboards
🕹
Retro Games
🎵
Audio Visualiser
🌐
IoT Dashboard
📋
Notice Boards
🤖
Robotics Display

Embedded systems tutorials, simulations, and maker project guides.

makemindz.com  ·  Tutorial by MakeMindz Team

Comments

try for free