Some of you may have noticed a trend - I'm posting pretty much around halloween each year.
Today I built a 6W TDA1517 based power amp, picked up a NiMH battery pack and I'm putting the finishing touches on the arduino + audioshield code.
With a bit of luck nick-a-tron will be sending me a wand kit tomorrow and if I burn the midnight oil over the next few days I may have a mostly complete pack.
For those who are interested here's the arduino code I worked with. Please note that this is almost entirely the work of tatawiki. I added a few bits of explanation, formatted it for readability and put in a few features of my own.
Code: Select all/*
Original code by Jeremey Choi, amendments made my martinus (maeglamor@yahoo.co.uk) to improve
clarity.
Thanks to Jeremy who did almost all the work and freely shared the code.
As Jeremy said, I'm not going for screen accuracy, simply something that looks pretty close.
Additions: On setup the powercell is reset (previously it may have had random lights on)
On poweroff there is a 'rewind' on the powercell lights and an alternative poweroff sound.
If you don't like it comment it out. :)
*/
#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
// ===============================================================================================
// BUTTONS
// array is in the format: buttons[power_on, gun_fire, music_change]
// thus, whatever pin is set to buttons[0] switches on the power cell.
// ===============================================================================================
byte buttons[] = {14, 15, 16}; // Analog 0, 1 and 2 respectively.
// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)
// we will track if a button is just pressed, just released, or 'pressed' (the current state
volatile byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];
// 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);
}
//Pin connected to ST_CP of 74HC595 (pin 12)
int latchPin = 8;
//Pin connected to SH_CP of 74HC595 (pin 11)
int clockPinPWR = 6; // Power cell
int clockPinGUN = 18; // Gun
////Pin connected to DS of 74HC595 (pin 14)
int dataPinPWR = 7; // Power cell
int dataPinGUN = 19; // Gun
// PINS SUMMARY:
// 2,3,4,5,10 = audioshield
// 6,7,18,19 = 595's
// 11,12,13 = SD card i/o
//holders for information we're going to pass to shifting function
byte dataPWRCL1;
byte dataPWRCL2;
byte dataGUN1;
byte dataGUN2;
byte dataGUNON1;
byte dataGUNON2;
// data arrays
byte daPWRCL1[15];
byte daPWRCL2[15];
byte daGUN1[26];
byte daGUN2[26];
byte daGUNON1[15];
byte daGUNON2[15];
void setup() {
byte i;
// set up serial port
Serial.begin(9600);
putstring_nl("WaveHC with ");
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);
pinMode(17, OUTPUT);
pinMode(latchPin, OUTPUT);
// LED's 0 through 7
daPWRCL1[0] = 0x03; //00000001
daPWRCL1[1] = 0x07; //00000011
daPWRCL1[2] = 0x0F; //00000111
daPWRCL1[3] = 0x1F; //00001111
daPWRCL1[4] = 0x3F; //00011111
daPWRCL1[5] = 0x7F; //00111111
daPWRCL1[6] = 0xFF; //01111111
daPWRCL1[7] = 0xFF; //11111111
daPWRCL1[8] = 0xFF; //11111111
daPWRCL1[9] = 0xFF; //11111111
daPWRCL1[10] = 0xFF; //11111111
daPWRCL1[11] = 0xFF; //11111111
daPWRCL1[12] = 0xFF; //11111111
daPWRCL1[13] = 0xFF; //11111111
daPWRCL1[14] = 0xFF; //11111111
// LED's 8 through 15
daPWRCL2[0] = 0x00; //00000000
daPWRCL2[1] = 0x00; //00000000
daPWRCL2[2] = 0x00; //00000000
daPWRCL2[3] = 0x00; //00000000
daPWRCL2[4] = 0x00; //00000000
daPWRCL2[5] = 0x00; //00000000
daPWRCL2[6] = 0x00; //00000000
daPWRCL2[7] = 0x01; //00000001
daPWRCL2[8] = 0x03; //00000011
daPWRCL2[9] = 0x07; //00000111
daPWRCL2[10] = 0x0F; //00001111
daPWRCL2[11] = 0x1F; //00011111
daPWRCL2[12] = 0x3F; //00111111
daPWRCL2[13] = 0x7F; //01111111
daPWRCL2[14] = 0xFF; //11111111
//second lights
daGUN1[0] = 0x00; //00000000
daGUN1[1] = 0x00; //00000000
daGUN1[2] = 0x00; //00000000
daGUN1[3] = 0x00; //00000000
daGUN1[4] = 0x00; //00000000
daGUN1[5] = 0x00; //00000000
daGUN1[6] = 0x00; //00000000
daGUN1[7] = 0x80; //10000000
daGUN1[8] = 0xC0; //11000000
daGUN1[9] = 0xE0; //11100000
daGUN1[10] = 0xF0; //11110000
daGUN1[11] = 0xF8; //11111000
daGUN1[12] = 0xFC; //11111100
daGUN1[13] = 0xFE; //11111110
daGUN1[14] = 0xFC; //11111100
daGUN1[15] = 0xF8; //11111000
daGUN1[16] = 0xF0; //11110000
daGUN1[17] = 0xE0; //11100000
daGUN1[18] = 0xC0; //11000000
daGUN1[19] = 0x80; //10000000
daGUN1[20] = 0x00; //00000000
daGUN1[21] = 0x00; //00000000
daGUN1[22] = 0x00; //00000000
daGUN1[23] = 0x00; //00000000
daGUN1[24] = 0x00; //00000000
daGUN1[25] = 0x00; //00000000
daGUN1[26] = 0x00; //00000000
//first lights
daGUN2[0] = 0x40; //01000000
daGUN2[1] = 0x40; //01000000
daGUN2[2] = 0x60; //01100000
daGUN2[3] = 0x70; //01110000
daGUN2[4] = 0x78; //01111000
daGUN2[5] = 0x7C; //01111100
daGUN2[6] = 0x7E; //01111110
daGUN2[7] = 0x7F; //01111111
daGUN2[8] = 0x7F; //01111111
daGUN2[9] = 0x7F; //01111111
daGUN2[10] = 0x7F; //01111111
daGUN2[11] = 0x7F; //01111111
daGUN2[12] = 0x7F; //01111111
daGUN2[13] = 0x7F; //01111111
daGUN2[14] = 0x7F; //01111111
daGUN2[15] = 0x7F; //01111111
daGUN2[16] = 0x7F; //01111111
daGUN2[17] = 0x7F; //01111111
daGUN2[18] = 0x7F; //01111111
daGUN2[19] = 0x7F; //01111111
daGUN2[20] = 0x7F; //01111111
daGUN2[21] = 0x7E; //01111110
daGUN2[22] = 0x7C; //01111100
daGUN2[23] = 0x78; //01111000
daGUN2[24] = 0x70; //01110000
daGUN2[25] = 0x60; //01100000
daGUN2[26] = 0x40; //01000000
// 7 led array LEFT
// 10000000 - Available for GUN
daGUNON2[0] = 0xC0; //11000000
daGUNON2[1] = 0x20; //00100000
daGUNON2[2] = 0x10; //00010000
daGUNON2[3] = 0x88; //10001000
daGUNON2[4] = 0x04; //00000100
daGUNON2[5] = 0x02; //00000010
daGUNON2[6] = 0x81; //10000001
daGUNON2[7] = 0x01; //00000001
daGUNON2[8] = 0x81; //10000001
daGUNON2[9] = 0x02; //00000010
daGUNON2[10] = 0x84; //10000100
daGUNON2[11] = 0x08; //00001000
daGUNON2[12] = 0x10; //00010000
daGUNON2[13] = 0xA0; //10100000
daGUNON2[14] = 0x40; //01000000
// 7 led array
// 00000001 available for gun pin
daGUNON1[0] = 0x02; //00000010
daGUNON1[1] = 0x05; //00000101
daGUNON1[2] = 0x08; //00001000
daGUNON1[3] = 0x11; //00010001
daGUNON1[4] = 0x20; //00100000
daGUNON1[5] = 0x40; //01000000
daGUNON1[6] = 0x81; //10000001
daGUNON1[7] = 0x80; //10000000
daGUNON1[8] = 0x81; //10000001
daGUNON1[9] = 0x40; //01000000
daGUNON1[10] = 0x20; //00100000
daGUNON1[11] = 0x11; //00010001
daGUNON1[12] = 0x08; //00001000
daGUNON1[13] = 0x05; //00000101
daGUNON1[14] = 0x02; //00000010
// Make input & enable pull-up resistors on switch pins
for (i=0; i< NUMBUTTONS; i++) {
pinMode(buttons[i], INPUT);
digitalWrite(buttons[i], HIGH);
}
// Initialise Power cell lights
digitalWrite(latchPin, 0);
shiftOut(dataPinPWR, clockPinPWR, 0x00);
shiftOut(dataPinPWR, clockPinPWR, 0x03);
digitalWrite(latchPin, 1);
// 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) {
check_switches();
}
void check_switches() {
static byte previousstate[NUMBUTTONS];
static byte currentstate[NUMBUTTONS];
byte index;
for (index = 0; index < NUMBUTTONS; index++) {
currentstate[index] = digitalRead(buttons[index]); // read the button
/*
Serial.print(index, DEC);
Serial.print(": cstate=");
Serial.print(currentstate[index], DEC);
Serial.print(", pstate=");
Serial.print(previousstate[index], DEC);
Serial.print(", press=");
*/
if (currentstate[index] == previousstate[index]) {
if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
// just pressed
justpressed[index] = 1;
}
else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
// just released
justreleased[index] = 1;
}
pressed[index] = !currentstate[index]; // remember, digital HIGH means NOT pressed
}
//Serial.println(pressed[index], DEC);
previousstate[index] = currentstate[index]; // keep a running tally of the buttons
}
}
void loop() {
byte i;
static int g = 0;
static int d = 0;
// Start up power cell
if (justpressed[0]) {
justpressed[0] = 0;
playfile("on.wav");
}
// Shut down power cell
else if (justreleased[0]) {
justreleased[0] = 0;
playfile("pwroff.wav");
powerdown();
digitalWrite(latchPin, LOW);
}
//START UP POWERCELL, CYCLOTRON, GUN LIGHTS
if (pressed[0] == HIGH) {
for (int j = 0; j < 15; j++) {
//load the light sequence you want from array
dataPWRCL1 = daPWRCL1[j];
dataPWRCL2 = daPWRCL2[j];
// THIS WILL SET GUN ARRAY TO HOW EVER MANY YOU HAVE IN YOUR ARRAY
if (g > 25) {
g = 0;
}
dataGUN1 = daGUN1[g];
dataGUN2 = daGUN2[g];
g++;
// THIS SETS GUN ARRAY TO RUN TWICE AS SLOW IF REQUIRED
// if (d > 2) {
// d = 0;
// g++;
// }
// d++;
//
//ground latchPin and hold low for as long as you are transmitting
digitalWrite(latchPin, 0);
//move 'em out
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL2);
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL1);
shiftOut(dataPinGUN, clockPinGUN, dataGUN2);
shiftOut(dataPinGUN, clockPinGUN, dataGUN1);
//return the latch pin high to signal chip that it
//no longer needs to listen for information
digitalWrite(latchPin, 1);
delay(60);
}
}
//checks if gun is ON. if not it will not do anything
if (pressed[0] == HIGH) {
// if the fire button is pressed
if (pressed[1]) {
playfile("gun2.wav");
while (wave.isplaying && pressed[1]) {
for (int j = 0; j < 15; j++) {
//load the light sequence you want from array
dataPWRCL1 = daPWRCL1[j];
dataPWRCL2 = daPWRCL2[j];
dataGUNON1 = daGUNON1[j];
dataGUNON2 = daGUNON2[j];
//ground latchPin and hold low for as long as you are transmitting
digitalWrite(latchPin, 0);
//move 'em out
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL2);
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL1);
shiftOut(dataPinGUN, clockPinGUN, dataGUNON2);
shiftOut(dataPinGUN, clockPinGUN, dataGUNON1);
//return the latch pin high to signal chip that it
//no longer needs to listen for information
digitalWrite(latchPin, 1);
delay(50);
}
}
wave.stop();
}
//play power down file
else if (justreleased[1]) {
justreleased[1] = 0;
playfile("off.wav");
}
}
else {
// Gun is not on
}
//count and increment X. this will play music and you can hit the button to go to the next song.
static int x = 0;
if (justpressed[2]) {
x++;
if (x == 1) {
justpressed[2] = 0;
playfile("1.wav");
}
if (x == 2) {
justpressed[2] = 0;
playfile("2.wav");
}
if (x == 3) {
justpressed[2] = 0;
playfile("3.wav");
}
if (x == 4) {
justpressed[2] = 0;
playfile("4.wav");
}
if (x == 5) {
justpressed[2] = 0;
playfile("5.wav");
}
}
}
// Power-down light sequence.
void powerdown() {
for (int j = 14; j > -1; j--) {
// load the light sequence you want from array
dataPWRCL1 = daPWRCL1[j];
dataPWRCL2 = daPWRCL2[j];
// ground latchPin and hold low for transmitting.
digitalWrite(latchPin, 0);
// Send data to 595's.
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL2);
shiftOut(dataPinPWR, clockPinPWR, dataPWRCL1);
// return the latch pin high to signal chip that it
// no longer needs to listen for information
digitalWrite(latchPin, 1);
delay(30);
}
}
// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
// call our helper to find and play this name
playfile(name);
while (wave.isplaying) {
// do nothing while its playing
}
// 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();
}
// the heart of the program
void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
// This shifts 8 bits out MSB first,
// on the rising edge of the clock,
// clock idles low
// internal function setup
int i = 0;
int pinState;
pinMode(myClockPin, OUTPUT);
pinMode(myDataPin, OUTPUT);
//clear everything out just in case to
//prepare shift register for bit shifting
digitalWrite(myDataPin, 0);
digitalWrite(myClockPin, 0);
//for each bit in the byte myDataOut?
//NOTICE THAT WE ARE COUNTING DOWN in our for loop
//This means that %00000001 or "1" will go through such
//that it will be pin Q0 that lights.
for (i = 7; i >= 0; i--) {
digitalWrite(myClockPin, 0);
//if the value passed to myDataOut and a bitmask result
// true then... so if we are at i=6 and our value is
// %11010100 it would the code compares it to %01000000
// and proceeds to set pinState to 1.
if ( myDataOut & (1 << i) ) {
pinState = 1;
}
else {
pinState = 0;
}
//Sets the pin to HIGH or LOW depending on pinState
digitalWrite(myDataPin, pinState);
//register shifts bits on upstroke of clock pin
digitalWrite(myClockPin, 1);
//zero the data pin after shift to prevent bleed through
digitalWrite(myDataPin, 0);
}
//stop shifting
digitalWrite(myClockPin, 0);
}
//blinks the whole register based on the number of times you want to
//blink "n" and the pause between them "d"
//starts with a moment of darkness to make sure the first blink
//has its full visual effect.
void blinkAll_2Bytes(int n, int d) {
digitalWrite(latchPin, 0);
shiftOut(dataPinPWR, clockPinPWR, 0);
shiftOut(dataPinPWR, clockPinPWR, 0);
digitalWrite(latchPin, 1);
delay(200);
for (int x = 0; x < n; x++) {
digitalWrite(latchPin, 0);
shiftOut(dataPinPWR, clockPinPWR, 255);
shiftOut(dataPinPWR, clockPinPWR, 255);
digitalWrite(latchPin, 1);
delay(d);
digitalWrite(latchPin, 0);
shiftOut(dataPinPWR, clockPinPWR, 0);
shiftOut(dataPinPWR, clockPinPWR, 0);
digitalWrite(latchPin, 1);
delay(d);
}
}