Touch pins
Ten pins can sense a finger through plastic with no components at all. The catch is that the reading drifts with humidity, with the case being closed, and with a hand merely nearby - so a fixed threshold is always the bug.
A number that never sits still
The pin charges a tiny capacitance and counts how long it takes. Your finger adds to that capacitance. So does humidity, so does the case, so does a hand 30 mm away — which is what makes proximity sensing possible and a fixed threshold useless.
The pattern that works
- Wait half a second after boot, then average 32 readings — that is your baseline.
- Compare each new reading against a fraction of the baseline, not against a constant.
- Update the baseline slowly, so it follows the weather and not the finger.
- Require two or three consecutive readings before calling it a press.
Waking from sleep with a touch
esp_sleep_enable_touchpad_wakeup() lets a sleeping board wake when a pad is
touched, for a few microamps. It is the reason to prefer touch over a mechanical
button on a battery device — and it uses a threshold set in hardware, so the
baseline you measured before sleeping is the one it compares against.
The code
Measure the baseline at boot and trigger on a percentage change, not on an absolute number. That single decision removes most touch bugs before they happen.
const int PAD = 4; // T0 on GPIO 4
int baseline = 0;
int average(int n = 32) {
long sum = 0;
for (int i = 0; i < n; i++) sum += touchRead(PAD);
return sum / n;
}
void setup() {
Serial.begin(115200);
delay(500); // let it settle before measuring
baseline = average();
Serial.printf("baseline %d\n", baseline);
}
void loop() {
int v = average(8);
bool touched = v < baseline * 0.7; // 30% drop
baseline = baseline * 0.995 + v * 0.005; // slow drift tracking
Serial.printf("%d %s\n", v, touched ? "TOUCH" : "");
delay(50);
}On the original ESP32 a touch makes the number go down. On the S2, S3 and P4 it goes up, because the hardware counts differently. Check the direction before copying a threshold.
Same structure. The slow running average is the part that matters - without it the button stops working when the weather changes.
from machine import TouchPad, Pin
import time
pad = TouchPad(Pin(4))
def avg(n=16):
return sum(pad.read() for _ in range(n)) // n
time.sleep_ms(500)
baseline = avg(32)
while True:
v = avg(8)
touched = v < baseline * 0.7
baseline = baseline * 0.995 + v * 0.005
print(v, 'TOUCH' if touched else '')
time.sleep_ms(50)TouchPad is only available on chips that have the hardware. On a C3 or a C6 the import fails, because those chips have no touch peripheral.
When it does not work
The baseline moved under your fixed threshold. Track the baseline with a slow running average and compare proportionally. This is the fix for nearly every ghost touch.
The case adds thickness between the finger and the pad, and screwing it down changes the capacitance. Re-measure the baseline with the case closed, and make the pad larger.
Check the chip. The C3 and C6 have no touch hardware. Check the pin too - only ten specific pins are touch-capable, and they are different on each chip.
Adjacent traces couple. Keep pads at least 5 mm apart, put a ground pour between them, and read them one at a time rather than in a tight loop.
Polling a pad in a loop works until the loop gets busy. The next chapter is about the pin telling you instead of you asking.
Interrupts on a pin →Edit this page — content/esp32/touch-pins.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.