ESP32/Pins and signals/16. Touch pins
Your chip
Your language
Pins and signals · 16 of 81

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.

/esp32/touch-pins · arduino · S3

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.

touchRead(), before and during a touch
quiet
What is happening at the pad
Your threshold45
Baseline drift (humid day, closed case)0%
touchRead()
78
Baseline now
78
Reads as
not touched
78 against a baseline of 78. On the original ESP32 a touch makes the number go down; on the S2, S3 and P4 it goes up, because the hardware counts the other way. Same idea, opposite comparison — check which chip you are on before copying a threshold from the internet.

The pattern that works

  1. Wait half a second after boot, then average 32 readings — that is your baseline.
  2. Compare each new reading against a fraction of the baseline, not against a constant.
  3. Update the baseline slowly, so it follows the weather and not the finger.
  4. 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.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

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.

touch_button.ino
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.

When it does not work

It fires on its own, especially on damp days

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.

It works on the bench and not in the enclosure

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.

Nothing happens at all

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.

Two pads interfere with each other

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.

Where this goes next

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.

Browse ESP32 on the forum