Touch buttons on 3D-printed parts with ESP32 touch pins and conductive paint
Make touch buttons on printed toys with ESP32 touch pins and conductive paint or filament. Set the threshold from a baseline; ESP32 readings fall on touch, S2/S3 rise.
- A touch pin measures capacitance: a conductive pad on one wire is a button, with no moving parts.
- On the original ESP32 the reading falls when touched; on the ESP32-S2 and S3 it rises. Your code must check the right direction.
- Set the threshold as a percentage of a baseline measured at power-up, not as a fixed number copied from elsewhere.
- Conductive filament measures in the kilohms: fine for a touch pad, never for carrying power.
If you want buttons on a 3D-printed toy with no moving parts, the ESP32 already has what you need: touch pins. Paint a pad with conductive paint or print one in conductive filament, run one wire to a touch pin and read it in code. The one trap that catches most people: on the original ESP32 the reading falls when touched, but on the ESP32-S2 and S3 it rises.
This is a how-to guide, not a write-up of a finished build. Every number in the examples shows the method; read your own values from the Serial Monitor.
How a touch pin works
A touch pin measures the capacitance of whatever is connected to it. The chip charges and discharges the pad over and over and times it. A finger nearby adds capacitance, each cycle takes longer, and the reading changes. There are no contacts, no springs and no mechanical bounce to filter as with a normal push button.
The chips differ in how they report the number. The ESP-IDF docs split them into two versions.
| ESP32 (original) | ESP32-S2 / ESP32-S3 | |
|---|---|---|
| Touch hardware version | V1 | V2 |
| Touch pins | 10 (T0–T9) | 14 (GPIO1–GPIO14) |
| Pins | GPIO4, 0, 2, 15, 13, 12, 14, 27, 33, 32 | GPIO1 to GPIO14 |
| Reading when touched | falls | rises |
touchRead() type | uint16_t | uint32_t |
| Arduino interrupt fires when | value goes below threshold | value goes above threshold |
The ESP32-C3 has no touch hardware. If you are still picking a board, see comparing ESP32 models first.
Pad material: paint or filament
There are two main routes, and you can mix them.
Conductive paint is a carbon-loaded paint you brush onto or under the surface of a part printed in ordinary PLA. One maker’s datasheet lists a sheet resistance of 55 Ω per square at a 50 micron film, says the paint is water-based and water-soluble, and says it is meant for low DC voltages at low currents. One Fab Academy project painted it under a white PLA shell and read touches through the plastic on the ESP32’s T6 pin (GPIO14).
Conductive filament is PLA loaded with carbon. You print the pad directly, alongside another colour in a multi-material print. One maker’s data gives three volume resistivity values:
| Condition | Volume resistivity |
|---|---|
| Moulded resin (not printed) | 15 Ω·cm |
| Printed, along the layers | 30 Ω·cm |
| Printed, across layers (Z) | 115 Ω·cm |
Across layers is almost four times worse than along them, because layer joints conduct less well than the strand itself. The maker recommends printing at 215–230 °C, toward the high end, and carbon-filled filaments wear brass nozzles (see nozzles).
| Conductive paint | Conductive filament | |
|---|---|---|
| Multi-material printer needed | No | Filament change or multi-material |
| Pad shape | Any surface, soft edges | Sharp, as modelled |
| Durability | Scratches, water-soluble: seal or hide it | Inside the part |
| Wire joint | Paint over the stripped end | Embed during a pause, or screw into it |
Worked example: resistance of a conductive track
A pad usually needs a long track from the surface to the board. Two cases:
Case 1, conductive filament. Track 50 mm long, 5 mm wide, 1 mm thick, along the layers.
- Convert to centimetres: length 5 cm, cross-section 0.5 cm × 0.1 cm = 0.05 cm².
- R = ρ × length ÷ area = 30 × 5 ÷ 0.05 = 3,000 Ω.
- If the same track runs vertically across layers, use 115: 115 × 5 ÷ 0.05 = 11,500 Ω.
Case 2, conductive paint. About 50 microns thick, 50 mm long, 5 mm wide.
- Number of “squares” = length ÷ width = 50 ÷ 5 = 10.
- R = 55 × 10 = 550 Ω.
Both are fine for a touch pin, which senses capacitance and passes no real current. But kilohm figures make it clear these materials are not wire. Never use them to feed a motor or high-current LEDs.
Wiring
Run one wire from each pad to one touch pin. No resistor, no ground on the pad. Common choices:
- Original ESP32: GPIO4 (T0), GPIO14 (T6), GPIO27 (T7), GPIO32/33 (T9/T8)
- ESP32-S3: GPIO1 to GPIO14, avoiding pins your board already uses
Keep the wire from pad to board short and away from motor or speaker wiring. The wire is part of the “pad” being measured; if touching the wire triggers the button, it is too long or too close to your hand.
Code that works on ESP32 and S3
Measure a baseline at power-up with nothing touching, then check how far the reading moves from it as a percentage. Pick the direction by chip with an ESP-IDF macro.
// Arduino-ESP32, original ESP32 or ESP32-S3
const int PAD_PIN = 4; // T0 on ESP32, GPIO4 (TOUCH4) on S3
const float CHANGE = 0.20; // a 20% change counts as a touch
uint32_t baseline = 0;
void setup() {
Serial.begin(115200);
delay(500); // hands off for now
for (int i = 0; i < 32; i++) { baseline += touchRead(PAD_PIN); delay(10); }
baseline /= 32;
Serial.printf("baseline=%u\n", baseline);
}
bool touched(uint32_t v) {
#if CONFIG_IDF_TARGET_ESP32
return v < baseline * (1.0 - CHANGE); // V1: falls on touch
#else
return v > baseline * (1.0 + CHANGE); // V2 (S2/S3): rises on touch
#endif
}
void loop() {
uint32_t v = touchRead(PAD_PIN);
Serial.printf("%u %s\n", v, touched(v) ? "TOUCH" : "");
delay(50);
}
Setting the threshold, step by step. Say an original ESP32 reads a baseline of 90 (the Fab Academy project above reported idle readings around 90, dropping when touched):
- Threshold = 90 × (1 − 0.20) = 72.
- A reading of 85 = not touched; 45 = touched.
On the S3 the numbers are much bigger and the direction flips. Say the baseline is 30,000: threshold = 30,000 × 1.20 = 36,000, and anything above counts as a touch. The 30,000 is only for the arithmetic; read your real values and tune CHANGE until a light touch triggers but a hand hovering nearby does not.
If code from the web works on an ESP32 but the button is “stuck” on an S3, the
<and>are almost always the wrong way round.
Designing the part so touches register
- Pad about finger size. A pad slightly larger than a fingertip gives a clearer change than a small dot.
- Thin cover. If the paint or pad hides under the surface, thinner is more sensitive. Print three test thicknesses and compare the numbers.
- Space between buttons. Pads too close together make a neighbour react too.
- A solid wire joint. A loose joint makes the reading jump. Embed the wire end in a pocket during a print pause, or paint over the stripped end until it is fully covered.
Common mistakes
- Copying a fixed threshold from another article. Values change with pad size, wiring and power. Measure your own baseline.
- Forgetting S2/S3 are inverted. Original-ESP32 logic on an S3 leaves the button dead or stuck.
- Buying a C3 board for touch buttons. The C3 has no touch hardware.
- Using conductive filament as a power wire. Kilohms of resistance mean voltage drop and heat. Pads and low-current signals only.
- Touching the pad at power-up. The baseline is wrong from the start and the button misbehaves all session.
This project stays on the board’s 3.3 V. Conductive paint and filament are made for low voltage; never connect them to mains or anything that plugs into the wall.
Sources
- Capacitive Touch Sensor (ESP32-S2) — ESP-IDF Programming Guide
- Capacitive Touch Sensor (ESP32) — ESP-IDF Programming Guide
- Touch API — Arduino-ESP32 documentation
- Project development (touch pod with conductive paint) — Fab Academy 2023, Barcelona
- Electric Paint Technical Data Sheet — Bare Conductive (via SparkFun)
- Conductive PLA product data — MatterHackers
FAQ
Can I do this with an ESP32-C3 or C6?
Not with this method. The C3 has no touch hardware. Use the original ESP32, the ESP32-S2 or the ESP32-S3, which have touch pins built in.
Does it work through plastic, or must I touch the paint?
It works through a thin plastic wall, but the thicker the wall, the smaller the change when touched. Print test pieces at several thicknesses and watch the numbers in the Serial Monitor before designing the real part.
Why do readings differ on USB and on battery?
The capacitance around the pad changes with grounding and surroundings. That is why you measure a fresh baseline at every power-up instead of hard-coding numbers.
Circuits and code were tested on sample boards. Boards differ in pin layout, so check your board's pinout before you power anything.