How to Build a 32x32 LED Matrix with Arduino Mega – Step by Step




Here's a full step-by-step breakdown of this project based on the simulation:


Step 1 — Understand the Components

You need two main parts: an Arduino Mega (the blue board on the right) and a large LED matrix display (the black panel with red LEDs on the left). The Mega is chosen here because it has enough GPIO pins (54 digital) to directly drive a large matrix without needing extra shift register chips.


Step 2 — Identify the LED Matrix Size

The matrix in the image appears to be a 32×32 single-color (red) LED matrix. This is likely made up of four 16×16 or sixteen 8×8 modules combined into one large panel. Each dot you see is one individual LED.


Step 3 — Understand How the Matrix Works (Row/Column Multiplexing)

The matrix doesn't have a separate wire for every LED. Instead it uses rows and columns. To light up a specific LED, you activate its row and its column at the same time. This is done very quickly in a loop so that your eye perceives a full image — this technique is called multiplexing.


Step 4 — Wire the Rows

The wires coming out of the top and bottom of the matrix (the colored wires on the left side — red, green, blue, yellow, etc.) represent the row connections. Each row wire connects to a digital pin on the Arduino Mega. For a 32×32 matrix, you have 32 row lines.


Step 5 — Wire the Columns

The wires going to the right side of the matrix (connecting toward the Mega on both the top and bottom) represent the column connections. Similarly, each column wire maps to another set of digital pins on the Mega. For 32 columns, you need 32 more pins — which is why the Mega is required.

diagram.json file:

{
  "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": [
    [ "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" ] ],
    [ "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" ] ],
    [ "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" ] ],
    [ "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" ] ],
    [ "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" ] ]
  ]
}

Step 6 — Connect Power and Ground

The yellow/orange wires looping around the board (visible at the top and bottom right) are the power (5V) and GND connections. The Mega supplies 5V to power the LED matrix, and ground completes the circuit.


Step 7 — Define Pin Mappings in Code

code:

// This version uses bit-banged SPI.
// If you see tearing (jagged edges on the circles) try the version
// which uses AVR's hardware SPI peripheral:
// 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)

// a framebuffer to hold the state of the entire matrix of LEDs
// laid out in raster order, with (0, 0) at the top-left
byte fb[8 * NUM_SEGMENTS];


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);

  // Setup each MAX7219
  shiftAll(0x0f, 0x00); //display test register - test mode off
  shiftAll(0x0b, 0x07); //scan limit register - display digits 0 thru 7
  shiftAll(0x0c, 0x01); //shutdown register - normal operation
  shiftAll(0x0a, 0x0f); //intensity register - max brightness
  shiftAll(0x09, 0x00); //decode mode register - No decode
}


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;

  // offset the origin in screen space
  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--;
  }
  // Quadrant III (bottom left)
  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;
      }
    }
    // Quadrant IV (bottom right)
    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();
}


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: // clear pixel
      *addr &= ~mask;
      break;
    case 1: // plot pixel
      *addr |= mask;
      break;
    case 2: // XOR pixel
      *addr ^= mask;
      break;
  }
}


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);
}


// turn off every LED in the framebuffer
void clear() {
  byte *addr = fb;
  for (byte i = 0; i < 8 * NUM_SEGMENTS; i++)
    *addr++ = 0;
}


// send the raster order framebuffer in the correct order
// for the boustrophedon layout of daisy-chained MAX7219s
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);
  }
}

In the Arduino sketch, you create two arrays — one listing all the row pins and one listing all the column pins — so the code knows which physical pin controls which row or column.


Step 8 — Store the Image as a 2D Array

The red pattern displayed (looks like a logo or symbol) is stored in the code as a 2D array of 0s and 1s. A 1 means that LED is ON (red), and a 0 means it is OFF (black). This array is essentially a bitmap of the image.


Step 9 — Write the Multiplexing Loop

The main loop() in the Arduino code cycles through each row one at a time. For each row, it activates that row pin, then turns on or off each column pin based on the 2D array. It holds each row for a short time (a few microseconds), then moves to the next row. This repeats fast enough (many times per second) that the full image appears stable to the human eye.


Step 10 — Run the Simulation

As you can see in the top-left corner, this is running in a Wokwi simulator. The simulation has been running for about 55 seconds at 99% speed. The red pattern on the matrix is the result of the multiplexing loop drawing the stored image frame by frame, continuously.


 

Comments