esp32 beginner 25 min

ESP32: drive a MAX7219 8-digit 7-segment display

Wire a MAX7219 to an ESP32 to drive an 8-digit 7-segment display over SPI. The right pick for showing sensor numbers, counters, or clocks.

Code available for: ESP32 ArduinoArduino CMicroPythonPython
Published Aug 25, 2026

The MAX7219 is the chip that drives every “8-digit 7-segment display module” you see on Amazon. The breakout has the chip, the 8 digits, and the current-limiting resistors all in one board. It speaks SPI, which means 3 wires from the ESP32, and you can chain multiple displays by daisy-chaining the DOUT of one to the DIN of the next.

I use these for counters, clocks, temperature displays, anything that needs to show a number from across the room. The OLED is better for graphs and text; the MAX7219 is better for big readable digits.

What you need

  • ESP32 dev board (or Arduino, Pico, Pi)
  • MAX7219 8-digit 7-segment display module (the common one with 5-pin header: VCC, GND, DIN, CS, CLK; about $3-5)
  • 5 jumper wires

Buy the “common cathode” version, which is the standard MAX7219 module. The “TM1637” 4-digit displays look similar but use a different (and less capable) chip; that one is its own tutorial.

Wiring

The MAX7219 uses SPI. The default SPI pins on the ESP32 are GPIO 18 (SCK), 19 (MISO, not used here), and 23 (MOSI, which becomes DIN on the display).

MAX7219 VCC -- ESP32 5V  (the module is 5V; logic is 3.3V-tolerant)
MAX7219 GND -- ESP32 GND
MAX7219 DIN -- ESP32 GPIO 23  (MOSI)
MAX7219 CS  -- ESP32 GPIO 5   (chip select, also called LOAD)
MAX7219 CLK -- ESP32 GPIO 18  (SCK)

The MAX7219 is a 5V chip, but its logic inputs are 3.3V-tolerant. The 5V VCC powers the LEDs; the 3.3V signals from the ESP32 drive the chip correctly.

The module has an ISET resistor on the back (usually labeled R1). It sets the LED current. The default is 10kohm, which is fine for indoor use. For outdoor or high-brightness, replace it with a smaller value (see “Brightness” below).

Install libraries

Sketch >> Include Library >> Manage Libraries >> search LedControl (by Eberhard Fahle). Install it. This is the standard library for MAX7219 with Arduino.

The code

ESP32 (Arduino)

#include <LedControl.h>

// DIN, CLK, CS, number of daisy-chained modules
LedControl lc = LedControl(23, 18, 5, 1);

void setup() {
  lc.shutdown(0, false);     // wake up the display
  lc.setIntensity(0, 8);     // brightness 0-15
  lc.clearDisplay(0);
}

void loop() {
  // Show a counter from 0 to 99999999
  for (long i = 0; i <= 99999999; i++) {
    lc.setNumber(0, i, false);   // false = no leading zeros
    delay(50);
    if (i >= 12345) break;       // stop early for the demo
  }

  // Show a static temperature
  lc.clearDisplay(0);
  lc.setChar(0, 7, 'C', false);   // 'C' on the rightmost digit
  lc.setNumber(0, 234, false);    // "234" on the left
  delay(2000);

  // Show a message (limited to 8 chars)
  lc.clearDisplay(0);
  lc.setRow(0, 0, 0x7E);   // 'b'
  lc.setRow(0, 1, 0x30);   // 'r'
  lc.setRow(0, 2, 0x79);   // 'E'
  lc.setRow(0, 3, 0x7C);   // 'd'
  delay(2000);
}

Arduino (Uno, Nano, Mega)

Same code, but the default SPI pins are 11 (MOSI) and 13 (SCK). The LedControl library lets you set them explicitly:

#include <LedControl.h>

// For Uno/Nano: DIN=11, CLK=13, CS=10
LedControl lc = LedControl(11, 13, 10, 1);

void setup() {
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);
  lc.clearDisplay(0);
}

void loop() {
  lc.setNumber(0, 12345, false);
  delay(1000);
  lc.clearDisplay(0);
  delay(1000);
}

The 5V Arduino drives the MAX7219’s logic at 5V, which is exactly what it wants. No level shifting needed.

MicroPython (ESP32 or Pico)

from machine import Pin, SPI
import time

# ESP32: SCK=18, MOSI=23; CS=5
# Pico: SCK=2, MOSI=3; CS=5
spi = SPI(0, sck=Pin(18), mosi=Pin(23), baudrate=10_000_000)
cs = Pin(5, Pin.OUT)

cs.value(1)

def send(cmd, data):
    cs.value(0)
    spi.write(bytes([cmd, data]))
    cs.value(1)

def init_display():
    send(0x0C, 0x01)   # shutdown register: normal operation
    send(0x0F, 0x00)   # display test: off
    send(0x0B, 0x07)   # scan limit: all 8 digits
    send(0x0A, 0x08)   # intensity: 8/16
    send(0x09, 0x00)   # decode mode: no decode (we use raw segments)

def clear():
    for d in range(1, 9):
        send(d, 0x00)

def show_number(n):
    """Show a number, right-aligned, up to 8 digits."""
    s = f"{n:08d}"
    for d, ch in enumerate(s):
        digit = int(ch)
        # digit patterns 0-9 (no decode mode)
        patterns = [0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B]
        send(d + 1, patterns[digit])

init_display()
clear()
print("MAX7219 ready")

n = 0
while True:
    show_number(n)
    n = (n + 1) % 100000000
    time.sleep(0.1)

The MicroPython version is verbose because there is no LedControl library port. The patterns array is the 7-segment encoding for digits 0-9; the same encoding the MAX7219’s “decode mode” would do in hardware. Without decode mode, the ESP32 sends raw segment bytes.

Raspberry Pi Python

import spidev
import time

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 10_000_000

def send(cmd, data):
    spi.xfer2([cmd, data])

def init_display():
    send(0x0C, 0x01)
    send(0x0F, 0x00)
    send(0x0B, 0x07)
    send(0x0A, 0x08)
    send(0x09, 0x00)

def clear():
    for d in range(1, 9):
        send(d, 0x00)

def show_number(n):
    s = f"{n:08d}"
    patterns = [0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B]
    for d, ch in enumerate(s):
        send(d + 1, patterns[int(ch)])

init_display()
clear()

n = 0
while True:
    show_number(n)
    n = (n + 1) % 100000000
    time.sleep(0.1)

Enable SPI on the Pi: sudo raspi-config >> Interface Options >> SPI

Enable. Then pip3 install spidev.

What you should see

A counter that increments from 0 to 12345, then shows “234C” (degrees Celsius), then shows “bred” (B-R-E-D in a custom font, this is the demo for raw segment control). Each message holds for 2 seconds.

If you see only one digit, the scan limit register is wrong. Set it to 7 (8 digits) using send(0x0B, 0x07).

Daisy-chaining multiple modules

The MAX7219 has a DOUT pin. Wire it to the DIN of the next module. Both share the same CLK and CS lines. The LedControl library takes the count of daisy-chained modules as the 4th argument:

LedControl lc = LedControl(23, 18, 5, 4);   // 4 daisy-chained modules

To write to module index 2 (the 3rd in the chain):

lc.setNumber(2, 42, false);   // show 42 on the 3rd module

The MAX7219 supports up to 8 modules per chain. That is 64 digits, which is enough for most clocks and large counters.

Brightness

The setIntensity value (0-15) controls brightness. The hardware limit is set by the ISET resistor:

  • 10 kohm (default): max current ~40 mA per segment
  • 5 kohm: ~80 mA per segment (bright but hot)
  • 20 kohm: ~20 mA per segment (dim, good for battery projects)

For an outdoor clock, swap to 5 kohm and run at intensity 12-15. For indoor, leave the resistor alone and use intensity 4-8 (lower is better for the eyes at night).

Showing negative numbers and decimals

setNumber does not show negative numbers. To show “-40” (a freezer alarm), use the raw segment byte for the minus sign (0x01) and write it directly to a specific digit:

lc.setRow(0, 0, 0x01);    // minus sign on the leftmost digit
lc.setDigit(0, 1, 4, false);
lc.setDigit(0, 2, 0, false);

For decimals, write the digit and the decimal point (0x80) to the same row:

lc.setDigit(0, 5, 2, true);   // '2.' on digit 5 (decimal point on)
lc.setDigit(0, 6, 7, false);  // '7' on digit 6

What you learned

  • MAX7219 is the standard chip for 8-digit 7-segment displays.
  • 3 wires for SPI (DIN, CLK, CS). Daisy-chain up to 8 modules.
  • The LedControl library handles all the register-level work.
  • Brightness is software-controllable (intensity 0-15) and hardware- settable (the ISET resistor).

When something breaks

  • Display is completely dark. shutdown(0, false) not called, or wrong VCC (must be 5V, not 3.3V).
  • All segments light up. Display test mode is on. Send 0x0F, 0x00 to disable.
  • Garbled digits. SPI clock too high. Drop to 1 MHz: SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)).
  • One digit works, the rest are blank. scan limit register is set too low. Set to 7 for all 8 digits.
  • Display flickers when Wi-Fi is active. Add a 10uF capacitor across VCC/GND at the module. The 5V rail can sag during Wi-Fi bursts.

What to build next

  • The shift register tutorial is the lower-level way to drive a 7-segment without a driver chip. More wiring, more code, more learning.
  • The OLED tutorial is the better pick when you need to show text, not numbers.
  • The book ESP32 Smart Home has a 4-digit clock with NTP time sync built from this pattern.