Introduction: Just Veggin With an Arduino Beetbox

About: Technologist, Electronic Engineer, sometime Coderdojo mentor.
Bring Touch Control to the Arduino. Use interesting touch sensors like Carrots or Beets to make a "Beetbox".

In this instructable you will learn:

* How to use the Cap Sense library to make Arduino responsive to touch
* How adding a Wave Shield (and some root vegetables), you can make a "Beetbox"



Step 1: You Will Need

For the Touch Sensor part you will need:
* Arduino UNO
* A 4.7M Ohm resistor  (or a value similar to that) (one for each sense channel)
* Breadboard for wiring up e.g. (http://goo.gl/1E1iI)
* Some Wires

For an initial experiment to show how touch sensing works:
* An LED
* a 560 Ohm resistor

For the "Beetbox" part:
* Some Root Vegetables (such as a Beet)
* An Adafruit Wave Shield as shown  http://goo.gl/KUUZz
* The WaveHC library for Arduino (http://www.ladyada.net/make/waveshield/download.html)


Adafruit Wave Shield for Arduino Kit

Step 2: The Principles of Capacitive Touch Sensing

For the Beetbox we use three sense channels.

To start we'll implement one touch sense channel and activate an LED when Arduino senses touch.

Wire up the circuit as shown in the Fritzing diagrams and photo's.

Get and Install the Capacitive Sense library for Arduino from here: http://goo.gl/EpSX0


Step 3: Here's How the Capacitor Sense Works

The Cap Sense circuit is made by the resistor between Arduino pins 6 and 9, and also the capacitance to ground on the Touch Sensor. When the CapacitiveSensor arduino library is asked to read the sensor value, pulses are Output on Pin 9 and Input into pin 6. The library routine measures the delay between when the pulses are transmitted and when they are received.

The measured delay is proportional  to the Resistor value R and the Capacitance C. i.e.   Delay = R*C
(strictly speaking there's a scaling factor too depending on the voltage level you're measuring from, but that's just a detail for us).


The Capacitance on the sensor varies as you bring your finger close to the sensor and as you touch it. Touching the sensor gives you the largest capacitance and the longest delay. In order to make the delay large enough to be detected by Arduino you need a large resistor e.g. 4.7Meg Ohms.

Step 4: Arduino Code: Key Concepts

There are a couple of Key Concepts in the Arduino code.

1. Activate an LED when the sensor is touched.

To do that we need to measure the delay (total1) and compare it to a Threshold value e.g.

=============

void setup()                  
{
....some setup stuff....
}

void loop()                  
{
    total1 =  cs_9_6.capacitiveSensor(30);        // Measure the Cap Sense Value from the Sensor

    if (total1 > THRESHOLD) {
      digitalWrite(LedPin, HIGH);
    }  
    else {
      digitalWrite(LedPin,LOW);
    }
}


=======

That's pretty straightforward. But what should the THRESHOLD value be?

The total1 value of delay we read back varies from day to day because it's very sensitive to capacitance (which is the whole idea), but it means we can't just put in any fixed THRESHOLD value because it will be different under different conditions.

To make sure we have an appropriate THRESHOLD value, we need to measure the THRESHOLD value during a calibration sequence at the start of the code.

We do this as follows. We initialise i=0 during setup() and calibrate for the first 50 iterations of loop(). The LED flashes during calibration and we need to touch the sensor to determine the appropriate THRESHOLD value.

=======

void loop()                   

    total1 =  cs_9_6.capacitiveSensor(30);     
   // Measure the Cap Sense Value from the Sensor

// Touch the Sensor while the LED is HIGH to Calibrate the TOUCH value
    if (i<50) {                                      // Calibrate Sensor baseline to Start
      digitalWrite(LedPin, HIGH);
      calVal1= 0.1*float(total1) + 0.9*calVal1;
      Serial.println(calVal1);               
// print calibartion value during calibration cycle
      delay(50);
      digitalWrite(LedPin, LOW);
      delay(50);
      i++;
    }
}


=======

The code:
calVal1= 0.1*float(total1) + 0.9*calVal1;

implements a digital filter. It works by weighting it's output 90% in favour of old outputs;  new data only accounts for a 10% weighting . This gives us a reliable calibration value, calVal1 by filtering out any noise on the readings.
In the code we print the calVal1 to the Serial Monitor. Take a look at it to see how it increases smoothly towards the total1 value.

The Next Step shows the Full Arduino Code for this Experiment

Step 5: Arduino Code (Experiment)

#include <CapacitiveSensor.h>

/*
* Uses a high value resistor e.g. 10M between send pin and receive pin
* Resistor effects sensitivity, experiment with values, 50K - 50M. Larger resistor values yield larger sensor values.
* Receive pin is the sensor pin.
*/


#define error(msg) error_P(PSTR(msg))

CapacitiveSensor   cs_9_6 = CapacitiveSensor(9,6);       
// 10M resistor between pins 9 & 6, pin 6 is sensor pin, add a wire and or foil if desired

float calVal1 = 0.0;

float total1 = 0.0;

int i = 0;
int LedPin = 2;   
         // Pin the LED is connected to

void setup()                   
{
  Serial.begin(9600);

  cs_9_6.reset_CS_AutoCal(); 
// autocalibrate channel 1
  cs_9_6.set_CS_AutocaL_Millis(0x00001000);     // autocalibrate interval on channel 1

  pinMode(LedPin, OUTPUT);
  i = 0;               
   // Initialise some values
  calVal1 = 0.0;
}

void loop()                   

    total1 =  cs_9_6.capacitiveSensor(30);       
// Measure the Cap Sense Value from the Sensor

// Touch the Sensor while the LED is HIGH to Calibrate the TOUCH value
    if (i<50) {                                      // Calibrate Sensor baseline to Start
      digitalWrite(LedPin, HIGH);
      calVal1= 0.1*float(total1) + 0.9*calVal1;
      Serial.println(calVal1);               
// print calibration value during calibration cycle
      delay(50);
      digitalWrite(LedPin, LOW);
      delay(50);
      i++;
    }
    else {                                     
// Calibration is Over
      Serial.print(calVal1);
      Serial.print("\t");
      Serial.println(total1);                 
// print sensor output 1

      if (total1 > 0.8*calVal1) {
        digitalWrite(LedPin, HIGH);
      }   
      else {
        digitalWrite(LedPin,LOW);
      }
    }
}

Step 6: Adding Sound With the Wave Shield

If we want to go further and add sound, attach the Wave Shield to the Arduino.

I've used three Cap Sense Channels on pins 6,7 and 8.

Put some .wav files on the sound card. I used "BUFF1.WAV" etc.. as shown in the code.

Add some vegetables of choice and off you go.....

Full Arduino Code in the Next Step.

It's very similar to the one channel example with some routines added for SD card file reading and playback.

The code uses the #define DEBUG construct to add in some Serial Monitor output for debug. Uncomment that line for debug.

The calibration cycle in this version is as follows:
1. LED ON : Touch channel 1 sensor
2. LED OFF : Touch Channel 2 sensor
3. LED ON : Touch channel 3 sensor
4. Enjoy playing your beetbox

Step 7: Full Arduino Beetbox Code

#include <CapacitiveSensor.h>
#include <WaveHC.h>
#include <WaveUtil.h>


/*
* Uses a high value resistor e.g. 10M between send pin and receive pin
* Resistor effects sensitivity, experiment with values, 50K - 50M. Larger resistor values yield larger sensor values.
* Receive pin is the sensor pin.
*/

SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the volumes root directory
FatReader file;   // This object represent the WAV file for a pi digit or period
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

char filename[13];


// Uncomment next line to enable Debug Serial.print() statements
//#define DEBUG     

#define error(msg) error_P(PSTR(msg))

CapacitiveSensor   cs_9_6 = CapacitiveSensor(9,6);        // 10M resistor between pins 9 & 6, pin 6 is sensor pin, add a wire and or foil if desired
CapacitiveSensor   cs_9_7 = CapacitiveSensor(9,7);
CapacitiveSensor   cs_9_8 = CapacitiveSensor(9,8);

float calVal1 = 0.0;
float calVal2 = 0.0;
float calVal3 = 0.0;
float total1 = 0.0;
float total2 = 0.0;
float total3 = 0.0;
int i = 0;

void setup()                   
{
  Serial.begin(9600);

  if (!card.init()) {
    error("Card init. failed!");
  }
  if (!vol.init(card)) {
    error("No partition!");
  }
  if (!root.openRoot(vol)) {
    error("Couldn't open dir");
  }

  PgmPrintln("Files found:");
  root.ls();

  cs_9_6.reset_CS_AutoCal();  // autocalibrate channel 1
  cs_9_7.reset_CS_AutoCal();  // autocalibrate channel 2
  cs_9_8.reset_CS_AutoCal();  // autocalibrate channel 3

  cs_9_6.set_CS_AutocaL_Millis(0x00001000);     // autocalibrate interval on channel 1
  cs_9_7.set_CS_AutocaL_Millis(0x00001000);     // autocalibrate interval on channel 2
  cs_9_8.set_CS_AutocaL_Millis(0x00001000);     // autocalibrate interval on channel 3

  pinMode(14, OUTPUT);
}

void loop()                   

    total1 =  cs_9_6.capacitiveSensor(30);
    total2 =  cs_9_7.capacitiveSensor(30);
    total3 =  cs_9_8.capacitiveSensor(30);

    if (i<60) {                                                 
// Calibrate Sensor baseline
      digitalWrite(14, HIGH);
      calVal1= 0.1*float(total1) + 0.9*calVal1;
      i++;
    }
    else if (i<120) {                                     
// Calibrate Sensor baseline
      digitalWrite(14, LOW);
      calVal2= 0.1*float(total2) + 0.9*calVal2;
      i++;
    }
    else if (i<180) {                                     
// Calibrate Sensor baseline
      digitalWrite(14, HIGH);
      calVal3= 0.1*float(total3) + 0.9*calVal3;
      i++;
    }
    else {
      digitalWrite(14,LOW);
    }

#ifdef DEBUG 
    Serial.print(total1);               
   // print sensor output 1
    Serial.print("\t");
    Serial.print(total2);              
  // print sensor output 2
    Serial.print("\t");
    Serial.println(total3);              
  // print sensor output 3

    Serial.print("CAL: \t");
    Serial.print(calVal1);               
   // print sensor output 1
    Serial.print("\t");
    Serial.print(calVal2);               
// print sensor output 2
    Serial.print("\t");
    Serial.println(calVal3);               
// print sensor output 3
#endif

    if (total1 > 0.8*calVal1 && total2 > 0.8*calVal2) {
      strcpy_P(filename, PSTR("BUFF1.WAV"));
      Serial.println(filename);
      playfile(filename);
    }   
    else if (total2 > 0.8*calVal2 && total3 > 0.8*calVal3) {
      strcpy_P(filename, PSTR("HAT.WAV"));
      Serial.println(filename);
      playfile(filename);
    }  
    else if (total1 > 0.8*calVal1) {
      strcpy_P(filename, PSTR("CLICK1.WAV"));
      Serial.println(filename);
      playfile(filename);
    }
    else if (total2 > 0.8*calVal2) {
      strcpy_P(filename, PSTR("BOOM1.WAV"));
      Serial.println(filename);
      playfile(filename);
    }
    else if (total3 > 0.8*calVal3) {
      strcpy_P(filename, PSTR("BOOMHAA1.WAV"));
      Serial.println(filename);
      playfile(filename);
    }

//   delay(10);                             // arbitrary delay to limit data to serial port
}

/////////////////////////////////// HELPERS

/*
* print error message and halt
*/
void error_P(const char *str) {
  PgmPrint("Error: ");
  SerialPrint_P(str);
  sdErrorCheck();
  while(1);
}

/*
* print error message and halt if SD I/O error
*/
void sdErrorCheck(void) {
  if (!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}

/*
* Play a file and wait for it to complete
*/
void playcomplete(char *name) {
  playfile(name);
  while (wave.isplaying);


  // see if an error occurred while playing
  sdErrorCheck();
}

/*
* Open and start playing a WAV file
*/
void playfile(char *name) {
  if (wave.isplaying) {
// already playing something, so stop it!
    wave.stop(); // stop it
  }
  if (!file.open(root, name)) {
    PgmPrint("Couldn't open file ");
    Serial.print(name);
    return;
  }
  if (!wave.create(file)) {
    PgmPrintln("Not a valid WAV");
    return;
  }

  // ok time to play!
  wave.play();

}

Step 8: Next Steps

Why not expand on capacitive touch sensing?

* Use a higher resistor value to sense when you are close to the sensor, not just touching it.

* Make a sound that varies in pitch as you got closer to the sensor.

* Use a Matrix of sensors arranged to sense the postion of your finger on a surface (a smart phone touch screen works like this)