Home MP3 Player

 

The SDFat library has changed since I wrote this. The function void listLfn(SdBaseFile* dirFile) no longer seems to be used... I will investigate more some time...

/*  MP3 Player built around the Waytronic Electronics WT5001M03-28P Module. *
    This module doesn't have a built-in SD card holder so allows the size of
    the SD card to be chosen. It also allows the Arduino to read the files on
    the card.         (c)2015   vwlowen.co.uk    */ 
    

// Use SdFat for long file names.
#include <SdFat.h>                           // https://github.com/greiman/SdFat-beta

SdFat SD;

#include <IRremote.h>                        // https://github.com/shirriff/Arduino-IRremote
#include <EEPROM.h>

#include <Adafruit_GFX.h>                    // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_QDTech.h>                 // https://github.com/zigwart/Adafruit_QDTech

#include <SPI.h>

#include <SoftwareSerial.h>
SoftwareSerial mp3(0, 8);                    // Define Software Serial pins for 'mp3' RX, TX.

#define sclk  13                             // SPI pins (ATmega328) for LCD.
#define mosi  11   
#define cs    9                              // Other control pins for LCD.      
#define dc    7      
#define rst   6     

Adafruit_QDTech tft = Adafruit_QDTech(cs, dc, rst);  // Invoke LCD library object instance.


int pinEnable_MP3 = 2;               // HIGH connects SD card to MP3 module. LOW connects it to Arduino.
int pinBusy = 3;                     // Busy signal from MP3 module.
int pinReset_MP3 = 4;                // Reset signal to MP3 module.

int CARD_CS = 10;                    // SD Card_select from Arduino (SPI SS pin).  

int pinIR = 5;                       // Input from Infra-Red receiver.
IRrecv irrecv(pinIR);                // Start Infra-Red library.
decode_results results;              // Variable structure for results from IR receiver.

// Defines for my IR remote. These are the raw values returned from *MY*  spare IR remote.
// Use the Arduino IRrecvDump example program to get the values from your remote.

#define VolUp 0x81CEA05F
#define VolDown 0x81CE609F
#define PlayPause 0x14E10EF
#define Stop 0x14E906F
#define Enter 0x81CE52AD

#define RandomPlay 0x14E807F

#define Next  0x81CE00FF
#define Prev  0x81CE20DF

#define Zero  0x81CE02FD
#define One  0x81CEE817
#define Two  0x81CE18E7
#define Three  0x81CE9867
#define Four  0x81CE58A7
#define Five  0x81CED827
#define Six  0x81CE38C7
#define Seven  0x81CEB847
#define Eight  0x81CE7887
#define Nine  0x81CEF807

#define Overrun 0xFFFFFFFF              // Returned when IR signal code is not recognized.

// Variables to build up a three-digit number with eah IR Remote press or after timeout.
int digit = -1;
int dig1 = -1;
int dig10 = -1;
int dig100 = -1;
int digCount = 0;

int number;                             // Flag shows a digit was received from the IR rather than
boolean isNumber = true;                // a code for  Vol Up, Vol Down, Next, Stop etc.

unsigned long int irTimer = 0;          // Timer for IR timeout. A 3-digit number (with leading zeros)
int irTimeout = 2000;                   // will be calculated from the digits already received.

unsigned long int textTimer;            // Timer for scrolling text
int textTimeout = 0;                    // Higher number means slower scroll.

unsigned long int volumeTimer;          // Timer for length of time to display volume value on LCD.

byte mp3Volume = 20;                    // Default volume setting for MP3 module.
int MaxTracks = 99;                     // Arduino will read SD card to get actual number of tracks.


// File-playing flags
boolean mp3Changed = false;             // A new track number has been selected.
boolean isRandom = false;
boolean isPaused = false;
boolean isStopped = true;
boolean firstTrack = true;

// Variables for scrolling filename on display

String longName;                        // Long File name obtained from reading the SD card.
int displayWidth = 13;                  // Width of LCD with size 2 letters.
int offset = 0;



void setup() {

  pinMode(pinReset_MP3, OUTPUT);
  digitalWrite(pinReset_MP3, HIGH);
   
  pinMode(pinEnable_MP3, OUTPUT);
  digitalWrite(pinEnable_MP3, LOW);         // Disconnect MP3 Module from SD card.
  
  pinMode(pinBusy, INPUT);
  
  randomSeed(analogRead(0));               // Initialize random numbers for 'Random Play' function.
    
  tft.init();                              // Initialize display
   
  tft.setRotation(1);	                   // LCD screen rotation: 0 - Portrait, 1 - Landscape
  tft.fillScreen(QDTech_BLACK);            // Clear LCD.
  tft.setTextWrap(false);
                    
  tft.setTextColor(QDTech_WHITE);          
           
  while (!SD.begin(CARD_CS)) {             // Initialize SPI bus for SD card.      
    tft.setCursor(10,10);  
    tft.print("Insert Card");              // Display while no card is inserted.
  }
  
  tft.fillScreen(QDTech_BLACK);  
  tft.setCursor(10,10);
  tft.print("Reading Card");    

  listLfn(SD.vwd(), -1);  // Long file names. Parameter -1 means get MaxTracks.
 
  tft.fillScreen(QDTech_BLACK);

  tft.setCursor(0,0);
  tft.setTextColor(QDTech_WHITE);
  tft.setTextSize (2);
  tft.print("MP3 Player ");
  tft.setTextSize(1);
  tft.setCursor(130, 10);
  tft.setTextColor(QDTech_BLUE);  
  tft.print(MaxTracks);                    // Print total number of tracks found on SD card.

  irrecv.enableIRIn();                     // Start the IR receiver
   
   mp3.begin(9600);                        // Initialize the serial communication to the MP3 Module.
   
   mp3Volume = EEPROM.read(0);             // Read Volume value from EEPROM.
   if (mp3Volume > 31) mp3Volume = 15;     // Keep Volume value if EEPROM hasn't been set yet.
   
   setStop();                              // Make sure the MP3 Module isn't playing a track.
   setVolume();                            // Set MP3 Module volume (0 - 31).

   number = 0;                             // Initial track number.
   showNumber();                           // Display track number as three digits (000).
}

void getTrackName(int num) {
   digitalWrite(pinEnable_MP3, LOW);       // Switch SD card over to Arduino SPI. 
   delay(50);  
   SD.begin(CARD_CS);
  
   listLfn(SD.vwd(), num);                 // List 'num' number of files & get long filename
   
   digitalWrite(pinEnable_MP3, HIGH);      // Switch SD card back to MP3 Module. 
   delay(50);  
   digitalWrite(pinReset_MP3, LOW);        // Reset MP3 Module. (Required after losing connection
   delay(10);                              // to SD card.
   digitalWrite(pinReset_MP3, HIGH);
   delay(800);                             // Delay to allow MP3 Module to initialize itself.
   setVolume();                            // Restore Volume. (Defaults to maximum after reset).
   
}

// Routine to work out which code was received from the IR receiver.

void irConvert() {
  digit = -1;
  switch (results.value) {
    
    case Next: if ((!isStopped) && (!isPaused)) {
                 number++;
                 mp3Changed = true; 
               }
               break;
               
    case Prev: if ((!isStopped) && (!isPaused)) {
                 number--;
                 mp3Changed = true; 
               }
               break;    
    
    case Zero: digit = 0; break;
    case One: digit = 1; break;
    case Two: digit = 2; break;
    case Three: digit = 3; break;
    case Four: digit = 4; break;
    case Five: digit = 5; break;
    case Six: digit = 6; break;
    case Seven: digit = 7; break;
    case Eight: digit = 8; break;
    case Nine: digit = 9; break;
    
    case Overrun: break;//digit = -2;
    default: break;//digit = -1;    
    
    case RandomPlay:  isRandom = !isRandom;
                      tft.setCursor(0, 90);
                      tft.fillRect(0, 90, 100, 105, QDTech_BLACK);   // Clear 
                      tft.setTextColor(QDTech_YELLOW);
                      tft.setTextSize (1);
                      if (isRandom) {
                        tft.print("Random Play");
                      }
                      else if (isStopped) {
                        tft.print("Stop");
                      } else
                        tft.print("Play");
                      break;
                      
    case VolUp: if (mp3Volume < 31) {
                  mp3Volume++;
                  setVolume();
                  EEPROM.write(0, mp3Volume);
                }
                break;
                
    case VolDown: if (mp3Volume > 0) {
                  mp3Volume--;
                  setVolume();
                  EEPROM.write(0, mp3Volume);
                }
                break;   
                
    case PlayPause: isPaused = !isPaused;
                    setPlayPause(); 
                    break;   
                    
    case Stop: setStop(); 
                isStopped = true;  
                isPaused = true;  
                break;
  }
  
  if ( digit > -1){ 
    isNumber = true;
    tft.setTextSize(2);
    tft.setCursor(100+(digCount * 11), 100);
    tft.setTextColor(QDTech_BLUE);
    tft.print(digit);
  } 
  else {
    isNumber = false;
    return;
  }
  
  // If a number is received, work out if it's the 1st, 2nd or 3rd of a sequence and
  // create a 3-digit number if it's the 3rd.
  
  if ((isNumber)) {
    irTimer = millis();
    if (digCount == 0) { 
      dig1 = digit;
      digCount = 1;
    } 
    else if (digCount == 1) {
        dig10 = digit;
        digCount = 2;
    } 
    else if (digCount = 2) {
      dig100 = digit;
      digCount = 3;
      number = (100*dig1) + (10*dig10) + dig100;

     showNumber();
     isNumber = false;
     mp3Changed = true;
    }
  }  
}

void resetCounters() {
  digCount = 0;
  dig1 = 0;
  dig10 = 0;
  dig100 = 0;
  digit = -1;
   
  isStopped = false;
  isPaused = false;
}

void showNumber() {
      
  resetCounters();
  
  tft.fillRect(85, 100, 150, 100, QDTech_BLACK);  // Clear input digits
  tft.fillRect(0, 50, 180, 20, QDTech_BLACK);  // Clear title text
  tft.setCursor(0,30);
  tft.fillRect(0, 30, 50, 14, QDTech_BLACK);   // Clear track number
  tft.setTextColor(QDTech_YELLOW);
  tft.setTextSize (2);
  if (number < 100) tft.print("0");
  if (number < 10) tft.print("0");
  tft.println(number);   
      
  tft.setCursor(0, 90);
  tft.fillRect(0, 90, 100, 9, QDTech_BLACK);   // Clear 
  tft.setTextColor(QDTech_YELLOW);
  tft.setTextSize (1);
  if ((isStopped) || (number == 0)){
     tft.print("Stop");
  } else
  isRandom ? tft.print("Random") : tft.print("Play");        
}

void setVolume() {                                   // Send the Volume value to the MP3 Module.
   mp3.write(0x7E); 
   mp3.write(0x03); 
   mp3.write(0xA7);
   mp3.write(mp3Volume); 
   mp3.write(0x7E);  
   tft.setCursor(0, 110);
   tft.fillRect(0, 110, 60, 9, QDTech_BLACK);
   tft.setTextColor(QDTech_BLUE);
   tft.setTextSize(1);
   tft.print("Volume: "); tft.print(mp3Volume); 
   volumeTimer = millis();                           // Set timer for displaying Volume on LCD.
}

void setPlayPause() {                                // Send Play/Pause to MP3 Module.
  if (number != 0) {
    mp3.write(0x7E); 
    mp3.write(0x02); 
    mp3.write(0xA3);
    mp3.write(0x7E);  
    tft.setCursor(0, 90);
    tft.fillRect(0, 90, 100, 9, QDTech_BLACK);   // Clear 
    tft.setTextColor(QDTech_YELLOW);
    tft.setTextSize (1);
    if (isPaused){ 
       tft.print("Pause");
    } else {
       isRandom ? tft.print("Random") : tft.print("Play");  
    }
  } else {
    isRandom ? number = random(1, MaxTracks+1) : number = 1;
    setPlayTrack(number);
  }
}

void setPlayTrack(int trk) {                     // Tell MP3 Module to play specified track number.
   mp3.write(0x7E); 
   mp3.write(0x04); 
   mp3.write(0xA0);             
   mp3.write((byte)0x00);                        // Track nUmber Hi Byte - always zero for us.

   mp3.write(trk);                               // Track number Lo Byte.
   mp3.write(0x7E);  
}

void setStop() {                                 // Tell MP3 Module to stop playing.
   mp3.write(0x7E); 
   mp3.write(0x02); 
   mp3.write(0xA4);
   mp3.write(0x7E);  
   tft.setCursor(0, 90);
   tft.fillRect(0, 90, 100, 9, QDTech_BLACK);   // Clear 

   tft.setTextColor(QDTech_YELLOW);
   tft.setTextSize (1);
   tft.print("Stop");  
}
   
void loop() {
   
   if (millis() > (volumeTimer + 5000)) {                  // Check Volume display timer.
       tft.setCursor(0, 110);
       tft.fillRect(0, 110, 60, 9, QDTech_BLACK);
   }
   
   if (irrecv.decode(&results)) {                          // Check if IR receiver has any results.
     if (results.value != Overrun) {
       irConvert();                                        // Decypher results.
     }
     irrecv.resume();                                      // Ready to receive the next value.
   }
  delay(100);
  
  // If a number is received from the IR, determine if it's the 1st or 2nd of a sequence
  // AND, if waiting for the 2nd and/or 3rd has timed out, calculate 3-digit number based on the
  // digits that have already been received.
  
  if ((digCount == 1) || (digCount == 2)) {
              
    if (millis() > (irTimer + irTimeout)) {        // Timed out so build number out of digits
      if (digCount == 1) {                         // that HAVE been received.
        number = dig1;
      }

      if (digCount == 2) {
        number = (10*dig1) + dig10;
      }
      if (digCount == 3) {
         number = (100*dig1) + (10*dig10) + dig100;
      }
      resetCounters();                  // Number has been determined so clear temporary variables.
      isRandom = false;
      isNumber = false;                 // Reset isNumber flag ready for next sequence.
      mp3Changed = true;                // New track number has been selected so set 'changed' flag.
    }
  }     // If module isn't busy (playing) and it's not stopped or paused, increment track number unless...
        // ...a new track number was received from the IR.
  else if ((digitalRead(pinBusy) == LOW) && (!isPaused) && (!isStopped) && (!firstTrack)){
      isRandom ? number = random(1, MaxTracks + 1) : number++;
      if (number > MaxTracks) number = 1;
      mp3Changed = true;
  } 

  if ((number > 0) && (mp3Changed) && (!isPaused)  && (!isStopped) ) {
     getTrackName(number);                   // Read the SD card to get the filename of the track number
     textTimer = millis();

     showNumber();
     digitalWrite(pinEnable_MP3, HIGH);      // Player bus = on  (should already be on anyway)
     setPlayTrack(number);                   // Tell MP3 Module to play track
     delay(2000);                            // Allow time for the MP3 Module to begin playing
     mp3Changed = false;
     firstTrack = false;
  }
   // Scroll track filename on LCD display.
   
    if ((digitalRead(pinBusy) == HIGH) && (millis() > (textTimer + textTimeout)) ){
      tft.setCursor(180,50);

      tft.setTextColor(QDTech_GREEN);
      tft.setTextSize (2);
      
      String t = "";
      for (int i = 0; i < displayWidth; i++)
         t += longName.charAt((i + offset) % longName.length());
      tft.setCursor(0, 50); 
      tft.fillRect(0, 50, 180, 20, QDTech_BLACK);
      tft.print(t);
      offset > longName.length() ? offset = 2 : offset++;
      
      textTimer = millis();
   } 

}


// List files on SD card with long filenames. If trackNumber = -1, then list all to
// get number of tracks (MaxTracks). If trackNumber = > 0, list upto that number to get
// long filename of that trackNumber.

void listLfn(SdBaseFile* dirFile,  int trackNumber) {
  uint8_t offset[] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30};
  char name[13];
  char lfn[131];
  bool haveLong = false;
  dir_t dir;
  uint8_t i;
  uint8_t lfnIn = 130;
  uint8_t ndir;
  uint8_t sum;
  uint8_t test;
  boolean found = false;
  
  if (trackNumber == -1) MaxTracks = 0;
  int trackCount = 0;

  dirFile->rewind();
  while ((dirFile->read(&dir, 32) == 32) && !found){
    if (DIR_IS_LONG_NAME(&dir)) {
      if (!haveLong) {
        if ((dir.name[0] & 0XE0) != 0X40) continue;
        ndir = dir.name[0] & 0X1F;
        test = dir.creationTimeTenths;
        haveLong = true;
        lfnIn = 130;
        lfn[lfnIn] = 0;
      } else if (dir.name[0] != --ndir || test != dir.creationTimeTenths) {
        haveLong = false;
        continue;
      }
      char *p = (char*)&dir;
      if (lfnIn > 0) {
        lfnIn -= 13;
        for (i = 0; i < 13; i++) {
          lfn[lfnIn + i] = p[offset[i]];
        }
      }
    } else if (DIR_IS_FILE_OR_SUBDIR(&dir) 
      && dir.name[0] != DIR_NAME_DELETED 
      && dir.name[0] != DIR_NAME_FREE) {
      if (haveLong) {
        for (sum = i = 0; i < 11; i++) {
           sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + dir.name[i];
        }
        if (sum != test || ndir != 1) haveLong = false;
      }
 //     SdFile::dirName(dir, name);
      if (dir.reservedNT) {
        bool dot = false;
        for (char *p = name; *p; p++) {
          if (*p == '.') {
            dot = true;
            continue;
          }
          if (dot && (dir.reservedNT & 0X8) 
            || !dot && (dir.reservedNT & 0X10)) {
              *p = tolower(*p);
            }
        }
      }
  
      if (haveLong) {
        longName = lfn + lfnIn;
        if (trackCount == trackNumber) {
          longName = longName.substring(0, longName.lastIndexOf(".mp3")) + "... ";
          found = true;
        }
        if (trackNumber == -1) {
          if (longName != "System Volume Information") {
            MaxTracks++;
          }
        }
      }
      trackCount++;

      if (dir.name[0] == DIR_NAME_FREE) return;
      haveLong = false;
    }
      
  }
}






More to follow...

 


This site and its contents are © Copyright 2005 - All Rights Reserved.