How Do You Configure the ESP32's Capacitive Touch Sensor Peripheral?
Last updated 19 August 2026 · 11 min read
Direct Answer
The ESP32's built-in capacitive touch peripheral measures touch by counting relaxation-oscillator charge and discharge cycles on a pad over a fixed measurement window; a finger's added capacitance changes that count in a way the hardware and firmware can distinguish from an untouched pad. Configuring it means three things: initialise the peripheral and enable specific channels with touch_pad_init() and touch_pad_config(), calibrate a per-pad threshold by reading each pad's own untouched baseline count and setting a threshold with margin below it rather than reusing one fixed value across boards, and enable the built-in IIR filter with touch_pad_filter_start() so application code reads a smoothed value instead of a noisy raw one. To wake the ESP32 from deep sleep on a touch event, configure the RTC-domain measurement timing with touch_pad_set_meas_time(), select a trigger source, and call esp_sleep_enable_touchpad_wakeup() before entering sleep. The original ESP32 has 10 touch channels and can wake on any combination of them; the ESP32-S2 and ESP32-S3 have an improved 14-channel touch peripheral but can only use one pad as a sleep wake source, and the RISC-V ESP32-C3, C6, and H2 have no capacitive touch peripheral at all.
Detailed Explanation
The ESP32 is one of the few mainstream MCU families with a capacitive touch sensor built directly into the silicon, alongside STM32's TSC peripheral. For the general principles behind capacitive touch (self- versus mutual-capacitance, electrode design, and the noise and drift problems every implementation has to solve), see How Does Capacitive Touch Sensing Work, and How Do You Design a Reliable Touch Button?. This page covers the ESP32-specific part: the touch_pad driver, how to calibrate a working threshold, how to filter out noise, and how to use a touch pad as a wake source from deep sleep.
How the ESP32 Touch Peripheral Measures Capacitance
Each touch-capable GPIO on the ESP32 connects to a relaxation oscillator built into the touch peripheral. Enabling a channel drives that pad's electrode through repeated charge and discharge cycles and counts how many cycles complete within a fixed measurement window set by the peripheral's internal RTC-domain clock. A finger placed on or near the electrode adds capacitance, which changes how quickly the pad charges and discharges, and that shows up as a different cycle count over the same window.
On the original ESP32, more capacitance means fewer completed cycles in the window, so the reading falls when a pad is touched. The whole measurement runs in dedicated hardware once started: the peripheral has its own finite state machine (FSM) that can scan channels on a timer without CPU involvement, and firmware only reads the resulting counts.
The original ESP32 exposes 10 touch channels, T0 through T9, each tied to a specific GPIO (for example T0 is GPIO4 and T7 is GPIO27; consult the datasheet's pin table for the full mapping, since several touch channels share pins with JTAG and other strapping functions that need to be accounted for in the schematic).
Legacy touch_pad Driver vs the Newer touch_sens Driver
Most existing ESP32 touch code, tutorials, and this page's own examples, use the driver/touch_pad.h API: touch_pad_init(), touch_pad_config(), touch_pad_set_thresh(), and related calls. As of ESP-IDF v5.5, Espressif redesigned the touch sensor driver into a newer, handle-based API under driver/touch_sens.h (component esp_driver_touch_sens) that unifies the original ESP32's touch hardware and the different touch hardware version used by the S2/S3 behind one API surface. The legacy header still works in current ESP-IDF releases but now emits a deprecation warning on include, which can be suppressed with the CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN Kconfig option. Since ESP-IDF versioning and driver churn move faster than this page can track, confirm which driver generation your targeted ESP-IDF release expects before starting a new design, and expect the underlying concepts below (relaxation-oscillator measurement, per-pad threshold calibration, filtering, and sleep wake configuration) to carry across both API generations even though the function names differ.
Basic Configuration: Enabling a Touch Channel
A minimal setup initialises the peripheral, selects the timer-driven FSM mode so the hardware scans continuously without firmware intervention, enables the channel, and starts the software IIR filter before starting the FSM:
#include "driver/touch_pad.h"
#define TOUCH_CHANNEL TOUCH_PAD_NUM0 /* T0 = GPIO4 on the original ESP32 */
void touch_button_init(void)
{
touch_pad_init();
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); /* hardware timer drives periodic scans */
touch_pad_config(TOUCH_CHANNEL, 0); /* enable the channel; threshold set later, after calibration */
touch_pad_filter_start(10); /* 10 ms IIR filter period for the smoothed reading */
touch_pad_fsm_start();
}
The threshold passed to touch_pad_config() here is a placeholder. Setting a real threshold before calibrating against the pad's own baseline is the single most common reason a touch button either never triggers or triggers constantly on first bring-up.
Threshold Calibration: Why a Fixed Value Doesn't Work
A touch pad's untouched baseline count depends on the electrode's copper area, the PCB stackup, trace length back to the ESP32, the overlay material and thickness in front of the electrode, and even normal manufacturing tolerance in the touch peripheral's internal oscillator components. Two boards built from the same design, or even two production units of the same board, will not read the identical baseline. Hardcoding one threshold value copied from an example project or a different design is a reliable way to get a button that is either too insensitive to trigger reliably or so sensitive it false-triggers from ambient noise.
The correct approach is to calibrate the threshold against each pad's own measured baseline, either once during firmware bring-up on a representative sample of boards, or at every boot if the environment (temperature, humidity, nearby objects) varies enough to matter for the product:
uint16_t calibrate_touch_threshold(touch_pad_t pad, float margin_fraction)
{
uint32_t sum = 0;
uint16_t raw;
/* Average several filtered readings with nothing touching the pad */
for (int i = 0; i < 16; i++) {
touch_pad_read_filtered(pad, &raw);
sum += raw;
vTaskDelay(pdMS_TO_TICKS(20));
}
uint16_t baseline = (uint16_t)(sum / 16);
/* Count falls when touched on the original ESP32, so the threshold
sits a margin below the untouched baseline, not at or above it */
uint16_t threshold = (uint16_t)(baseline * (1.0f - margin_fraction));
touch_pad_set_thresh(pad, threshold);
return baseline;
}
The margin fraction needs to be validated on the actual production overlay and enclosure, not tuned once on a bare development board, for the same reason covered in the general touch-sensing design guide: overlay thickness and material set how much capacitance a finger can actually add, and that determines how tight a margin the design can safely use. A margin too small produces false triggers from ordinary drift; a margin too large makes a genuine touch unreliable near the edges of the electrode.
Filtering and Denoising to Avoid False Triggers
The touch peripheral's built-in IIR filter, enabled with touch_pad_filter_start(filter_period_ms), smooths the raw per-scan count into a steadier value that application code reads with touch_pad_read_filtered(). This reduces the effect of ordinary electrical noise on the value a state machine actually compares against the threshold.
One detail catches a lot of first-time users: hardware touch interrupts trigger on the raw, unfiltered count, not the filtered value the application reads. Enabling the software filter changes what your polling code sees; it does not change what the hardware interrupt logic compares against. A design that relies on the touch interrupt for wake or event detection still needs its threshold set against the raw measurement's noise characteristics, not the smoothed one.
Beyond the built-in filter, the same practical noise-reduction steps that apply to any capacitive touch design apply here: route touch electrode traces away from switching power supply nodes and other noisy digital signals, keep the ground plane clear directly beneath the electrode itself, and require several consecutive above-threshold readings (a simple debounce count in firmware) before declaring a touch event, rather than acting on a single reading. The ESP32-S2 and ESP32-S3's redesigned touch peripheral additionally includes a dedicated hardware denoise channel that can reject noise appearing simultaneously across all channels, such as power supply ripple; the original ESP32's touch peripheral does not have this dedicated denoise channel, so noise mitigation on that variant relies more heavily on the IIR filter, debounce logic, and layout discipline.
Using Touch Pads to Wake from Deep Sleep
Waking the ESP32 from deep sleep on a touch event is one of the peripheral's more useful capabilities, since deep sleep powers down the main CPU cores while the RTC domain, including the touch peripheral, stays active. This is a genuinely different code path from touch configuration during normal active operation:
#include "driver/touch_pad.h"
#include "esp_sleep.h"
void touch_wake_init(void)
{
touch_pad_init();
/* Configure RTC-domain measurement timing used while asleep */
touch_pad_set_meas_time(0xFFFF, 0x1000); /* sleep cycle, measurement cycle */
touch_pad_config(TOUCH_PAD_NUM0, 0);
/* Calibrate and set a real threshold before sleeping, using the
same approach as calibrate_touch_threshold() above */
/* Wake fires when any pad in group SET1 is touched; touch_pad_set_group_mask()
assigns pads to SET1/SET2 if a two-group wake condition is needed */
touch_pad_set_trigger_source(TOUCH_TRIGGER_SOURCE_SET1);
esp_sleep_enable_touchpad_wakeup();
esp_deep_sleep_start();
}
After wake, esp_sleep_get_wakeup_cause() reports ESP_SLEEP_WAKEUP_TOUCHPAD, and on the original ESP32 esp_sleep_get_touchpad_wakeup_status() identifies which specific pad caused the wake, since multiple pads can be armed as wake sources at once. Two things commonly go wrong when wiring this up. First, the threshold must be set before entering sleep using a baseline calibrated while the design is in the same physical and thermal state it will actually sleep in; a threshold calibrated at room temperature on a bench can drift enough overnight, or in a hot enclosure, to either fail to wake or wake spuriously. Second, on the ESP32-S2 and S3 only one pad can be armed as a sleep wake source, unlike the original ESP32, which is a real constraint for a multi-button product that needs to wake from any button press. For the broader picture of ESP32 power modes, RTC memory retention, and the other available wake sources (timer, GPIO, ULP), see How Do You Manage Power and Use Deep Sleep on the ESP32?
For touch interface firmware that has to hold up across production tolerance, enclosure variation, and real deployment environments rather than just a bench prototype, Zeus Design's firmware team develops and validates ESP32 sensor and touch interface firmware as part of complete embedded product development.
Design Considerations
- Calibrate thresholds per pad, not once for the whole product. Even electrodes of identical size and layout on the same board can read slightly different baselines. Calibrating each pad against its own measured baseline, rather than applying one shared threshold, avoids a design where some buttons on the same product are noticeably more or less sensitive than others.
- Decide whether calibration happens once at first boot or continuously. A one-time calibration at manufacturing test is simpler but assumes the baseline stays stable for the product's life; continuous re-referencing during periods with no detected touch (the same technique covered in the general capacitive touch design guide) handles genuine long-term drift from temperature and material aging but adds firmware complexity.
- Validate the whole chain on production-representative hardware. Baseline counts, threshold margins, and filter settings tuned against a bare development board or an early prototype enclosure commonly need re-tuning once the real overlay material, thickness, and production PCB stackup are in place.
- Check the ESP-IDF version's driver generation before writing new code. The function names in this page's examples are the legacy
touch_pad.hAPI, still functional but deprecated as of ESP-IDF v5.5 in favour of the newertouch_sens.hdriver; confirm which one a given project targets before copying example code from an unfamiliar source.
Common Mistakes
- Setting a fixed threshold copied from an example or a different board. Per-pad baseline variation between boards and even between electrodes on the same board makes a hardcoded threshold value unreliable across production units; calibrate against each pad's own measured baseline instead.
- Forgetting that hardware touch interrupts compare against the raw, unfiltered count. Enabling
touch_pad_filter_start()changes whattouch_pad_read_filtered()returns to application code, but the interrupt trigger logic still evaluates the raw measurement, which can produce interrupt behaviour that doesn't match what the filtered reading in the debug log suggests. - Calibrating the wake threshold in different thermal conditions than the product will actually sleep in. A threshold set on a bench at room temperature can be wrong enough after the product has been sitting in a warm enclosure overnight to either miss genuine touches or wake spuriously.
- Assuming a design built around the original ESP32's touch behaviour ports directly to the ESP32-S2 or S3. The reading direction is reversed (increases with touch rather than decreases), the channel count and pin mapping differ, and only one pad can be armed as a sleep wake source on the S2/S3 versus multiple on the original ESP32. None of this is a drop-in port between variants.
- Choosing a RISC-V ESP32 variant (C3, C6, H2) for a design that assumes a capacitive touch peripheral is available. These variants have no built-in touch hardware; the requirement needs to be caught during MCU selection, not discovered partway through firmware bring-up. See ESP32 Variants Compared: How Do You Choose the Right One? when touch capability is one of the selection criteria.
Frequently Asked Questions
- Which ESP32 variants have a capacitive touch sensor peripheral?
- Only the original ESP32, the ESP32-S2, and the ESP32-S3 have a built-in capacitive touch peripheral. The RISC-V variants, including the ESP32-C3, ESP32-C6, and ESP32-H2, do not. The original ESP32 provides 10 touch channels (T0 to T9) whose measured count decreases as capacitance increases. The S2 and S3 use a redesigned touch peripheral with up to 14 channels, a reversed convention where the reading increases with touch, and a dedicated hardware denoise channel not present on the original ESP32. If a design needs capacitive touch and is currently scoped around a RISC-V ESP32 variant, either move to a touch-capable variant or add an external touch controller IC, since there is no software workaround for the missing hardware block.
- Does Wi-Fi or BLE radio activity affect ESP32 touch sensor readings?
- Some designs report brief touch-count disturbances correlated with Wi-Fi or BLE transmit bursts, consistent with RF energy coupling onto touch electrode traces the same way it can couple onto any sensitive analog measurement on the board. This is not a documented hard peripheral conflict in the way ADC2 is blocked outright while Wi-Fi is active; it is closer to a noise-source consideration. The IIR filter, a touch state machine that requires several consecutive readings past threshold before declaring a touch, and PCB layout discipline that keeps touch traces away from the antenna and RF section all reduce the practical impact. If a specific design shows touch glitches that correlate with radio activity, treat it as an RF noise problem and apply the same mitigations used for any other noise-sensitive analog signal near the ESP32's Wi-Fi and BLE hardware.
- How many touch pads can wake the ESP32 from deep sleep at once?
- On the original ESP32, any combination of the 10 touch channels can be armed as a deep sleep wake source simultaneously, and esp_sleep_get_touchpad_wakeup_status() after wake reports which specific pad triggered it. On the ESP32-S2 and ESP32-S3, only one touch pad can be configured as a sleep wake source at a time. This is a real design constraint on those variants: a product with several touch buttons that all need to wake the device from deep sleep needs either the original ESP32 or a different wake strategy, such as an external interrupt-capable touch controller IC, or accepting that only one designated button (for example, a dedicated wake/power button) can perform the wake role.
References
Related Questions
How Does Capacitive Touch Sensing Work, and How Do You Design a Reliable Touch Button?
How capacitive touch sensing works: self- vs mutual-capacitance, electrode design, dedicated controller ICs, and avoiding false triggers and drift.
How Do You Use GPIO, ADC, and Timers on the ESP32?
ESP32 GPIO, ADC, and timers in ESP-IDF: pin configuration, interrupts, ADC calibration and attenuation, and periodic timers with esp_timer and GPTimer.
How Do You Manage Power and Use Deep Sleep on the ESP32?
ESP32 power modes: active, modem sleep, light sleep, deep sleep; RTC timer, GPIO, and ULP wake sources; measured currents and battery runtime estimation.
How Do You Use the ESP32 RMT Peripheral to Drive WS2812 LEDs and IR Remotes?
How the ESP32 RMT peripheral generates and captures precise pulse trains for WS2812/NeoPixel LEDs and IR remote control, with the ESP-IDF v5 driver API.
ESP32 Variants Compared: How Do You Choose the Right One?
Compare ESP32 variants: ESP32 classic, S3 (ML/USB), S2 (USB), C3 and C6 (RISC-V BLE+WiFi), and H2 (Thread/Zigbee). When to choose each.
ESP-IDF vs Arduino for ESP32: Which Framework Should You Use?
ESP-IDF gives full FreeRTOS control and is production-grade; Arduino is faster to start. Covers the differences, limitations of each, and when to switch.
Related Forum Discussions
ESP32 Matter device advertises fine over BLE but commissioning fails every time — stale QR code after a firmware rebuild?
Bringing up my first Matter product on an ESP32-C6 using esp-matter (built on ESP-IDF 5.2). It's a basic on/off light accessory for now, jus
ESP32 keeps dropping Wi-Fi after 20–30 minutes in deployed location — reconnect loop doesn't always recover
Having a frustrating one. Built an ESP32 environmental monitor (SHT40 temp/humidity, reports to an MQTT broker every 5 minutes). Works flawl
Is a double-sided PCB enough for a simple ESP32 sensor board, or should I go multi-layer?
Building a little battery-powered sensor board around an ESP32 module (the kind with the PCB antenna already built into the module, not desi