Skip to content

Touch Sensors

This lesson needs no new parts at all. The ESP32 has capacitive touch sensing built in, the same idea as a phone touchscreen. Ten of its pins can tell when you touch them (or anything conductive wired to them), so a jumper wire, a coin, a sheet of foil or even a banana can become a button.

You'll need:

  • 2 × jumper wires
  • Optional: coins, aluminium foil, or pieces of fruit
  • The LED from the LEDs lesson

Hardware

Your body can hold a tiny electrical charge (that's its capacitance). The ESP32 constantly charges and discharges each touch pin and times how long that takes. When your finger touches the pin, it adds your body's capacitance, and the timing changes.

Wiring

From To
ESP32 GPIO 4 A jumper wire with its other end free (pad A)
ESP32 GPIO 15 A jumper wire with its other end free (pad B)

That's it! Touch the metal end of each wire to use it. To make a bigger pad, press the loose end of the wire onto a coin or a square of foil and hold it down with tape.

Which pins can sense touch?

On the classic ESP32, touch works on GPIO 0, 2, 4, 12, 13, 14, 15, 27, 32 and 33. This guide uses 4 and 15, which aren't needed by anything else in the pin plan.

Software

Reading the raw value

touch_read.py
# Print the raw value of a touch pin. Touch the wire and watch it change!
import time
from machine import Pin, TouchPad

TOUCH_PIN = 4

touch = TouchPad(Pin(TOUCH_PIN))

while True:
    print(touch.read())   # gets SMALLER when you touch it
    time.sleep(0.2)

Run it and watch the numbers, then touch the end of the wire. On the ESP32, the number gets smaller when you touch the pin. You might see something like 600 untouched and 150 touched.

Try it in the REPL

>>> from machine import Pin, TouchPad
>>> pad = TouchPad(Pin(15))
>>> pad.read()
587

Turning it into a button

The exact numbers depend on your board, the length of the wire, and even the humidity in the room, so picking one fixed threshold for "touched" doesn't work everywhere. Instead, this code measures a baseline when it starts and treats any reading below 70 % of it as a touch:

touch_button.py
# A reusable touch "button": works out its own threshold at startup,
# then reports each new touch once, just like Button.was_pressed()
import time
from machine import Pin, TouchPad

LED_PIN = 27


class TouchButton:
    def __init__(self, pin_number, sensitivity=0.7):
        self.pad = TouchPad(Pin(pin_number))
        # Measure the untouched value a few times and average it.
        # Don't touch the pad while the program starts!
        total = 0
        for i in range(10):
            total += self.pad.read()
            time.sleep_ms(20)
        baseline = total // 10
        # A touch drops the reading well below the baseline
        self.threshold = int(baseline * sensitivity)
        self._was_down = False

    def is_touched(self):
        return self.pad.read() < self.threshold

    def was_touched(self):
        # True once each time a finger lands on the pad
        down = self.is_touched()
        new_touch = down and not self._was_down
        self._was_down = down
        return new_touch


led = Pin(LED_PIN, Pin.OUT)
pad_a = TouchButton(4)
pad_b = TouchButton(15)
print("Ready: touch the wire on GPIO 4 or GPIO 15")

while True:
    if pad_a.was_touched():
        led.value(not led.value())   # pad A toggles the LED
        print("Pad A: LED", "on" if led.value() else "off")
    if pad_b.was_touched():
        print("Pad B touched!")
    time.sleep_ms(20)
  • TouchButton is a class, a bit like the Button class from the Buttons lesson. Each touch pad gets its own object with its own threshold.
  • __init__ runs once, when the object is created. It averages 10 readings to find the untouched baseline, so don't touch the wires while the program starts.
  • was_touched() only returns True at the moment a finger lands, just like Button.was_pressed(). _was_down remembers what happened last time.
  • If a pad is too twitchy, lower the sensitivity (for example 0.6). If it doesn't respond, raise it (for example 0.8).

Longer wires, weaker touches

Very long wires and large pads pick up more "noise". Keep the wires short, and if a pad misbehaves, restart the program to take a fresh baseline.

Challenge

Make pad A turn the LED on and pad B turn it off, like a pair of light switches. Then try swapping the wires for two pieces of fruit!

Show a hint

Replace the two if blocks in the main loop:

if pad_a.was_touched():
    led.on()
if pad_b.was_touched():
    led.off()

Next up: NeoPixels: strings of colour LEDs you control one by one.