Scary ghost / Mask & distance reminder

I've had some fun creating PCBs this year, and it carried over into a fun Halloween project for my kids' daycare classmates. I decided to create a fun project to remind them to wear masks and keep their distance. As I write this, the kids are back home from daycare (for 2+weeks) for the THIRD time as people keep contracting COVID-19 and the center has to shut down for two weeks...

image

 

 

What is it?

I've created a fun ghost-shaped PCB that plugs into a USB power bank with some blinkenlights and a piezo speaker. When the ghost's mask is removed, he gets angry! The eyes turn red and start flashing. We hear some 'gloomy' noises coming out of the speaker. This is an interactive piece of "art" to encourage mask wearing and make it fun. The users hold the device and remove the mask to make it come to life.

I'm creating about 30 for all the classmates, so hope everything works out... and isn't too pricey!

This is a simple state-machine and originally I had planned to 'hardcode' the entire sequence with hardware only; no processor. I ended up dropping that idea since BOM cost actually got higher and it was riskier for me to finish in time.

The sequence is:

  1. Power up / mask on. Green lights for eyes.
  2. Mask removed - LDR light sensor picks this up.
    1. Green lights OFF. Red lights blink alternatively (think: astable multivibrator circuit)
    2. piezo speaker buzzes out a classic du-DA-du-DAAAAAHHHHH!!!! (you'll hear in the video). This is actually done with three frequencies (sequence of C, E-flat, C, F#) that could be sequentially generated with 555's or vibrator/oscillator circuits.
    3. Speaker stops buzzing but lights continue to blink until mask is restored.

You'll read down below that I caved in and used an Arduino brain instead of the hardware route to sequence this... maybe next time!

 

What's new for me?image

As always, I try to stretch my boundaries when doing new projects. Sometimes that is out of fun, other times out of necessity.

 

USBPCB

This time around, I opted for creating a "USB PCB". That is; I created the PCB in a way that mimics a USB male connector so it can plug directly into a USB port without extra components required. The concept is that this will go into a power bank; which doubles as a regulated power supply and also a sturdy base to display the ghost. This keeps the BOM cost down and also assembly time. I don't need to add any voltage regulation circuit or any connectors. I just have fingers on the edge of the board to pick up the power. I created a custom library component for USBPCB and ran with that. I couldn't easily find a component library for Eagle and just made my own based on a few different posts I found and using a design from Freetronics called "LeoStick" which has the same thing. The creator of Freetronics, Jon Oxer has a discord and does a live stream every week so I was able to get a few tips on doing it. The PCB was generated to be 2.0 mm thick instead of the 'standard' 1.6 mm. I used ENIG (electrolysis nickel-immersion gold) coating to prevent corrosion and give some longevity to the connectors (through the use of Nickel) instead of the 'free' HASL (hot air solder leveling). ENIG is often used for sliding contacts and connectors, so should work out well as a USB port. For some reason, Fusion360 doesn't properly display the USBPCB in the 3D render, but I made sure it would be there in the final.

 

Board Design elements

I did a few more things that I haven't before. I noted in a previous post how I generated a fancier board shape for the first time doing a Smart Speaker project. I carried that on here with a more complicated ghost outline PCB, and included some holes routed through the face-area so I could snap in a 3D Printed mask. The USB port required a special shape (~12mm wide, 9mm tall; rounded edges for ease of insertion). I also played some more with silkscreen. I created a scary 'mouth' shape and some googley-eyes. I attempted to do custom silkscreen (akin to adding a company logo in silkscreen) and had some issues running the ULP script (or rather even /finding/ how to run it from within Fusion 360 and not Eagle [stand-alone]. I ended up just doing some text with the words "STAY BACK! 6 FT / 2 M" as the process was cumbersome and really just generates a bunch of rectangles that represent the artwork in a bitmap file.

 

Finally, I will play with a new PCB soldermask color - White! This makes the ghost... well.. white! And the silkscreen will become black for all the text.

Arduino on a breadboard

To help keep BOM cost down, and since it isn't really required, I opted to run the ATMEGA 328p using its internal 8 MHz oscillator instead of an external crystal. This needs a custom bootloader called something to the effect of "Arduino on a breadboard." Then I can eliminate the crystal, two caps, and two resistors. Buying raw chips would already require uploading a bootloader to make them "Arduino-compatible" so having to do this step is required either way.

 

More custom components

I'm building up my custom component library in Fusion. I had to add a custom Red/Green LED since I got these special to use for the eyes. Green means happy (aka mask-ON). They turn red when the mask is removed and BLINK in ANGER! I added a piezo buzzer to play some 'doom and gloom' notes as best as an Atmega 328P Arduino can muster. I've added programming headers for ICSP (bootloader) and regular sketch uploading. I did a recent project for my 10 year anniversary that was somewhat similar and totally forgot to add these. This project uses the 328P-AU which is the QFP version of the chip instead of the DIP-28. So it makes doing bodge-wires that much more difficult; and they would be clearly visible since this project is a hand-held one for kids (no enclosure). I additionally had to create my own component for an LDR (light-dependent resistor aka photo-resistor) since there wasn't one available in the canned libraries that matched mine. I generated my own 3D model and linked it. Once final element was the routed holes for the 3D printed mask. I created these by editing the sketch in Fusion 360 for the board and adding them as an outline.image

 

I'm always nervous to add custom components since if they don't line up or the pinout is wrong, it is hard to correct a PCB. I try to safeguard this by printing out on paper or on my 3D printer the board itself, then install the components to check fitment. I think that the USBPCB is the biggest risk of this round of custom components...

 

Notes about the board / PCB Options

One thing I ran into on this was that using ENIG, 2.0mm thickness, and white soldermask; all add time and cost to production. Since they are all required to make this work, I had to spec them in. Each one added about $20 to the bill. I also wasn't sure exactly how many to make; but targeting 30. On the website for JLCPCB, the quantity breaks are at 30 and 50; with nothing in-between. I was worried about ordering 30 and needing 31; but figured out that my combination of board size, thickness, and finishing options, the 50 QTY was actually /cheaper/ than the 30. It seemed to cross over from 'prototype' quantity into 'small batch' according to them. So I went ahead and got 50... this is easily the largest board order I've done so far - over $100 image. (Note: I work in the automation industry and regularly handle single components worth over $4,000 and machines and systems in the $60,000-$200,000 range; so $100 is peanuts, but it is different here since I'm personally footing the bill...)image

 

Anything I would do differently?

Order a prototype ahead of time would be amazing! I didn't have time for a prototype round, so just check and double checked, and went for it.

I had originally concepted this to bypass a microcontroller entirely and use a setup with 555 timers and/or astable multivibrator circuits. I started breadboarding it and had some working, but ultimately passed on the idea since the assembly time and BOM cost would far outweigh just using an Atmega 328P. Any changes to the original plan wouldn't be possible without ordering more PCBs to add components and I didn't have the time; or feel like paying $20 each time. So that will have to wait for another project!

 

I had also originally planned to use a reed switch and a magnet to detect the mask being removed. I was pricing these all out and got worred that some were unreliable - they'd stick open or closed; and some would break their glass housings. I could get the plastic-covered housings for a little more. I would also then be suppling a bunch of 4-year olds with magnets to eat... As with above, going this route looked more risky and expensive, and I realized I could probably do a much better job for cheaper using the LDR instead. Then the mask is just a simple 3D print.

 

I knowingly made this to plug into power banks; making the double as power supplies and a stable base / display stand. I also know that power banks often shut down with low current draws. I opted to let that happen and not add a dummy resistor to get 2-300mA draw just to keep it running. This one will require plugging in and interacting with, then it will let the power bank shut down on its own. If the design criteria required it, I would have upped the current draw to ensure getting enough current to keep running.

 

I'm building up my list of "Do's and Don't Do's" as well with creating PCBs.

  1. Don't put any text over vias! The silkscreen doesn't print there.
  2. Double/triple check pinouts! I got caught out with testing the DIP28 on a breadboard, but ordering a QFP package. The output that I used for the speaker uses a different physical pin number. The schematic was correct, but my Arduino sketch was using the wrong pin #.
  3. Through-hole is easier to hand solder, but I still prefer SMT... I guess I will take the hard route...

image

This image shows programming the bootloader using ICSP pins. There is a second 5-pin header that I used to upload the sketches. I just used unpopulated holes to create the headers, and use jumper wires. This means less soldering on each board and fewer components.

 

image

 

Enough! Show us the video already!

Here we have the final device inaction! (or in action image)

 

Doing the build

I ordered 50 boards, but only 40 microcontrollers; so I'll be building up 40 units. I need 32 for my daughter's class, so I'll have 8 extra to give away (or otherwise have spares if some don't work and can't be repaired). The boards were delayed in shipment, and manufacturing took a few extra days. That meant I only had time to complete building about 5 to have on Halloween. That's fine because daycare is closed for another week and I'll have time to get the rest done by then.image

 

Soldering the QFP was a real task. I got a lot of practice doing 'drag soldering' and found some good techniques to use a flux pen and desoldering braid to pull off excess solder from the devices.

The biggest pain was that I wasn't paying enough attention when soldering the first row of pins, and I had to pull many of them back up due to poor alignment. The white soldermask got really dirty when soldering, and caused me to need to clean the boards with acetone afterwards.

I also had a lot of trouble with the Red/Green LEDs. Often one or the other color wasn't illuminating and I would have to trace down the root cause. Removing them and installing a new one was a pain due to the tight tolerances, and the LEDs kept failing since I was getting them too hot and melting through the plastic casing.

 

 

 

 

 

Anything else?

Yes - see what the board says? It is scary to /not/ wear a mask, and you should stay back about 6 ft (or 2M). And wash your hands! We can get through this! Also if checks out the source code, he may find some semblance of code that I stole from him for the Piezo speaker. I had to modify the code to be non-blocking so that the eyes can still flash while the buzzer plays out the song.

 

Here is the source code. All files are also posted out to Github here: https://github.com/aspork42/halloween2020

 

/*
 * Created 10/2020 by James O'Gorman
 * 
 * This is a light-up kids toy for Halloween. 
 * This uses a PCB in the shape of a ghost 
 * with a 3D Printed 'mask' covering the mouth.
 * 
 * When the mask is removed, the green lights 
 * (eyes) turn off and the red ones come on and 
 * start to blink. We get a doom and gloom noise 
 * from a piezo buzzer.
 * 
 * This uses a 2mm thick PCB which plugs into a 
 * USB power bank for both power and a display base.
 * 
 */




// LDR pin for light sensor
#define LightSensor A5
#define LightSensorAnalogThreshold 450


// Eyes
#define RightLEDGreen A0
#define RightLEDRed A1
#define LeftLEDGreen A2
#define LeftLEDRed A3


const long redBlinkTime = 100; //ms
unsigned long PreviousLedMillis =0; //last time we flipped the LEDs




//Piezo speaker
#define Speaker 4 // output pin #
const double notes_hz[19] = {130.81,138.59,146.83,155.56,164.81,  174.61,185.0,196.0,207.65,220.0,233.07,246.94, 261.62, 277.18, 293.66, 311.13, 329.63, 349.23, 0};
const double tempo = 100.0;
/*  
# Notes  
# C = 0, C# = 1, D = 2, D# = 3, E = 4, F = 5, F# = 6, G = 7, G# = 8, A = 9, A# = 10, B = 11, middle c = 12, c# =13, d = 14, d# = 15, e = 16, f = 17, off = 18
# Duration  
# Qua = 0.5, half = 1, whole = 2, etc.  
  */
  
const int songnotes[] =    {0, 3, 0, 6};
const double songdurau[] = {1,1,1,4};


int currentNote=0;
  
void setup() {
  pinMode(LightSensor, INPUT);
  
  pinMode(RightLEDGreen, OUTPUT);
  pinMode(RightLEDRed, OUTPUT);
  pinMode(LeftLEDGreen, OUTPUT);
  pinMode(LeftLEDRed, OUTPUT);


  pinMode(Speaker, OUTPUT);


  //start with just the green lights on.
  digitalWrite(RightLEDGreen, HIGH);
  digitalWrite(RightLEDRed, LOW);
  digitalWrite(LeftLEDGreen, HIGH);
  digitalWrite(LeftLEDRed, LOW);
}


void loop() {
  if(analogRead(LightSensor) < LightSensorAnalogThreshold) doScaryThing();
}




void doScaryThing(){
  currentNote = 0; 
  //turn off green lights
  digitalWrite(LeftLEDGreen, LOW);
  digitalWrite(RightLEDGreen, LOW);
  
  while(analogRead(LightSensor) < (LightSensorAnalogThreshold+100)){
    blinkLights();
    playDoomAndGloom();
  }


  //turn speaker off
  noTone(Speaker);


  //restore lights to 'normal' state 
  digitalWrite(RightLEDGreen, HIGH);
  digitalWrite(RightLEDRed, LOW);
  digitalWrite(LeftLEDGreen, HIGH);
  digitalWrite(LeftLEDRed, LOW);


  currentNote=0;
  return;
}


void blinkLights(){
  if(millis() - PreviousLedMillis >= redBlinkTime){
    bool ledVal = digitalRead(RightLEDRed);
    digitalWrite(RightLEDRed, !ledVal);
    digitalWrite(LeftLEDRed, ledVal);
    PreviousLedMillis = millis();
  }
}


unsigned long timeend;


void playDoomAndGloom(){
  
  //prepeare our music array by calc'ing length and populating a timing table [array].
  double songdurat[sizeof(songdurau)/sizeof(songdurau[0])]; //find how many notes are in the song via array size. Create array of same size
  for(int x=0; x< (sizeof(songdurau)/sizeof(songdurau[0])); x++){
    songdurat[x] = songdurau[x]*60.0/tempo; // fill array with length of each note.
  }


  if(currentNote > (sizeof(songnotes)/sizeof(songnotes[0]))) {
    //song is over.
    noTone(Speaker);
    return;
  }
  
  if((millis() > timeend) || currentNote ==0){
    timeend = millis() + (songdurat[currentNote]*1000);   
    tone(Speaker, notes_hz[songnotes[currentNote]], songdurat[currentNote]*950);
    currentNote++;
  }
}

 

 

Happy Halloween everyone!

Parents
  • I love your project. It was a lot of work!

    I have a question though. I am trying to understand how do red LEDs blinking during the whole time notes are playing, not just at the end of each note.

    Reading the code:

    1. timeend = millis() + (songdurat[currentNote]*1000);    
    2. tone(Speaker, notes_hz[songnotes[currentNote]], songdurat[currentNote]*950); 

     

    So the loop is done only after time = duration * 950 is played, and only then you enter the loop again and get to call blinkLights() again.

    I must be missing something but how do reds blink so often during the tone played?

  • Hello,

     

    Very eagle-eyed of you to notice that. There are two or three things involved in answering your question.

    1. You note the difference in one calculation using 1000 ms and another using 950. That difference accounts for a very short time period where there is no notes being played. The timer uses 1000 for each note, but the Tone() command accepts an optional value for 'how long' to play each note. This means that the note will stop by itself without having to call noTone().

    2. Playing the music was done using non-blocking code. The line shown here:

    1. if((millis() > timeend) || currentNote ==0){ 

    simply looks to see if we should change to the next note. If we don't have to play the next note, then this block of code is skipped. If we do need to play another note, we go in, and set the Tone() command, then drop out again. The Tone() command itself is also non-blocking. It generates a square wave on the pin of the desired frequency and doesn't require the processor to continually move it high and low. So we can always just call Tone() and go on and do other things like blink the lights.

    3. The song duration is calculated using a base of 1000 ms for each note. We just play the tone based on 950 so that they stop just a bit early. The For loop on line 128 calculates the duration for each part of the song.

Comment
  • Hello,

     

    Very eagle-eyed of you to notice that. There are two or three things involved in answering your question.

    1. You note the difference in one calculation using 1000 ms and another using 950. That difference accounts for a very short time period where there is no notes being played. The timer uses 1000 for each note, but the Tone() command accepts an optional value for 'how long' to play each note. This means that the note will stop by itself without having to call noTone().

    2. Playing the music was done using non-blocking code. The line shown here:

    1. if((millis() > timeend) || currentNote ==0){ 

    simply looks to see if we should change to the next note. If we don't have to play the next note, then this block of code is skipped. If we do need to play another note, we go in, and set the Tone() command, then drop out again. The Tone() command itself is also non-blocking. It generates a square wave on the pin of the desired frequency and doesn't require the processor to continually move it high and low. So we can always just call Tone() and go on and do other things like blink the lights.

    3. The song duration is calculated using a base of 1000 ms for each note. We just play the tone based on 950 so that they stop just a bit early. The For loop on line 128 calculates the duration for each part of the song.

Children
Related
Engagement
Recommended