Touch capability is built-in as a standard features for most ESP32 boards. Simply attach a conductive surface to a touch-enabled GPIO pin. However, using these touch pin directly comes with a number of caveats. So this article will explore this method, but also look at a simple $2 component that can also add touch and avoid many of the potential issues when using the touch pins.
This article is a companion to the video version of this project, No More Buttons! Add Touch Control to Your ESP32, which you can watch on YouTube.
Sure, you can use plain ol' buttons and switches with your ESP32 to control devices or launch automations, but you can also add touch capability. Turn the entire body of an old lamp into an on/off switch so you can easily turn on the light in the dark. Build a smooth enclosure that still offers 'switch-like' capability but without any protruding buttons or switches. Wake devices from deep-sleep... and more!
Note: This article isn't going into details on proper GPIO pin use, methods to power your project safely or other wiring details. If you are newer to ESP boards, you may wish to review the following before proceeding:
Written Companion Guide: ESP Microcontrollers: An Introduction
Parts List
These are the parts I used or showed in the video version. There are many other parts that could be substituted for the ones I am using.
|
Part |
Notes |
|
Standard 30-pin
NodeMCU-style |
|
|
|
|
|
|
|
|
|
|
|
Or appropriate value
based on your LED |
|
|
|
|
|
|
|
|
|
|
|
Or appropriate size
breadboard |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Some of these links may be Amazon affiliate links. Use of these links will not affect your pricing, but as an affiliate this blog may earn a small commission if you make a purchase.
Note that the ESP8266 does not have touch-capable pins. However, you can still implement touch with an ESP8266 using a TTP223 sensor. The process for the ESP8266 is nearly identical to the ESP32 when using this sensor and I cover this method a bit later in this article.
ESP32 Touch Pins
![]() |
| Click to enlarge |
A standard ESP32 development board may provide up to 10 touch-enabled GPIO pins, labeled as touch channels 0-9. Not every ESP32 board will break out all ten pins and some specialty boards may not have any available. But if your board has any, they will like correspond to the same GPIO pins as shown above.
The board I am using, for example, does not break out GPIO0 to a dedicated pin. But I still have nine other touch-enabled pins to choose from.
Also note that each of the touch pins are also ADC (analog-to-digital conversion) pins, meaning they can read an analog signal. That's no accident! As charge in a capacitor builds up and then is released via the touch process, the voltage on the GPIO pin changes. And this voltage change is what is used to determine whether a touch has occurred. This will make a bit more sense in a minute. Just note that built-in touch uses analog-capable GPIO pins.
While there can be up to 10 touch pins (T0 - T9), these are tied to only one of two ADC busses, ADC1 and ADC2. In some cases, using WiFi at the same time as ADC2 can cause problems. If you are using WiFi in your project, the safest bet is to use one of the ADC1 pins for touch. That means GPIO32 and GPIO33 should be your first choices for touch pins if possible.
Creating a Basic Touch Sensor
For an initial test, all we need to do is connected a short male Dupont cable to a GPIO pin.
![]() |
| Click to enlarge |
I'm using GPIO32 (an ADC1 pin) and I've connected a short 10 cm (~3.9") Dupont cable with an exposed male pin, which will act as the test touch point. I'm initially powering the board via USB connected to a computer. This is because we still need to flash some firmware to the board and use that to determine the touch "threshold". I'll discuss this threshold once we get some firmware installed. I'll show examples for both Arduino/C++ and ESPHome.
Arduino Test Code
The initial Arduino test code for touch is very simple:
void setup() {
Serial.begin(115200);
delay(1000); // give me time to bring up serial monitor
Serial.println("ESP32 Touch Test");
}
void loop() {
Serial.println(touchRead(32)); // get value of Touch pin = GPIO32
delay(1000);
}
The serial monitor is enabled in setup() and the loop() just reads the value on the GPIO pin once per second. This code is compiled and flashed to the ESP32 and then the serial monitor is opened to see the output.
![]() |
| Click to enlarge |
You may need to click the above image to see the serial output, but for my setup there is a consistent reading of around 91-92 when no touch is present. However, as soon as I touch the bare end of the male Dupont jumper the reading falls to around 17-18. So I could probably use a value of something like '70' as my threshold. Any value above 70 means 'no-touch' and a value below 70 means 'touch'.
To provide an easy-to-see visual test for the threshold, I added a simple red LED and resistor to the circuit, attaching the LED to GPIO21.
I then modified the Arduino code to simply turn on the LED when the touch value falls below the established threshold and turn it back off when above the threshold, while still outputting the pin value once per second:
uint32_t lastOutput = 0;
void setup() {
Serial.begin(115200);
pinMode(21, OUTPUT);
digitalWrite(21, LOW);
delay(1000); // give me time to bring up serial monitor
Serial.println("ESP32 Touch Test");
}
void loop() {
uint16_t touchVal = touchRead(32);
if (touchVal < 70) {
digitalWrite(21, HIGH);
} else {
digitalWrite(21, LOW);
}
if (millis() > lastOutput) {
Serial.println(touchVal); // show value of Touch pin (GPIO 32)
lastOutput = millis() + 1000;
}
delay(100);
}
![]() |
| Click to enlarge |
Now I have a visual indicator that will light up any time a touch reading below my threshold is read. If above the threshold, the LED will turn off.
But here is the first caveat when directly using the touch GPIO pins: you must calibrate the system for your particular setup to determine a threshold value for touch vs. no-touch. And this can change based on a number of factors.
![]() |
| Click to enlarge |
Here I swapped out the shorter Dupont cable for a longer one attached to an alligator lead. When I run the sample Arduino code, note that the "no-touch" value is now around 55-56. This is because wire itself has resistance... and even its own capacitance... so using different length, gauge or even type of wire will impact the reading on the GPIO pin. So if I was still using my threshold value of '70' with this new wiring, touch would be continuously reported even when no touch is occurring, since my new 'non-touch' value is already less than this.
I'd have to redefine my threshold, to maybe like '45' for this new wiring. And the reading can also be impacted by other nearby electronic components. So you'll need to test and possibly retest and update any touch thresholds after you've completed your project build.
And this threshold value is also needed when using ESPHome.
ESPHome Test Code
This article isn't going to cover installation, setup or initial board preparation for ESPHome. If you are new to ESPHome, I'd recommend watching and reviewing the following first:
This article will only show the component configuration as related to touch. First you need to add the touch platform to the ESPHome configuration and then define touch as a binary sensor:
#Define the touch platform
esp32_touch:
setup_mode: true #set to false after threshold known
#Create the binary touch sensor
binary_sensor:
- platform: esp32_touch
name: "ESP32 Touch Pad"
pin: GPIO32
threshold: 850
#=====================
#Added for LED toggle
#=====================
id: touch_1
on_state:
then:
- if:
condition:
lambda: 'return x;' #true when sensor touched
then:
- light.turn_on: my_led
else:
- light.turn_off: my_led
#Define the LED output as binary light
light:
- platform: binary
pin: GPIO21
name: "My Status LED"
id: my_led
The touch platform has a number of additional parameters available (see the ESPHome documentation) but one of note here is the setup_mode. When set to TRUE, the measured value of the touch GPIO pin is output to the ESPHome logs just like we did with the Arduino serial monitor. However, this will spam the logs and could even impact the ESP32's performance. So once the threshold is determined, the setup_mode should be set to FALSE (or removed, as the default is also false).
For the actual binary sensor (which will be ON or OFF based on the threshold), only the platform, name, pin and threshold are required. Everything added below that is just to toggle the LED on/off like with the Arduino example. Of course if you are using ESPHome with Home Assistant, both the touch binary sensor and LED light would be exposed as entities so the automation could be created in Home Assistant (or used for other automations). That's beyond the scope of this article, but the linked ESPHome video above goes into more detail.
With the setup_mode set to true, after flashing the configuration to your ESP32, you can open the ESPHome logs and touch your sensor to determine a good threshold value:
![]() |
| Example output - click to enlarge |
For my particular setup resulted in a threshold value of around 850 (note that the values returned in ESPHome are likely to be significantly different from Arduino so you can't use the threshold value from one platform with the other). I then set the setup_mode back to FALSE. With this flashed to the ESP32, the ESPHome code executes and toggles the LED exactly like the Arduino version.
But there is another issue here, regardless of whether using Arduino or ESPHome... the end of a Dupont cable or alligator clip isn't the ideal touch surface. But any conductive surface can be used.
Here I switched back to the longer cable with an alligator clip, which is attached to a small square of aluminum foil. Naturally both the longer cable and the aluminum foil changes the threshold value needed. But once the new threshold is determined and the code (Arduino or ESPHome) is updated:
There's one more thing to consider. What if you want to have your touch surface within some sort of enclosure, like maybe a 3D printed box. Will touch work through this surface?
![]() |
| Click to enlarge |
For this test, I placed a 2mm think PLA printed panel over the aluminum foil and repeated the test. Of course the threshold will change, but if you look at the output you'll see that no-touch shows around 42, while touch shows around 35. This is a very narrow range and any threshold is likely to result in inconsistent results.
But instead of messing around with changing calibration values, analog inputs and limited ability to use under a non-conductive substrate, we can make our lives much simpler by just adding a $2 touch sensor.
The TTP223 Capacitive Touch Sensor
The TTP223 is a simple binary sensor. While power needs to be supplied, it still uses a single GPIO pin on the ESP32. However, since it is a digital on/off signal, we can use any appropriate digital GPIO pin and aren't limited to just using touch-enabled or analog pins. But before getting into the hookup and testing, let's take a little closer look at the specs and a few special options that can be used to change the behavior.
This TTP223 has an operating voltage range of 2.5 - 5.5V, meaning we could power it with either 3.3V or 5V from the ESP32. I'll power mine with 3.3V so I don't have to be concerned about the voltage from the output signal exceeding the 3.3V tolerance of a GPIO pin.
The component can draw up to 10mA and the output signal will be around 6mA... both of which are fine for the ESP32's GPIO pins. But there are a few jumpers that can also be used to change the default behavior of the sensor.
Terminal A determines the type of output signal sent by the module.
OPEN: When this terminal is 'open' (as shown above), the module will put out a HIGH signal when touched and a LOW signal when not touched.
CLOSED: When you close this terminal (normally by creating a short solder bridge), then the opposite output is true. When touched, the sensor will send a LOW signal and a HIGH signal when not touched.
Config Terminal B
Terminal B determines how the sensor operates when touched.
OPEN: The sensor acts like a momentary push button... the signal will be HIGH when touched (assuming Terminal A is open) but immediately returns to LOW when touch ends. This is just like a push button.
CLOSED: When closed, then the module acts like a latching switch. The first touch will toggle the state to HIGH. The state remains in the "HIGH" state even after touch ends and will only flip back to "LOW" when touched again. So in this mode, the touch sensor act much like a normal switch.
Sensitivity Adjustment (C2)
This can be used to lower the sensitivity of the module by adding a capacitor across the terminals.
When open (no capacitor), the sensor is at maximum sensitivity. You can add a capacitor up to 50 pF, which would set the sensitivity to the lowest value.
For my testing, all sensors will have Terminal A open (touch = HIGH) and no capacitor will be installed so they will operate at maximum sensitivity. However, I will have one sensor with Terminal B open but I'll create a solder bridge to close these terminals for a separate sensor so we can test the momentary vs. latching feature.
To test both touch sensor modes, I opted to wire up two TTP223 sensor and two LEDs.
![]() |
| Click to enlarge |
For this test, I opted to power everything with an external 5V 5A power supply. This is used to create a 5V power rail on the protoboard. The ESP32 is powered off of this rail via the VIN and GND pins. On the opposite side, the ESP32's 3V3 and GND pins are used to create a 3.3V power rail. This power rail will be used to power the TTP223 sensors. Technically, since I'm not powering anything other than the ESP32 from the 5V rail, I could still power the ESP32 via USB and eliminate the 5V rail. I'd still need to create the 3.3V rail since I need to power two sensors from only one available 3.3V power pin on the ESP32.
While I'm using two touch sensors, each will control its own LED. You could easily omit one of the sensors (or even add more!). Just modify the example code to match the number of sensors/LEDs used. Just be aware of the total current draw that is being pulled through the 3.3V pin. This is more likely to be the limiting factor on the number of peripherals attached than the available GPIO pins.
Arduino Code
This code is nearly identical to the initial test. I've just added code for the second touch sensor and LED. And since I'm no longer connected to the PC via USB and the sensors don't put out any "values" like the analog pins, I removed the Serial output.
//Sample TTP223 Sketch
//No external libraries needed!
#define touch1Pin 18
#define touch2Pin 19
#define led1Pin 26
#define led2Pin 27
void setup() {
//Setup GPIO pins
pinMode(touch1Pin, INPUT);
pinMode(touch2Pin, INPUT);
pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
//Assure LEDs initialize to off
digitalWrite(led1Pin, LOW);
digitalWrite(led2Pin, LOW);
}
void loop() {
//Read touch sensor states (debounce logic should really be added)
bool touch1Touched = digitalRead(touch1Pin); // true if pin "HIGH"
bool touch2Touched = digitalRead(touch2Pin);
//Set state of LED based on touch sensor
digitalWrite(led1Pin, touch1Touched); //Turns on when touch1 HIGH
digitalWrite(led2Pin, touch2Touched); //Turns on when touch2 HIGH
//Lazy man's debounce
delay(500);
}
This is just sample code to test the sensors. As noted, each sensor should really be debounced to eliminate multiple touches, but for this testing I just added a 500ms delay in the main loop.
![]() |
| Touch1 - Momentary Mode |
For the Touch1 sensor, which was configured with config terminal B open, touching the sensor turns on the LED. As soon as touch is removed, the LED turns off.
![]() |
| Touch2 - Switch/Latched Mode |
For the Touch2 sensor, which had terminal B closed, the sensor acts like a switch. When the sensor is touched, the LED turns on. However, the LED remains on after touch ends. The LED will remain on until the sensor is touched again, which will toggle the LED off.
Note that both sensors (and their related LEDs) are coded exactly the same. No code changes were necessary based on whether the sensor is operating in momentary or latched mode. It is all handled by the sensor itself.
ESPHome
Since we don't have to mess around with a touch platform anymore, and both sensors are really just binary sensors, the ESPHome configuration is simplified as well.
# ========================================
# Your component configuration starts here:
# ========================================
#Create the binary touch sensors (Just with GPIO platform)
binary_sensor:
- platform: GPIO
name: "touch1"
id: touch_1
pin: GPIO18
filters:
- delayed_on: 50ms
- delayed_off: 50ms
- platform: GPIO
name: "touch2"
id: touch_2
pin: GPIO19
filters:
- delayed_on: 50ms
- delayed_off: 50ms
The filters handle debouncing the sensor. But that's all that is needed to define the touch sensors. These would be created as entities when the ESPHome device is adopted in Home Assistant.
![]() |
| Example Home Assistant Dashboard showing the two sensors |
Of course, you could use the sensors in scripts and automations as well as showing on a dashboard.
But if we want to create the same Arduino functionality using just ESPHome, the LED and automation can be added to the configuration.
binary_sensor:
- platform: GPIO
name: "touch1"
id: touch_1
pin: GPIO18
filters:
- delayed_on: 50ms
- delayed_off: 50ms
on_state:
then:
- if:
condition:
lambda: 'return x;'
then:
- light.turn_on: led_1
else:
- light.turn_off: led_1
light:
- platform: binary
pin: GPIO26
name: "led1"
id: led_1
This shows the process for Touch1. Adding the second LED and automation for Touch2 would be identical. Again, since the momentary/latching function is handled by the sensor, the code is same regardless of how the touch sensor is configured.
Additional Ideas and Use Cases
While I've been demonstrating the TTP223 touch sensor, there are many other types of touch sensors available, but most will work in a similar manner. And while it's fine for simple testing, odds are that you will want to control something other than a simple LED in your projects. But since touch acts like a binary sensor, you can use its state to control just about anything else that an ESP32 can control.
![]() |
| Using a relay with touch - Click to enlarge |
For example, by adding a relay and using the state of the touch sensor to control the state of the relay (just like was done with the LED), you can add touch control to a 120V AC device like a light or lamp. Just assure any external device and your wiring will support the expected current draw of the device. Covering use of relays is beyond the scope of this article, but if you want to know more about using relays with the ESP32, you can check out the following:
Let me know down in the comments if you have questions or if you've found some unique uses for touch in your own projects. And as always, thanks for reading!
Links and Additional Information
Supporting this blog and related YouTube channel
It takes significant time and effort (and often expense) to create these articles and videos. If you'd like to support future content on this blog and the related YouTube channel, or just say thanks for something that helped you out, you can consider buying me a one-off cup of coffee or two at:























No comments:
Post a Comment
To help eliminate spam and to keep all conversations civil, submitted comments are moderated. Therefore your post may not show up immediately. Please be patient as most reviews are completed within a few hours.