Step 3: Behavioral Code Overview

In the main loop, we read continuously from the light sensor
If the light sensor input goes above a certain value, then the MP3 starts to play
If the light sensor input goes below a certain value, then the MP3 is stopped

While the MP3 is playing, the light sensor input is converted into a decibel level for the MP3, where more light equals louder sound
In addition, the light sensor input controls the LED values inside the main loop and the MP3 loop
As the light sensor input increases, the red LED increases and the blue LED decreases

Here is the code used:


#include <SPI.h>

#include <SdFat.h>
#include <SdFatUtil.h>

#define TRUE 1
#define FALSE 0

Sd2Card card;
SdVolume volume;
SdFile root;
SdFile track;

//MP3 Player Shield pin mapping. See the schematic
#define MP3_XCS 6 //Control Chip Select Pin (for accessing SPI Control/Status registers)
#define MP3_XDCS 7 //Data Chip Select / BSYNC Pin
#define MP3_DREQ 2 //Data Request Pin: Player asks for more data
#define MP3_RESET 8 //Reset is active low
//Remember you have to edit the Sd2PinMap.h of the sdfatlib library to correct control the SD card.

//VS10xx SCI Registers
#define SCI_MODE 0x00
#define SCI_STATUS 0x01
#define SCI_BASS 0x02
#define SCI_CLOCKF 0x03
#define SCI_DECODE_TIME 0x04
#define SCI_AUDATA 0x05
#define SCI_WRAM 0x06
#define SCI_WRAMADDR 0x07
#define SCI_HDAT0 0x08
#define SCI_HDAT1 0x09
#define SCI_AIADDR 0x0A
#define SCI_VOL 0x0B
#define SCI_AICTRL0 0x0C
#define SCI_AICTRL1 0x0D
#define SCI_AICTRL2 0x0E
#define SCI_AICTRL3 0x0F

//This is the name of the file on the microSD card you would like to play
//Stick with normal 8.3 nomeclature. All lower-case works well.
//Note: you must name the tracks on the SD card with 001, 002, 003, etc.
//For example, the code is expecting to play 'track002.mp3', not track2.mp3.
char trackName[] = "sound.mp3";
int trackNumber = 1;
int previousTrigger = 1; //This indicates that we've already triggered on 1

char errorMsg[100]; //This is a generic array used for sprintf of error messages

int inputPin = A0;
int inputValue = 0;
int blueOutputValue = 0;
int blueOutputLED = 5;
int redOutputValue = 0;
int redOutputLED = 10;
int outputVolume = 0;

long lastCheck; //This stores the last millisecond since we had a trigger

int is_playing;
int time_since_play;

int checkTriggers(void) {

#define DEBOUNCE  100

  int foundTrigger = 255;

  //Once a trigger is activated, we don't want to trigger on it perpetually
  //But after 3 seconds, reset the previous trigger number
  if( (previousTrigger != 255) && (millis() - lastCheck) > 3000) {
    lastCheck = millis();
    previousTrigger = 255;
    Serial.println("Previous trigger reset");

  if(foundTrigger != previousTrigger){ //We've got a new trigger!
    previousTrigger = foundTrigger;

    Serial.println(foundTrigger, DEC);

    return(255); //No triggers pulled low (activated)

// playMP3 - included function from SparkFun example

void playMP3(char* fileName) {

  if (!track.open(&root, fileName, O_READ)) { //Open the file in read mode.
    sprintf(errorMsg, "Failed to open %s", fileName);

  sprintf(errorMsg, "Playing track %s", fileName);

  uint8_t mp3DataBuffer[32]; //Buffer of 32 bytes. VS1053 can take 32 bytes at a go.
  int need_data = TRUE;

  while(1) {
    while(!digitalRead(MP3_DREQ)) {
      //DREQ is low while the receive buffer is full
      //You can do something else here, the buffer of the MP3 is full and happy.
      //Maybe set the volume or test to see how much we can delay before we hear audible glitches

      //If the MP3 IC is happy, but we need to read new data from the SD, now is a great time to do so
      if(need_data == TRUE) {
        if(!track.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Try reading 32 new bytes of the song
          //Oh no! There is no data left to read!
          //Time to exit
        need_data = FALSE;

      //Check to see if we need to bail on this track
      if(checkTriggers() != 255) {
        Serial.println("Exiting MP3!");
        track.close(); //Close this track!
        previousTrigger = 255; //Trick the next check into thinking we haven't seen a previous trigger

    if(need_data == TRUE){ //This is here in case we haven't had any free time to load new data
      if(!track.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Go out to SD card and try reading 32 new bytes of the song
        //Oh no! There is no data left to read!
        //Time to exit
      need_data = FALSE;

    //Once DREQ is released (high) we now feed 32 bytes of data to the VS1053 from our SD read buffer
    digitalWrite(MP3_XDCS, LOW); //Select Data
    for(int y = 0 ; y < sizeof(mp3DataBuffer) ; y++)
      SPI.transfer(mp3DataBuffer[y]); // Send SPI byte

    digitalWrite(MP3_XDCS, HIGH); //Deselect Data
    need_data = TRUE; //We've just dumped 32 bytes into VS1053 so our SD read buffer is empty. Set flag so we go get more data

    inputValue = analogRead(inputPin);

    outputVolume = map(inputValue, 0, 1023, 60, -20); // <--- this is where you make it louder/softer depending on how much light input your puppet gets

    redOutputValue = map(inputValue, 0, 1023, 0, 255);
    blueOutputValue = map(inputValue, 0, 1023, 255, 0);

    Mp3SetVolume(outputVolume, outputVolume);

    analogWrite(redOutputLED, redOutputValue);
    analogWrite(blueOutputLED, blueOutputValue);


  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating transfer is complete
  digitalWrite(MP3_XDCS, HIGH); //Deselect Data

  track.close(); //Close out this track

  sprintf(errorMsg, "Track %s done!", fileName);

void setup()
  pinMode(MP3_DREQ, INPUT);
  pinMode(MP3_XCS, OUTPUT);
  pinMode(MP3_XDCS, OUTPUT);
  pinMode(MP3_RESET, OUTPUT);

  digitalWrite(MP3_XCS, HIGH); //Deselect Control
  digitalWrite(MP3_XDCS, HIGH); //Deselect Data
  digitalWrite(MP3_RESET, LOW); //Put VS1053 into hardware reset

  pinMode(inputPin, INPUT);       // declare the LDR as an INPUT
  pinMode(redOutputLED, OUTPUT);  // declare the ledPin as an OUTPUT
  pinMode(blueOutputLED, OUTPUT);

  Serial.begin(57600); //Use serial for debugging
  Serial.println("MP3 Player Example using Control");

  //Setup SD card interface
  pinMode(10, OUTPUT);       //Pin 10 must be set as an output for the SD communication to work.
  if (!card.init(SPI_FULL_SPEED))  Serial.println("Error: Card init"); //Initialize the SD card and configure the I/O pins.
  if (!volume.init(&card)) Serial.println("Error: Volume ini"); //Initialize a volume on the SD card.
  if (!root.openRoot(&volume)) Serial.println("Error: Opening root"); //Open the root directory in the volume.

  //We have no need to setup SPI for VS1053 because this has already been done by the SDfatlib

  //From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
  //Internal clock multiplier is 1.0x after power up.
  //Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe.
  SPI.setClockDivider(SPI_CLOCK_DIV16); //Set SPI bus speed to 1MHz (16MHz / 16 = 1MHz)
  SPI.transfer(0xFF); //Throw a dummy byte at the bus

  //Initialize VS1053 chip
  digitalWrite(MP3_RESET, HIGH); //Bring up VS1053

  //Mp3SetVolume(20, 20); //Set initial volume (20 = -10dB) LOUD
  Mp3SetVolume(40, 40); //Set initial volume (20 = -10dB) Manageable
  //Mp3SetVolume(80, 80); //Set initial volume (20 = -10dB) More quiet

  //Now that we have the VS1053 up and running, increase the internal clock multiplier and up our SPI rate
  Mp3WriteRegister(SCI_CLOCKF, 0x60, 0x00); //Set multiplier to 3.0x

  //From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
  //Internal clock multiplier is now 3x.
  //Therefore, max SPI speed is 5MHz. 4MHz will be safe.
  SPI.setClockDivider(SPI_CLOCK_DIV4); //Set SPI bus speed to 4MHz (16MHz / 4 = 4MHz)

  //MP3 IC setup complete

  Serial.println("Done with setup");


void loop()
  inputValue = analogRead(inputPin);

  //LED eyes bits (needs tweaking of values when placed in final puppet)

  redOutputValue = map(inputValue, 0, 1023, 0, 255);
  blueOutputValue = map(inputValue, 0, 1023, 255, 0);
  /* if (inputValue > 50)
   blueOutputValue = 0;
   if (inputValue < 20)
   redOutputValue = 0;
   } */

  if (inputValue > 600) //if mouth is open enough
    if (is_playing == 0)
      is_playing = 1;
  if (inputValue < 500) // if mouth is closed enough
    is_playing = 0;


  Serial.print(inputValue); // to see if your light sensor is working properly
  analogWrite(redOutputLED, redOutputValue);
  analogWrite(blueOutputLED, blueOutputValue);

//Write to VS10xx register - from SparkFun example
//SCI: Data transfers are always 16bit. When a new SCI operation comes in
//DREQ goes low. We then have to wait for DREQ to go high again.
//XCS should be low for the full duration of operation.
void Mp3WriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating IC is available
  digitalWrite(MP3_XCS, LOW); //Select control

  //SCI consists of instruction byte, address byte, and 16-bit data word.
  SPI.transfer(0x02); //Write instruction
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
  digitalWrite(MP3_XCS, HIGH); //Deselect Control

//Set VS10xx Volume Register - volume set function from SparkFun example
void Mp3SetVolume(unsigned char leftchannel, unsigned char rightchannel){
  Mp3WriteRegister(SCI_VOL, leftchannel, rightchannel);


About This Instructable




More by dillerj:Toothless, the Roaring Plush Puppet The Skittish Nightscape - Jed Diller and Ken Hoff 
Add instructable to: