This is the second part in the series on creating the Overwatch prop blaster for Lucio. You can check out the first part here.
In learning how to build a multi-function prop we started off by making individual, seperate circuits for three things: lighting, music, and sound effects. I like to start off with discreet circuits, each running with its own microcontroller and power supplly. This helps to get things up and running quickly and is straightforward to troubleshoot.
These three, separate circuits create the lighting, music, and audio effects for the prop. Let's integrate them into a single system.
Now that those individual parts are working, it's time to combine them into a single, integrated system. This integration covers a number of elements, including doing multiple tasks with a single microcontroller, sharing inputs, such as a trigger pull creating both a sound effect and a lighting change, and sharing power supplies.
Lights and Music
Leaping off from the previous lesson, you can first combine the music circuit and the lighting circuit. They each ran from their own Arduino UNO or Adafruit Metro board in part 1, but now we'll run both circuits from a single board.
Start with the UNO or Metro board with the Music Maker MP3 shield attached. The mode toggle switch will now serve double duty, changing both lights an music. This will still be attached to pin 5 as an input pullup switch. You'll also plug the NeoPixel ring into pin 2 as before.
This time, we're going to use a smaller 3W amplifier connected to the MP3 shield for the background music playing through the three small speakers. We'll continue to use the 20W amp to pump the shooting sound effects from the Audio FX board through the big speaker in the front of the gun. Keeping these two sound circuits seperated allows us to have stereo music channels playing through the left and right small speakers, and still create loud mono effects through the front speaker.
The Arduino sketch needs to be adjusted so that it will both play back music and light the LEDs. Below is a simple sketch to do this, as well as switch modes when the toggle is flipped. Copy it and upload it to the UNO or Metro.
//Lucio Blaster code for UNO and Metro // by John Park for Adafruit //based upon: /*************************************************** Adafruit VS1053 Codec Breakout Designed specifically to work with the Adafruit VS1053 Codec Breakout ----> https://www.adafruit.com/products/1381 Written by Limor Fried/Ladyada for Adafruit Industries. BSD license, all text above must be included in any redistribution ****************************************************/ #include <Adafruit_VS1053.h> #include <SD.h> #include <Adafruit_NeoPixel.h> #define NEOPIN 2 // pin for NeoPixel ring #define NUMPIXELS 24 //adjust depeding on ring size Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, NEOPIN, NEO_GRBW + NEO_KHZ800); int NEODELAY = 0; //8 millis is 120BPM #define NEOVUPIN 8 #define NUMVUPIXELS 8 Adafruit_NeoPixel vupixels = Adafruit_NeoPixel(NUMVUPIXELS, NEOVUPIN, NEO_GRBW + NEO_KHZ800); int NEOVUDELAY = 42; //the tempo for lighting effects #define NEOJWLPIN 10 #define NUMJWLPIXELS 5 Adafruit_NeoPixel jewelpixels = Adafruit_NeoPixel(NUMJWLPIXELS, NEOJWLPIN, NEO_GRBW + NEO_KHZ800); int NEOJWLDELAY = 84; //the tempo for lighting effects // These are the pins used for the music maker shield #define SHIELD_RESET -1 // VS1053 reset pin (unused!) #define SHIELD_CS 7 // VS1053 chip select pin (output) #define SHIELD_DCS 6 // VS1053 Data/command select pin (output) // These are common pins between breakout and shield #define CARDCS 4 // Card chip select pin // DREQ should be an Int pin, see http://arduino.cc/en/Reference/attachInterrupt #define DREQ 3 // VS1053 Data request, ideally an Interrupt pin Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS); //state change detection const int MODESWITCHPIN = 5; // the pin that the pushbutton is attached to int modeSwitchPushCounter = 0; // counter for the number of button presses bool modeSwitchState = 0; // current state of the button int lastModeButtonState = 0; // previous state of the button const char* SONGS[] = {"h.mp3", "s.mp3"};//change to reflect your file names int songPick = 0; const int VOL = 1; //volume higher numbers are quieter //// void setup() { pixels.begin(); // This initializes the NeoPixel library. pixels.setBrightness(30); pixels.show(); vupixels.begin(); vupixels.setBrightness(20); vupixels.show(); jewelpixels.begin(); jewelpixels.setBrightness(10); jewelpixels.show(); pinMode(MODESWITCHPIN, INPUT_PULLUP); Serial.begin(9600); // initialise the music player if (! musicPlayer.begin()) { // initialise the music player Serial.println(F("Couldn't find VS1053, do you have right pins defined?")); while (1); } if (!SD.begin(CARDCS)) { Serial.println(F("SD failed, or not present")); while (1); // don't do anything more } Serial.println(F("SD OK!")); // Set volume for left, right channels. lower numbers == louder volume! musicPlayer.setVolume(VOL,VOL); if (! musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)) Serial.println(F("DREQ pin is not an interrupt pin")); //check the switch state to start up with a NeoPixel color modeSwitchState = digitalRead(MODESWITCHPIN); //read switch input pin if (modeSwitchState==HIGH){ for(int i=(NUMPIXELS-1);i>=0;i--){ //change the color "left to right" //sweep pixels.setPixelColor(i, pixels.Color(150,120,0,10)); //yellow color pixels.show(); // This sends the updated pixel color to the hardware delay(NEODELAY); // Delay for a period of time (in milliseconds) } for(int i=(NUMJWLPIXELS-1);i>=0;i--){ //change the color "left to right" //sweep jewelpixels.setPixelColor(i, jewelpixels.Color(150,120,0,10)); //yellow color jewelpixels.show(); // This sends the updated pixel color to the hardware delay(NEOJWLDELAY); // Delay for a period of time (in milliseconds) } } else { for(int i=0;i<NUMPIXELS;i++){ //change the color "right to left" sweep pixels.setPixelColor(i, pixels.Color(0,150,0,10)); // green color pixels.show(); delay(NEODELAY); } for(int i=0;i<NUMJWLPIXELS;i++){ //change the color "right to left" sweep jewelpixels.setPixelColor(i, jewelpixels.Color(0,150,0,10)); // green color jewelpixels.show(); delay(NEOJWLDELAY); } } } void loop() { // Start playing a file, then we can do stuff while waiting for it to finish if (! musicPlayer.startPlayingFile(SONGS[songPick])) { Serial.println(F("Could not open file")); while (1); } //Serial.println(F("Started playing")); while (musicPlayer.playingMusic) { modeSwitchState = digitalRead(MODESWITCHPIN); //read switch input pin if (modeSwitchState != lastModeButtonState) { // compare the modeSwitchState //to its previous state if (modeSwitchState == HIGH) { // if the current state is HIGH then the button // went from left to right : modeSwitchPushCounter++; songPick=0; musicPlayer.stopPlaying(); //does this so it'll restart the player with // the new song choice for(int i=(NUMPIXELS-1);i>=0;i--){ //change the color "left to right" //sweep pixels.setPixelColor(i, pixels.Color(150,120,0,10)); // yellow color } pixels.show();//show the new values for(int i=(NUMVUPIXELS-1);i>=0;i--){ //change the color "left to right" vupixels.setPixelColor(i, vupixels.Color(150,120,0,10)); //yellow } vupixels.show(); for(int i=(NUMJWLPIXELS-1);i>=0;i--){ //change the color "left to right" jewelpixels.setPixelColor(i, jewelpixels.Color(150,120,0,10)); //yellow } jewelpixels.show(); } else { // if the current state is LOW then the button // went from on to off: songPick=1; musicPlayer.stopPlaying(); //restart the player with the new song choice for(int i=0;i<NUMPIXELS;i++){ //change the color "right to left" sweep pixels.setPixelColor(i, pixels.Color(0,150,0,10)); //yellow color } pixels.show();//show the new values for(int i=0;i<NUMVUPIXELS;i++){ //change the color "right to left" sweep vupixels.setPixelColor(i, vupixels.Color(0,150,0,10)); //yellow color } vupixels.show(); for(int i=0;i<NUMJWLPIXELS;i++){ //change the color "right to left" sweep jewelpixels.setPixelColor(i, jewelpixels.Color(0,150,0,10)); //yellow color } jewelpixels.show(); } // Delay a little bit to avoid bouncing delay(50); } // save the current state as the last state, //for next time through the loop lastModeButtonState = modeSwitchState; if(modeSwitchState==LOW){ //VU meter pixel bounce -- brute force, needs to clean up or become function vupixels.setPixelColor(0, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(1, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(2, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(3, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(4, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(5, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(6, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(7, vupixels.Color(0,0,0,0)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(2, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(3, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(4, vupixels.Color(0,150,0,10)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(5, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(6, vupixels.Color(0,150,0,10)); vupixels.setPixelColor(7, vupixels.Color(0,150,0,10)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(5, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(6, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(7, vupixels.Color(0,0,0,0)); vupixels.show(); delay(NEOVUDELAY); } else{ //VU meter pixel bounce vupixels.setPixelColor(0, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(1, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(2, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(3, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(4, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(5, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(6, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(7, vupixels.Color(0,0,0,0)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(2, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(3, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(4, vupixels.Color(150,120,0,10)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(5, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(6, vupixels.Color(150,120,0,10)); vupixels.setPixelColor(7, vupixels.Color(150,120,0,10)); vupixels.show(); delay(NEOVUDELAY); vupixels.setPixelColor(5, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(6, vupixels.Color(0,0,0,0)); vupixels.setPixelColor(7, vupixels.Color(0,0,0,0)); vupixels.show(); delay(NEOVUDELAY); } } }
One thing to add to the circuit at this point is an on/off switch. The PowerBoost has an enable pin (marked "EN" on the board) which cuts power to the 5V output when it is grounded. You can test this out with a single jumper wire, but we'll use a switch instead.
You can also add a volume knob, such as this. Unlike the 20W amp, there is not an on-board volume pot connector. Instead, we'll use a traditional 10K stereo audio potentiometer, which will act as a middle-man between the output of the MP3 shield and the input of the amplifier. You can wire it as seen in the circuit diagram.
You now have two of the three functions integrated. Sweet! You turn it on and off with the PowerBoost enable switch, adjust the volume, and flip modes from health to speed, causing both lights and music to change as needed.
Next up, is integration of the Audio FX board for shooting sounds.
Text editor powered by tinymce.