Buttons are a design compromise. They’re visible, they wear out, they need holes drilled in things, and they announce “this is a machine.” A lot of interactive work would rather the interface were invisible — touch the painting, touch the wall, touch the sculpture.
Capacitive sensing does that, and the MPR121 is the chip that makes it easy: twelve independent touch electrodes, read over two I²C wires.
How capacitive sensing actually works
The MPR121 measures the capacitance of each electrode. Your body is a big conductive mass; bringing a finger near an electrode changes its capacitance measurably. The chip tracks a baseline for each channel, watches for deviations, and reports touches.
Two consequences follow, and both matter for design:
- The electrode doesn’t need to be exposed. It senses through thin non-conductive material — paper, fabric, wood veneer, plastic, a few millimetres of paint. This is why the interface can be invisible.
- The baseline drifts. Humidity, temperature and nearby objects change it. The chip auto-recalibrates, which is usually what you want and occasionally exactly what you don’t.
What you need
- MPR121 breakout — Adafruit’s is the standard; Bare Conductive’s Touch Board is an MPR121 plus an Arduino plus an MP3 player, and is worth knowing about for installation work
- A microcontroller — Arduino, ESP32, Pi Pico, Raspberry Pi. Anything with I²C.
- Electrodes — copper tape, conductive paint, conductive thread, wire, nails, foil, conductive fabric
- Wire to each electrode
Wiring
Four connections:
- VIN → 3.3V or 5V (check your breakout)
- GND → GND
- SDA → SDA
- SCL → SCL
Then electrodes to pins 0–11. Default I²C address is 0x5A; the address is configurable, so you can run up to four MPR121s on one bus for 48 inputs.
Reading it
#include <Wire.h>
#include "Adafruit_MPR121.h"
Adafruit_MPR121 cap = Adafruit_MPR121();
uint16_t lasttouched = 0;
uint16_t currtouched = 0;
void setup() {
Serial.begin(9600);
if (!cap.begin(0x5A)) {
Serial.println("MPR121 not found — check wiring");
while (1);
}
Serial.println("MPR121 ready");
}
void loop() {
currtouched = cap.touched(); // 12-bit mask, one bit per electrode
for (uint8_t i = 0; i < 12; i++) {
if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)))
Serial.printf("electrode %d touched\n", i);
if (!(currtouched & _BV(i)) && (lasttouched & _BV(i)))
Serial.printf("electrode %d released\n", i);
}
lasttouched = currtouched;
}
cap.touched() returns a 12-bit mask. Comparing it against the previous reading is how you get clean touch and release events rather than a continuous state — which is what you almost always want.
Thresholds are the whole game
This is the step that separates a sensor that feels good from one that feels broken.
cap.setThresholds(12, 6); // touch threshold, release threshold
Defaults are 12 and 6. Two rules:
- Touch threshold higher than release threshold. That gap is hysteresis, and it prevents the chattering you get when a reading hovers right at the boundary.
- Lower touch threshold = more sensitive. Necessary for sensing through thick material or with small electrodes; also more prone to false triggers.
To tune properly, read the raw values rather than guessing:
Serial.println(cap.filteredData(0)); // current reading
Serial.println(cap.baselineData(0)); // auto-tracked baseline
Watch filteredData untouched and touched, note the difference, and set the touch threshold to roughly 60–70% of that delta. Do this on the finished piece, with the real electrodes and the real material — a bench test with a wire tells you very little about a sensor behind 3mm of plywood.
Electrode practicalities
- Copper tape is the reliable default. Solder to it rather than relying on the adhesive for electrical contact — the adhesive on most copper tape is not conductive.
- Conductive paint (Bare Conductive) lets you paint electrodes and traces directly onto a surface. Higher resistance than tape; works fine.
- Bigger electrodes are more sensitive and have more crosstalk with their neighbours.
- Keep electrode wires short. Long runs pick up noise and act as antennas. If you must run long, use shielded cable with the shield grounded.
- Space electrodes apart. Adjacent ones will influence each other; leave a gap or ground between.
- Ground matters. Capacitive sensing on a battery-powered device with no earth reference is noticeably less stable than on a mains-powered one. If it works on USB and fails on battery, this is why.
Proximity mode, which nobody uses
The MPR121 can gang all twelve electrodes into one large proximity sensor, detecting a hand approaching before contact.
For installation work that’s genuinely useful: wake a piece as someone approaches, then switch to individual touch when they arrive. Two interaction states from one chip, and the approach detection is what makes a piece feel like it noticed you rather than waiting to be poked.
Failure modes in exhibitions
- Drift over hours. Auto-recalibration is normally good, but a piece touched continuously for a long time can have the baseline creep toward “touched.” Periodically re-initialise if you see this.
- Humidity. Sensitivity changes noticeably between a dry morning and a crowded, humid afternoon. Test in the busy condition.
- Water. Water is conductive. A wet surface is a permanently touched surface.
- Visitors wearing gloves won’t trigger it. In winter, this is a real accessibility issue worth designing around.
Related Reading
- MPR121 Capacitive Touch Sensor with Arduino — ElectronicThings (YouTube)
- Adafruit MPR121 12-Key Capacitive Touch Sensor — guide
- adafruit/Adafruit_MPR121 — Arduino library, GitHub
- Bare Conductive Touch Board
- Detect Touch with the MPR121 STEMMA QT Breakout Board — CircuitPython School (YouTube)
- MPR121 datasheet — NXP