George Profenza

From PhysicalComputing

Jump to: navigation, search

Contents

Sensor Buzzer Toy + WaveShield Toy

Aim

The Aim was to modify sounds by using sensors. Ideally I would modify the pitch/volume of WAV files playing. A Wave Shield would be use for the task.

Reseach:
Existing stuff found online looked pretty fun:


Wave Shield Fun
Old School Noise
More 8 bit joy

User Scenario

Although I had bigger plans that would involve drawing robots,
I had to settle for something less dramatic. No gallery, no installation, just plain fun with old school noises...Yeah! I still play old school NES games! Got a problem ?!

Development Process

I recently got my hands on a decent camera, so here are a few pictures from the development process:

Completed Wave Shield:

Smooth soldering action going on.
I don't see why there's not hit song called "Solder Boy!" :)

The huge 104 coded capacitor I had run to maplins to get, my kit missed 1.

The lovely WaveHC librarie problems:
Image:Wave_hc_test3.gif
Problems with memory, not all files were read,
no chance to play a .wav

Never the less I settled for the simple approach:
Using a capacitive sensor, an Light Dependent Resistor(LDR),
a speaker and a Piezzo Buzzer I managed to get some noise using the
Tone Library.

The code looks like this:


#include <Tone.h>
//Tone objects
Tone beeperSmall;            //small buzzer
Tone beeperBig;              //big buzzer
//pin setup
int capacitiveSensorPin = 0; //press pin
int ldrSensorPin        = 1; //LDR pin
/*int redPin              = 12;//red LED
int uvPin               = 11;//PWM UV LED
float duty              = 0; //PWM duty
float angle             = 0; //angle for cycle
float rad5              = PI/18;//for uvLED sine      
float redValue          = 0;    //for red LED*/
//SENSOR READING
int vals[10];        //Declared our 10 position averaging array
int capacitiveValue = 0;     //variable to store the value coming frm the center
int ldrValue = 0;

void setup(){
  beeperSmall.begin(8);
  beeperBig.begin(7);
  //pinMode(uvPin,OUTPUT);
  //pinMode(redPin,OUTPUT);
  Serial.begin(9600);
   for (int i=0;i<10;i++)
  {
    vals[i] = 0;        //initialize the array
  }
}

void loop(){
  //analogRead gets values from 0 to 1023
  capacitiveValue = average(analogRead(capacitiveSensorPin));
  ldrValue = analogRead(ldrSensorPin);
  //let's keep noiseMaker's pitch up to 1000
  beeperSmall.play(capacitiveValue - 23);
  beeperBig.play(ldrValue);
  Serial.println(capacitiveValue);
  Serial.println(ldrValue);
  //PWM
 /* angle += rad5;
  if(angle > TWO_PI) angle = 0;
  duty = abs(sin(angle) * 255);
  analogWrite(uvPin,capacitiveValue*4);
  redValue = random(0,100);
  if(redValue > 75) digitalWrite(redPin,HIGH);
  else              digitalWrite(redPin,LOW);
  delay(250);*/
}

int average(int newVal)
{
  int total = 0;
  for (int i = 9; i > 0; i--)    //shift values by 1
  {
    vals[i] = vals[i-1];
    total+=vals[i];
  }
  vals[0] = newVal;//add the newVal
  total += vals[0];//sum it up
  return total/=10;//divide and send
}



And the diagrams like this:
Image:Buzz_toy.gif Image:Buzz_toy_schem.gif


Final Outcome:

Old school noise is the result:


Lessons learned:
- fail early
- fail often
- keep trying
The Obvious:
I suck at time management


Update:

Disclaimer: I am adding new content as I have not given up the wave shield that was almost done. Due to the fact that the display date was delayed I have taken advantage to update this page. Any member of the teaching staff may ignore the following content. This is for the sake of documentation.

Ok, Gert was kind enough to lend me an Atmel328 chip, as I had a 168 (50% less memory, older version).
Apparently downgrading a Duemilanove board to a 168 chip is straight forward and there is an option in the Board list within the Arduino IDE. Not the same for upgrading a Diecimila with a 328 chip. This doesn't make much sense to me as more than likely you'll upgrade an older board with a newer chip rather than the other way around.

In case anyone one's to do this, you need to create a board definition in the boards.txt file within Arduino's hardware folder. That is slightly more than the explanations on the LadyAda site.

In case anyone needs this, here are my custom boards definitions. I defined the diecimila328 by using most of the diecimila definition, but using atmega328's definitions for maximum upload speed, (baud)speed, low, high and extended fuses and the mcu. Here's how it looks:

##############################################################

atmega328.name=Arduino Duemilanove or Nano w/ ATmega328

atmega328.upload.protocol=stk500
atmega328.upload.maximum_size=30720
atmega328.upload.speed=57600

atmega328.bootloader.low_fuses=0xFF
atmega328.bootloader.high_fuses=0xDA
atmega328.bootloader.extended_fuses=0x05
atmega328.bootloader.path=atmega
atmega328.bootloader.file=ATmegaBOOT_168_atmega328.hex
atmega328.bootloader.unlock_bits=0x3F
atmega328.bootloader.lock_bits=0x0F

atmega328.build.mcu=atmega328p
atmega328.build.f_cpu=16000000L
atmega328.build.core=arduino

##############################################################

diecimila.name=Arduino Diecimila, Duemilanove, or Nano w/ ATmega168

diecimila.upload.protocol=stk500
diecimila.upload.maximum_size=14336
diecimila.upload.speed=19200

diecimila.bootloader.low_fuses=0xff
diecimila.bootloader.high_fuses=0xdd
diecimila.bootloader.extended_fuses=0x00
diecimila.bootloader.path=atmega
diecimila.bootloader.file=ATmegaBOOT_168_diecimila.hex
diecimila.bootloader.unlock_bits=0x3F
diecimila.bootloader.lock_bits=0x0F

diecimila.build.mcu=atmega168
diecimila.build.f_cpu=16000000L
diecimila.build.core=arduino

##############################################################

diecimila328.name=Arduino Diecimila, Duemilanove, or Nano w/ ATmega328

diecimila328.upload.protocol=stk500
diecimila328.upload.maximum_size=30720
diecimila328.upload.speed=57600

diecimila328.bootloader.low_fuses=0xFF
diecimila328.bootloader.high_fuses=0xDA
diecimila328.bootloader.extended_fuses=0x05
diecimila328.bootloader.path=atmega
diecimila328.bootloader.file=ATmegaBOOT_168_diecimila.hex
diecimila328.bootloader.unlock_bits=0x3F
diecimila328.bootloader.lock_bits=0x0F

diecimila328.build.mcu=atmega328p
diecimila328.build.f_cpu=16000000L
diecimila328.build.core=arduino


##############################################################

mega.name=Arduino Mega

mega.upload.protocol=stk500
mega.upload.maximum_size=126976
mega.upload.speed=57600

mega.bootloader.low_fuses=0xFF
mega.bootloader.high_fuses=0xDA
mega.bootloader.extended_fuses=0xF5
mega.bootloader.path=atmega
mega.bootloader.file=ATmegaBOOT_168_atmega1280.hex
mega.bootloader.unlock_bits=0x3F
mega.bootloader.lock_bits=0x0F

mega.build.mcu=atmega1280
mega.build.f_cpu=16000000L
mega.build.core=arduino

##############################################################

mini.name=Arduino Mini

mini.upload.protocol=stk500
mini.upload.maximum_size=14336
mini.upload.speed=19200

mini.bootloader.low_fuses=0xff
mini.bootloader.high_fuses=0xdd
mini.bootloader.extended_fuses=0x00
mini.bootloader.path=atmega
mini.bootloader.file=ATmegaBOOT_168_ng.hex
mini.bootloader.unlock_bits=0x3F
mini.bootloader.lock_bits=0x0F

mini.build.mcu=atmega168
mini.build.f_cpu=16000000L
mini.build.core=arduino

##############################################################

bt.name=Arduino BT

bt.upload.protocol=stk500
bt.upload.maximum_size=14336
bt.upload.speed=19200
bt.upload.disable_flushing=true

bt.bootloader.low_fuses=0xff
bt.bootloader.high_fuses=0xdd
bt.bootloader.extended_fuses=0x00
bt.bootloader.path=bt
bt.bootloader.file=ATmegaBOOT_168.hex
bt.bootloader.unlock_bits=0x3F
bt.bootloader.lock_bits=0x0F

bt.build.mcu=atmega168
bt.build.f_cpu=16000000L
bt.build.core=arduino

##############################################################

lilypad328.name=LilyPad Arduino w/ ATmega328

lilypad328.upload.protocol=stk500
lilypad328.upload.maximum_size=30720
lilypad328.upload.speed=57600

lilypad328.bootloader.low_fuses=0xFF
lilypad328.bootloader.high_fuses=0xDA
lilypad328.bootloader.extended_fuses=0x05
lilypad328.bootloader.path=atmega
lilypad328.bootloader.file=ATmegaBOOT_168_atmega328_pro_8MHz.hex
lilypad328.bootloader.unlock_bits=0x3F
lilypad328.bootloader.lock_bits=0x0F

lilypad328.build.mcu=atmega328p
lilypad328.build.f_cpu=8000000L
lilypad328.build.core=arduino

##############################################################

lilypad.name=LilyPad Arduino w/ ATmega168

lilypad.upload.protocol=stk500
lilypad.upload.maximum_size=14336
lilypad.upload.speed=19200

lilypad.bootloader.low_fuses=0xe2
lilypad.bootloader.high_fuses=0xdd
lilypad.bootloader.extended_fuses=0x00
lilypad.bootloader.path=lilypad
lilypad.bootloader.file=LilyPadBOOT_168.hex
lilypad.bootloader.unlock_bits=0x3F
lilypad.bootloader.lock_bits=0x0F

lilypad.build.mcu=atmega168
lilypad.build.f_cpu=8000000L
lilypad.build.core=arduino

##############################################################

pro328.name=Arduino Pro or Pro Mini (3.3V, 8 MHz) w/ ATmega328

pro328.upload.protocol=stk500
pro328.upload.maximum_size=30720
pro328.upload.speed=57600

pro328.bootloader.low_fuses=0xFF
pro328.bootloader.high_fuses=0xDA
pro328.bootloader.extended_fuses=0x05
pro328.bootloader.path=atmega
pro328.bootloader.file=ATmegaBOOT_168_atmega328_pro_8MHz.hex
pro328.bootloader.unlock_bits=0x3F
pro328.bootloader.lock_bits=0x0F

pro328.build.mcu=atmega328p
pro328.build.f_cpu=8000000L
pro328.build.core=arduino

##############################################################

pro.name=Arduino Pro or Pro Mini (3.3V, 8 MHz) w/ ATmega168

pro.upload.protocol=stk500
pro.upload.maximum_size=14336
pro.upload.speed=19200

pro.bootloader.low_fuses=0xc6
pro.bootloader.high_fuses=0xdd
pro.bootloader.extended_fuses=0x00
pro.bootloader.path=atmega
pro.bootloader.file=ATmegaBOOT_168_pro_8MHz.hex
pro.bootloader.unlock_bits=0x3F
pro.bootloader.lock_bits=0x0F

pro.build.mcu=atmega168
pro.build.f_cpu=8000000L
pro.build.core=arduino

##############################################################

atmega168.name=Arduino NG or older w/ ATmega168

atmega168.upload.protocol=stk500
atmega168.upload.maximum_size=14336
atmega168.upload.speed=19200

atmega168.bootloader.low_fuses=0xff
atmega168.bootloader.high_fuses=0xdd
atmega168.bootloader.extended_fuses=0x00
atmega168.bootloader.path=atmega
atmega168.bootloader.file=ATmegaBOOT_168_ng.hex
atmega168.bootloader.unlock_bits=0x3F
atmega168.bootloader.lock_bits=0x0F

atmega168.build.mcu=atmega168
atmega168.build.f_cpu=16000000L
atmega168.build.core=arduino

##############################################################

atmega8.name=Arduino NG or older w/ ATmega8

atmega8.upload.protocol=stk500
atmega8.upload.maximum_size=7168
atmega8.upload.speed=19200

atmega8.bootloader.low_fuses=0xdf
atmega8.bootloader.high_fuses=0xca
atmega8.bootloader.path=atmega8
atmega8.bootloader.file=ATmegaBOOT.hex
atmega8.bootloader.unlock_bits=0x3F
atmega8.bootloader.lock_bits=0x0F

atmega8.build.mcu=atmega8
atmega8.build.f_cpu=16000000L
atmega8.build.core=arduino


Here is a screen shot of the updated Boards list in the Arduino IDE:


Image:Boards.gif

Ok, back to the wave shield. I have finally got it to work, all the programming issues were because my chip was running out of memory while reading the directory list of an SD, not to mention play a file. Apparently all the soldering was fine as the code worked well. Basic setup is: 1XUpgradedArduino, 1XWaveShield, 1X10KPotentiometer and 1XLDR plus a few sample wav files from Max5.
I've decided to wrap the thing up a bit so it's not connected to the computer anymore. Here are the new images:
Image: Ws_front.jpg
Image: Ws_back.jpg
Image: Ws_side1.jpg
Image: Ws_side2.jpg
Image: Ws_side3.jpg
Image: Ws_side4.jpg
And here is a badly filmed short video:



The code for waveshield:

#include <FatReader.h>
#include <SdReader.h>
#include <avr/pgmspace.h>
#include "WaveUtil.h"
#include "WaveHC.h"


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 filesystem on the card
FatReader f;      // This holds the information for the file we're play

WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 5  // button debouncer

//sensor setup
int vals[10];//for averaging capacitive sensor
int16_t lastpressval = 0;
#define HYSTERESIS 3

// this handy function will return the number of bytes currently free in RAM, great for debugging!   
int freeRam(void)
{
  extern int  __bss_end; 
  extern int  *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
} 

void sdErrorCheck(void)
{
  if (!card.errorCode()) return;
  putstring("\n\rSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  putstring(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}

void setup() {
  byte i;
  
  // set up serial port
  Serial.begin(9600);
  putstring_nl("WaveHC with sensor control");
  //Serial.print(NUMBUTTONS, DEC);
  //putstring_nl("buttons");
  
  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
  
  // Set the output pins for the DAC control. This pins are defined in the library
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  // pin13 LED
  pinMode(13, OUTPUT);
 
 //init averaging array
  for(int i=0 ; i < 10 ; i++) vals[i] = 0;
  
  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!)  
    putstring_nl("Card init. failed!");  // Something went wrong, lets print out why
    sdErrorCheck();
    while(1);                            // then 'halt' - do nothing!
  }
  
  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);
 
// Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {     // we have up to 5 slots to look in
    if (vol.init(card, part)) 
      break;                             // we found one, lets bail
  }
  if (part == 5) {                       // if we ended up not finding one  :(
    putstring_nl("No valid FAT partition!");
    sdErrorCheck();      // Something went wrong, lets print out why
    while(1);                            // then 'halt' - do nothing!
  }
  
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?
  
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    putstring_nl("Can't open root dir!"); // Something went wrong,
    while(1);                             // then 'halt' - do nothing!
  }
  
  // Whew! We got past the tough parts.
  putstring_nl("Ready!");
  
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= 1<<TOIE2;


}

SIGNAL(TIMER2_OVF_vect) {
}

void loop() {
  byte i;
  
  int track = int(analogRead(0) / 170.67);
  if(track == 0) playcomplete("4RAINS~1.WAV");
  if(track == 1) playcomplete("5CELLO.WAV");
  if(track == 2) playcomplete("6ANTON.WAV");
  if(track == 3) playcomplete("3SHO0630.WAV");
  if(track == 4) playcomplete("1DRUML~1.WAV");
  if(track == 5) playcomplete("2JONGLY.WAV");
}

// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  //store pressure value and newsamplerate
  int16_t pressval;
  uint32_t newsamplerate;
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
    pressval = abs(16000 - (average(analogRead(1)) * 456.25));//inverted, nomalized, avreahed, scaled
    if ( ((pressval - lastpressval) > HYSTERESIS) || ((lastpressval - pressval) > HYSTERESIS)) {
        newsamplerate = pressval;                       // scale it by the analog value
        wave.setSampleRate(newsamplerate);  // set it immediately!
        Serial.println(newsamplerate, DEC);  // for debugging
        lastpressval = pressval;
    }
  }
  // now its done playing
}

void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
  
  // ok time to play! start playback
  wave.play();
}

int average(int newVal){
  int total = 0;
  for(int i = 9; i > 0; i--) {
    vals[i] = vals[i-1];
    total += vals[i];
  }
  vals[0] = newVal;
  total += vals[0];
  return total *= .1;
}


A few more links

Along the way I've stumbled on a few interesting resources: ArduinoBoy - Gameboy <> Arduino library
- Touch Screen on GBA
- Nice Arduino presentation including nice tips like:
- Freeduino - open-source Arduino-compatible

And that's it!