If you want a surface that responds to touch — a panel, an instrument, an object with no visible controls — the usual advice is to add a capacitive touch breakout. That advice is often unnecessary. The ESP32 has capacitive touch sensing in the silicon, on up to ten GPIO pins, and the parts you need beyond the board are a length of wire and some copper tape.
Learning By Tutorials walks through the touch peripheral on hardware.
How it works, briefly
Each touch pin drives a small oscillator and counts cycles over a fixed window. The pin plus whatever conductor you attach to it forms one plate of a capacitor; your finger, coupled to ground through your body, forms the other. Adding your finger increases capacitance, which slows the oscillator, which lowers the count.
So the mental model is simple and worth internalising because every problem downstream follows from it:
Touch is a drop in a number, and the number is relative.
There is no absolute “touched” value. The baseline depends on the electrode’s size, the wire’s length, what the board is sitting on, the humidity in the room, and whether a power supply is nearby.
Step 1 — Read the raw value first
Before any logic, look at the numbers.
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.println(touchRead(T0)); // T0 is GPIO 4 on the original ESP32
delay(100);
}
Open the serial monitor and watch the untouched value, then touch the electrode. On an original ESP32 with a small pad you will typically see something in the high tens of thousands untouched, dropping substantially on contact.
Note the direction. On the original ESP32, touching makes the value go down. On the ESP32-S2 and S3 the peripheral was redesigned and the value goes up on touch. Code written for one and moved to the other appears to work and never triggers, which is a confusing afternoon if you have not been told.
Write down your own two numbers — untouched, and touched. Everything else is built on them.
Step 2 — Pick a threshold that will still work tomorrow
The instinct is to split the difference. Do not.
Put the threshold close to the untouched baseline, not halfway. A light touch — a fingertip through a thin laminate, or a finger that is barely resting — produces a much smaller change than a firm press, and a threshold at the midpoint will miss all of them.
Then measure again in different conditions. Humidity moves the baseline measurably. A board on a wooden bench and the same board in a metal enclosure read differently. Powered from a laptop versus a phone charger reads differently, because the ground reference changes. A threshold tuned once on your desk in October will behave differently in a gallery in July.
The robust answer, if the project matters, is to track the baseline rather than hard-code it: keep a slow-moving average of the untouched value and compare against a delta from that average. Ten lines of code, and it survives the weather.
Step 3 — Use the hardware interrupt and filter
Polling in loop() works for a demo. The peripheral will do better.
#define THRESHOLD 40000 // below baseline, for the original ESP32
volatile bool touched = false;
void IRAM_ATTR onTouch() {
touched = true;
}
void setup() {
Serial.begin(115200);
touchAttachInterrupt(T0, onTouch, THRESHOLD);
}
void loop() {
if (touched) {
touched = false;
Serial.println("touch");
}
}
Two details that are easy to get wrong:
IRAM_ATTR is not optional. The handler must live in instruction RAM, because it can fire while flash is busy. Without it you get intermittent crashes that look like anything but an interrupt problem.
Do almost nothing in the handler. Set a flag, return. No Serial.print, no delays, no allocation.
The ESP-IDF layer also exposes a hardware denoise filter and configurable measurement timing for the touch peripheral, which is the real advantage of on-chip sensing over an external breakout: you can trade response time against noise immunity in the peripheral itself rather than averaging in software.
Step 4 — Debounce, because capacitance is noisy
A finger approaching a pad crosses the threshold several times on the way in and the way out. Untreated, one touch becomes four.
The pattern that works:
- Two thresholds, not one. Trigger on a larger change, release on a smaller one. The gap between them — hysteresis — is what stops a finger hovering at the boundary producing a stream of events.
- A minimum hold. Require the condition to persist for 30–50 ms before accepting it. Short enough to feel instant, long enough to reject noise.
- A release delay before accepting the next touch.
This is the same hysteresis logic a mechanical button needs, with a wider margin, because the signal is analog and the transition is gradual rather than a contact bounce.
Step 5 — Touch to wake from deep sleep
This is the capability that external touch chips generally cannot give you, and it is what makes the built-in peripheral worth using for anything battery-powered.
The touch peripheral can run in the RTC domain while the main cores are asleep, and wake the chip on a touch. An object with no switch, drawing microamps, that comes alive when you put your hand on it:
#include "driver/rtc_io.h"
void goToSleep() {
touchSleepWakeUpEnable(T0, THRESHOLD);
esp_deep_sleep_start();
}
On wake, execution restarts from setup() — deep sleep is not a pause, it is a reboot with RTC memory preserved. Anything you need to survive goes in RTC_DATA_ATTR variables.
The constraint: one wake pin in the classic implementation. If you need several touch surfaces to wake the device, wire one large shared electrode as the waker and read the individual pads once awake.
Step 6 — Making the actual electrodes
The sensing is the easy half. Electrodes are where projects succeed or fail.
Copper tape is the default: cheap, cuttable, sticks to anything. Solder to it rather than relying on the adhesive for electrical contact — most copper tape’s adhesive is not conductive, and a joint that works on the bench will fail in a month.
Size sets sensitivity. A larger pad has more baseline capacitance and detects a finger further away. A fingertip-sized pad needs contact; a hand-sized sheet responds at several centimetres. Choose deliberately: proximity detection and discrete buttons want different geometries.
You can sense through a non-conductor. This is the property that makes the technique interesting for objects rather than panels. Paper, veneer, fabric, vinyl, acrylic, thin plywood — all work, with sensitivity falling as the dielectric gets thicker. A few millimetres is comfortable; around 5 mm is where it gets difficult. No visible hardware, no holes, no wear.
A ground plane behind the electrode kills crosstalk. With several pads close together, a finger near one affects its neighbours. Grounded copper between and behind them confines each field.
Keep the runs short. Wire from pin to pad is itself part of the electrode, so a long lead adds baseline capacitance and picks up interference. Over roughly 10 cm, expect to retune; well over that, expect trouble.
Which ESP32 you have matters
- Original ESP32 — 10 touch channels, T0–T9. Touch makes the value decrease. The most documented variant.
- ESP32-S2 / S3 — 14 channels, redesigned peripheral, touch makes the value increase, with better noise handling and a proper benchmark/filter configuration.
- ESP32-C3, C6, H2 — no capacitive touch peripheral. These are the RISC-V parts, and the feature is simply absent. A great many project write-ups do not mention this, and it is the most common way to lose an evening here: the code compiles, the function exists in the framework, and nothing ever registers.
Check the datasheet for your exact module before designing a board around touch pads.
Where this goes
- Continuous control rather than buttons. The raw value is proportional to coupling, so distance and contact area are readable. A copper strip gives you a crude position; an array of pads interpolated between them gives you a smooth one. That is a ribbon controller, for the price of tape.
- Hidden interfaces. Sensing through a surface means an object with no apparent controls. For instrument builds and installation work this is the whole appeal — the interaction is discovered rather than signposted.
- Conductive thread and paint. Both work as electrodes, which puts the same technique into textiles and painted surfaces.
- Fewer, better channels. Ten pads is a menu. One pad that reads pressure, position or proximity well is an instrument.