Touch Capacitance

Capacitance touch sensors are typically found where physical buttons might hinder the aesthetics or practical function of an user interface. Your smartphone, as with other touchscreens, also operate along the same principle of sensing our internal electrical capacitance in our bodies to determine ‘touches’ on a surface.

You might be surprised how sensitive these are – often when it comes to instrumentation design, sensing electrodes (basically conductive material) are embedded hidden below non-conductive material such as polycarbonate/wood. The capacitance of a human hand/finger is picked up by the electrode and registers a ‘touch’. Larger electrodes = more sensitive (but also ‘noisier’)

Read this technical paper for a full definition and application suggestions from Texas Instruments, one of many capacitance-sensing IC manufacturers.

Code-wise, we have things that are quite specific to this sensor, so refer to the code below carefully. We have two separate recipes here.

In the first recipe, much like the A/D Blackbox, we are polling the sensor and publishing the state of the sensor's 12 electrodes every 5 seconds (adjustable down to 1 second). That's great if you want to silently log human interactions with surfaces.

For those who might want to record a ‘touch’ only upon the actuation itself, refer to the second code recipe B. Note that activating the electrodes using the second recipe too often (more than once per second for a sustained period of time) will cause your Photon to stop publishing events. You might also possibly get banned (rate-limited) from their server for a few hours as your Photon will be hogging the bandwidth of Particle.io's servers.


Libraries Used

(learn how to import them in the Build IDE):


Code A: fixed interval-based sensing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_MPR121.h>

// how long between each log update? feel free to change this value,
// but this must not be anything less than 1000 milliseconds!
#define INTERVAL_BETWEEN_LOGS 5000

// initialise the timer
Timer dataTimer(INTERVAL_BETWEEN_LOGS, doDataUpdate);

// You can have up to 4 on one i2c bus but one is enough for testing!
Adafruit_MPR121 cap = Adafruit_MPR121();

// Keeps track of the last pins touched
// so we know when buttons are 'released'
uint16_t lasttouched = 0;
uint16_t currtouched = 0;


// code in this setup function runs just once, when Photon is powered up or reset
void setup() {
    Serial.begin(9600);         // Open a connection via the Serial port / USB cable – useful for debugging

    // Default address is 0x5A, if tied to 3.3V its 0x5B
    // If tied to SDA its 0x5C and if SCL then 0x5D
    if (!cap.begin(0x5A)) {
        Serial.println("MPR121 not found, check wiring?");
        while (1) {
            Particle.process();
        }
    }
    Serial.println("MPR121 found!");

    delay(5000);                // Common practice to allow board to 'settle' after connecting online

    dataTimer.start();
}

// code in this loop function runs forever, until you cut power!
// for the A/D blackbox, there is nothing to do in here because data updates are handled by our timer
void loop() {

}

// doDataUpdate runs every interval set in INTERVAL_BETWEEN_LOGS
void doDataUpdate() {
    // IMPORTANT: to prevent server overload, the Particle cloud can only accept
    // update rates of once per second, with the option to 'burst' 4 updates in a
    // second (but then you'll get blocked for the next 4 seconds). 'Ration' your
    // INTERVAL_BETWEEN_LOGS and the number of readings you are publishing; in our
    // default example, we are frugally using just 1 publish, by concatenating
    // all the data we want into a single publish statement

    // get ready to print out all states of the touch sensor
    String output = "";

    // Get the currently touched pads, and concatenate all readings into a single string.
    currtouched = cap.touched();
    for (uint8_t i=0; i<12; i++) {

        output += "T" + String(i) + ":";

        // it if *is* touched and *wasnt* touched before
        if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) ) {
            output += "1";
        } else {
            output += "0";
        }

        // // if it *was* touched and now *isnt*
        // if (!(currtouched & _BV(i)) && (lasttouched & _BV(i)) ) {
        //     output += "0";
        // }

        output += ",";
    }

    // reset our state
    lasttouched = currtouched;

    output += Time.timeStr();

    // prints this out the Serial port (coolterm) for us humans to verify and debug; comment the next line if you don't want to see it in Coolterm
    Serial.println(output);

    // finally, send it out:
    Particle.publish("sensorData", output);
 }

Code B: actuation-based sensing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// import the MPR121 library
#include "Adafruit_MPR121/Adafruit_MPR121.h"
Adafruit_MPR121 cap = Adafruit_MPR121();

// Keeps track of the last pins touched
// so we know when buttons are 'released'
uint16_t lasttouched = 0;
uint16_t currtouched = 0;

// we use just a tiny delay between each sensor read this time; this delay is used to constantly 'ping' the
// capacitive touch sensor and detect for changes. Only changes in state on any of the electrodes are published
#define INTERVAL_BETWEEN_LOGS   100

// initialise the timer
Timer dataUpdateTimer(INTERVAL_BETWEEN_LOGS, doDataUpdate);

// code in this setup function runs just once, when Photon is powered up or reset
void setup() {
    Serial.begin(9600);             // Open a connection via the Serial port – useful for debugging

    // initialise the MPR121 sensor – remember not to touch the electrodes when powering up!    
    if (!cap.begin(0x5A)) {
        Serial.println("MPR121 not found, check wiring?");
        while (1)
        {
            Particle.process();
        }
    }

    delay(5000);                    // Common practice to allow board to 'settle' after connecting online
    dataUpdateTimer.start();
}


// code in this loop function runs forever, until you cut power!
// for the A/D blackbox, there is nothing to do in here
void loop() {
}

// doDataUpdate runs every loop cycle (i.e. all the time), and only reports when there is a
// CHANGE in the touch sensors. Spam the electrodes with touches+releases too much and you
// might get banned from the Particle servers, so keep it to 1 touch per second if you can!
void doDataUpdate() {
    // get ready to print out all states of the touch sensor
    String output = "";
    bool toPub = false;

    // Get the currently touched pads, and concatenate all readings into a single string.
    currtouched = cap.touched();
    for (uint8_t i=0; i<12; i++) {

        // it if *is* touched and *wasnt* touched before
        if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) ) {
            output = "T" + String(i) + ":1";
            toPub = true;
        }

        // // if it *was* touched and now *isnt*
        if (!(currtouched & _BV(i)) && (lasttouched & _BV(i)) ) {
            output = "T" + String(i) + ":0";
            toPub = true;
        }
    }

    if(toPub==true) {
        output += "," + Time.timeStr();

        // prints this out the Serial port (coolterm) for us humans to verify and debug; comment the next line if you don't want to see it in Coolterm
        Serial.println(output);

        // finally, send it out (and perhaps have an IFTTT recipe ready to use it):
        Particle.publish("sensorData", output);
    }

    // reset our state
    lasttouched = currtouched;
}