I'm having an issue with my code locking up during an attempt to play an MP3 file with the Sparkfun MP3 Player Shield (SparkFun MP3 Player Shield - DEV-12660 - SparkFun Electronics) on an Arduino Mega2560. I have a box full of 30 LEDs and 18 buttons that I have to also control at the same time.
Okay. so here is the box, to give you an idea of what the physical thing is all about:
https://dl.dropbox.com/u/9161524/kiosk.JPG (these images are pretty big, don't want to throw the post width out of whack)
You hit a button in the left bank and it plays an audio clip of someone talking. You make a guess as to what city that person is from (based on their accent) and hit a corresponding button in the right bank. Various blinkenlichten and other audio clips play along the way to direct you, give you instructions, and score your responses. If you get the answer correct, the green strobe on the top flashes and the score meter on the bottom increments. If you get the answer wrong, the red strobe flashes only. You're returned to the beginning of the selection process, this time with your previous choice unavailable, until you get through all 9 tracks.
Here is a basic schematic of what how this is built. The schematic doesn't include the MP3 Shield, which is connected in the usual way to the Arduino. I had to change some of the pin connections for the shield, as the mega puts SS, MOSI, MISO, and SCK pins in a different place than the shield expects. In this case, pins 9, 11, 12, and 13 on the shield need to be wired to pins 53, 51, 50, and 52, respectively.
https://dl.dropbox.com/u/9161524/mp3-kiosk.png
My code is a little too long to post in the thread, but I will pull out excerpts that I think are important. Here is the full code: https://dl.dropbox.com/u/9161524/KioskSketch.zip
I'm using a 12v wall-wart power supply, with a 5v voltage regulator to use for power some of my LEDs and Vref for my buttons. All of the LEDs are prepackaged with the necessary resistors to have them run on 12v. The strobes also run on 12v. The buttons in the two banks are momentary, normally open switches. They have space inside of them to hold some of my LEDs (in this case, I'm running these at 5v because they are too bright at 12v for the buttons).
The MP3 Shield runs out to an amp of some kind. I'm not sure what exactly the amp is, as my project partner did that part, but the issue I'm having happened long before we integrated an amp into the circuit, running just off of the headphone jack on the MP3 shield.
The LEDs along the bottom are "score indicators", and they are running on the full 12v, with NPN BJTs switching them to ground. Whoops, forgot to draw in my schematic that each of those transistors has a 39k ohm resistor between its base pin and the Arduino pin that controls it.
The buttons are read through the analog pins via what I've been calling a "selectable voltage divider". Maybe this has a different name and I just don't know what it is called. Each button in a single column switches a different value resistor for that column. Different combinations of the buttons in that column result in distinct voltage values that I can then detect with the analog input pin and figure out which buttons were pressed. Here is the basic idea for one column of buttons:
This is an older image. That last resistor is a 4.7K, not a 1K.
The code is an implementation of a Finite State Machine. The main setup and loop manage the FSM but don't do any of the logic of the game on its own
#include <SPI.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <SFEMP3Shield.h>
#include "kiosk.h"
#include "attract.h"
#include "intro.h"
#include "select.h"
#include "play.h"
#include "answer.h"
#include "game_over.h"
//#define MP3_DISABLE
States currentState, nextState;
GameState game;
int stateStarted, currentFrame;
// Enter_function and body_function are function pointer types
enter_function enter[NUM_STATES] = {&enter_ATTRACT, &enter_INTRO, &enter_SELECT_TRACK, &enter_PLAY_TRACK, &enter_SELECT_REGION, &enter_PLAY_REGION, &enter_CORRECT_ANSWER, &enter_INCORRECT_ANSWER, &enter_GAME_OVER};
body_function body[NUM_STATES] = { &body_ATTRACT, &body_INTRO, &body_SELECT_TRACK, &body_PLAY_TRACK, &body_SELECT_REGION, &body_PLAY_REGION, &body_CORRECT_ANSWER, &body_INCORRECT_ANSWER, &body_GAME_OVER};
void setup()
{
static int i;
currentState = NONE;
nextState = ATTRACT;
#ifndef MP3_DISABLE
game.mp3.begin();
#endif
for(i = MIN_OUTPUT_PIN; i < MAX_OUTPUT_PIN; ++i)
{
pinMode(i, OUTPUT);
}
}
void enterState()
{
if(enter[currentState] != NULL)
(*enter[currentState])(game);
}
States updateState(int dt)
{
if(body[currentState] != NULL)
return (*body[currentState])(dt, game);
return currentState;
}
void loop()
{
if(nextState != currentState)
{
#ifndef MP3_DISABLE
if(game.mp3.isPlaying())
{
game.mp3.stopTrack();
}
#endif
allLightsOff();
delay(500); // give the user some time to let go of the button
currentState = nextState;
stateStarted = millis();
enterState();
}
readInput();
currentFrame = millis();
nextState = updateState(currentFrame - stateStarted);
}
When I wrote the code, the hardware hadn't been finished yet, so I had some regular, resistorless LEDs that I was using to debug the display, while setting up the code to select the first available option with a few delays to simulate options being selected and let the code progress through its various states. Running in this way, the MP3s all played fine and my LEDs lit up as expected.
Building the button array and the "selectable voltage dividers", I wrote a small sketch that tested the combinations of button presses, and that worked exactly as expected. Figuring out the button combinations took some math but it's not that hard to do by testing, either.
int readBank(int column)
{
static int voltage;
voltage = analogRead(column);
// these need to be selected based on the resistors in the analog button encoders
if(voltage < 209) return 0;
else if(voltage < 465) return 1;
else if(voltage < 577) return 2;
else if(voltage < 670) return 3;
else if(voltage < 727) return 4;
else if(voltage < 766) return 5;
else if(voltage < 794) return 6;
return 7;
}
This has to be ran for each of the 6 columns of buttons, connected to one each of the analog input pins, in this case 8 through 13, because the MP3 shield covered up pins the first 8 pins.
Integrating the button and score LEDs into the circuit, I wrote a small sketch that just lit each light in sequence, then lit all of them and turned each off in sequence. That worked exactly as expected as well. The LEDs inside of the buttons are only ran at 5v, both because they were too bright at 12v and because my project partner and I didn't want to wire up transistors for each of them. Looking back now, the transistors should have been used anyway, just to keep everything consistent, and still run the LEDs on 5v. I'm afraid my issue is related to too much current draw from doing these LEDs in this way, but not really sure and don't really know how to properly test this idea. Because of this, the code for turning the LEDs on and off is a little different than normal
void ledOff(int p)
{
// this sets the pin into a high-impedance state, essentially "disconnecting" it from the circuit
pinMode(p, INPUT);
digitalWrite(p, LOW);
}
void ledOn(int p)
{
pinMode(p, OUTPUT);
//S1 is the first score indicator LED, which is controlled by a BJT instead of directly
digitalWrite(p, p < S1 ? LOW : HIGH);
}
In the case of the button LEDs, I'm grounding the desired LED through the Arduino to turn it on, then "disconnecting" the LED cathode to turn it off. It's not pretty, should have done the BJT, but it works okay for now.
The first state in the FSM is ATTRACT. It animates the lights and plays an audio clip to try to draw attention to itself (this is going to be setup in a room full of people in an art gallery). ATTRACT mode starts up fine, animates as expected, and plays the MP3 as expected. The ATTRACT code is pretty simple, it's just running back and forth through all of the LEDs, turning them on and off in sequence:
#include <arduino.h>
#include <SFEMP3Shield.h>
#include "attract.h"
#include "kiosk.h"
unsigned int ATTRACT_currentFrame;
unsigned int ATTRACT_lastFrame;
void enter_ATTRACT(GameState& game)
{
ATTRACT_currentFrame = MIN_OUTPUT_PIN;
ATTRACT_lastFrame = 0;
// strobes run on their own, just turn them on.
lightButton(2, 12);
lightButton(2, 13);
#ifndef MP3_DISABLE
game.mp3.playMP3("attract.mp3");
#endif
}
States body_ATTRACT(int dt, GameState& game)
{
States next = ATTRACT;
if((dt - ATTRACT_lastFrame) >= ATTRACT_MILLIS_PER_FRAME)
{
ATTRACT_lastFrame = dt;
ledOff(ATTRACT_currentFrame);
++ATTRACT_currentFrame;
if(ATTRACT_currentFrame > S12)
ATTRACT_currentFrame = MIN_OUTPUT_PIN;
ledOn(ATTRACT_currentFrame);
}
if(wasAnyButtonPressed())
{
next = INTRO;
}
return next;
}
whoops, hit the character limit. Continued...