RGB LEDs and PWM Color Mixing

How to control RGB LEDs and mix colors using PWM on ESP32

An RGB LED packs three LEDs -- red, green, and blue -- into a single package. By controlling the brightness of each color channel independently with PWM, you can mix virtually any color. This is the same principle your computer monitor uses, just at a much smaller scale. In this guide, you will learn the difference between common cathode and common anode RGB LEDs, how to wire one up, and how to mix colors in code.

This guide uses the ESP32-WROOM-32 DevKit. Pin labels and GPIO numbers may differ on your board -- always check your board's pinout diagram.

Need individually addressable LEDs? WS2812B NeoPixel strips let you set a different colour on each LED using a single data pin — ideal for light strips and status bars. For a single on/off indicator, a plain LED is simpler.

🔗Common Cathode vs. Common Anode

RGB LEDs come in two varieties:

TypeShared PinHow It Works
Common cathodeGND (-)Shared cathode connects to GND. Apply voltage to R/G/B pins to turn on each color. HIGH = on.
Common anodeVCC (+)Shared anode connects to 3.3V. Pull R/G/B pins LOW to turn on each color. LOW = on (inverted logic).

This guide uses a common cathode RGB LED. If you have a common anode type, connect the shared pin to 3.3V instead of GND, and invert your PWM values (use 255 - dutyCycle instead of dutyCycle).

Tip: To identify your RGB LED type, connect the longest leg to GND. If individual colors light up when you apply 3.3V through a resistor to the other legs, it is common cathode. If nothing lights up, try connecting the longest leg to 3.3V instead -- it is likely common anode.

🔗Key Specs

ParameterValue
Forward voltageRed: $\approx 2.0\,\text{V}$, Green: $\approx 2.2\,\text{V}$, Blue: $\approx 3.0\,\text{V}$
Forward current$20\,\text{mA}$ per channel (typical max)
ESP32 GPIO output$3.3\,\text{V}$

Note that the three color channels have different forward voltages. In practice, using $220\,\Omega$ resistors on all three channels works well and keeps the current within safe limits for both the LED and the ESP32 GPIO pins.

🔗What You'll Need

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
RGB LED (common cathode)1AliExpress | Amazon.de .co.uk .com
220 ohm resistor3One per color channelAliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires5AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

🔗Wiring (Common Cathode)

A common cathode RGB LED typically has four legs. The longest leg is the common cathode (GND).

RGB LED PinConnection
Red legGPIO 25 through $220\,\Omega$ resistor
Common cathode (longest leg)GND
Green legGPIO 26 through $220\,\Omega$ resistor
Blue legGPIO 27 through $220\,\Omega$ resistor

Important: Each color channel needs its own resistor. Do not share a single resistor on the common pin -- this would cause the brightness of each color to change depending on how many colors are active, making color mixing unpredictable.

Note: GPIOs 34--39 on the ESP32 are input-only and cannot drive PWM output.

🔗Required Libraries

No external libraries are needed. The ESP32 Arduino core includes the LEDC (LED Control) functions for PWM.

🔗Code Example: Basic Color Mixing

This sketch cycles through several colors by setting different PWM duty cycles on each channel.

#define RED_PIN   25
#define GREEN_PIN 26
#define BLUE_PIN  27

void setup() {
    // Attach each pin to LEDC with 5000 Hz and 8-bit resolution (0-255)
    ledcAttach(RED_PIN, 5000, 8);
    ledcAttach(GREEN_PIN, 5000, 8);
    ledcAttach(BLUE_PIN, 5000, 8);
}

void setColor(int red, int green, int blue) {
    ledcWrite(RED_PIN, red);
    ledcWrite(GREEN_PIN, green);
    ledcWrite(BLUE_PIN, blue);
}

void loop() {
    setColor(255, 0, 0);     // Red
    delay(1000);
    setColor(0, 255, 0);     // Green
    delay(1000);
    setColor(0, 0, 255);     // Blue
    delay(1000);
    setColor(255, 255, 0);   // Yellow (red + green)
    delay(1000);
    setColor(255, 0, 255);   // Magenta (red + blue)
    delay(1000);
    setColor(0, 255, 255);   // Cyan (green + blue)
    delay(1000);
    setColor(255, 255, 255); // White (all channels)
    delay(1000);
    setColor(0, 0, 0);       // Off
    delay(1000);
}

🔗Code Example: Smooth Rainbow Fade

This sketch smoothly transitions through the entire color spectrum using a hue-based approach.

#define RED_PIN   25
#define GREEN_PIN 26
#define BLUE_PIN  27

void setup() {
    ledcAttach(RED_PIN, 5000, 8);
    ledcAttach(GREEN_PIN, 5000, 8);
    ledcAttach(BLUE_PIN, 5000, 8);
}

void setColor(int red, int green, int blue) {
    ledcWrite(RED_PIN, red);
    ledcWrite(GREEN_PIN, green);
    ledcWrite(BLUE_PIN, blue);
}

void loop() {
    // Fade from red to green
    for (int i = 0; i <= 255; i++) {
        setColor(255 - i, i, 0);
        delay(5);
    }
    // Fade from green to blue
    for (int i = 0; i <= 255; i++) {
        setColor(0, 255 - i, i);
        delay(5);
    }
    // Fade from blue to red
    for (int i = 0; i <= 255; i++) {
        setColor(i, 0, 255 - i);
        delay(5);
    }
}

🔗How It Works

🔗Additive Color Mixing

RGB LEDs use additive color mixing -- the same principle as your screen. When two or more color channels are active simultaneously, your eyes perceive the blended result:

RedGreenBluePerceived Color
25500Red
02550Green
00255Blue
2552550Yellow
2550255Magenta
0255255Cyan
255255255White
12800Dim red
2551280Orange

By varying the duty cycle of each channel from 0 to 255, you can produce $256^3 = 16{,}777{,}216$ possible color combinations.

🔗PWM on the ESP32

The ESP32 does not have analogWrite(). Instead, it uses its LEDC (LED Control) peripheral. The ledcAttach(pin, frequency, resolution) function configures a pin for PWM output, and ledcWrite(pin, dutyCycle) sets the duty cycle. With 8-bit resolution, duty cycle values range from 0 (off) to 255 (fully on).

🔗Troubleshooting

ProblemPossible CauseSolution
Only one or two colors workWrong wiring or bad LED leg connectionCheck each leg individually by setting only one channel to 255
Colors look wrong or invertedCommon anode LED with common cathode codeInvert the duty cycle: use 255 - value for each channel
LED does not light up at allCommon pin not connectedVerify the longest leg goes to GND (common cathode) or 3.3V (common anode)
White looks pinkish or bluishDifferent forward voltages per channelAdjust individual channel values -- try lower red, higher blue
Colors are too bright or dimResistor values too low or too high$220\,\Omega$ is a good starting point for all channels

🔗Next Steps

  • Use a potentiometer or rotary encoder to dial in custom colors
  • Control the RGB LED over WiFi with a web-based color picker
  • Move on to WS2812B NeoPixel LEDs for individually addressable multi-LED strips
  • Combine with a sensor to create a color-coded status indicator (for example, green for good air quality, red for poor)