LEDs & Lighting

ESP32 LEDs, resistors, multi-LED setups, and cockpit-ready driver solutions.

Basic LED Wiring (ESP32)

Every LED needs a current-limiting resistor. The ESP32 runs at 3.3 V and can source/sink up to 40 mA per GPIO, but keep typical LEDs at 5–15 mA for longevity.

Color Forward voltage (V f ) Resistor at 10 mA
Red~2.0 V130 Ω → use 150 Ω
Yellow~2.1 V120 Ω → use 150 Ω
Green~2.2 V110 Ω → use 120 Ω
Blue / White~3.0–3.4 V0–33 Ω (dim at 3.3 V)

Formula: R = (V supply − V f ) / I LED — round up to the nearest standard value.

Single LED Circuit
GPIO 23 220 Ω LED GND +

Connect the resistor on either side of the LED — before the anode or after the cathode. The cathode (−) is the shorter lead and the flat side of the LED case.

Multiple LEDs from One GPIO

Parallel (correct)

GPIO ──[R]── LED1 ── GND
     ──[R]── LED2 ── GND
     ──[R]── LED3 ── GND

Each LED has its own resistor and shares the GPIO. Current adds up: three LEDs at 10 mA = 30 mA total — stay within the 40 mA GPIO limit.

Series (avoid at 3.3 V)

GPIO ──[R]── LED ── LED ── GND

3.3 V minus two forward voltages leaves little headroom. Series wiring is only practical at 5 V or higher.

74HC595 — 8-Bit Shift Register

The 74HC595 converts 3 serial GPIO lines into 8 parallel outputs — ideal when you need more LED outputs than available GPIOs. Multiple chips can be daisy-chained (Q7S → next DS) for 16, 24 … outputs with the same 3 pins.

74HC595 GPIO23 DS DS GPIO18 CLK SRCLK GPIO5 LAT RCLK GND /OE VCC /SRCLR VCC / GND VCC/GND Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q7S next chip DIN + R each
PinNameConnect to
DS (14)Serial data inGPIO23
SRCLK (11)Shift-register clockGPIO18
RCLK (12)Latch / storage clockGPIO5
/OE (13)Output enable (active low)GND (always enabled)
/SRCLR (10)Shift-register clear (active low)VCC (never clear)
Q7S (9)Serial out for daisy chainDS of next chip
VCC (16), GND (8)Power3.3 V or 5 V, GND

Arduino code

#define PIN_DS    23   // serial data
#define PIN_SRCLK 18   // shift-register clock
#define PIN_RCLK  5    // latch / storage clock

void shiftWrite(byte data) {
    digitalWrite(PIN_RCLK, LOW);
    shiftOut(PIN_DS, PIN_SRCLK, MSBFIRST, data);
    digitalWrite(PIN_RCLK, HIGH);   // latch: outputs update here
}

void setup() {
    pinMode(PIN_DS,    OUTPUT);
    pinMode(PIN_SRCLK, OUTPUT);
    pinMode(PIN_RCLK,  OUTPUT);
    shiftWrite(0x00);               // all LEDs off at boot
}

void loop() {
    shiftWrite(0b01010101);         // LEDs 0, 2, 4, 6 on
    delay(500);
    shiftWrite(0b10101010);         // LEDs 1, 3, 5, 7 on
    delay(500);
}
MAX7219 / MAX7221 — Dedicated LED Driver

The MAX7219 and MAX7221 are serial-interface, 8-digit LED display driver ICs. They handle multiplexing, constant-current drive, and brightness control internally — leaving the ESP32 free of timing-critical refresh loops. One chip drives up to 64 individual LEDs (as an 8×8 matrix) or eight 7-segment display digits over a 3-wire SPI interface.

MAX7219 vs MAX7221

FeatureMAX7219MAX7221
SPI compatibilityNot fully — LOAD latches on rising edge while CS highFull SPI — CS-controlled like standard SPI
Slew-rate limited inputsNoYes — reduces EMI on long wires
Logic input threshold (V IH ) 0.7 × V CC (3.5 V at 5 V) 1.5 V fixed — 3.3 V logic safe
Max segment current40 mA100 mA
Cockpit wiringShort runs onlyPreferred — tolerates cable runs
MAX7221 GPIO23 DIN GPIO18 CLK GPIO5 CS/LOAD 5 V VCC GND GND 5 V ISET RSET SEG A–G, DP DIG 0–7 LED Load 8×8 matrix (64 LEDs) 8× seven-segment digits up to 64 annunciators SEG = anodes (columns) DIG = cathodes (rows) multiplexed internally DOUT next DIN

RSET resistor (ISET pin)

A single resistor from ISET to V CC sets the peak segment current. All 64 outputs share this limit — choose based on your LED forward current rating.

RSET I SEG (at V CC = 5 V) Use case
10 kΩ~1.9 mADim cockpit annunciators (night vision)
4.7 kΩ~4 mALow-brightness indicators
2.2 kΩ~8.5 mAStandard annunciators
1 kΩ~18.6 mABright panel LEDs

Intensity register (0x0A, values 0–15) provides a further 16-step software brightness control on top of RSET — useful for day/night dimming.

Register map

AddressRegisterNotes
0x00No-OpUsed to pass data through in daisy chains
0x01–0x08Digit 0–78-bit LED row data. Bit 7 = leftmost output.
0x09Decode Mode0x00 = raw bits; 0xFF = BCD for 7-segment
0x0AIntensity0x00 (min) – 0x0F (max brightness)
0x0BScan Limit0x07 to enable all 8 rows
0x0CShutdown0x00 = power-save off; 0x01 = normal operation
0x0FDisplay Test0x01 = all LEDs on at full brightness

With LedControl library (recommended)

// Library Manager → search "LedControl" by Eberhard Fahle
#include <LedControl.h>

// LedControl(DIN, CLK, CS, numDevices)
LedControl lc(23, 18, 5, 1);

void setup() {
    lc.shutdown(0, false);      // wake from power-save mode
    lc.setIntensity(0, 8);      // brightness 0–15
    lc.clearDisplay(0);
}

void loop() {
    // Set individual LED: device 0, row, col, on/off
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 8; col++) {
            lc.setLed(0, row, col, true);
            delay(40);
            lc.setLed(0, row, col, false);
        }
    }
}

// Set an entire row at once (e.g. annunciator bank)
// lc.setRow(device, row, 0b10110001);

Raw SPI — no library

#define PIN_DIN  23
#define PIN_CLK  18
#define PIN_CS   5

void max7221Send(byte reg, byte data) {
    digitalWrite(PIN_CS, LOW);
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, reg);
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, data);
    digitalWrite(PIN_CS, HIGH);
}

void setup() {
    pinMode(PIN_DIN, OUTPUT);
    pinMode(PIN_CLK, OUTPUT);
    pinMode(PIN_CS,  OUTPUT);
    digitalWrite(PIN_CS, HIGH);

    max7221Send(0x09, 0x00);    // decode mode: none (raw LED bits)
    max7221Send(0x0A, 0x08);    // intensity: 8/15
    max7221Send(0x0B, 0x07);    // scan limit: rows 0–7
    max7221Send(0x0C, 0x01);    // shutdown register: normal operation
    max7221Send(0x0F, 0x00);    // display test: off
}

void loop() {
    max7221Send(0x01, 0b10101010);   // row 1: alternate LEDs on
    delay(500);
    max7221Send(0x01, 0b01010101);
    delay(500);
}

Daisy-chaining two MAX7221 chips

Connect DOUT of the first chip to DIN of the second. Share CLK, CS, and GND. Each transaction must clock through data for every device in the chain.

// Daisy-chaining two MAX7221 chips
// ESP32 → chip A (DOUT) → chip B DIN

void sendToAll(byte reg, byte data) {
    digitalWrite(PIN_CS, LOW);
    // Shift to chip B first (furthest in chain)
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, reg);
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, data);
    // Then chip A
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, reg);
    shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, data);
    digitalWrite(PIN_CS, HIGH);
}

void sendToDevice(int device, byte reg, byte data) {
    // device 0 = chip A (first), device 1 = chip B (last)
    digitalWrite(PIN_CS, LOW);
    for (int i = 1; i >= 0; i--) {
        if (i == device) {
            shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, reg);
            shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, data);
        } else {
            shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, 0x00);  // no-op
            shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, 0x00);
        }
    }
    digitalWrite(PIN_CS, HIGH);
}
Single LED — Arduino Code
const int LED_PIN = 23;

void setup() {
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
}
Choosing the Right Driver
ScenarioRecommendedReason
1–4 LEDsDirect GPIONo extra parts needed
5–16 LEDs, simple on/off74HC595Cheap, daisy-chainable, 3.3 V native
Annunciator panel, brightness controlMAX7221Constant-current, software dimming, 3.3 V logic safe
7-segment display digitsMAX7219 / MAX7221Built-in BCD decode, handles multiplexing