Lab 6: Capacitive Sensing

Sam Stromberg
7 min readOct 12, 2021

As suggested, this week did indeed put all the pieces together. I wanted to come up with an application that would use capacitive sensing (in which the stored charge between a human and a conductive object wired to the Arduino yields an indicator of proximity) as well as several of the other components we’ve used so far.

I came up with an alarm clock — with proximity-detection of whether the person was in bed, a photoresistor to distinguish between night and day, and a force sensor to use as a snooze button. Designing the circuit was relatively straightforward, but it turned out that programming all of the possible states of the device was a huge pain. In the end, though, having something with a *function*, as opposed to demonstrating a concept, was worth the extra time. And, hey, now, I know a couple things about C++, which I hope to never need to use (when you learn Python first, everything else just feels fiddly).

The operation runs as follows: when the photosensor detects it’s dark, the alarm is armed and the LED lights; once it’s light again (and the person is in bed), it plays a tune (“Move on Up” by Curtis Mayfield, but in the wrong key and missing the harmonies between horns… maybe next week). You can snooze the alarm for a fixed duration, but as long as you’re still in bed, it will keep re-starting. Once you get out of bed, the alarm idles, with the indicator LED blinking slowly, for a bit so that you don’t accidentally set it off again and irritate your roommates (more than they already are, depending on how many times you hit snooze).

The device itself (minus pillow simulation)

Components

This rig was the most complex yet — I needed the constant power and ground pins, as well as two analog inputs, one digital input for the capacitive sensor, and three digital outputs (the sending side of the capacitive sensor, plus the piezo speaker and an indicator LED). It was right on the edge of needing a schematic drawn, but I started with a crude sketch and mostly didn’t get confused once I was plugging stuff into the breadboard.

  • Arduino Uno
  • Mini breadboard
  • 4x Resistors (S), 10k Ohms; 1x Resistor (L), 1M Ohms
  • 1x Photoresistor
  • 1x Force sensor
  • 1x Piezo speaker
  • (mostly) Jumper wires
A crude sketch

Code

I had to incorporate a bunch of stuff — the CapacitiveSensor library (which controls the signal from the send pin and measures the time delay at the sensor pin); the movingAvg library, which I used to smooth the capacitive sensor readings so that I could use it as a binary indicator; the piezo speaker tone library; and the case-switch control flow structure, to appropriately handle the machine state each time the loop is run.

#include <CapacitiveSensor.h>
#include <movingAvg.h>

#include “pitches.h”

/*
* an alarm clock that arms when it is dark + person is in bed
* then goes off when it gets light again
* includes alarm melody, snooze function
* Sam Stromberg 2021
*/

// FIRST: run cap_piezo_debug in order to set these thresholds appropriately
int photoThreshold = 100;
int capThreshold = 600;
int forceThreshold = 180;
int snoozeDuration = 3000;

// the alarm arms once it gets dark, then triggers once it’s light again
int photocellPin = 3;
int photocellVal = 200;
int alarmStatusPin = 9;
bool alarmArmed = false;
bool alarmTriggered = false;
unsigned i = 0;

// we set the pin for the audio alarm output
int speakerPin = 7;
int toneVal;
//int noteDuration = 15; // ms

// we set the parameters for the snooze button
int forcePin = 5;
int forceVal = 0;
bool alarmSnoozed = false;

// we set the parameters for the capacitive sensor (“still in bed”)
// we’ll moderate these values with the movingAvg library in the actual code
CapacitiveSensor cs_4_2 = CapacitiveSensor(4,2); // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired
bool inBed = false;
movingAvg capReadings(15); // smooth how noisy this sensor is

// then we input our alarm melody, following the model by Robson Couto (https://github.com/robsoncouto/arduino-songs/blob/master/takeonme/takeonme.ino)
// using the song “Move On Up” by Curtis Mayfield, as transcribed by Gary Badger (https://garybadger.files.wordpress.com/2020/12/curtis-mayfield-move-on-up-trumpet-tenor-sax-trombone.pdf)
int tempo = 160;

// notes of the melody followed by the duration, stored as an array
// a 4 means a quarter note, 8 an eighth , 16 sixteenth, so on
// !!negative numbers are used to represent dotted notes

int melody[] = {

REST,2, NOTE_CS6,4, NOTE_CS6,8, NOTE_B5,8,
REST,8, NOTE_GS5,8, REST,8, NOTE_E5,4, NOTE_GS5,8, NOTE_CS5,8, REST,8,
NOTE_B4,8, NOTE_CS5,8, NOTE_E5,8, REST,8, NOTE_CS5,-4, NOTE_B4,8,
NOTE_CS5,8, NOTE_E5,8, REST,8, NOTE_E5,4, NOTE_FS5,8, NOTE_GS5,8, REST,8,

REST,2, NOTE_CS5,4, NOTE_GS4,8, NOTE_B4,8,
REST,8, NOTE_GS4,8, REST,8, NOTE_E4,4, NOTE_GS4,8, NOTE_CS4,8, REST,8,
NOTE_B3,8, NOTE_CS4,8, NOTE_E4,8, REST,8, NOTE_CS4,-4, NOTE_B3,8,
NOTE_CS4,8, NOTE_E4,8, REST,8, NOTE_E4,4, NOTE_CS4,8, NOTE_E4,8, REST,8,

REST,4, NOTE_B4,8, NOTE_CS5,8, NOTE_E5,-4, NOTE_CS5,4, REST,-2, REST,8,
NOTE_B4,8, NOTE_CS5,8, NOTE_E5,8, REST,8, NOTE_FS5,4, NOTE_CS5,8, NOTE_E5,8, REST,2, REST,1
};

int notes = sizeof(melody) / sizeof(melody[0]) / 2;
int wholenote = (60000 * 4) / tempo;
int divider = 0, noteDuration = 0;

// combine multiple state variables to control the switch/case structure
char machineState() {
if ( !alarmArmed && !alarmTriggered ) {
return ‘o’; // for OFF, but we can only case-switch with single chars
}
else if ( alarmTriggered && alarmSnoozed ) { // this needs to be evaluated before “ringing”
return ‘s’; // for SNOOZED
}
else if ( alarmArmed && !inBed ) {
return ‘u’; // for UP, NOT IN BED
}
else if ( alarmArmed && inBed ) {
return ‘r’; // for READY
}
else if ( alarmTriggered && !inBed ) {
return ‘d’; // for DISARMED
}
else if ( alarmTriggered ) {
return ‘t’; // for TRIGGERED
}
}

// initialize a bunch of stuff
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
pinMode(alarmStatusPin, OUTPUT);
pinMode(speakerPin, OUTPUT);

cs_4_2.set_CS_AutocaL_Millis(0xFFFFFFFF); // turn off autocalibrate on channel 1 — just as an example
capReadings.begin();

Serial.begin(9600);
}

// run the actual loop
void loop()
{
// gather sensor readings
long capVal = cs_4_2.capacitiveSensor(30); // take reading
int capMeasure = int(capVal); // the two libraries have different expectations
int capAvg = capReadings.reading(capMeasure); // returns the current rolling avg

if ( capAvg < 0.75 * capThreshold ) { // map sensor reading onto inBed
inBed = false; // this runs every cycle b/c we’re using rolling avg
digitalWrite(LED_BUILTIN, LOW);
}
else if ( capAvg > capThreshold ) {
inBed = true;
digitalWrite(LED_BUILTIN, HIGH);
}

photocellVal = analogRead(photocellPin); // this one is comparatively easy

// decide what procedure we should execute this loop
char checkState = machineState();
Serial.println(checkState);

switch ( checkState )
{
case ‘o’: // OFF check to see if it’s “night” to arm the alarm
if ( photocellVal < photoThreshold ) {
alarmArmed = true;
digitalWrite(alarmStatusPin, HIGH);
}
break;

case ‘u’: // UP, NOT IN BED give it a sec and check again
delay (100);
break;

case ‘r’: // READY when it’s ready (armed + in bed), check to see if it’s dawn
if ( photocellVal > 1.33 * photoThreshold ) {
alarmArmed = false;
alarmTriggered = true;
analogWrite(alarmStatusPin, 64);
}
break;

case ‘t’: // TRIGGERED play melody, but also check for snooze / stop conditions
for ( int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2 ) {
// calculates the duration of each note
divider = melody[thisNote + 1];
if (divider > 0) {
// regular note, just proceed
noteDuration = (wholenote) / divider;
} else if (divider < 0) {
// dotted notes are represented with negative durations!!
noteDuration = (wholenote) / abs(divider);
noteDuration *= 1.5; // increases the duration in half for dotted notes
}

tone(speakerPin, melody[thisNote], noteDuration * 0.9); // we only play the note for 90% of the duration, leaving 10% as a pause
delay(noteDuration); // Wait for the specief duration before playing the next note.
noTone(speakerPin); // stop the waveform generation before the next note.

// check for Snooze button, inBed — IN BETWEEN EACH NOTE
long capVal = cs_4_2.capacitiveSensor(30); // after iteration of the melody, recheck
int capMeasure = int(capVal); // the two libraries have different expectations
capAvg = capReadings.reading(capMeasure); // returns the current rolling avg
if ( capAvg < 0.75 * capThreshold ) {
inBed = false;
digitalWrite(LED_BUILTIN, LOW);
}

forceVal = analogRead(forcePin);
if ( forceVal > forceThreshold ) {
digitalWrite(alarmStatusPin, HIGH);
alarmSnoozed = true;
break; // breaks the alarm-melody loop
}
}

if ( !inBed ) { // check this after the melody plays
noTone(speakerPin);
delay(1000);
}
break; // ends the switch-case

case ‘s’: // SNOOZED stop running the loop for the duration, then trigger the alarm again
delay (snoozeDuration);
alarmSnoozed = false;
break;

case ‘d’: // DISARMED just a lil blinking status light
for ( int i = 0; i < 36; i++ ) {
digitalWrite(alarmStatusPin, HIGH);
delay(750);
digitalWrite(alarmStatusPin, LOW);
delay(750);
}
}

delay(20); // put a lower bound on the rate
}

Exploration

For this device, I found it worked best when the capacitive sensor was made from a wire clipped to a sheet of aluminum foil; even when folded in the middle of a dish towel (a substitute for a pillow), it was much more sensitive than other objects I tried attaching it to (such as a metal water bottle or a piece of anodized aluminum). I’m not sure why this was, and understanding ideal capacitors is already at the frontier of my electrical engineering knowledge, so I’ll defer to someone else to explain.

--

--

Sam Stromberg

2nd-year Masters student at the UC Berkeley School of Information. Moving into Product; interested in data and uncertainty, sensor data, behavioral change.