Overview

Here is a quick project for an electronic halloween pumpkin. With a bit of hacking a $1 plastic pumpkin is upgraded: a sensor embedded in the nose detects when people get close and will play scarey sounds and animates LEDs on the face. The sounds are stored on an SD card so it's easy to change and customize what the pumpkin says, while the code is written for an Arduino so it's easy to modify the behavior. I'm going to have this pumpkin outside my door to freak out the little kids who go to daycare nearby. Boo!

What you will need

This isn't a particularly tough project, but you'll need to know basic Arduino/Microcontroller programming and soldering.

Sound effects

There are tons of CDs and mp3 packages available for purchase that contain a large assortment of sounds. Here are some free download sites that you can scour:

Here is my collection (in a zip file) of converted sounds: they're ready to go and have been tweaked to improve the sound where possible.

Step 1: Playing sounds through the speaker

Step 1: Playing sounds through the speaker

Assemble the Wave Shield kit and get your Arduino board up & running. Copy the zip of example halloween wave's onto the SD card and run the DAPHC example sketch from the WaveHC Library. Each sound should play in sequence. Look at the debug log if there are any issues.

Next, solder two wires to the speaker and connect them to the shield as shown, you should be able to hear the effects pretty well.

If you want a power boost, you can 'piggyback' another amplifier chip (TS922) on top of the existing one. This will approximately double the maximum volume.

Step 2: Choose and connect sensor

Next, we will connect up a distance sensor. There are two basic 'flavors' of distance sensing: infrared and sonar. IR works better for close range (about a meter or more) and sonar works better for longer ranges (up to 10 meters).

Sonar and IR sensors
Since I want this pumpkin to activate from a few meters away, I'm going with a sonar sensor. The nicest one I've seen so far is the Maxbotics EZ1 which has both digital and analog signal outputs, runs from a range of voltages and can detect more than 6 meters away. If you're using a different sonar, the code may be different so you will have to first get that device working and then come back once you have functional interface code.
The maxbotix is easy to use. Connect three wires: Power, Ground and Analog signal as shown.
Finally connect the sonar to the shield so that the analog signal feeds into Analog In #0 and the power and ground wires are connected to power and ground.
Here's the code we'll be running:
Download: file
#include <WaveHC.h>
#include <WaveUtil.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 volumes root directory
FatReader file;   // This object represent the WAV file 
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time


#define playcomplete(x) ROM_playcomplete(PSTR(x))         // save RAM by using program memory strings

#define servo 7
#define redled 9
#define eyeleds 18
#define mouthleds 17
#define midmouthleds 16
#define outermouthleds 19

void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println(F("Wave test!"));

  pinMode(2, OUTPUT); 
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(redled, OUTPUT);
  pinMode(servo, OUTPUT);
  pinMode(eyeleds, OUTPUT);
  pinMode(outermouthleds, OUTPUT);
  pinMode(midmouthleds, OUTPUT);
  pinMode(mouthleds, OUTPUT);
  
  randomSeed(analogRead(0));


  if (!card.init()) {
    Serial.println(F("Card init. failed!")); return;
  }
  // 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  :(
    Serial.println(F("No valid FAT partition!"));  // Something went wrong, lets print out why
  }
  
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  Serial.print(F(", type is FAT"));
  Serial.println(vol.fatType(), DEC);     // FAT16 or FAT32?
  
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    Serial.println(F("Can't open root dir!"));      // Something went wrong,
  }
  
  // Whew! We got past the tough parts.
  Serial.println(F("Files found (* = fragmented):"));

  // Print out all of the files in all the directories.
  root.ls(LS_R | LS_FLAG_FRAGMENTED);
}


void pulseServo(uint8_t servopin, uint16_t p) {
  
 digitalWrite(servopin, HIGH);
 delayMicroseconds(600);
 while (p--) {
   delayMicroseconds(4);
 }
 digitalWrite(servopin, LOW);
  delay(18);
}

uint8_t pumpkinstate = 0;

void loop() { 
   int distsensor, i;
   long time;
   /*
   for (i=0; i<50; i++) {
     pulseServo(servo,0);
   }
   for (i=0; i<50; i++) {
     pulseServo(servo,400);
   }
   return;
   */
   distsensor = 0;
   for (i=0; i<8; i++) {
     distsensor += analogRead(0);
     delay(50);
   }
   distsensor /= 8;

   Serial.print(F("Sensor = ")); Serial.println(distsensor);
   
   if (distsensor <= 500) {
     digitalWrite(eyeleds, HIGH); 
   } 
   if (distsensor > 500) {
     digitalWrite(eyeleds, LOW);  
     pumpkinstate = 1;
     // nobody there. one out of 200 times play one of the scary sounds (once every few minutes)
     i = random(200);
     //Serial.println(i);
     if (i == 0) {
       i = random(3);
       if (i == 0) {
           playcomplete("CACKLE.WAV");
       } else if (i == 1) {
           playcomplete("GOSTMOAN.WAV");
       } else {
           playcomplete("CATSCREM.WAV");   
       }
     }
   } else if ((distsensor > 300) && (distsensor < 400)) {
     if (pumpkinstate <= 1) {    // play "hello children"
        playcomplete("HELOCHIL.WAV"); 
     } else {
       i = random(60);            // more often
       //Serial.println(i);
       if (i == 0) {
         i = random(3);
         if (i == 0) {
           playcomplete("KNOCKING.WAV");
         } else if (i == 1) {
           playcomplete("MONSTER.WAV");
         } else {
           playcomplete("SCREAM2.WAV");   
         }
       } 
     }
     pumpkinstate = 2; 
   } else if ((distsensor > 100) && (distsensor < 200)) {
     if (pumpkinstate <= 2) {    // play "hello children"
       playcomplete("GOBACK.WAV"); 
     } else {
       i = random(50);            // more often
       //Serial.println(i);
       if (i == 0) {
         i = random(3);
         if (i == 0) {
           playcomplete("GHOULLAF.WAV");
         } else if (i == 1) {
           playcomplete("SCREAM.WAV");
         } else {
           playcomplete("SCREECH.WAV");   
         }
       }
     }
     pumpkinstate = 3;
   } else if (distsensor < 50) {
     if (pumpkinstate <= 3) {    // play "hello children"
        playcomplete("HPYHALWN.WAV");    
     } else {
       i = random(30);            // more often
     //Serial.println(i);
     if (i == 0) {
       i = random(2);
       if (i == 0) {
           playcomplete("BOOHAHA.WAV");
       } else if (i == 1) {
           playcomplete("WELCOME.WAV");
       } 
     }
       
   }
    pumpkinstate = 4;
 }
}



void ROM_playcomplete(const char *romname) {
  char name[13], i;
  uint8_t volume;
  int v2;
  
  for (i=0; i<13; i++) {
    name[i] = pgm_read_byte(&romname[i]);
  }
  name[12] = 0;
  Serial.println(name);
  playfile(name);
  while (wave.isplaying) {
   volume = 0;
   for (i=0; i<8; i++) {
     v2 = analogRead(1) - 512;
     if (v2 < 0) 
        v2 *= -1;
     if (v2 > volume)
       volume = v2;
     delay(5);
   }
   if (volume > 200) {
     digitalWrite(outermouthleds, HIGH);
   } else {
     digitalWrite(outermouthleds, LOW);
   }
   if (volume > 150) {
     digitalWrite(midmouthleds, HIGH);
   } else {
     digitalWrite(midmouthleds, LOW);
   } 
   if (volume > 100) {
     digitalWrite(mouthleds, HIGH);
   } else {
     digitalWrite(mouthleds, LOW);
   } 
   //Serial.print(F("vol = ")); Serial.println(volume, DEC);
  }
  file.close();
}

void playfile(char *name) {

   if (!file.open(root, name)) {
      Serial.println(F(" Couldn't open file")); return;
   }
   if (!wave.create(file)) {
     Serial.println(F(" Not a valid WAV")); return;
   }
   // ok time to play!
   wave.play();
}

Load up the sketch above into the Arduino. Note the following chunks of code.

In this bit we read 8 seperate measurements from the sonar and average them. This avoids one or two oddball readings:

Download: file
void loop() { 
...
   distsensor = 0;
   for (i=0; i<8; i++) {
     distsensor += analogRead(0);
     delay(50);
   }
   distsensor /= 8;

   putstring_nl("Sensor = "); Serial.println(distsensor);
...
After that we have some 'repeating' if-then statements. Here, we see how large the reading was. A reading of 500 indicates more than 6 meters away and in that case we will pick a random number between 0 and 199 and if its 0 (1/200 times) then we'll play one of three scary sounds.
Download: file
...
     if (distsensor > 500) {
     ...
     pumpkinstate = 1;
     // nobody there. one out of 200 times play one of the scary sounds (once every few minutes)
     i = random(200);
     if (i == 0) {
       i = random(3);
       if (i == 0) {
           playcomplete("CACKLE.WAV");
       } else if (i == 1) {
           playcomplete("GOSTMOAN.WAV");
       } else {
           playcomplete("CATSCREM.WAV");   
       }
     }
...

The 'pumpkinstate' variable is used to keep track of how far the target was last we checked. This will let us know if they are getting closer or farther away over time.

Next get yourself one of those $1 plastic pumpkins from your local drug store.

They are hollow, making them perfect for stashing electronic wiring!
Cut a hole in the nose so that the sonar can be press-fit in.

Set up the sonar so that it is pointing at a large open space (the readings are consistantly over 500) Then experiment with walking near and around it. Adjust the randomness if you want to make it more or less noisy. You can also change what audio files get played when.

Step 3: Adding LEDs

It's not a real electronics project without some blinky LEDs! Let's add some LEDs for the pumpkins eyes and mouth.

Punch 5mm (or 10mm, depending on what size LEDs you're using) holes in the eyes and mouth. In this case I'm using 2 for the eyes and 5 for the mouth.

Next heat up a hot glue gun and secure the LEDs so they don't slip out. In general I don't suggest hot glue for -anything- but in this case it's OK.

Next it's time to wire up the LEDs. I'm going to control the eye LEDs at once and divide the mouth into three symmetric sections so that I can do some simple animations with the mouth. Since red LEDs have a voltage of about 2 volts and the Arduino runs at 5V, I can stack two at a time to simplify wiring:

The LEDs are controlled from the analog in pin that aren't used - don't forget that they can also be used as digital input or outputs! The 2 eyes are connected to analog pin #4, the two outer mouth LEDs to analog #5, the middle-outer mouth LEDs to #2 and the one remaining LED from the center goes to analog #3. (It doesn't particularly matter which pins go to which LEDs, just that this ended up being what I did)

I used some leftover ribbon cable to do the wiring. Watch that you don't burn your fingers or the pumpkin!

If you have a handy 5v power supply kicking around, you can test the LEDs - I used my benchtop supply.

Step 4: Wiring up the lights

Finally connect the LEDs to the Arduino as indicated in the schematic.

The plan is to make the pumpkin's mouth animate with the sound. Although we could do this all in 'software' it turns out to be a heck of a lot easier to just use an analog input to measure the output of the amplifier. So connect a wire from the 1.5K resistor back to analog input #1.
Now let's make some blinky! There is code in the sketch that will turn on the eye LEDs when someone gets near:
Download: file
...
   if (distsensor <= 500) {
     digitalWrite(eyeleds, HIGH); 
   } 
   if (distsensor > 500) {
     digitalWrite(eyeleds, LOW);  
...
And then in the wave playing code, we will animate the pumpkin. First we take a few readings from the analog sensor. The audio signal is centered around 2.5 (ranges from 0 to 5) so we subtract 512 (half of the maximum analog read value) and then invert it if it is negative. This gives the 'absolute value' volume. Then we take the maximum value of 8 reads. Then depending on how loud it is we light up the mouth. You can mess with the values a bit if you want to have a different effect.
Download: file
...
while (wave.isplaying) {
   volume = 0;
   for (i=0; i<8; i++) {
     v2 = analogRead(1) - 512;
     if (v2 < 0) 
        v2 *= -1;
     if (v2 > volume)
       volume = v2;
     delay(5);
   }
   if (volume > 200) {
     digitalWrite(outermouthleds, HIGH);
   } else {
     digitalWrite(outermouthleds, LOW);
   }
   if (volume > 150) {
     digitalWrite(midmouthleds, HIGH);
   } else {
     digitalWrite(midmouthleds, LOW);
   } 
   if (volume > 100) {
     digitalWrite(mouthleds, HIGH);
   } else {
     digitalWrite(mouthleds, LOW);
   } 
   //putstring("vol = "); Serial.println(volume, DEC);
  }
...
I wrote a version of the sketch that is a little 'accellerated' for easy videoing. But basically it will look and act just like this:

Step 5: Finishing Up

Time to wrap up the project. You'll have to decide how to power the pumpkin. If you want to have it be fully contained you can use a battery pack. Either make one from 4 AA batteries or use a Mintyboost kit. Basically you'll want about 4.5-6V

Or you can cut a small hole in the back of the pumpkin and use a 9-12V DC adapter.
Finally, use a plastic bowl (preferrably orange, all I had in the kitchen was a blue one!) and put some candy in there! (Like leftover altoids gum)
Happy halloween!
This guide was first published on Sep 28, 2013. It was last updated on Sep 28, 2013.