Sunday, February 20, 2011

Arduino application

The Arduino application is fairly straightforward, even if the implementation took a little effort to make it work.

To get the Arduino to play MP3 files, I'm using the Rogue MP3 shield.  This shield does double duty.  It provides storage on an SD card (for the MP3s and "piano scroll" files), and it decodes/plays the music files.  The card comes with nicely written libraries (SD library / MP3 library) to get access to the functionality.  I'll talk more about the Rogue MP3 more in my next blog entry.

#include <NewSoftSerial.h>
#include <RogueMP3.h>
#include <RogueSD.h>

I'm also using a Centipede shield to give me more digital output pins.  Instead of the usual 14 on an Arduino board, the Centipede gives you 64!  Just like the Rogue MP3 shield, the card comes with nicely written libraries to access the functionality.  I'll talk more about the Centipede shield in a future blog entry.

#include <Wire.h>
#include <Centipede.h>

The setup() and loop() routines are straightforward.  I suppose I could have used more complex logic to find and loop through all the MP3 files on the SC card.  Maybe I'll do that in a future iteration.


void setup()
{
  Serial.begin(9600);
  rmp3_serial.begin(9600);


  //initialize RogueSD
  filecommands.sync();  //initialize RogueSD
  filecommands.closeall();  //initialize RogueSD


  //initialize RogueMP3
  rmp3.sync();  //initialize RogueMP3
  rmp3.setvolume(50);  //smaller numbers are louder


  //initialize Centipede shield
  Wire.begin();  //start I2C for Centipede shield
  CS.initialize(); //set all registers to default
  for (i=0;i<32;i++) {  //set first 32 channels to output
    CS.pinMode(i, OUTPUT); 
  }
}


void loop()
{
  playSong("/test.mp3", "/test.data");
  delay(5000);
  playSong("/Let_It_Snow.mp3", "/Let_It_Snow.data");
  delay(5000);
}


The only real logic in the Arduino application happens in the playSong routine.  The "while" loop continues as long as the MP3 status=='P' (play).  Each time through the loop, I check to see if we've arrived at the time (currentTime-startTime) to display the next set of lights (it's after waitForTime).  I added 200 ms to the time to adjust for a slight timing difference between the Arduino sketch execution time and the MP3 play time.


void playSong(char* SongFile, char* DataFile) {
  loadSong(SongFile, DataFile);
  readLine (); 
  while (rmp3.getplaybackstatus()=='P') {
    currentTime=millis();
    if((currentTime-startTime+200) >= waitForTime) {
      setLights();
      readLine ();
    } 
  }
  filecommands.closeall();
}

After displaying a set of lights (setLights), the next line in the piano scroll is read (readLine) from the SD card, and the loop is repeated.  The last line of the file has a waitForTime of 999999, which is never reached, so there is no need to worry about end-of-file type errors.

The RogueRobotics.com site has a cool demo showing how they synchronized a song to the lyrics coming from a standard LRC file.  The example is great, but overkill for my purpose.  Because I controlled the input file, I was able to assume some things (6 digits for the waitForTime, 5 digits for value1, and 5 digits for value2).  This simplified the way I was able to read from my data file.

void readLine () {
  readStatus = filecommands.readln(fileHandle, 20, lineBuffer);
  waitForTime=getNumber(lineBuffer[0],100000)+
    getNumber(lineBuffer[1],10000)+
    getNumber(lineBuffer[2],1000)+
    getNumber(lineBuffer[3],100)+
    getNumber(lineBuffer[4],10)+
    getNumber(lineBuffer[5],1);
 lights1=getNumber(lineBuffer[7],10000)+
    getNumber(lineBuffer[8],1000)+
    getNumber(lineBuffer[9],100)+
    getNumber(lineBuffer[10],10)+
    getNumber(lineBuffer[11],1);
 lights2=getNumber(lineBuffer[13],10000)+
    getNumber(lineBuffer[14],1000)+
    getNumber(lineBuffer[15],100)+
    getNumber(lineBuffer[16],10)+
    getNumber(lineBuffer[17],1);
}

I knew that position [0] is the 100,000's digits, position [1] is the 10,000's digit, etc.  I didn't have to look for brackets or commas to parse the file.

This little function turns the character into a number (with the right 10's place)

long getNumber(char c, long multiplier) {
  return (int(c)-int('0'))*multiplier;
}

The Centipede shield makes it easy to set the lights, but more about that in a future post.

void setLights () {
  CS.digitalWrite(0, (lights1 & 1)?HIGH:LOW);  //0-LRLeft
  CS.digitalWrite(1, (lights1 & 2)?HIGH:LOW);  //1-RRight
  CS.digitalWrite(2, (lights1 & 4)?HIGH:LOW);  //2-CharlieLeft
  CS.digitalWrite(3, (lights1 & 8)?HIGH:LOW);  //3-CharlieRight
  ...
  CS.digitalWrite(30, (lights2 & 16384)?HIGH:LOW);  //30-Tree7
  CS.digitalWrite(31, (lights2 & 32768)?HIGH:LOW);  //31-Tree8
}


If you'd like my code, just send me an e-mail.  I'd be happy to zip it up and send it to you.


In my next blog entry, I'll discuss the Rogue MP3 Shield.

No comments:

Post a Comment