What you will learn
- Distinguish common-cathode and common-anode RGB LEDs.
- Connect R, G and B to separate PWM outputs through three individual resistors.
- Use a debounced button to select a channel.
- Store three intensities in an array and adjust only the selected channel.
- Explain immediate application of the potentiometer position when changing channels.
- Create and check magenta and cyan combinations.
Before you start
Complete courses 02, 03 and 05: LED and resistor, INPUT_PULLUP and debouncing, analogue reading and PWM.
Reference setup
- Board in Arduino IDE
- ESP32 Dev Module
- Arduino-ESP32
- 3.3.12
- Serial Monitor
- 115200 baud
This is a reference profile, not an identification of the pictured board. Adapt the GPIO mapping and verify the circuit before using ESP32-C3/S2/S3 or a differently labelled board.
What we will build
Build a colour laboratory with an RGB LED, potentiometer and button. The button selects red, green or blue; the potentiometer adjusts that channel. The other values remain in working memory until selected again.
Learning objectives
- Distinguish common-cathode and common-anode RGB LEDs.
- Connect three LED channels with separate resistors.
- Control three values using one potentiometer.
- Use a button to cycle through R, G and B.
- Distinguish requested brightness from the electrical PWM output.
Prerequisites
Complete courses 02, 03 and 05. You should understand LED polarity, the series resistor, INPUT_PULLUP, button debouncing and analogue scaling. Use a classic ESP32/ESP32-WROOM-32 board and its GPIO labels. C3, S2 and S3 boards require a separately adapted wiring profile.
Equipment
| Component | Quantity | Specification and notes |
|---|---|---|
| Development board with a classic ESP32-WROOM-32 module | 1 | Match the reference GPIO labels; check physical header positions on the actual board. |
| USB data and power cable | 1 | Use the connector fitted to your board; the pictured kit lists Micro-USB. |
| Solderless breadboard | 1 | The kit lists 830 tie points. Check whether the power rails are split. |
| Four-lead RGB LED | 1 | R, G, B and COM. Separate common-cathode and common-anode circuits are supplied; physical lead order is not assumed. |
| 220 Ω resistor | 3 | One current-limiting resistor per LED branch; use the kit’s 220 Ω resistors. |
| 10 kΩ potentiometer | 1 | Two end terminals and a wiper W; identify their physical positions on the actual component. |
| Momentary pushbutton | 1 | Use two terminals connected only when pressed; two legs on the same side may already be joined. |
| Jumper wires | 10 | Use male-to-male or female-to-male leads to suit the board headers. Approximate quantity; select M-M/F-M leads to suit the board. |
Use a four-lead RGB LED with one common and three colour leads. An addressable RGB module does not fit this circuit. Prepare three 220 Ω resistors, one per channel.
How colours combine
The RGB package contains red, green and blue light-emitting elements. We request each intensity with a value from 0 to 255. Red and green together produce yellowish light; red and blue produce magenta; green and blue produce cyan. The exact appearance depends on the LED and the relative output of its channels.
For a common-cathode LED, the common lead connects to GND, and more PWM on-time produces more light. For a common-anode LED, the common lead connects to 3V3: a channel lights when its output sinks current towards GND. The sketch therefore calculates its output as 255 minus the requested intensity for that variant.
Wiring and choosing the variant
RGB — common cathode
Default circuitConnect COM to GND. Keep COMMON_ANODE false. R/G/B/COM are functional labels, not a physical lead order.
Required sketch setting: #define COMMON_ANODE false
| From | To | Connection |
|---|---|---|
ESP32.GPIO25 | R1.1 | Red-channel PWM. |
R1.2 | RGB1.R | 220 Ω resistor for the red channel. |
ESP32.GPIO26 | R2.1 | Green-channel PWM. |
R2.2 | RGB1.G | 220 Ω resistor for the green channel. |
ESP32.GPIO33 | R3.1 | Blue-channel PWM. |
R3.2 | RGB1.B | 220 Ω resistor for the blue channel. |
RGB1.COM | ESP32.GND | Common cathode; leave COMMON_ANODE set to false. |
ESP32.3V3 | RV1.END_A | One end of the 10 kΩ potentiometer. |
ESP32.GND | RV1.END_B | The other potentiometer end. |
RV1.W | ESP32.GPIO34 | Wiper to the ADC1 input. |
ESP32.GPIO27 | SW1.CONTACT_A | Digital input configured as INPUT_PULLUP. |
SW1.CONTACT_B | ESP32.GND | The selected contact pair closes only while pressed. |
RGB — common anode
Alternative circuitConnect COM to 3V3. Set COMMON_ANODE true in the sketch. Each channel still needs its own 220 Ω resistor.
Required sketch setting: #define COMMON_ANODE true
| From | To | Connection |
|---|---|---|
ESP32.GPIO25 | R1.1 | Red-channel PWM. |
R1.2 | RGB1.R | 220 Ω resistor for the red channel. |
ESP32.GPIO26 | R2.1 | Green-channel PWM. |
R2.2 | RGB1.G | 220 Ω resistor for the green channel. |
ESP32.GPIO33 | R3.1 | Blue-channel PWM. |
R3.2 | RGB1.B | 220 Ω resistor for the blue channel. |
RGB1.COM | ESP32.3V3 | Common anode; set COMMON_ANODE true. |
ESP32.3V3 | RV1.END_A | One end of the 10 kΩ potentiometer. |
ESP32.GND | RV1.END_B | The other potentiometer end. |
RV1.W | ESP32.GPIO34 | Wiper to the ADC1 input. |
ESP32.GPIO27 | SW1.CONTACT_A | Digital input configured as INPUT_PULLUP. |
SW1.CONTACT_B | ESP32.GND | The selected contact pair closes only while pressed. |
The diagrams show electrical functions, not physical lead order. Identify R, G, B, the common lead and LED type from the actual part's documentation, markings or a verified diode test. A photograph and lead length alone cannot identify every colour reliably.
- Disconnect USB. Connect logical R through 220 Ω to GPIO25, G through another 220 Ω to GPIO26, and B through the third 220 Ω to GPIO33.
- For common cathode, connect the common lead to GND and keep
#define COMMON_ANODE false. - For common anode, use the alternative diagram: connect the common lead to 3V3 and change the setting to
#define COMMON_ANODE true. Use the variant matching your LED. - Connect the potentiometer ends to 3V3 and GND, with its wiper on GPIO34.
- Connect the button between GPIO27 and GND. On a four-leg button, choose contacts joined only when pressed; some pairs are already internally connected.
- Check separate breadboard groups, all three resistors and the common ground before reconnecting USB.
Arduino setup
Use Arduino IDE 2.x, esp32 by Espressif Systems 3.3.12, and ESP32 Dev Module. Download the Arduino example (.zip) and extract it. Select your port, open rgb_mixer.ino from its rgb_mixer folder and check COMMON_ANODE. Select Verify, then Upload. Set the Serial Monitor to 115200 baud. No additional libraries are needed. Package installation
Complete sketch
#include <Arduino.h>
// Set true ONLY for a common-anode RGB LED wired with its common lead to 3V3.
#ifndef COMMON_ANODE
#define COMMON_ANODE false
#endif
const uint8_t RGB_PINS[3] = {25, 26, 33};
const char* CHANNEL_NAMES[3] = {"R", "G", "B"};
constexpr uint8_t POT_PIN = 34;
constexpr uint8_t BUTTON_PIN = 27;
constexpr uint32_t PWM_FREQUENCY_HZ = 5000;
constexpr uint8_t PWM_RESOLUTION_BITS = 8;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t SAMPLE_INTERVAL_MS = 20;
constexpr uint32_t REPORT_INTERVAL_MS = 200;
uint8_t brightness[3] = {0, 0, 0};
uint8_t selectedChannel = 0;
bool pwmReady = true;
int lastReading = HIGH;
int stableButtonState = HIGH;
uint32_t lastChangeMs = 0;
uint32_t lastSampleMs = 0;
uint32_t lastReportMs = 0;
void writeColors() {
for (uint8_t channel = 0; channel < 3; channel++) {
const uint8_t duty = COMMON_ANODE ? 255 - brightness[channel] : brightness[channel];
ledcWrite(RGB_PINS[channel], duty);
}
}
void sampleSelectedChannel() {
const uint16_t rawValue = analogRead(POT_PIN);
brightness[selectedChannel] = (static_cast<uint32_t>(rawValue) * 255U) / 4095U;
writeColors();
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(POT_PIN, INPUT);
analogReadResolution(12);
analogSetPinAttenuation(POT_PIN, ADC_11db);
for (uint8_t channel = 0; channel < 3; channel++) {
pinMode(RGB_PINS[channel], OUTPUT);
digitalWrite(RGB_PINS[channel], COMMON_ANODE ? HIGH : LOW);
if (!ledcAttach(RGB_PINS[channel], PWM_FREQUENCY_HZ, PWM_RESOLUTION_BITS)) {
pwmReady = false;
}
}
if (!pwmReady) {
// Leave every channel off if any PWM channel could not be configured.
for (uint8_t channel = 0; channel < 3; channel++) {
ledcDetach(RGB_PINS[channel]);
pinMode(RGB_PINS[channel], OUTPUT);
digitalWrite(RGB_PINS[channel], COMMON_ANODE ? HIGH : LOW);
}
Serial.println("ERROR: RGB PWM setup failed. Check the board and ESP32 core.");
return;
}
writeColors();
lastReading = digitalRead(BUTTON_PIN);
stableButtonState = lastReading;
lastChangeMs = millis();
lastSampleMs = lastChangeMs;
lastReportMs = lastChangeMs;
Serial.println("WB Maker Bridge - Button selects R/G/B; potentiometer sets its value.");
Serial.println("A newly selected channel immediately uses the current potentiometer position.");
}
void loop() {
if (!pwmReady) {
delay(100);
return;
}
const uint32_t now = millis();
const int reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) {
lastReading = reading;
lastChangeMs = now;
}
if (now - lastChangeMs >= DEBOUNCE_MS && reading != stableButtonState) {
stableButtonState = reading;
if (stableButtonState == LOW) {
selectedChannel = (selectedChannel + 1) % 3;
sampleSelectedChannel();
lastSampleMs = now;
}
}
if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
lastSampleMs = now;
sampleSelectedChannel();
}
if (now - lastReportMs >= REPORT_INTERVAL_MS) {
lastReportMs = now;
Serial.print("Selected: ");
Serial.print(CHANNEL_NAMES[selectedChannel]);
Serial.print(" | R: ");
Serial.print(brightness[0]);
Serial.print(" | G: ");
Serial.print(brightness[1]);
Serial.print(" | B: ");
Serial.println(brightness[2]);
}
}
The download matches this sketch. Confirm your LED's lead functions before testing.
Understanding the sketch
RGB_PINS associates colours with GPIO pins; brightness stores three intensities. selectedChannel chooses the element changed by the potentiometer. The expression (selectedChannel + 1) % 3 produces R → G → B → R.
With INPUT_PULLUP, the released button reads HIGH and the pressed button reads LOW. The sketch accepts a change after 30 ms of stability and advances only on a new press. Holding the button does not repeat the selection. Espressif: GPIO
The potentiometer is sampled approximately every 20 ms, scaling its 12-bit reading to 0–255. ADC clipping can occur at the endpoints, as in course 05. PWM uses 5000 Hz and 8 bits. ledcAttach() and ledcWrite() take GPIO pins in the 3.x API. Espressif: LEDC
After an accepted press, the newly selected channel immediately takes the current potentiometer position. Its previous value is replaced; the other two remain unchanged. The writeColors() function converts stored intensities into outputs, including inversion for common anode. Approximately every 200 ms, the report displays Selected, R, G and B.
Run and observe
Set the potentiometer to minimum before resetting. All initial intensities are zero and R is selected. The first sample, about 20 ms later, sets R from the potentiometer. If the control is above minimum, red will therefore appear immediately.
Increase R fully. Press once: G takes the same position. Lower G and observe that R remains stored. Press again for B, holding the button for two seconds: it should advance only once. Release before pressing again.
| Target intensities R/G/B | Expected light contribution |
|---|---|
| 255 / 0 / 0 | Red |
| 0 / 255 / 0 | Green |
| 0 / 0 / 255 | Blue |
| 255 / 255 / 0 | Yellowish |
| 255 / 0 / 255 | Magenta |
| 0 / 255 / 255 | Cyan |
These are experimental targets; the potentiometer adjusts one selected channel at a time.
Troubleshooting
| Problem | Check and correction |
|---|---|
| Selecting R produces blue | Identify the colour leads again and correct the R/G/B connections. |
| Brightness is reversed or colours stay on | Match the LED type, common lead connection and COMMON_ANODE setting. |
| Colour jumps after a press | This is intentional: the new channel immediately uses the current potentiometer position. |
| Button does not change channels | Check its switched contact pair, GPIO27 and GND. |
| One colour is very faint | Check that resistor and connection; equal numbers need not produce equal channel brightness. |
| LEDC calls cause compilation errors | Check the selected board and ESP32 package version 3.3.12. |
Independent challenge
Starting from a reset, make magenta with R and B at maximum and G at zero. Record your sequence of presses and adjustments. Then make cyan and explain what happens when changing from G to B.
Check your understanding
- Why does our circuit use separate resistors rather than one in the common lead?
- What changes in the wiring and sketch for common anode?
- Does a channel retain its old value when you select it again?
Answer guidance and worked solution
Separate resistors limit each channel's current. For common anode, connect the common lead to 3V3 and set COMMON_ANODE to true; requested intensity 255 then becomes electrical duty 0. A newly selected channel immediately takes the potentiometer value.
For magenta, reset with the potentiometer at maximum: R becomes 255. Select G, then turn the potentiometer to zero. Select B and turn it to maximum. For cyan, reset at minimum so R remains zero. Select G and increase it; select B while the potentiometer remains at maximum. B immediately takes the high value while G keeps it. Confirm the values in the Serial Monitor.
Downloads
Download the example and matching diagram. Record compilation checks separately from physical board tests.
