LEDs & Lighting
ESP32 LEDs, resistors, multi-LED setups, and cockpit-ready driver solutions.
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 V | 130 Ω → use 150 Ω |
| Yellow | ~2.1 V | 120 Ω → use 150 Ω |
| Green | ~2.2 V | 110 Ω → use 120 Ω |
| Blue / White | ~3.0–3.4 V | 0–33 Ω (dim at 3.3 V) |
Formula: R = (V supply − V f ) / I LED — round up to the nearest standard value.
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.
Parallel (correct)
GPIO ──[R]── LED1 ── GND
──[R]── LED2 ── GND
──[R]── LED3 ── GNDEach 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.
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.
| Pin | Name | Connect to |
|---|---|---|
| DS (14) | Serial data in | GPIO23 |
| SRCLK (11) | Shift-register clock | GPIO18 |
| RCLK (12) | Latch / storage clock | GPIO5 |
| /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 chain | DS of next chip |
| VCC (16), GND (8) | Power | 3.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);
}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
| Feature | MAX7219 | MAX7221 |
|---|---|---|
| SPI compatibility | Not fully — LOAD latches on rising edge while CS high | Full SPI — CS-controlled like standard SPI |
| Slew-rate limited inputs | No | Yes — 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 current | 40 mA | 100 mA |
| Cockpit wiring | Short runs only | Preferred — tolerates cable runs |
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 mA | Dim cockpit annunciators (night vision) |
| 4.7 kΩ | ~4 mA | Low-brightness indicators |
| 2.2 kΩ | ~8.5 mA | Standard annunciators |
| 1 kΩ | ~18.6 mA | Bright 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
| Address | Register | Notes |
|---|---|---|
| 0x00 | No-Op | Used to pass data through in daisy chains |
| 0x01–0x08 | Digit 0–7 | 8-bit LED row data. Bit 7 = leftmost output. |
| 0x09 | Decode Mode | 0x00 = raw bits; 0xFF = BCD for 7-segment |
| 0x0A | Intensity | 0x00 (min) – 0x0F (max brightness) |
| 0x0B | Scan Limit | 0x07 to enable all 8 rows |
| 0x0C | Shutdown | 0x00 = power-save off; 0x01 = normal operation |
| 0x0F | Display Test | 0x01 = 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);
}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);
}| Scenario | Recommended | Reason |
|---|---|---|
| 1–4 LEDs | Direct GPIO | No extra parts needed |
| 5–16 LEDs, simple on/off | 74HC595 | Cheap, daisy-chainable, 3.3 V native |
| Annunciator panel, brightness control | MAX7221 | Constant-current, software dimming, 3.3 V logic safe |
| 7-segment display digits | MAX7219 / MAX7221 | Built-in BCD decode, handles multiplexing |