Buttons Without Buttons: A Dimmer on the ESP32's Capacitive Touch Pins
The ESP32 has 10 built-in touch inputs. A piece of foil, a coin or a copper pad under glass becomes a button: tap to toggle an LED and hold to change brightness smoothly.
How it works
Each ESP32 touch input measures the capacitance of whatever conductor is attached. When your finger touches it, capacitance goes up and on the classic ESP32 the touchRead() value drops (e.g. from 70 to 20). No mechanical buttons — just a conductor that can hide behind plastic or glass a few millimetres thick.
Parts
- ESP32 DevKit (classic ESP32; see below for S2/S3)
- 3 "buttons": foil, coins, copper pads or PCB pads
- LED + 330 Ω resistor (or the on-board LED on GPIO 2)
Wiring
| Pad | Input | GPIO |
|---|---|---|
| On/off | T0 | 4 |
| Brighter | T3 | 15 |
| Dimmer | T4 | 13 |
Keep the wires to the pads as short as possible — a long wire reacts to an approaching hand by itself.
Code
const int PADS[3] = {T0, T3, T4}; // GPIO 4, 15, 13
const int LED_PIN = 2;
uint32_t baseline[3];
bool ledOn = false;
bool wasTouched = false;
int brightness = 128;
unsigned long lastStep = 0;
unsigned long lastPrint = 0;
bool touched(int i) {
// Touch = reading dropped below 70 % of the idle level
return touchRead(PADS[i]) < baseline[i] * 0.7;
}
void apply() {
ledcWrite(LED_PIN, ledOn ? brightness : 0);
}
void setup() {
Serial.begin(115200);
ledcAttach(LED_PIN, 5000, 8); // 5 kHz PWM, 8 bits
// Calibration: don't touch the pads for the first 0.5 s
for (int i = 0; i < 3; i++) {
uint32_t sum = 0;
for (int n = 0; n < 20; n++) {
sum += touchRead(PADS[i]);
delay(20);
}
baseline[i] = sum / 20;
Serial.printf("Pad %d baseline: %u\n", i, baseline[i]);
}
apply();
}
void loop() {
// Pad 1 is a toggle (react to the moment of touch)
bool t = touched(0);
if (t && !wasTouched) {
ledOn = !ledOn;
apply();
}
wasTouched = t;
// Pads 2 and 3 — brightness changes while you hold your finger
if (ledOn && millis() - lastStep > 30) {
lastStep = millis();
if (touched(1)) { brightness = min(255, brightness + 3); apply(); }
if (touched(2)) { brightness = max(5, brightness - 3); apply(); }
}
// Raw readings — for tuning the threshold
if (millis() - lastPrint > 500) {
lastPrint = millis();
Serial.printf("%u\t%u\t%u\n", touchRead(PADS[0]), touchRead(PADS[1]), touchRead(PADS[2]));
}
delay(10);
}
Tuning sensitivity
- Open the Serial Plotter — you'll see three lines.
- Touch each pad and see how far the value drops.
- If it triggers when a hand merely approaches, lower the
0.7factor to0.5; if it doesn't react through thick plastic, raise it to0.8.
ESP32-S2 / S3
On these chips the sensor works "the other way round": the value rises on touch and is measured in thousands. Change the condition to touchRead(...) > baseline[i] * 1.3.
Wake on touch
A touch input can wake the ESP32 from deep sleep — perfect for battery devices:
touchSleepWakeUpEnable(T3, 40); // threshold for the classic ESP32
esp_deep_sleep_start();
Ideas
- A touch light switch panel behind glass.
- A counter for touches on a "magic" door handle.
- A 10-key piano made of foil and a piezo speaker.