Halloween Pumpkin Shocker

This will be a more complex project. I recommend it for ages above 12 years. But you can include younger children in different stages of the project. The project is split up in following chapters.

  1. Playing Wav files on ESP32
  2. Amplifying the sound
  3. Light effects
  4. Putting it all together

Hardware you will need:

1 x ESP32 micro controller
1 x 9 V battery or 4 x AA batteries
1 x PIR sensor


For the sound
1 x Speaker 8 Ohm
2 x transistor BC547 or similar (npn)
4 x resistor 10 kOhm
2 x resistor 100 Ohm
2 x resistor 10 Ohm
2 x capacitor 100 nF
2 x capacitor 100 uF

For the light effect
6 x LED red
1 x transistor BC547 or similar (npn)
2 x resistor 220 Ohm
1 x resistor 10 kOhm
Alternatively you can use ws2812 LEDs

1. Overview

In this project I’d like to build a circuit which can be hidden inside of a scalloped halloween pumpkin. If a personen passes the pumpkin the circuit will be triggered by a PIR (pyroelectric infrared) sensor. Once triggered the curcuit will play some scary sounds and flash LEDs accordingly.

2. Playing Wav files on ESP32

2.1 Preparations

Trying to keep this project simple I recommend to use the Arduino IDE.

2.1.1 Getting the sound files

Well, first you need audio files you want to play on your ESP board. You can search the internet for suitable files, I recommend https://freesound.org they have a great database of free sounds. As ESP32’s memory is limited you should go for rather short files of about 5 seconds maximum.

I decided to record the files myself with the help of my children: My daughter has the perfect voice for a shrill scream. In addition we recorded halloween wishes and spooky laughing. I used the speech memo app of my iPhone to record three files: Scream, „Happy Halloween“ and Laughing. To transfer the three m4a files to my Mac I just used airdrop.

2.1.2 InsTalling Audacity and bin2h

In order to prepare the audio files for replay you will need to download the app Audiacity and some kind of binary to header converter.

Audacity ia available as freeware for alle OS on https://www.audacityteam.org/download/

If you work on a windows machine, you can use HxD for converting the binary files to a c header. Mac Os users and Linux users might check out bin2h. You can compile bin2h. I just used the python script in /src/bin2header.py folder.
bin2h will read the files generated by Audacity and convert their content to c header files. I edited the script a little as I wanted the length of the array included in its defintion to:

text += '\nconst unsigned char {}['
text += str(len(data))+'] = {{\n'

 

2.1.3 DAC Audio Library

Please download the latest version of the DAC audio library of xtronical.com. and install it to your Arduino IDE. Select Scatch -> Include libary -> Add Zip library. Then navigate to the just downloaded archive and install it.

2.2 Sound

The sound the ESP32 is going to play will be stored as a array of unsigned char in the flash memory of the ESP32. Therefore we need to convert the M4A files into c header files. We will not be able to directly achive this, but instead we need to put some stages in between.

2.2.1 Converting m4a to wav

I tried to convert the m4a files of the speech memo app into wav with the help of audacity. But due to some legal reasons it does not come with the necessary library preinstalled. I tried to install it according to the instructions, but for a reason I did not find out, it did not work. Even resetting the preferences and the plug in config did not help. So, I decided to simply used zamzar.com to convert the files online to mp3.

To convert the mp3 files to wav please use Audacity:

Opening your file with File -> Open will show you a screen similar to this:

You can mark the parts of the file you want to delete – the white part in the picture – by just clicking at the startindex (e.g. index 0,0, which is the most left part of the graph part of the window.) Keep the left mouse button pressed and drag to the index where you’d like to start your file, e.g. 0,3 in the picture. The select Edit -> Delete.

Changing the sample rate to 16 kHz

Once you have trimmed your file it is time to think about reducing memory. Memory on an ESP32 is quite big compared with an Arduino, but it still must be treated as a rare good. To save some memory I advise to reduce the sampling rate of your audio file. 44.1 kHz can be assumed as a good audio quality file, but as I’d like to replay spoken words I reduced the sampling rate to 16 kHz.

Changed project rate

Select the audio track by clicking onto the graph. Then change the the sampling rate with Track -> Sampling rate .

You should also change the sampling rate of your Audacity project. Just select the same rate in the drop down menu in the lower left corner of the window:

2.2.2 From Wav to header

In order to convert the files to c header files, you need to export them from Audacity. Select File -> Audio Export

In the file select dialog which will pop up change the setting to

  • Other uncompressed audio
  • WAV (Microsoft)
  • Unsigned 8 bit PCM
Export settigns of Audacity

Open a terminal, change directory to bin2h/src and execute the python script with the WAV file as input:

python3 bin2header.py ../path/to/file.wav

bin2h will generate a file called file.wav.h in the folder of file.wav.

The generated file will looks like this

#ifndef FILE_WAV_H
#define FILE_WAV_H

#ifdef __cplusplus
#include <vector>
#endif

const unsigned char {}[17289] = {{
0x52, 0x49, 0x46, 0x46, 0x81, 0x43, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00

};

#ifdef __cplusplus
static const std::vector<char> EmmasSChrai_wav_v(file_wav, file_wav + sizeof(file_wav));
#endif

#endif /* FILE_WAV_H */

You can delete all preprocessor directives, the empty curly  brakets and the redundant opening curly braket. Please add a variable name. This will result in a file like this

const unsigned char my_variable[17289] PROGMEM = {
0x52, 0x49, 0x46, 0x46, 0x81, 0x43, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00

};

Please note: I added the keyword PROGMEM in the variable declaration. The keyword tells the compiler to stored the variable in flash memory instead in the SRAM. Please the Arduino reference for further information on PROGMEM.

2.2.3 Including header files in Arduino project

To include the generated c header files in an Arduino project, open a new project and select Scatch -> Add File. Navigate to the header file created in the previous chapter and select it.

Arduino will create a second tab in the project window. All you have to do is include the file in your project’s main window using an include statement.

Included header file

2.2.4 Replay the wav file

Thanks to Xtronical.com it is pretty easy to replay the just included sound file.

Include his library in you Arduino scatch as described in chapter 2.1.3. You can have a look in the included examples: PlayWav is a good example for playing just one Wav file. If you’d like to play multiple files as a sequence, you should have alook at the Sequence example.

#include "file.h"
#include "XT_DAC_Audio.h"

// create a object of class XT_Wav_Class which will keep
// the data of the wav file. Pass the variable to the constructor
// which is stored in file.h
XT_Wav_Class myWavFile(my_variable);

// Create an instance of XT_DAC_Audio_Class which will play
// the wav file on DAC channel 1 (GPIO 25)
XT_DAC_Audio_Class DacAudio(25,0);

void setup() {
}

void loop() {
DacAudio.FillBuffer(); // Fill the sound buffer with data
if(myWavFile.Playing==false)
DacAudio.Play(&myWavFile);
}

3. Amplifying the sound

Now that our ESP32 is ready to play the wav file, we need an amplifier circuit to boost our output. For the sake of simplicitiy we are going to create a common emitter amplifier which is used in class A amplifiers. Class A amplifier do have less efficiency than class D amplifier, but I did not have the right MOSFETs laying around nor a suitable MOSFET driver. Hence class A.

I had a housing for 4 AA batteries. This means Vcc of the circuit will be 6V. This will not result in a boom box which is suitable to entertain the neigborhood, but will give us enough volume to frighten some trick or treating kids.

I wanted to bias the input voltage at around 3V. As I just wanted to replay voice records I decided to use two 10 kOhm resistors for biasing. The audio wave will not be exactly at 3V, but it will work. I set RE to 100 Ohm and RC to 10 Ohm to get a gain of 10. To protect input and output I added a 100nF capcitor (input) and a 100 uF capacitor (output). The amplifier already did a good job, but I wanted more power. So I added a second stage as shown in the picture below.

Class A amplifier with two stages

4. Light effects

The light effect will be very simple: I want the scalloped pumpkin to be enlighted in red. So I used 6 red LEDs. Once the PIR triggers the sound effect, the LEDs shall blink.

Switching the LEDs will be done with the help of a npn-transitor which is attached to GPIO 5 of the ESP32.

5. Putting it all together

The PIR will be connected to GPIO 18.

Scheme of the project

The usual style of a blink scetch does not work here: Usually the mode of the LED pin is switched in the main loop. To be able to start the sound and the blink effect in parallel, I needed to create parallel task which is triggered by an interrupt service routine (ISR).

Here’s how that works:

By attaching the ISR callback function to the GPIO, the trigger can by configured: In this case I decided to trigger on a rising edge of the PIR signal, i.e. if the PIR signal switches from 0 to 1 function pirISR shall be executed.

To prevent the sound and light effects to be triggered on a bouncing signal, I implemented a simple debouncing mechanism which forces breaks of 20 seconds between two trigger (time between two ISRs).

Hence i wanted the blink function to be simple I used the vTaskDelay function to delay the switching of the LEDs. As vTaskDelay is a blocking function it would also block the execution of the audio playback. So i needed to execute the blink function on a different core than the audio functions. The default core on esp32 is core 0. This is the audio playback core. With the help of xTaskCreatePinnedToCore I created a blink task on core 1.

Now I have a blink task on a seperate core, but task callback functions must implement an infinite loop, e.g. for(;;). This means I need a mechanism

a) to pause the function on entry

b) to pause the function after the blink effect

FreeRTOS offers a variety of such mechanisms, but I decided for a very simple one: A notification, which is a simple implementation of a semaphore. The task to be notified must use function ulTaskNotifyTake, the notifying task must use vTaskNotifyGive or if it is an ISR vTaskNotifyGiveFromISR.

Sequence diagram

And here’s the code:

#include "EmmasSchrei.h"
#include "Happy_Halloween.h"
#include "Lachen.h"
#include <XT_DAC_Audio.h>

#define PIN_LED 5
#define PIN_PIR 18


XT_DAC_Audio_Class DacAudio(25,0);
XT_Wav_Class wavEmma(emma);
XT_Wav_Class wavHalloween(halloween);
XT_Wav_Class wavLachen(lachen);
XT_Sequence_Class Sequence;

unsigned long lastISR = 0;
unsigned long lastFlash = 0;
unsigned long pauseFlsh = 500; // 0.5 secs flashing interval
unsigned long pauseISR = 20000; // time between two ISRs in milliseconds

TaskHandle_t tskHndBlink;

void IRAM_ATTR pirISR();
void taskBlinking(void* vParameter);

void setup() {
Serial.begin(115200);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);

pinMode(PIN_PIR, INPUT_PULLUP);
attachInterrupt(PIN_PIR, pirISR, RISING);

Sequence.AddPlayItem(&wavEmma);
Sequence.AddPlayItem(&wavHalloween);
Sequence.AddPlayItem(&wavLachen);

xTaskCreatePinnedToCore(
taskBlinking,
"Blink",
2048,
NULL,
10,
&tskHndBlink,
1);
Serial.println("Setup finished");
}

void loop() {
DacAudio.FillBuffer();
}

void IRAM_ATTR pirISR()
{
if(millis() - lastISR > pauseISR)
{
vTaskNotifyGiveFromISR(
tskHndBlink,
NULL
);
DacAudio.Play(&Sequence);
lastISR = millis();
}
}

void taskBlinking(void* vParameter)
{
for(;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

for(int i=0; i<10; i++)
{
digitalWrite(PIN_LED, LOW);
delay(100);
digitalWrite(PIN_LED, HIGH);
delay(100);
}
}
}