Reading Touch Data in Code
Learn polling vs interrupt-driven touch detection, how to use touchStatusChanged and isNewTouch, and how to measure how long an electrode has been held.
Reading Touch Data in Code
Once you understand the MPR121’s internals, the next question is: how do you read that touch data efficiently inside an Arduino sketch? There are two fundamental approaches — polling and interrupt-driven — and each suits different kinds of projects. This tutorial covers both, explains the library’s event functions in detail, and shows you how to detect holds and measure touch duration.
Polling vs. Interrupt-Driven
Polling
Polling means checking the sensor’s state on every pass through loop(). It’s the simplest approach and works well for most beginner projects.
#include <MPR121.h>
void setup() {
Serial.begin(9600);
MPR121.begin(0x5C);
}
void loop() {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
Serial.print("Touch: E");
Serial.println(i);
}
if (MPR121.isNewRelease(i)) {
Serial.print("Release: E");
Serial.println(i);
}
}
}
The catch with naive polling is that updateTouchData() issues an I2C read every time it’s called, which takes roughly 200–400 µs. If your loop is doing other slow work (like reading an SD card or driving servos), you’ll miss the state-change flag that tells you whether new data is actually available.
The fix is to guard the read with touchStatusChanged():
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
// handle events here
}
// other fast work here
}
touchStatusChanged() compares the chip’s current status register against the last cached value without doing a full update, so it’s very cheap. Only call updateTouchData() when it returns true.
Interrupt-Driven
The MPR121 has an active-low IRQ pin that pulses whenever the touch status changes. On the Touch Board this pin is connected to Arduino digital pin 4. Using an interrupt means your code reacts the instant a touch is detected, without any polling overhead — ideal for latency-sensitive applications like musical instruments.
#include <MPR121.h>
const int IRQ_PIN = 4;
volatile bool touchEvent = false;
void onTouchChange() {
touchEvent = true;
}
void setup() {
Serial.begin(9600);
MPR121.begin(0x5C);
pinMode(IRQ_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(IRQ_PIN), onTouchChange, FALLING);
}
void loop() {
if (touchEvent) {
touchEvent = false;
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
Serial.print("IRQ Touch: E");
Serial.println(i);
}
if (MPR121.isNewRelease(i)) {
Serial.print("IRQ Release: E");
Serial.println(i);
}
}
}
}
Important: never call I2C functions (MPR121.updateTouchData()) inside the ISR itself. I2C is interrupt-driven on AVR, so calling it inside an interrupt will cause a deadlock. Set a flag in the ISR and do the actual read in loop(), as shown above.
Understanding the Event Functions
After calling MPR121.updateTouchData(), the library stores both the previous and current touch status internally. That lets it calculate which electrodes changed state.
isNewTouch(i)
Returns true if electrode i was not touched before the last updateTouchData() call but is touched now. This fires exactly once per touch event — the moment of contact.
isNewRelease(i)
Returns true if electrode i was touched before the last updateTouchData() call but is not touched now. This fires exactly once when the finger lifts.
getTouchData(i) — the Current State
Returns the current touch state of electrode i. Unlike isNewTouch, this returns true for the entire duration of a touch, not just the moment it starts. Use this when you need to know “is it currently held?” rather than “did it just get touched?”.
// Continuous pressure response — do something while electrode is held
if (MPR121.getTouchData(3)) {
analogWrite(LED_PIN, 200); // LED on while electrode 3 is held
} else {
analogWrite(LED_PIN, 0);
}
Reading the Full Bitmask
getTouchData() with no argument returns the full 12-bit bitmask. You can use bitmask operations for efficient multi-electrode logic:
uint16_t mask = MPR121.getTouchData();
// Check if both electrode 0 AND electrode 1 are held simultaneously
bool bothHeld = (mask & 0b000000000011) == 0b000000000011;
// Check if any of electrodes 6-11 are touched
bool upperHalf = (mask >> 6) > 0;
Detecting Holds and Measuring Touch Duration
The MPR121 library doesn’t natively track hold duration — but adding it yourself takes only a few lines. Store the time of each touch event in a millis() array:
#include <MPR121.h>
unsigned long touchStart[12] = {0};
const unsigned long HOLD_THRESHOLD = 500; // ms
void setup() {
Serial.begin(9600);
MPR121.begin(0x5C);
}
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
touchStart[i] = millis();
Serial.print("Touch start: E");
Serial.println(i);
}
if (MPR121.isNewRelease(i)) {
unsigned long duration = millis() - touchStart[i];
Serial.print("Release E");
Serial.print(i);
Serial.print(" held for ");
Serial.print(duration);
Serial.println(" ms");
}
}
}
// Check for holds in progress (non-blocking)
for (int i = 0; i < 12; i++) {
if (MPR121.getTouchData(i)) {
unsigned long heldFor = millis() - touchStart[i];
if (heldFor > HOLD_THRESHOLD) {
// Electrode i has been held for over 500 ms
// Do something: loop a sound, animate an LED, etc.
}
}
}
}
This pattern — recording millis() on touch start, checking the difference on release or during hold — is extremely versatile. You can use it to implement:
- Tap vs. hold — short touch plays a one-shot sound; hold loops it
- Repeat triggers — fire an event every 200 ms while held
- Long press actions — switch modes after a 2-second hold
A Note on delay() in Touch Code
Avoid delay() in any sketch that reads touch data. When the processor is inside delay(), it cannot respond to touch events, causing missed touches or apparent lag. Replace delays with non-blocking timing using millis():
// Instead of delay(500):
unsigned long lastAction = 0;
void loop() {
if (millis() - lastAction >= 500) {
lastAction = millis();
// do periodic work
}
// touch reading still happens every loop iteration
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
// handle events
}
}
This keeps your touch detection responsive regardless of what else the sketch is doing.
Choosing the Right Approach
| Scenario | Recommended approach |
|---|---|
| Simple playback trigger | Polling with touchStatusChanged() guard |
| Musical instrument | Interrupt-driven for minimum latency |
| Hold-to-loop audio | Polling + millis() hold timer |
| Multi-electrode chords | Bitmask comparison after updateTouchData() |
| Gallery installation (unattended) | Interrupt-driven to reduce CPU load |
The next tutorial builds directly on this foundation, showing how to map each electrode’s touch event to a specific MP3 track and implement polyphonic playback.