Things used in this project

Hardware components:
11013 04
SparkFun Lilypad MP3 Board
×1
Adafruit industries ada640 image 75px
Sewable Conductive Thread
×1
Conductive Fabric
×1
Resistor 1.5k Ohm
×10
Zipper
With metal slider and plastic teeth
×1
Magnet Ring 3/16"
×5
Speaker
×2
Micro SD card
×1
Coin
Needs to be attracted by magnets ;-)
×1
Hand tools and fabrication machines:
Hand sewing needle and needle threader
Sewing machine

Schematics

Arduino LilyPad Textile MP3 Player

Code

Mp3MatArduino
Sketch for the Arduino Lilypad wall mounted mp3 player.
// ******************************* INFORMATION ***************************//

// ***********************************************************************//
// "MP3Mat" sketch for Lilypad MP3 Player
// Stefanie Noell
// me@stefanienoell.com

// March 2016
// Version 1.0


// Tries to find five directories, whose names start with "1" - "5".
// If a directory is chosen via UI, it reads files within it and starts playing MP3s until the last one is finished.
// If no directory is chosen it stops playing.
// Goes into sleep mode after set period of time where no input has been made and no MP3 has been played.
// Wakes up when change at any input is detected.

// Uses four inputs:
// 1) Analog - Voltage divider circuit (range 0-5) to choose directory from which to play Mp3 files("0" = no directory chosen)
// 2) Analog - Voltage divider circuit (range 0-5) to adjust volume
// 3) Digital - Switch to play previous track
// 4) Digital - Switch to play next track


// Code dealing with the voltage divider reading was adapted from
// http://forum.arduino.cc/index.php?topic=20125.0
// as published by Doug LaRue in November 2008 under Creative Commons Attribution-Noncommercial-Share Alike 3.0


// Code dealing with sleep mode was adapted from 
// http://playground.arduino.cc/Learning/ArduinoSleepCode
// Copyright (C) 2006 MacSimski 2006-12-30 
// Copyright (C) 2007 D. Cuartielles 2007-07-08 - Mexico DF


// ***********************************************************************//


// ******************************* TODOS *********************************//

// ***********************************************************************//

// - set volume as per UI at start

// ***********************************************************************//



// ******************************* LIBRARIES *****************************//

// ***********************************************************************//
#include <avr/sleep.h>
#include <avr/interrupt.h>

// Uses the SdFat library by William Greiman, which is supplied
// with this archive, or download from http://code.google.com/p/sdfatlib/

// Uses the SFEMP3Shield library by Bill Porter, which is supplied
// with this archive, or download from http://www.billporter.info/

#include <SPI.h>            // To talk to the SD card and MP3 chip
#include <SdFat.h>          // SD card file system
#include <SFEMP3Shield.h>   // MP3 decoder chip


// Create library objects:
SFEMP3Shield MP3player;
SdFat sd;

// ***********************************************************************//



// ******************************* CONSTANTS *****************************//

// ***********************************************************************//

// Set DEBUG = true if you'd like status messages sent
// to the serial port. Note that this will take over trigger
// inputs 4 and 5. (You can leave triggers connected to 4 and 5
// and still use the serial port, as long as you're careful to
// NOT ground the triggers while you're using the serial port).

const boolean DEBUG = false;

// INPUTS
const byte SET_DIRECTORY_PIN = A5;   // voltage divider circuit input connected to ANALOG pin 5 (T3 on Lilypad MP3 with SJ1 cut/disconnected!)
const byte SET_VOLUME_PIN = A0;   // voltage divider circuit input connected to ANALOG pin 0 (T1 on Lilypad MP3)
const byte NEXT_TRACK_SWITCH_PIN = A4; // switch circuit input connected to ANALOG pin 4 (T2 on Lilypad MP3)
const byte PREVIOUS_TRACK_SWITCH_PIN = 1; // switch circuit input connected to DIGITAL pin 1 (T4 on Lilypad MP3)

// OUTPUTS
const byte SD_CS = 9;     // Chip Select for SD card
const int EN_GPIO1 = A2;  // Amp enable + MIDI/MP3 mode select

// Error window for voltage dividers
const int ERROR_WINDOW = 50;  // +/- this value

// Delay between voltage divider readings
const int BUTTONDELAY = 500;

// Max number of files to be read per directory (more files = more memory needed!)
const byte MAX_NUM_FILES = 20;

// Delay after which sleep mode is turned on if there is no interaction and no mp3 playing
const int SLEEP_DELAY = 20000; // in ms

// ***********************************************************************//



// ******************************* VARIABLES *****************************//

// ***********************************************************************//

SdFile file;
byte result;

// We'll store the five directories and up to MAX_NUM_FILES filenames as arrays of characters.
// "Short" (8.3) filenames are used, followed by a null character.
char dirname[5][13];
char filename[MAX_NUM_FILES][13] = {};

// store the index of the last chosen directory as well as index of playing file
byte last_dir = 0;   // previous chosen directory/trigger (0 = none)
byte last_file = 0;  // previous (playing) file (0 = none)

byte volume = 50; // actual volume value
byte target_vol = 50; // volume we want to fade to
byte target_vol_range = -1;  // previous volume trigger ( range of 0 to 5);

boolean nextTrackSwitchOn = false;
boolean previousTrackSwitchOn = false;

long buttonLastChecked = 0; // variable to limit the voltage divider triggers getting checked every cycle

long sleepCounter = 0;  // counts up to SLEEP_DELAY and then turn on sleep mode

// ***********************************************************************//



// ******************************* SETUP *********************************//

// ***********************************************************************//
void setup()
{

  // =======================================================================//
  // ! Initialize the serial port                                           //
  // =======================================================================//
  // (The 'F' stores constant strings in flash memory to save RAM)
  if (DEBUG)
  {
    Serial.begin(9600);
    Serial.println(F("Lilypad MP3 Player trigger sketch"));
  }

  // =======================================================================//
  // ! Initialize switch buttons as inputs                                  //
  // =======================================================================//
  pinMode(NEXT_TRACK_SWITCH_PIN, INPUT);
  pinMode(PREVIOUS_TRACK_SWITCH_PIN, INPUT);
  // and turn on internal pullup resistors (IS THIS NEEDED?? WHAT DOES IT DO???)
  digitalWrite(NEXT_TRACK_SWITCH_PIN, HIGH);
  digitalWrite(PREVIOUS_TRACK_SWITCH_PIN, HIGH);

  // =======================================================================//
  // ! Initialize amplifier chip                                           //
  // ======================================================================//
  // The board uses a single I/O pin to select the
  // mode the MP3 chip will start up in (MP3 or MIDI),
  // and to enable/disable the amplifier chip:
  
  pinMode(EN_GPIO1,OUTPUT);
  digitalWrite(EN_GPIO1,LOW);  // MP3 mode / amp off


  // =======================================================================//
  // ! Initialize the SD card                                               //
  // =======================================================================//
  // SS = pin 9, half speed at first
  if (DEBUG) Serial.print(F("initialize SD card... "));

  result = sd.begin(SD_CS, SPI_HALF_SPEED); // 1 for success

  if (result != 1) // Problem initializing the SD card
  {
    if (DEBUG) Serial.println(F("error, halting"));
  }
  else if (DEBUG) Serial.println(F("success!"));

  // =======================================================================//
  // ! Start up the MP3 library                                             //
  // =======================================================================//
  if (DEBUG) Serial.print(F("initialize MP3 chip... "));

  result = MP3player.begin(); // 0 or 6 for success

  // Check the result, see the library readme for error codes.
  if ((result != 0) && (result != 6)) // Problem starting up
  {
    if (DEBUG)
    {
      Serial.print(F("error code "));
      Serial.print(result);
      Serial.println(F(", halting."));
    }

  }
  else if (DEBUG) Serial.println(F("success!"));

  // =======================================================================//
  // ! Read directories                                                     //
  // =======================================================================//
  readRootDirnames();

  // =======================================================================//
  // ! Set initial volume                                                   //
  // =======================================================================//
  // Set the VS1053 volume. 0 is loudest, 255 is lowest (off):
  MP3player.setVolume(50, 50);

  // =======================================================================//
  // ! Turn on the amplifier chip                                           //
  // =======================================================================//
  digitalWrite(EN_GPIO1,HIGH);

  delay(2);
} // /setup()
// ***********************************************************************//



// ******************************* LOOP **********************************//

// ***********************************************************************//
void loop()
{

  volatile boolean detectedInteraction = false;

  if ( buttonLastChecked == 0 ) // see if this is the first time checking the buttons
    buttonLastChecked = millis() + BUTTONDELAY; // force a check this cycle

  // =======================================================================//
  // ! Check voltage divider inputs                                         //
  // =======================================================================//
  if ( millis() - buttonLastChecked > BUTTONDELAY ) { // make sure a reasonable delay passed
    int buttNum;

    // ========================== DIRECTORY ================================//
    // check if the directory switch is triggered
    if ( buttNum = buttonPushed(SET_DIRECTORY_PIN)) {

      // try to start playing from new directory if trigger is new
      if (buttNum != last_dir) {
        if (DEBUG) {
          Serial.print(F("DIRECTORY ")); Serial.print(buttNum); Serial.println(F(" was triggered."));
        }
        last_dir = buttNum;
        detectedInteraction = true;

        // Do we have a dirname for this trigger?
        if (!dirname[buttNum - 1])
        {
          if (DEBUG)
            Serial.println(F("no directory for that trigger"));
        }
        else // We do have a directory for this trigger!
        {

          if (DEBUG)
          {
            Serial.print(F("got new trigger with directory "));
            Serial.println(buttNum);
          }

          // If a file is already playing, stop the playback before playing the new file.
          if (MP3player.isPlaying())
          {
            stopPlayback();
          }

          // read filenames of triggered directory ()
          readFilenames(buttNum);

        }
      }
      else {
        // not a new button was trigger, but trigger is still on

        // Play next track if music is not playing
        if (!MP3player.isPlaying())
        {
          playNextTrack();
        }

      }

    } else {
      // no directory triggered at all
      if (DEBUG) Serial.println(F("No DIRECTORY triggered at all."));
      if (MP3player.isPlaying())
      {
        stopPlayback();
        detectedInteraction = true;
      }
      last_dir = 0;
      last_file = 0;
    }

    // ========================== VOLUME ================================//
    // check if the volume voltage divider has changed

    if ( buttNum = buttonPushed(SET_VOLUME_PIN)) {

      // change volume if trigger is new
      if (buttNum != target_vol_range) {

        if (DEBUG) {
          Serial.println(F("// ***************** VOLUME *******************//"));
          Serial.print(F("Volume Range ")); Serial.print(buttNum); Serial.println(F(" was triggered."));
        }

        target_vol_range = buttNum;
        setTargetVolumeByRange(buttNum);

        detectedInteraction = true;

      }

    }

    buttonLastChecked = millis(); // reset the lastChecked value
  }

  // =======================================================================//
  // ! Check switch inputs                                                  //
  // =======================================================================//
  int previousTrackSwitchState = digitalRead(PREVIOUS_TRACK_SWITCH_PIN);
  int nextTrackSwitchState = digitalRead(NEXT_TRACK_SWITCH_PIN);

  // play new track if PREVIOUS switch has been triggered
  // only do this if debug == false as trigger is used by serial monitoring as well otherwise!
  if (!DEBUG  && last_dir > 0 && previousTrackSwitchState == LOW && !previousTrackSwitchOn) {
    if (DEBUG) Serial.println(F("// ***************** play PREVIOUS track triggered *******************//"));
    if (MP3player.currentPosition() >= 3000) { // has played more than 3 seconds of track
      // go to beginning of track
      //MP3player.skipTo(0); // !PROBLEM: skipping doesn't reset position to 0!
      // so basically, play same song again
      stopPlayback();
      playMp3File(last_file);
    } else {
      playPreviousTrack();
    }
    previousTrackSwitchOn = true;
  } else {
    previousTrackSwitchOn = false;
  }

  // play new track if NEXT switch has been triggered
  if (last_dir > 0 && nextTrackSwitchState == LOW && !nextTrackSwitchOn) {

    if (DEBUG) Serial.println(F("// ***************** play NEXT track triggered *******************//"));
    playNextTrack();
    nextTrackSwitchOn = true;
  } else {
    nextTrackSwitchOn = false;
  }

  if (nextTrackSwitchState == LOW) {
    detectedInteraction = true;
  }

  // =======================================================================//
  // ! Handle sleep mode                                                    //
  // =======================================================================//
  if (!detectedInteraction && !MP3player.isPlaying()){;
    
    if (millis() - sleepCounter >= SLEEP_DELAY){
      //Serial.print(F(" ++++++++ Sleep Counter ++ :")); Serial.print(sleepCounter);
      //Serial.print(F(" ++++++++ Sleep DELAY ++ :")); Serial.println(SLEEP_DELAY);
      if (DEBUG) Serial.println(F("// ******** GO TO SLEEP NOW !!!! *******//"));
     
      sleepCounter = millis();
      sleepNow();
    }
    
  }else{
    sleepCounter = millis();
  }

  
  // =======================================================================//
  // ! Adjust volume                                                        //
  // =======================================================================//
  adjustVolume();

}// loop()
// ***********************************************************************//



// =======================================================================//
// ! MP3 Helper Functions                                                 //
// =======================================================================//
void stopPlayback() {

  if (DEBUG) Serial.println(F("stopping playback"));

  MP3player.stopTrack();

}

void playNextTrack() {

  if ( (last_file + 1) <= MAX_NUM_FILES && filename[last_file] != "") {
    MP3player.stopTrack();
    last_file++;
    playMp3File(last_file);
  }

}

void playPreviousTrack() {

  if ( (last_file - 1) >= 0 && filename[last_file - 2] != "") {
    MP3player.stopTrack();
    last_file--;
    playMp3File(last_file);
  }

}

void playMp3File(byte fileNum) {

  // Play the filename associated with the index
  // (If a file is already playing, this command will fail
  //  with error #2).

  result = MP3player.playMP3(filename[fileNum - 1]);

  if (result == 0) last_file = fileNum;  // Save playing trigger

  if (DEBUG)
  {
    if (result != 0)
    {
      Serial.print(F("error "));
      Serial.print(result);
      Serial.print(F(" when trying to play track "));
    }
    else
    {
      Serial.print(F("playing "));
    }
    Serial.println(filename[fileNum - 1]);
  }
}


void setTargetVolumeByRange(byte val) {
  target_vol = map(val, 0, 5, 0, 60);
  target_vol = constrain(target_vol, 0, 60);

  if (DEBUG) {
    Serial.print(F("Setting target volume to: "));
    Serial.println(target_vol);
  }

}

void adjustVolume() {

  if (volume < target_vol) {
    volume ++;
    //    if (DEBUG) {
    //      Serial.print(F("Volume = "));
    //      Serial.println(volume);
    //    }
  } else if (volume > target_vol) {
    volume --;
    //    if (DEBUG) {
    //      Serial.print(F("Volume = "));
    //      Serial.println(volume);
    //    }
  }

  MP3player.setVolume(volume, volume);

}

// =======================================================================//
// ! Filesystem Helper Functions                                          //
// =======================================================================//
void readRootDirnames() {

  byte index;
  char tempdirname[13];

  // Now we'll access the SD card to look for directories
  if (DEBUG) Serial.println(F("reading root directory"));

  // Start at the first file in root and step through all of them:
  sd.chdir("/", true);

  while (file.openNext(sd.vwd(), O_READ))
  {
    // get dirname
    file.getFilename(tempdirname);

    // Does the filename start with char '1' through '5' and is a directory?
    if (tempdirname[0] >= '1' && tempdirname[0] <= '5' && file.isDir())
    {
      // Yes! subtract char '1' to get an index of 0 through 4.
      index = tempdirname[0] - '1';

      // Copy the data to our dirname array.
      strcpy(dirname[index], tempdirname);

      if (DEBUG) // Print out file number and name
      {
        Serial.print(F("found an entry with a leading "));
        Serial.print(index + 1);
        Serial.print(F(": "));
        Serial.println(dirname[index]);
      }
    }
    else if (DEBUG)
    {
      Serial.print(F("found an entry w/o a leading number: "));
      Serial.println(tempdirname);
    }

    file.close();
  }


  if (DEBUG)
    Serial.println(F("done reading root directory"));

  if (DEBUG) // List all the files we saved:
  {
    for (byte x = 0; x <= 4; x++)
    {
      Serial.print(F("trigger "));
      Serial.print(x + 1);
      Serial.print(F(": "));
      Serial.println(dirname[x]);
    }
  }

}

void readFilenames(byte dirNum) {
  stopPlayback();

  char tempfilename[13];
  byte index = 0;

  // delete previously stored filenames 
  memset(filename,0,sizeof(filename));

  // Now we'll access the SD card to look for directories
  if (DEBUG) {
    Serial.print(F("reading directory: "));
    Serial.println(dirname[dirNum - 1]);
  }

  // Start at the first file in directory and step through all of them:
  sd.chdir("/", true);
  if (!sd.chdir(dirname[dirNum - 1], true)) {
    if (DEBUG) Serial.println(F("chdir failed for directory "));
  }
  else {
    // directory exists, so read files
    file.close();
    file.rewind();
    while (file.openNext(sd.vwd(), O_READ))
    {

      // get filename
      file.getFilename(tempfilename);

      // Copy the data to our filename array is name doesn't start with "_"
      if (tempfilename[0] != 95) {
        strcpy(filename[index], tempfilename);
        if (DEBUG) // Print out file number and name
        {
          Serial.print(F("found a file: "));
          Serial.println(filename[index]);
        }
        index++;
      }

      file.close();
    }

  }

  if (DEBUG) {
    Serial.print(F("done reading directory "));
    Serial.println(dirname[dirNum - 1]);

    // List all the files we saved:
    for (byte x = 0; x < MAX_NUM_FILES; x++)
    {
      Serial.print(F("file "));
      Serial.print(x);
      Serial.print(F(": "));
      Serial.println(filename[x]);
    }
  }

  // start playing first file
  last_file = 0;
  playMp3File(0);
}

// =======================================================================//
// ! Read Voltage Divider input                                           //
// =======================================================================//

int buttonPushed(byte pinNum) { //  Read voltage divider
  int val = 0;         // variable to store the read value
  pinMode(pinNum, INPUT);
  digitalWrite((pinNum), HIGH); // enable the 20k internal pullup
  val = analogRead(pinNum);   // read the input pin

//  if (DEBUG) {
//    Serial.print(F("pinNum: "));
//    Serial.print(pinNum);
//    Serial.print(F(", val: "));
//    Serial.println(val);
//  }

  // we don't use the upper position because that is the same as the
  // all-open switch value when the internal 20K ohm pullup is enabled.
  //  if( val >= 923 and val <= 1023 ){
  //    if (DEBUG) {
  //      Serial.println(F("switch 0 pressed/triggered"));
  //    }
  //    return 0;
  //  } else

  if ( val >= 780 and val <= 880 ) { // 830
    if (DEBUG) {
      Serial.print(pinNum);
      Serial.println(F(": switch 1 pressed/triggered"));
    }
    return 1;
  }
  else if ( val >= (630 - ERROR_WINDOW) and val <= (630 + ERROR_WINDOW) ) { // 630
    if (DEBUG) {
      Serial.print(pinNum);
      Serial.println(F(": switch 2 pressed/triggered"));
    }
    return 2;
  }
  else if ( val >= (430 - ERROR_WINDOW) and val <= (430 + ERROR_WINDOW) ) { // 430
    if (DEBUG) {
      Serial.print(pinNum);
      Serial.println(F(": switch 3 pressed/triggered"));
    }
    return 3;
  }
  else if ( val >= (230 - ERROR_WINDOW) and val <= (230 + ERROR_WINDOW) ) { // 230
    if (DEBUG) {
      Serial.print(pinNum);
      Serial.println(F(": switch 4 pressed/triggered"));
    }
    return 4;
  }
  else if ( val >= 0 and val <= (20 + ERROR_WINDOW) )  {
    if (DEBUG) {
      Serial.print(pinNum);
      Serial.println(F(": switch 5 pressed/triggered"));
    }
    return 5;
  }
  else
    return 0;  // no button found to have been pushed
}

// =======================================================================//
// ! Sleep Mode and Interrupt Helper Functions                            //
// =======================================================================//
void myAttachInterrupt() {
  cli();    // switch interrupts off while messing with their settings
  //PCIFR  |= 0x02; // clear any outstanding interrupt
  PCIFR  |= 0b00000110; // clear any outstanding interrupt
  //PCICR = 0x02;         // Enable PCINT1 interrupt
  PCICR |= 0b00000110;   // Enable port C & D (PCINT1_vect & PCINT2_vect) interrupts
  PCMSK1 |= 0b00110001;  // turn on pins A0, A4 & A5
  PCMSK2 |= 0b00000011;    // turn on pins D0 & D1
  sei();
}

void myDetachInterrupt() {
  cli();    // switch interrupts off while messing with their settings
  PCIFR  |= 0; // clear any outstanding interrupt
  PCICR = 0;         // Disable PCINT1 interrupt
  PCMSK1 = 0;
  PCMSK2 = 0;
  sei();
}

ISR(PCINT1_vect) {    // Interrupt service routine. Every single PCINT8..14 (=ADC0..5) change
  // will generate an interrupt: but this will always be the same interrupt routine

 // Nothing to be done here. We only wanted to wake up from sleep.
}

ISR(PCINT2_vect){}    // Port D, PCINT16 - PCINT23 (D0-...)

void sleepNow()         // here we put the arduino to sleep
{
   
  if(DEBUG){
    Serial.println("Sleep Now!");
  }
  /* Now is the time to set the sleep mode. In the Atmega8 datasheet
   * http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf on page 35
   * there is a list of sleep modes which explains which clocks and
   * wake up sources are available in which sleep mode.
   *
   * In the avr/sleep.h file, the call names of these sleep modes are to be found:
   *
   * The 5 different modes are:
   *     SLEEP_MODE_IDLE         -the least power savings
   *     SLEEP_MODE_ADC
   *     SLEEP_MODE_PWR_SAVE
   *     SLEEP_MODE_STANDBY
   *     SLEEP_MODE_PWR_DOWN     -the most power savings
   *
   * For now, we want as much power savings as possible, so we
   * choose the according
   * sleep mode: SLEEP_MODE_PWR_DOWN
   *
   */
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // sleep mode is set here

  sleep_enable();          // enables the sleep bit in the mcucr register
  // so sleep is possible. just a safety pin

  /* Now it is time to enable an interrupt. We do it here so an
   * accidentally pushed interrupt button doesn't interrupt
   * our running program.
   */
  myAttachInterrupt();

  sleep_mode();            // here the device is actually put to sleep!!
  // THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP

  sleep_disable();         // first thing after waking from sleep:
  // disable sleep...
  myDetachInterrupt();      // disables interrupt

  if(DEBUG){
    Serial.println("Woken up!");
  }
}

Credits

Steffi tour j0iqfww3in
Steffi N
1 project • 1 follower
Contact

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Sign up / LoginProjectsPlatformsTopicsContestsLiveAppsBetaBlog