The Start


Hi, What and Why

Plug it in

Rule number 1

Water Sensor

Sound Sensor

Joystick

Tri Colour LED

RTC (Real Time Clock) DS1302

RTC (Real Time Clock) DS3231

Matrix LED step 1

LCD

Stepper Motor

LCD revisited with PCF8574T

Humidity Sensor

Shift Register

RFID tags (RC-522)

7 Segment display

Ultrasonic distance sensor

5V regulator

analogRead and analogWrite

Wiring an Array of Switches

The next step


Other things I have bought

Infra red and Processing

Programming a separate arduino chip

Creating your own PCB

L293D for a DC motor

4 digit 7 segment display

Starting with motors

RF433 Wireless Comms

Sort a character array

More stuff


I2C devices (SDA,SCL)

I2C scanner

SPI devices (MOSI,MISO)

HMC5883L Compass

MMA7361 Accelerometer

Added projects


Message Display System

4WD robot car
4WD robot car II

4WD robot car COMPLETE

MP3 Player

MP3 Player

I got thoroughly fed up with cheap MP3 players 2 years ago, and built my own. It works so well for me. Project completed; December 2014. Updated January 2016

This project was great; there are a number of issues that you will also face, if you choose to investigate this more.

- The arduino has 2K of RAM, and the libraries use a significant amount of this. The code stores file names in RAM; this is limited. Currently, there's a max of 20 files per directory.
- Storing the filenames in RAM allows a sort function to display the mp3 files in sequence. This is essential for me, since I have audio books with chapters that obviously have to be played in sequence.
- The arduino SD library, which reads the SD card, uses 8.3 filename format. I looked at ways to support long filenames, and drew a blank once I understood more about it. Help yourself to solving this issue.
- The MP3 codec board has a very important bug in it. It boots into MIDI mode. More on this issue later; it's a biggie.
- The code used on the arduino Uno (development) worked differently on the Arduino Pro Mini. The code was written to deal with this; sending an MP3 file to the codec board several times forced the board to reset into mp3 mode on the Uno, not on the Pro Mini.

Here is what I built, and it works so beautifully for what I need.
I often update the code for this thing; if you would like the latest code version, then please just send me an email and ask for it.
try version 1.36 : here

MP3 Player pic 1 I used a cardboard hollow book as my casing for my mp3 player, with a suitable small hole drilled to allow the earphones out.
MP3 Player pic 2 Opening that nice looking box up; I used a cardboard box piece as a backing, and fixed my components using fishing wire.
MP3 Player pic 3 The battery (at the top) is a 7.2v 2000 mAh remote controlled car battery. It had a Tamiya ending, which I cut off and replaced with a standard thingy.
Below that, you can see a small but normal LCD screen.
Below that and in the middle, on the PCB, is the 5V transformer. The 7.2V battery comes into this, and 5V comes out of it. There are also 5 switches soldered in, for me to choose which song to play.
At the bottom, and from left to right, is : the vs1053 mp3 decoder with the headphones plugged in; the arduino; and velcro'ed to the side is the SD card holder.

2000 mAh lasts me about 3 days, and I have 2 batteries that I recharge in turn.

MP3 player components :
- 5 switches
- Arduino Uno
- 16x2 LCD screen
- SD card reader
- MP3 codec decoder
MP3 Player

Obviously, please copy some mp3 files onto the SD card first. Try a max of 10 ? And we're using the Arduino SD library, so fat32 format please.

The SD card you choose should be class 6 or above, class 10 is best. This is the speed that the card can read/write at. I tested a class 4 SD card; it could not be read fast enough, which caused stuttering when trying to play a file.

The first step is to plug in the SD card reader and the LCD screen, and test that these work.
I'll be using a normal development breadboard as a power distribution board and for the 5 switches, until I'm happy that the components work - then, I'll solder everything into a final and permanent project.
(Note: The power distribution board will take power from a battery; convert it to both 5V and 3.3V and provide ample solder points for me to power all the components).

Test 1 - the screen

Plug the screen in and test it like this :
LCD (16 x 2) with I2C controller.
SDA -> A4
SCL -> A5
Obvious Vcc (5v) and GND.
MP3 Player

Test the LCD screen works using this basic code.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup()
{
  lcd.init();                      // initialize the lcd 

  // Print a message to the LCD.
  lcd.backlight();
  lcd.print("Test Display I2C");
  lcd.setCursor(0,1);
  lcd.print("Version 1.0");

}

void loop()
{
}

Test 2 - the screen and the SD card

A bit more complicated; 2 GND pins, a 5v pin, a 3.3v pin, and the SPI pins (mosi, miso, sck and CS)

SD card attached; SPI pins as detailed here, 3.3v to the arduino and 5v/GND to the dvlpmnt board.
MOSI goes to pin 11
MISO goes to pin 12
SCK goes to pin 13

CS - connect to pin 5 !
MP3 Player

You can test that the SD card and the LCD are working, using the following code.
If it works, you will see on the LCD, the lines "Works line 1" and then the line "Yay the SD card".

Also, note the use of PROGMEM to store and display strings. This makes a big memory (RAM) saving.


#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SD.h>
#include <SPI.h>

#define SD_CS 5


LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

// ---------------------------------------
// Define our printable strings in PROGMEM
// ---------------------------------------
const prog_uchar welcomeLine1[] PROGMEM = "Pauls MP3 player";   // "String 0" etc are strings to store - change to suit.
const prog_uchar welcomeLine2[] PROGMEM = "Version 1.0";
const prog_uchar playingFile[] PROGMEM = "Playing :";
const prog_uchar volume[] PROGMEM = "Volume : ";
const prog_uchar paused[] PROGMEM = "Paused ...";
const prog_uchar cardError[] PROGMEM = "SD Card ERROR.";
const prog_uchar cardWorks[] PROGMEM = "YAY THE SD CARD !";

const prog_uchar restoring[] PROGMEM = "Restoring file";
const prog_uchar lowMemory[] PROGMEM = "LOW MEMORY !!!";

char myChar;    // Used throughout, to read a char/string from PROGMEM.
byte tempValue;

// ---------------------------------------------------------------------------------
// SELECT SD
// ---------------------------------------------------------------------------------
void SelectSD()
    {
      digitalWrite(SD_CS, LOW);
    }

void DeselectSD()
    {
      digitalWrite(SD_CS, HIGH);
    }



void setup()
{
  Serial.begin(9600);
  Wire.begin();
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
       // PAUL !!! These are the 2 lines that make it work/not work each time you do a soft reset !!!
  SPI.setClockDivider(SPI_CLOCK_DIV64);//slow SPI bus speed


  // SD card setup.
  pinMode(10, OUTPUT);    // PIN 10 MUST BE OUTPUT, EVEN IF UNUSED for the SD library functions to work
  pinMode(SD_CS, OUTPUT);


  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Works line 1");
  lcd.setCursor(0,1);
  lcd.print("Works line 2");
  delay(1000);
  
  SelectSD();
                              // ERROR WITH THE SD CARD ...
  if (!SD.begin(SD_CS)) 
    {
      lcd.clear();
      lcd.setCursor(0, 0);

      tempValue = 0;
      myChar = pgm_read_byte(cardError); // Necessary casts and dereferencing, just copy. 
      while (myChar != '\0')
           {
             lcd.print(myChar);
             myChar = pgm_read_byte_near(cardError + (++tempValue));
           }
     delay(1000);
     return;
    }
   else
    {
      lcd.clear();
      lcd.setCursor(0, 0);

      tempValue = 0;
      myChar = pgm_read_byte(cardWorks); // Necessary casts and dereferencing, just copy. 
      while (myChar != '\0')
           {
             lcd.print(myChar);
             myChar = pgm_read_byte_near(cardWorks + (++tempValue));
           }
     delay(1000);
     return;
    }
   

  
}


void loop()
{
}

Step 3 - the MP3 codec board

This MP3 codec board is one of the cheap variety from Hong Kong. I have got it to work, but it does contain some problems. If you want your mp3 codec board to work first time and completely, then you should consider buying a different board, such as those designed by Adafruit.
The major flaw in these cheap boards is that they start up in MIDI mode.
A second major flaw is that they don't respond as they should to changing bass, treble, or earphone acoustic mode. I haven't even bothered trying to make it record something.
I have got mine to work successfully to play an mp3 file and change the volume - all attempts to make it sing and dance have failed. I have written more on this separately. I spent weeks poring over spec sheets and researching other people's experiences.

Wiring it up:

You can see from the pic that we are starting to have a spaghetti bolognese of electronics. Hence the plan of developing this to check it all works before soldering switches and battery power supplies. Ignore the switches in this pic below until step-4. Just connect the MP3 codec board up.

From the MP3 codec board, connect MOSI MISO SCK 5V and GND to the same pins on the SD card.
Connect other pins as below.
MP3 Player

The underside of this MP3 decoder board tells you the 10 pins that should be connected. Do it like this :

5V - Connect to the SD card 5V pin.
5V - leave unconnected
DGND - Connect to the SD card GND pin.
MISO - Connect to the SD card MISO pin.
MOSI - Connect to the SD card MOSI pin.
SCK - Connect to the SD card SCK pin.
DREQ - Connect to Arduino Uno pin 3.
XRST - Connect to Arduino Uno pin 7.
XCS - Connect to Arduino Uno pin 9.
XDCS - Connect to Arduino Uno pin 8.

Step 4 - the switches

As a temporary (development) measure, the switches are wired into the breadboard.
Even more temporary, only 3 out of 5 switches are needed for a test - "up, down and select." "Volume and Options" can wait.

MP3 Player switches

Connect arduino pins A0, A1 and A2 into the breadboard, then via a resistor to GND. This ensures the switches on pins A0, A1 and A2 are pulled low whilst the program is running.
Connect another wire (pink, in this picture) to 5V. When you want to activate a switch, touch it gently to the switch wire. (push the wire into the breadboard, next to the switch wire is easiest). The resistor ensures that the current doesn't flow to GND; instead, the current flows at 5V into either A0, A1 or A2, sending that pin high. This is how a switch works.

THE CODE - TO MAKE IT ALL WORK

This code has been written by myself, and is (c) me, and I am proud of it. Please use it for yourself, and if you have the time to say Hi below, then that would be nice.

I have created a .ino file to download, here; and here is the code below also.

While you load the code, and test that it all works, there are some important discussion points on the next MP3 player page.

Here's the code :
// Author  : Paul Goodliffe
// Date    : Oct 2014
//
// Changes : 1.19 - added size of file (in blocks of 32)
//                - Using blocksInFile, prevent fast forward past end of file
//                - fixed bug in fast rewind (incorrect # of blocks subtracted to actual position)
//           1.20 - Only include directories and file suffix *.mp3; ignore others.
//           1.21 - Use button 5 to restore saved file/position
//
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SD.h>
#include <SPI.h>
#include <avr/pgmspace.h>
#include <VS1053.h>
#include <MemoryFree.h>
byte DIR =1;    // Is the file a directory, or a playable file ?
byte PLAY =2;    // Is the file a directory, or a playable file ?

char currentOpenDirectory[40] = "/";   // 40 is enough for 2 directories, a file, and the slashes
char thisFile[40];
File playFile;
File backupFile;
char backupFileName[] = "/backup.dat";

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
#define lcdDimmed 0
#define lcdLit 1
byte lcdStatus;
  

VS1053 player(/* cs_pin */ 9,/* dcs_pin */ 8,/* dreq_pin */ 3,/* reset_pin */ 7);


byte fileNumber = 1;        // This is between 1 and 50, reflectinq_pin */ g which message number is printed on the LCD
byte tempValue, i;
uint16_t tempValue16;
byte msgUpPin = A1;    // The switch to scroll messages UP - switch up/down as you want !
byte msgDownPin = A0;  // The switch to scroll messages DOWN
byte msgSelectPin = A2; // Pin number for SELECT switch
byte msgMenuPin = A3;    // Pin number for MENU switch
byte msgFifthPin = 6;    // Pin number for 5th switch. Usage ?
unsigned long lastButtonPress; // Used to prevent repeat button presses, and also to dim the display

unsigned long lastDecodeTime; // Used to show decode time every n seconds

unsigned long newFilePos; // Used in fast fwd / rewind
// ----------------------------------------------------------
// Menu list items: ensure MENU_END is the highest number !!
// ----------------------------------------------------------
#define MENU_NOTSET 0
#define MENU_VOL 1
#define MENU_END 2
byte wantedMenuStatus = MENU_NOTSET;
byte currentMenuStatus = MENU_NOTSET;
//
byte numFilesInArray = 0; // This is the number of files; max 10 per directory.
#define MAXINARRAY 20
char *fileName[MAXINARRAY];  // Number of files per directory. Pointers to mallocs.
byte fileType[MAXINARRAY];


    // #define STOP 0
    byte actionStatus;
    #define ACTION_BROWSING_MP3 2
    #define ACTION_SELECT_MP3 3
    #define ACTION_PLAYING_MP3 4
    #define ACTION_PAUSED_MP3 5
    #define ACTION_STOP_PLAYING 6
    #define ACTION_RESTORE 7
    #define ACTION_END_MP3 8
    
    //#define SELECT_DONE 0
    //#define SELECT_PLAY 1
  
byte wantedDisplayStatus;
byte currentDisplayStatus;
#define DISPLAY_WELCOME 1 
#define DISPLAY_BROWSING 2
#define DISPLAY_PLAYING 3
#define DISPLAY_PAUSED 4
#define DISPLAY_MENU 5

//    byte myStatus;
    byte noOfBytesRead;
    uint8_t dataBuffer[32];

    //unsigned long filePos = 0;
    //unsigned long fileSize = 0;
    char serialIn ;
    byte thisVolume = 60;
   
    unsigned long resetMillis;
    byte actuallyPlaying;
    byte failureToPlay = 0;
    unsigned long blocksPlayed;
    unsigned long lastBackupBlocks;
    unsigned long blocksInFile;
    char blocksPlayedStr[17];
    byte actionRestore = 0;
    
    /** Control Chip Select Pin (for accessing SPI Control/Status registers) */
    #define MP3_XCS 9

    /** Data Chip Select / BSYNC Pin */
    #define MP3_XDCS 8

    #define SD_CS 5

    //** Data Request Pin: Player asks for more data */
    #define MP3_DREQ 3
    #define SD_POWER 6
    #define MP3_HARDRESET 7

    /** VS10xx SCI Registers */
    #define SPI_MODE 0x0   /**< VS10xx register */
    #define SPI_STATUS 0x1   /**< VS10xx register */
    #define SPI_BASS 0x2   /**< VS10xx register */
    #define SPI_CLOCKF 0x3   /**< VS10xx register */
    #define SPI_DECODE_TIME 0x4   /**< VS10xx register */
    #define SPI_AUDATA 0x5   /**< VS10xx register */
    #define SPI_WRAM 0x6   /**< VS10xx register */
    #define SPI_WRAMADDR 0x7   /**< VS10xx register */
    #define SPI_HDAT0 0x8   /**< VS10xx register */
    #define SPI_HDAT1 0x9   /**< VS10xx register */
    #define SPI_AIADDR 0xa   /**< VS10xx register */
    #define SPI_VOL 0xb   /**< VS10xx register */
    #define SPI_AICTRL0 0xc   /**< VS10xx register Song number to play at power up*/
    #define SPI_AICTRL1 0xd   /**< VS10xx register */
    #define SPI_AICTRL2 0xe   /**< VS10xx register USED FOR LOUDNESS*/
    #define SPI_AICTRL3 0xf   /**< VS10xx register Play mode and misc config*/
    
    // SCI_MODE bits

const uint8_t SM_DIFF = 0;
const uint8_t SM_LAYER12 = 1;
const uint8_t SM_RESET = 2;
const uint8_t SM_OUTOFWAV = 3;
const uint8_t SM_EARSPEAKER_LO = 4;
const uint8_t SM_TESTS = 5;
const uint8_t SM_STREAM = 6;
const uint8_t SM_EARSPEAKER_HI = 7;
const uint8_t SM_DACT = 8;
const uint8_t SM_SDIORD = 9;
const uint8_t SM_SDISHARE = 10;
const uint8_t SM_SDINEW = 11;
const uint8_t SM_ADPCM = 12;
const uint8_t SM_ADCPM_HP = 13;
const uint8_t SM_LINE_IN = 14;
const uint8_t SM_CLK_RANGE = 15;

// ---------------------------------------
// Define our printable strings in PROGMEM
// ---------------------------------------
const prog_uchar welcomeLine1[] PROGMEM = "Pauls MP3 player";   // "String 0" etc are strings to store - change to suit.
const prog_uchar welcomeLine2[] PROGMEM = "Version 1.21";
const prog_uchar playingFile[] PROGMEM = "--> ";
const prog_uchar volume[] PROGMEM = "Volume : ";
const prog_uchar paused[] PROGMEM = "Paused ...";
const prog_uchar cardError[] PROGMEM = "SD Card ERROR.";
const prog_uchar restoring[] PROGMEM = "Restoring file";
const prog_uchar lowMemory[] PROGMEM = "LOW MEMORY !!!";

char myChar;    // Used throughout, to read a char/string from PROGMEM.

    /** Pull the VS10xx Control Chip Select line Low */
    void Mp3SelectControl()
    {
      digitalWrite(MP3_XCS, LOW);
    }

    /** Pull the VS10xx Control Chip Select line High */
    void Mp3DeselectControl()
    {
      digitalWrite(MP3_XCS, HIGH);
    }

    /** Pull the VS10xx Data Chip Select line Low */
    void Mp3SelectData()
    {
      digitalWrite(MP3_XDCS, LOW);
    }

    /** Pull the VS10xx Data Chip Select line High */
    void Mp3DeselectData()
    {
      digitalWrite(MP3_XDCS, HIGH);
    }

    void SelectSD()
    {
      digitalWrite(SD_CS, LOW);
    }

    void DeselectSD()
    {
      digitalWrite(SD_CS, HIGH);
    }
    
    
void WriteRegister(uint8_t _reg, uint16_t _value)
{
   // SPI.setClockDivider(SPI_CLOCK_DIV64); // PAUL !! TEST

  // DREQ MUST BE HIGH BEFORE YOU START !!!
  while ( digitalRead(MP3_DREQ) == LOW )
    {
      delayMicroseconds(1);
    }
  DeselectSD();
  Mp3DeselectData();
  Mp3SelectControl();
  delayMicroseconds(1); // tXCSS
  SPI.transfer(B10); // Write operation
  SPI.transfer(_reg); // Which register
  SPI.transfer(_value >> 8); // Send hi byte
  SPI.transfer(_value & 0xff); // Send lo byte
  Mp3DeselectControl();        // DEselect sets XDCS HIGH, thus ending the current operation.
  delayMicroseconds(1); // tXCSH
  awaitDataRequest();
  //  SPI.setClockDivider(SPI_CLOCK_DIV2); // PAUL !! TEST

}

void awaitDataRequest(void) 
  {
    while ( !digitalRead(MP3_DREQ) );
  }

// ----------------------------------------------------------------------------------------------
// SOFT RESET
// Soft Reset of VS10xx (Between songs) 
/*
In some cases the decoder software has to be reset. This is done by activating bit SM_RESET
in register SCI_MODE). Then wait for at least 2 micro-s, then look at DREQ. 
DREQ will stay down for about 22000 clock cycles, which means an approximate 1.8 ms 
delay if
VS1053b is run at 12.288 MHz. After DREQ is up, you may continue playback as usual

ALSO : If the user wants to powerdown VS1053b with a minimum power-off transient, set SCI_VOL to
0xffff, then wait for at least a few milliseconds before activating reset.*/
// ----------------------------------------------------------------------------------------------
void Mp3SoftReset()
    {
       //Serial.println("Soft Reset:");
      // SPI.setDataMode(SPI_MODE0);
      // SPI.setBitOrder(MSBFIRST);
       // PAUL !!! These are the 2 lines that make it work/not work each time you do a soft reset !!!
      SPI.setClockDivider(SPI_CLOCK_DIV64);//slow SPI bus speed
      SPI.transfer(0xFF);

       DeselectSD();
       Mp3DeselectData();
       Mp3SelectControl();
       awaitDataRequest();
       delay(1);
       WriteRegister(SPI_MODE, (uint16_t)(_BV(SM_RESET) | _BV(SM_SDINEW) | _BV(SM_LAYER12)) | _BV(SM_STREAM) );
       delay(1);
       /* Set clock register, doubler etc. */
       /* CHANGE 1 */
       awaitDataRequest();
       WriteRegister(SPI_CLOCKF, (uint16_t)0x8800);
   //   Mp3DeselectControl();
    //  SPI.setClockDivider(SPI_CLOCK_DIV2); //basically just set fast SPI bus speed //SPI_CLOCK_DIV2

       //Mp3WriteRegister(SPI_CLOCKF, 0xb3, 0xfe);  //VS1003
       //Mp3WriteRegister(SPI_CLOCKF, 0x88, 0x00);  //VS1058
       //PAUL !! 3 lines
       //DeselectSD();
       //Mp3DeselectControl();
       //Mp3SelectData();
       delay(1);
       WriteRegister(SPI_MODE, (uint16_t)0x0804);
       //WriteRegister(SPI_MODE, (uint16_t)(_BV(SM_RESET) | _BV(SM_SDINEW) | _BV(SM_LAYER12)) | _BV(SM_STREAM) );
       
      delay(1); /* One millisecond delay */
      awaitDataRequest();
       
      /***********************************ADDED AS TESTS*/
  //    DeselectSD();
  //    Mp3DeselectControl();
  //    Mp3DeselectData();
//      player.setVolume(0xff);
 //     WriteRegister(SPI_VOL, (uint16_t)0xffff);
      //Mp3SetVolume(0xff,0xff); //Declick: Immediately switch analog off
       /* Declick: Slow sample rate for slow analog part startup */
//       WriteRegister(SPI_AUDATA, (uint16_t)0x1000);
      //Mp3WriteRegister(SPI_AUDATA, 0, 10); /* 10 Hz */
      delay(100);
      /* Switch on the analog parts */
//      player.setVolume(0xfe);
      WriteRegister(SPI_VOL, (uint16_t)0xfefe);
 //     WriteRegister(SPI_BASS, (uint16_t)0xfefe);
      //Mp3SetVolume(0xfe,0xfe);
      //
     // WriteRegister(SPI_AUDATA, 0xAC45); // This is the value used for MIDI mode...
      delay(2);
      WriteRegister(SPI_AUDATA, (uint16_t)44101);
      delay(2);
      WriteRegister(SPI_DECODE_TIME, (uint16_t)0x00);
      delay(2);
       WriteRegister(SPI_AUDATA, 0x9C60);
      delay(2);

      player.setVolume(thisVolume); //  This should set the volume to prev value; check thisVolume.
      //WriteRegister(SPI_VOL, (uint16_t)thisVolume); // WRONG VAUE
      delay(2);
       SPI.setClockDivider(SPI_CLOCK_DIV2);//slow SPI bus speed
      SPI.transfer(0xFF);

    }

// ------------------------------------------------------------------------------
// TRANSFER DATA TO MP3 PLAYER
// Correct pin selection should be done in this routine. NB deselect the data line
// when finished.
// ------------------------------------------------------------------------------
    void TransferMP3Data(byte NumOfBytesToSend)
    {
       DeselectSD();
       Mp3DeselectControl();
       Mp3SelectData();
       awaitDataRequest();        
       for (tempValue=0; tempValue < NumOfBytesToSend; tempValue++) 
         {
          SPI.transfer(dataBuffer[tempValue]); //Send out 32 bytes
         }
       Mp3DeselectData();
       blocksPlayed++;
         // The decodetime register must be read WHILE data is being correctly decoded.
       displayDecodeTime();
    }

// ------------------------------------------------------------------------------
// TRANSFER END OF FILE TO MP3 PLAYER
// Correct pin selection should be done in this routine
// ------------------------------------------------------------------------------
    void TransferEndOfFile()
    {
       // End of file - send 2048 zeros before next file
       DeselectSD();
       Mp3DeselectControl();
       Mp3SelectData();
       for (int i=0; i<2048; i++) {
          awaitDataRequest();
          SPI.transfer(0);
       }
       Mp3DeselectData();
       awaitDataRequest(); // Wait until SPI transfer is completed
    }

// ------------------------------------------------------------------------------
// SETUP VS1053 MP3 PLAYER
// ------------------------------------------------------------------------------
void setupVS()
    {
       //Serial.println("Starting Up MP3... ");
       DeselectSD();
       Mp3DeselectData();
       Mp3SelectControl();
       player.begin();
    
    /*  
       Mp3SelectControl();
       delay(1);
       WriteRegister(SPI_MODE, _BV(SM_LAYER12) | _BV(SM_RESET) );
       Mp3DeselectControl();
              delay(2);
       
       Mp3SelectControl();
       delay(1);
            Mp3WriteRegister (SPI_MODE, 0x08, 0x04); // Newmode, Reset, No L1-2 
      delay(1);
      Mp3DeselectControl();
      awaitDataRequest();
 
     */
       //WriteRegister(SPI_MODE, 0x0804);
       DeselectSD();
       Mp3DeselectData();
       Mp3SelectControl();
       WriteRegister(SPI_MODE, _BV(SM_RESET) | _BV(SM_SDINEW) | _BV(SM_LAYER12) );
       delay(200);
       Mp3SelectData();
       delay(1);
       awaitDataRequest();
       for (int i = 0; i<2100; i++)
         {
           awaitDataRequest();
           SPI.transfer(0);
         }
        Mp3DeselectData();
          player.setVolume(thisVolume);
          
       return;
    }

// ------------------------------------------------------------------------------
// SETUP
// ------------------------------------------------------------------------------
void setup() 
  {
  Serial.begin(9600);    // Only needed if you want to view the messages on your monitor as well
  Wire.begin();
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);

  // LCD init
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  
  // SD card setup.
  pinMode(10, OUTPUT);    // PIN 10 MUST BE OUTPUT, EVEN IF UNUSED for the SD library functions to work
  pinMode(msgUpPin, INPUT);
  pinMode(msgDownPin, INPUT);
  pinMode(msgSelectPin, INPUT);
  pinMode(msgMenuPin, INPUT);
  pinMode(msgFifthPin, INPUT);
  pinMode(MP3_HARDRESET,OUTPUT);
  pinMode(SD_POWER, OUTPUT);
  pinMode(MP3_DREQ, INPUT);
  pinMode(SD_CS, OUTPUT);
  pinMode(MP3_XCS, OUTPUT);
  pinMode(MP3_XDCS, OUTPUT);
       
  // Set up the MP3 player, VS1053
  // BOOT NOT MIDI !
 /* After a hardware reset (or at power-up) DREQ will stay down for around 22000 clock cycles,
which means an approximate 1.8 ms delay if VS1053b is run at 12.288 MHz. After this the
user should set such basic software registers as SCI_MODE, SCI_BASS, SCI_CLOCKF, and
SCI_VOL before starting decoding.*/
  DeselectSD();
  Mp3DeselectData();
  Mp3DeselectControl();
  digitalWrite(MP3_HARDRESET, LOW);  //ACTIVE LOW HARD RESET

  SPI.setClockDivider(SPI_CLOCK_DIV64);//slow SPI bus speed
  SPI.transfer(0xFF); //Throw a dummy byte at the bus
//Initialize VS1053 chip 
  delay(10); //We don't need this delay because any register changes will check for a high DREQ
  digitalWrite(MP3_HARDRESET, HIGH);  //ACTIVE HARD RESET in NO MODE
  delay(10);

  // DREQ MUST BE HIGH BEFORE YOU START !!!
  while ( digitalRead(MP3_DREQ) == LOW )
    {
      delayMicroseconds(1);
    }
  Mp3SelectControl();
  WriteRegister(SPI_MODE, (uint16_t) (_BV(SM_RESET) | _BV(SM_LAYER12) |  _BV(SM_SDINEW) | _BV(SM_STREAM) ) );
  delay(10);
  WriteRegister(SPI_VOL,(uint16_t)0xffff); // VOL
  delay(2);
 //

  /* Declick: Slow sample rate for slow analog part startup */
  //Mp3WriteRegister(SPI_AUDATA, 0, 10); /* 10 Hz */
  WriteRegister(SPI_AUDATA,(uint16_t)10);

  //Delay(100);
 // delay(10);

  /* Switch on the analog parts */
  //Mp3SetVolume(0xfe,0xfe);
  WriteRegister(SPI_VOL,(uint16_t)0xfefe); // VOL
  delay(1);
  //Mp3WriteRegister (SPI_AUDATA, 31, 64); /* 8kHz */
  WriteRegister(SPI_AUDATA,(uint16_t)44101); // 44.1kHz stereo
//  delay(1);
 //
 // WriteRegister(SPI_MODE, _BV(SM_RESET) | _BV(SM_LAYER12) |  _BV(SM_SDINEW) | _BV(SM_STREAM) );
//  WriteRegister(SPI_MODE, _BV(SM_LAYER12) |  _BV(SM_SDINEW) );
//delay(1);
  awaitDataRequest();
  //delay(1);
  //awaitDataRequest();
  player.setVolume(thisVolume);
//  WriteRegister(SPI_VOL, (uint16_t)thisVolume); WRONG - see function in class player
 //WriteRegister(SPI_AUDATA, 0x9C60);
//  awaitDataRequest();
  WriteRegister(SPI_CLOCKF,(uint16_t)0xB800); // Experimenting with higher clock settings
  SPI.setClockDivider(SPI_CLOCK_DIV2);//fast SPI bus speed
  delay(2);  
       // Set up the SD data card
       Mp3DeselectControl();
       Mp3DeselectData();
       SelectSD();
                              // ERROR WITH THE SD CARD ...
        if (!SD.begin(SD_CS)) 
          {
              lcd.clear();
              lcd.setCursor(0, 0);

              tempValue = 0;
              myChar = pgm_read_byte(cardError); // Necessary casts and dereferencing, just copy. 
              while (myChar != '\0')
                {
                  lcd.print(myChar);
                  myChar = pgm_read_byte_near(cardError + (++tempValue));
                }

            return;
          }
      populateFileArray();
      printWelcome();
      
      actionStatus = ACTION_BROWSING_MP3;
      wantedDisplayStatus = DISPLAY_BROWSING;
      currentDisplayStatus = 0;
             // set the Last Button Press, in order to dim the display.
      lastButtonPress = millis();
      lastDecodeTime = millis();
      lcdStatus = lcdLit;
}

void loop() 
{
      // -----------------------------------------------------------------
      // Dim the display after n seconds of inactivity
      // -----------------------------------------------------------------
      dimDisplay();
      
      // -----------------------------------------------------------------
      // Show the correct Display
      // -----------------------------------------------------------------
      showDisplay();
      
      // -----------------------------------------------------------------
      // STOP PLAYING: Close the file.
      // Transfer end of file wil clear the buffer out.
      // Return to browse mode
      // -----------------------------------------------------------------
      if (actionStatus == ACTION_STOP_PLAYING)
        {
          if (playFile)
            {
              TransferEndOfFile();
              playFile.close();
            }
          actionStatus = ACTION_BROWSING_MP3;
          wantedDisplayStatus = DISPLAY_BROWSING;
        }
        
      if (actionStatus == ACTION_BROWSING_MP3)
          {
            wantedDisplayStatus = DISPLAY_BROWSING;   
          }
      //
      // We have SELECTED an MP3 to play. Get the file name, open it, and set the 
      // appropriate variables for sending the MP3 data.
      //
      if (actionStatus == ACTION_SELECT_MP3)
          { 
          Mp3DeselectControl();
          Mp3DeselectData();
          SelectSD();
          sprintf(thisFile, "%s%s", currentOpenDirectory, fileName[fileNumber-1]);
          
          // Serial.print("Open ");
          playFile = SD.open(thisFile); // default is open for READ
          if (playFile) 
             {
             // Serial.print("OK.");
             // Serial.flush();
             actionStatus = ACTION_PLAYING_MP3;
             wantedDisplayStatus = DISPLAY_PLAYING;
             // HERE: Check for file being restored from backup, and set the filePos.
             //
             if (actionRestore == 1)
               {
                 newFilePos = blocksPlayed * 32;
                 playFile.seek(newFilePos);
                 lastBackupBlocks = blocksPlayed;
               
                 actionRestore = 0;
               }
             else
               {
               blocksPlayed = 0;
               lastBackupBlocks = 0;
               }
             resetMillis = millis()+1000; /* Current time plus 1 second */
             actuallyPlaying = 0; /* We don't know if it's actually playing yet ... */
             failureToPlay = 0; // Keep a count of how many times it fails !
             blocksInFile = playFile.size() / 32;
             }
          else
            {
             // Serial.println("FAIL");
             delay(1500);
             actionStatus = ACTION_BROWSING_MP3;
             wantedDisplayStatus = DISPLAY_BROWSING;
            }
           
          }
     
     /* If we've failed to play an mp3 5 times, then stop trying ... */
 if (failureToPlay > 5)
  {
   failureToPlay = 0;
   actionStatus = ACTION_BROWSING_MP3;
   wantedDisplayStatus = DISPLAY_BROWSING;
   if (playFile)
     {
       playFile.close();
     }
  }
   
   /* If we're playing an mp3, HDAT1 is between FFE0 and FFFF. If the playback is failing, HDAT1
      contains 0. A soft reset and resending the file usually works.
      So: If we're playing, and have been playing for more than n seconds, but HDAT1 shows that we 
      are NOT actually playing, then: Reset the MP3 and try it again
   */
   
 if ( (actionStatus == ACTION_PLAYING_MP3) &&  (millis() > resetMillis ) && (actuallyPlaying == 0) )
              {
                  DeselectSD();
                  Mp3DeselectData();
                  Mp3SelectControl();
                  tempValue16 = ReadRegister(SPI_HDAT1);
                  Mp3DeselectControl();
                  if (tempValue16 < 0xFFE0)
                    {
                     failureToPlay++;
                     TransferEndOfFile();
                     
                     playFile.close();
                    
                     Mp3SoftReset();
                     delay(10);
                     actionStatus = ACTION_SELECT_MP3;
                     wantedDisplayStatus = DISPLAY_BROWSING;
                     resetMillis = millis()+1500;
                    }
                  else
                    {    // If HDAT1 contains >= 0xFFE0, then we are actually playing an mp3
                      actuallyPlaying = 1;
                      failureToPlay = 0;
                    }
              }
              
      
       // -----------------------------------------------------------------
       // We're in the middle of playing an MP3
       // -----------------------------------------------------------------
      if (actionStatus == ACTION_PLAYING_MP3)
          {
          Mp3DeselectControl();      // read 32 bytes from the file on the card into a buffer
          Mp3DeselectData();
          SelectSD();
          noOfBytesRead = playFile.read(dataBuffer, 32);
          TransferMP3Data(noOfBytesRead);
          if (noOfBytesRead < 32)    // If less than 32 bytes were read, then we're at the end of the file.
            {
              actionStatus = ACTION_END_MP3;
            }
          //wantedDisplayStatus = DISPLAY_PLAYING; // dont set this, we want to dispay menus as well while playing.
          }
       
       // -----------------------------------------------------------------
       // Pause an mp3
       // -----------------------------------------------------------------
       if (actionStatus == ACTION_PAUSED_MP3)
         {
           wantedDisplayStatus = DISPLAY_PAUSED; // this is wrong; we want menus whist paused also ?
         }
         
       // -----------------------------------------------------------------
       // Finish an mp3 and start playing the next track
       // -----------------------------------------------------------------
       if (actionStatus == ACTION_END_MP3)    // If we're at the end of the file, then transfer 2048 zeros to tell it so
         {
           //Serial.println("ENDING !");
           TransferEndOfFile();
           playFile.close();
            // Play the next file.
            // If we're at the last file, then repeat the last file again. (Easiest to code, sort out your
            // own requirements :-) BUGFIX: next file could be a directory ... REWRITE ? No, it will deal with
            // it and select that directory for browsing
           // while (fileType[fileNumber++] != PLAY
           fileNumber++;
           if (fileNumber > numFilesInArray)
             {
               fileNumber = 1;
             }
           sendToDisplay();           
           actionStatus = ACTION_SELECT_MP3;
           //actionStatus = ACTION_BROWSING_MP3;
           //wantedDisplayStatus = DISPLAY_BROWSING;
         }
      // Whether we're playing a file or not, we still want to change the VOLUME using the menu button

      // -----------------------------------------------------------------------
      // Check the Fifth button
      // -----------------------------------------------------------------------
      tempValue = digitalRead(msgFifthPin);
      if (tempValue == HIGH  && (millis() > lastButtonPress + 100) )
        {
            lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
            fifthButtonPressed();
        }
      
      // -----------------------------------------------------------------------
      // Check the SELECT button
      // -----------------------------------------------------------------------
      tempValue = digitalRead(msgSelectPin);
      if (tempValue == HIGH  && (millis() > lastButtonPress + 600) )
        {
            lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
            selectButtonPressed();
        }

     // --------------------------------------------------------------------------------
     // MENU BUTTON
     // Check to see if the menu button is pressed
     // If not in Menu, Go into menu
     // If in Menu, rotate through menu options
     // If at end of menu, go back to non-menu display
     // --------------------------------------------------------------------------------
     tempValue = digitalRead(msgMenuPin);
     if (tempValue == HIGH && (millis() > lastButtonPress + 500))
       {
          lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
          menuButtonPressed();
       }

         

      // -----------------------------------------------------------------------
      // Check the DOWN button
      // If we're playing MP3, then fast rewind.
      // NOTE DIFFERENCE IN TIME - FAST REWIND REQUIRES FASTER, BECAUSE OF TIME ELAPSING WHILST PRESSING !!!
      // -----------------------------------------------------------------------
      tempValue = digitalRead(msgDownPin);
      if ( tempValue == HIGH && 
          (actionStatus == ACTION_PLAYING_MP3) && 
          (currentDisplayStatus == DISPLAY_PLAYING) &&
          (millis() > lastButtonPress + 10))
              {
                fastRewind();
                lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
              }
      
      if (tempValue == HIGH && (millis() > lastButtonPress + 400)/* && (actionStatus != ACTION_PLAYING_MP3)*/ )
        {
            lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
            downButtonPressed();
        }

      
      // -----------------------------------------------------------------------
      // Check the UP button
      // If we're playing an MP3, (and not in the MENU !) then fast forward, otherwise deal with it in a
      // separate function..
      // -----------------------------------------------------------------------
      tempValue = digitalRead(msgUpPin);
         if (tempValue == HIGH && 
            (actionStatus == ACTION_PLAYING_MP3) && 
            (currentDisplayStatus == DISPLAY_PLAYING) &&
            (millis() > lastButtonPress + 15) )
                {
                fastForward();
                lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
                }
   
      if (tempValue == HIGH  && (millis() > lastButtonPress + 400)/*  && (actionStatus != ACTION_PLAYING_MP3)*/ )
        {
            lastButtonPress = millis();    // Ensure we don't interpret a single human press as several presses...         
            upButtonPressed();
        }
        
  //
  // End of the main loop.
  //
}

// ---------------------------------------------------------------------------
// Fifth button - used to restore the file and position that was ast backed up.
// Must be in file browsing mode.
// ---------------------------------------------------------------------------
void fifthButtonPressed()
{
   if (actionStatus != ACTION_BROWSING_MP3)
    {
      return;
    }
   
  restoreBackup();
}

// ---------------------------------------------------------------------------
// These are the FAST FORWARD and FAST REWIND functions
// There are 2 ways of controlling the speed; firstly, by timing the button presses
// (controlled in the button-press detection lines, see main loop)
// and secondly by the multiplier of the blocksPlayed.
// ---------------------------------------------------------------------------
void fastRewind()
{ 
  // Fast Forward function
  if (actionStatus != ACTION_PLAYING_MP3)
    {
      return;
    }
  Mp3DeselectControl();      
  Mp3DeselectData();
  SelectSD();
if (blocksPlayed < 400)
  {
    blocksPlayed = 400;
  }
newFilePos = (blocksPlayed-400) * 32;
blocksPlayed = blocksPlayed - 400;
playFile.seek(newFilePos);

}
  
void fastForward()
{ 
  // Fast Forward function - only if we're actually playing an MP3
  if (actionStatus != ACTION_PLAYING_MP3)
    {
      return;
    }
  // If we're near the end of the file, then stop
  if (blocksPlayed+601 >= blocksInFile)
    {
      return;
    }
  Mp3DeselectControl();      
  Mp3DeselectData();
  SelectSD();
newFilePos = (blocksPlayed+600) * 32;
blocksPlayed = blocksPlayed + 600;
playFile.seek(newFilePos);

}

// -----------------------------------------------------------------------------------------------
// These functions read the SD card, and store the filenames in memory. (There is a max)
// The filenames are sorted.
// -----------------------------------------------------------------------------------------------
void switchArray(byte value)
{
 // Paul, switch i and i+1, using a temp pointer. 
 char *tempPointer;
 byte tempByte;
// Serial.println("Switching");
     // Switch the filenames
 tempPointer = fileName[value-1];
 fileName[value-1] = fileName[value];
 fileName[value] = tempPointer;
 
     // Switch the file types
 tempByte = fileType[value-1];
 fileType[value-1] = fileType[value];
 fileType[value] = tempByte;
}

// Check 2 character arrays; return FALSE if #2 > 1; 
// return TRUE if #2 > #1 and switch 1 = TRUE, 0 = FALSE
byte arrayLessThan(char *ptr_1, char *ptr_2)
{
  char check1;
  char check2;
  
  int i = 0;
  while (i < strlen(ptr_1))
    {
      //Serial.println(i);
        check1 = (char)ptr_1[i];
          //Serial.print("Check 1 is "); Serial.print(check1);
          
        if (strlen(ptr_2) < i)    // If string 2 is shorter, then switch them
            {
              return 1;
            }
        else
            {
              check2 = (char)ptr_2[i];
           //   Serial.print("Check 2 is "); Serial.println(check2);
              
              //Serial.print(check1); Serial.print(check2);
              if (check2 > check1)
                {
                  return 1;
                }
              if (check2 < check1)
                {
                  return 0;
                }
               // OTHERWISE theyre equal; check the next char !!
             i++; 
            }
    }
    
return 0;  
}

void sortFileArray()
{
  //byte changesMade = 1;
  //byte  startPos = 0;
  
  int innerLoop ;
  int mainLoop ;
  
//  sendToDisplay();
//  delay(1000);



  for ( mainLoop = 1; mainLoop < numFilesInArray; mainLoop++)
    {
      innerLoop = mainLoop;
      while (innerLoop  >= 1)
          {
          if (arrayLessThan(fileName[innerLoop], fileName[innerLoop-1]) == 1)
            {
             // Serial.print("Switching ");
             // Serial.print(fileName[innerLoop]);
             // Serial.print(" and ");
             // Serial.println(fileName[innerLoop-1]);
              
              switchArray(innerLoop);
            }
            innerLoop--;
          }
    }
  //  Serial.println("Done");
  //  sendToDisplay();
}

void freeMessageMemory()
{
  // If we have previous messages, then free the memory
      for (byte i=1; i<=numFilesInArray; i++)
        {
          free(fileName[i-1]);
        }
        
    numFilesInArray = 0;
    fileNumber = 1;
  
}

void populateFileArray()
{
  Mp3DeselectControl();
  Mp3SelectData();
  SelectSD();
  
  freeMessageMemory();  // Start by freeing previously allocated malloc pointers
  
        // The first file in the list should be '..' (parent directory)
        // unless we're already in the root directory.
  if (strlen(currentOpenDirectory) > 1)  // IF we are not at the root directory
    {
    fileName[0] = (char *)malloc(3);    // Only allocate the req'd bytes !

    sprintf(fileName[0],"..");
    fileType[0] = DIR;

    numFilesInArray++;
    }

  File thisDirectory = SD.open(currentOpenDirectory);            // Open the current directory

  while(true) 
    {    
     File entry =  thisDirectory.openNextFile();

     if (! entry) 
       {
       // no more files
       //Serial.println("**Read all the files");
       break;
       }
       
      // If it's not a valid file (test filename .mp3)
      // At the moment, we have a limit of n files per directory; we're running out of RAM
      if (numFilesInArray < MAXINARRAY && 
            (  
              ( strcasestr(entry.name(), ".mp3") != NULL ) || ( entry.isDirectory() )
            )
         )
        {
        numFilesInArray++;
        
        fileName[numFilesInArray-1] = (char *)malloc(13);
        checkMemory();

        sprintf(fileName[numFilesInArray-1],"%s",entry.name()); 
    
        // Is it a directory or a playable file ?
        if (entry.isDirectory())
          {
          fileType[numFilesInArray-1] = DIR;
          }
        else    // else assume it's an MP3. This may not be true, of course. Your choice if your SD card has non-MP3 files.
          {
          fileType[numFilesInArray-1] = PLAY;
          }  
        }

      entry.close();
     }
  thisDirectory.close();

  sortFileArray();
}

// ==============================================================
// The SELECT button has been pressed.
// ==============================================================
void selectButtonPressed()
 {
     // If the display was DIM, then turn on the display and ignore the button press.
     if (lcdStatus == lcdDimmed)
        {
          lastButtonPress = millis();
          return;
        }

  // Serial.println("Select Button !");
  // If we're in the MENU subsystem, then do NOTHING
  if (currentDisplayStatus == DISPLAY_MENU)
    {
      return;
    }

  // If we're playing a file, and not in the menu, then pause it
  // If we're pausing a file, and not in the menu, then stop it
  // If we're in the menu, then exit it
  // Therefore, we must be browsing the files:
  // If the file type is a directory, then open that directory
  // If the file type is an MP3, then play it !
  // else flag an error, please, and work out whats going on ...
  if (currentDisplayStatus == DISPLAY_PLAYING)
      {
       //Serial.println("Pausing it");
        
        actionStatus = ACTION_PAUSED_MP3;
        wantedDisplayStatus = DISPLAY_PAUSED;
        return;
      }
   if (currentDisplayStatus == DISPLAY_PAUSED)
       {
        //Serial.println("Stopping it");
         actionStatus = ACTION_PLAYING_MP3;
         wantedDisplayStatus = DISPLAY_PLAYING;
         return;
       }
    if (currentDisplayStatus == DISPLAY_BROWSING)
      {
         if (fileType[fileNumber-1] == DIR)
         {
        //Serial.println("Its a directory");

        // For the parent directory, replace the last / in the string with a \0
        // If the filename starts with a '.', assume we're going up a directory
        // and trim the currentOpenDirectory accordingly.
          if (fileName[fileNumber-1][0] == '.')    // We're going UP a directory
            {
            //Serial.println("Going UP a directory");

            byte strLength = strlen(currentOpenDirectory) - 2;
            while ((currentOpenDirectory[strLength] != '/') && (strLength > 0))
                  {
                    strLength--;
                  }
             currentOpenDirectory[strLength+1] = '\0';
             populateFileArray();
             currentDisplayStatus = 0;
             wantedDisplayStatus = DISPLAY_BROWSING;
             actionStatus = ACTION_BROWSING_MP3;
             return;
             }
            else 
             {
             // Serial.println("Going DOWN a directory");

              sprintf(currentOpenDirectory,"%s%s/", currentOpenDirectory, fileName[fileNumber-1]);        
              populateFileArray();
              currentDisplayStatus = 0;
              wantedDisplayStatus = DISPLAY_BROWSING;
              actionStatus = ACTION_BROWSING_MP3;
              return;
              }
         } // End of if its a directory
         
         if (fileType[fileNumber-1] == PLAY)
          {
          //Serial.println("Selected a file ! ");      // We're playing a file.
          //We will be playing file : fileName[fileNumber-1]
          actionStatus = ACTION_SELECT_MP3;
          return;
          }
      } // End of If we're browsing
} // End of function



// --------------------------------------------------------------
// Print the list of files on the display.
// We currently have a 16x2 LCD connected, so display 2 files.
// --------------------------------------------------------------
void sendToDisplay()
{
  // If you want to see the messages on your serial monitor (eg for testing the code)
/*
  for (byte i = 1; i <= numFilesInArray; i++)
    {
      Serial.println(fileName[i-1]);
      if (fileType[i-1] == DIR)
        {
          Serial.println(" - DIR");
        }
        
      if (fileType[i-1] == PLAY)
        {
          Serial.println(" - PLAY");
        }
    }
*/ 
  // Now, print the messages out to the LCD screen.
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(fileName[fileNumber-1]);
  // Print the next message on the following line, if it exists
   lcd.setCursor(0, 1);
  if (fileNumber < numFilesInArray)
    {
    lcd.print(fileName[fileNumber]);
    }
  // Serial.println(fileName[fileNumber-1]); Serial.println(fileName[fileNumber]);
  currentDisplayStatus = DISPLAY_BROWSING;
}

// --------------------------------------------------------------------------------------
// For testing : 
// See how much RAM you have got left. I average about 390 bytes before reading the filelist.
// This function freeMemory returns an interger
//
// --------------------------------------------------------------------------------------
void printMemory()
{
  printf(PSTR("Free memory : "));
  //Serial.println(freeMemory());
}


// --------------------------------------------------------------
// Print a WELCOME screen for a few seconds 
// --------------------------------------------------------------
void printWelcome()
{
  lcd.clear();
  lcd.setCursor(0, 0);

  tempValue = 0;
  myChar = pgm_read_byte(welcomeLine1);  
  while (myChar != '\0')
    {
    lcd.print(myChar);
    myChar = pgm_read_byte_near(welcomeLine1 + (++tempValue));
    }
    
  lcd.setCursor(0, 1);
  
  tempValue = 0;
  myChar = pgm_read_byte(welcomeLine2); 
  while (myChar != '\0')
    {
    lcd.print(myChar);
    myChar = pgm_read_byte_near(welcomeLine2 + (++tempValue));
    }
    // Serial.println("Welcome screen ...");
  delay(2000);
}

// --------------------------------------------------------------
// Volume control: SHOW THE VOLUME
// --------------------------------------------------------------
void showVolume()
{
    lcd.clear();
    lcd.setCursor(0,0);
    tempValue = 0;
    myChar = pgm_read_byte(volume); // Print a message "Volume :" on the LCD. 
    while (myChar != '\0')
        {
        lcd.print(myChar);
        myChar = pgm_read_byte_near(volume + (++tempValue));
        }

    lcd.print(thisVolume);
    
    // Serial.print("Volume "); Serial.println(thisVolume);
    currentDisplayStatus = DISPLAY_MENU;
    currentMenuStatus = MENU_VOL;
}

// --------------------------------------------------------------
// Print the name of the currently playing file on the LCD
// --------------------------------------------------------------
void printFilePlaying()
{
    lcd.clear();
    lcd.setCursor(0,0);
    tempValue = 0;
    myChar = pgm_read_byte(playingFile); // Print a message "Playing :" on the LCD. 
    while (myChar != '\0')
        {
        lcd.print(myChar);
        myChar = pgm_read_byte_near(playingFile + (++tempValue));
        }
    lcd.setCursor(0,1);
    lcd.print(fileName[fileNumber-1]);
    // Serial.print("Playing "); Serial.println(fileName[fileNumber-1]);
    currentDisplayStatus = DISPLAY_PLAYING;
}

// --------------------------------------------------------------
// Dim the LCD after n minutes of no button presses
// --------------------------------------------------------------
void dimDisplay()
{
  if (millis() > (lastButtonPress + 30000) )   // Time to dim the display
    {
      if (lcdStatus == lcdLit)
          {
            lcd.noBacklight();
            lcdStatus = lcdDimmed;
//            Serial.println("Dimmed");
          }
    }
  else
    {
      if (lcdStatus == lcdDimmed)
          {
            lcd.backlight();
            lcdStatus = lcdLit;
 //           Serial.println("Lit");
          }
    }
      
}

// --------------------------------------------------------------
// Print the PAUSED message
// --------------------------------------------------------------
void printPaused()
{
    lcd.clear();
    lcd.setCursor(0,0);
    tempValue = 0;
    myChar = pgm_read_byte(paused); // Print a message "Paused :" on the LCD. 
    while (myChar != '\0')
        {
        lcd.print(myChar);
        myChar = pgm_read_byte_near(paused + (++tempValue));
        }
    // Serial.println("Pause");
    currentDisplayStatus = DISPLAY_PAUSED;
}

// --------------------------------------------------------------
// Ensure the correct display is displayed on the screen
// --------------------------------------------------------------
void showDisplay()
{
  // DEAL WITH MENUS FIRST
  if (wantedDisplayStatus == DISPLAY_MENU) //&& currentDisplayStatus == DISPLAY_MENU)
    {
     if (wantedMenuStatus == MENU_VOL && currentMenuStatus != MENU_VOL)
           {
             
              showVolume();
           }
       
      return;
    }
    
  if (wantedDisplayStatus == DISPLAY_BROWSING && currentDisplayStatus != DISPLAY_BROWSING)
    {
      sendToDisplay();
      return;
    }

  if (wantedDisplayStatus == DISPLAY_PLAYING && currentDisplayStatus != DISPLAY_PLAYING)
    {
      printFilePlaying();
      return;
    }

  if (wantedDisplayStatus == DISPLAY_PAUSED && currentDisplayStatus != DISPLAY_PAUSED)
    {
      printPaused();
      return;
    }
}

// ------------------------------------------------------------------------
// The DOWN button has been pressed
// ------------------------------------------------------------------------
void downButtonPressed()
{
    // If the display was DIM, then turn on the display and ignore the button press.
     if (lcdStatus == lcdDimmed)
        {
          lastButtonPress = millis();
          return;
        }

  // If we're in the menu volume, alter the volume
  if (currentMenuStatus == MENU_VOL)
    {
      thisVolume = thisVolume - 10;
      player.setVolume(thisVolume);
      //Mp3SetVolume(thisVolume, thisVolume);
      wantedMenuStatus = MENU_VOL;
      currentMenuStatus = MENU_NOTSET;    // Force the menu to be re-displayed
      return;
    }

  // If we're browsing the filelist, go down the filelist
  if (actionStatus == ACTION_BROWSING_MP3)
    {
      fileNumber--;
      if (fileNumber < 1)
        {
          fileNumber = 1;
        }
      sendToDisplay();
      return;
    }
}

// ------------------------------------------------------------------------
// The UP button has been pressed
// ------------------------------------------------------------------------
void upButtonPressed()
{
    // If the display was DIM, then turn on the display and ignore the button press.
     if (lcdStatus == lcdDimmed)
        {
          lastButtonPress = millis();
          return;
        }

   // If we're in the menu volume, alter the volume
   if (currentMenuStatus == MENU_VOL)
     {
       thisVolume = thisVolume + 10;
       player.setVolume(thisVolume);
       //Mp3SetVolume(thisVolume, thisVolume);
       wantedMenuStatus = MENU_VOL;
       currentMenuStatus = MENU_NOTSET;  // Force the menu to be re-displayed
       return;
     }

   // If we're browsing the filelist, go down the filelist
   if (actionStatus == ACTION_BROWSING_MP3)
     {
       fileNumber++;
       if (fileNumber > numFilesInArray)
         {
           fileNumber = numFilesInArray;
         }
       sendToDisplay();
       return;
     }
 
}

// ------------------------------------------------------------------------
// The MENU button has been pressed
// If we're PAUSED then stop playing.
// ------------------------------------------------------------------------
void menuButtonPressed()
{
  // If the display was DIM, then turn on the display and ignore the button press.
     if (lcdStatus == lcdDimmed)
        {
          lastButtonPress = millis();
          return;
        }

     switch (currentDisplayStatus)
           {
             case DISPLAY_MENU:
                 wantedMenuStatus++;  // Rotate the menu through the options
                 if (wantedMenuStatus == MENU_END)
                     {
                       if (actionStatus == ACTION_BROWSING_MP3)
                         {
                           wantedDisplayStatus = DISPLAY_BROWSING;
                           currentMenuStatus = MENU_NOTSET;
                         }
                       if (actionStatus == ACTION_PLAYING_MP3)
                         {
                           wantedDisplayStatus = DISPLAY_PLAYING;
                           currentMenuStatus = MENU_NOTSET;
                         }   
                     }
              break;
              case DISPLAY_PAUSED:
                   actionStatus = ACTION_STOP_PLAYING;
              break;
              default:
                 wantedDisplayStatus = DISPLAY_MENU;
                 wantedMenuStatus = MENU_VOL;
                 break;
           }
       
}

uint16_t ReadRegister(uint8_t _reg) 
{
  uint16_t result;
  //SPI.setClockDivider(SPI_CLOCK_DIV64); // PAUL !! TEST
  DeselectSD();
  Mp3DeselectData();
  awaitDataRequest();  // PAUL!!! decode time is returning random crap !
  Mp3SelectControl();
  delayMicroseconds(1); // tXCSS
  SPI.transfer(0x03); // Read operation
  awaitDataRequest();    // Tried adding these in v1.20; makes no diff to the decodetime reg
  SPI.transfer(_reg); // Which register
  awaitDataRequest();
  result = SPI.transfer(0xff); // read high byte
  result = result << 8;
  awaitDataRequest();
  result |= SPI.transfer(0xff); // read low byte
  awaitDataRequest();
  Mp3DeselectControl();
  delayMicroseconds(1); // tXCSH
//  awaitDataRequest();
 // SPI.setClockDivider(SPI_CLOCK_DIV2); // PAUL !! TEST

  return result;
}

void printModeRegister()
{
  DeselectSD();
  Mp3DeselectData();
  Mp3SelectControl();
  uint16_t temp16 = ReadRegister(SPI_MODE);
  Mp3DeselectControl();
  //Serial.print("MODE IS ");
  //Serial.println(temp16, HEX);
}

void printAUDATARegister()
{
  DeselectSD();
  Mp3DeselectData();
  Mp3SelectControl();
  uint16_t temp16 = ReadRegister(SPI_AUDATA);
  Mp3DeselectControl();
  // Serial.print("AUDATA IS ");
  // Serial.println(temp16, HEX);
}
void printHDAT1Register()
{
  DeselectSD();
  Mp3DeselectData();
  Mp3SelectControl();
  uint16_t temp16 = ReadRegister(SPI_HDAT1);
  Mp3DeselectControl();
  // Serial.print("HDAT1 IS ");
  // Serial.println(temp16, HEX);
}

// -----------------------------------------------------------------------------
// Dispay the DECODE_TIME : the time that the track has been playing for.
// Note that this doesn' work, and I have no idea why not.
// Workaround for my own purposes: count the blocks that have been played
// Divide by 1000, to get a suitable displayable value.
// -----------------------------------------------------------------------------
void displayDecodeTime()
{
  
  /*
  if (lcdStatus == lcdDimmed)
      {
        return;
      }
      */
      // We only need to update the display once per second
  if (millis()  < lastDecodeTime + 1000)
      {
        return;
      }
      // We don't need to display the time if we're (eg) changing the volume.
  if (currentDisplayStatus != DISPLAY_PLAYING)
      {
        return;
      }
      
  tempValue16 = blocksPlayed >> 6;    // This displays a sensible value. It does NOT, of course,
                                      // reflect the time the song has been playing for. However, it
                                      // does give me a way of restarting a song at a known point.
  
  lcd.setCursor(4,0);
  lcd.print(tempValue16);
  lcd.print("/"); // During fast fwd / rewind, ensure there is a clear space for ease of reading screen
  lcd.print(blocksInFile >> 6);
  lcd.print(" ");
  
  // This _should_ display the decode time. It doesn't. return either int or 16.
  /*
  tempValue = ReadRegister(SPI_DECODE_TIME);
  lcd.setCursor(4,0);
  lcd.print(tempValue);
  lcd.print(" ");*/
  // PAUL TEMP : Write a backup every n blocks. 1000 is about 3 seconds of time.
  if (blocksPlayed > lastBackupBlocks + 5000)
    {
      lastBackupBlocks = blocksPlayed;
      backup();
    }
   
  // Serial.println(temp16);
  lastDecodeTime = millis();
      
}
// -----------------------------------------------------------------------------------
// Write out our current playfile and time
// -----------------------------------------------------------------------------------
void backup()
{
  Mp3DeselectControl();
  Mp3DeselectData();
  SelectSD();
  //Serial.println("backup");
  backupFile = SD.open(backupFileName, FILE_WRITE);
  if (!backupFile)
    {
      return;  // DO NOTHING - eg perhaps the SD card lock is in place ?
      //Serial.println("BACKUP FILE ERROR");  
    }
  else
    {
      backupFile.seek(0);        // Start at 0 each time, and overwrite the first n chars; does NOT shorten file !?
      
      sprintf(blocksPlayedStr, "%ld", blocksPlayed);
//      blocksPlayedStr[16] = '\0';
      backupFile.println(currentOpenDirectory);
      backupFile.println(fileNumber);
      backupFile.println(blocksPlayedStr);
      backupFile.close();      // NOTE: This does not shorten the backup file !!!
 //     Serial.println("backup OK");
    }
}

// -----------------------------------------------------------------------------------
// CHECK FREE RAM and display warning ...
// -----------------------------------------------------------------------------------
void checkMemory()
{
  int freeMem;
  freeMem = freeMemory();
  if (freeMem < 150)
    {
    lcd.clear();
    lcd.setCursor(0,0);
    tempValue = 0;
    myChar = pgm_read_byte(lowMemory); // Print a message "Low memory :" on the LCD. 
    while (myChar != '\0')
        {
        lcd.print(myChar);
        myChar = pgm_read_byte_near(lowMemory + (++tempValue));
        }
    delay(3000);
    }
}
// -----------------------------------------------------------------------------------
// On startup, go to the same file and approx the same place
// This is currently UNUSED
// NEW 1.21: Backup file contains
// currentOpenDirectory \r\n
// fileNumber \r\n
// blocksPlayed \r\n
// -----------------------------------------------------------------------------------
void restoreBackup()
{
  Mp3DeselectControl();
  Mp3DeselectData();
  SelectSD();
  
  backupFile = SD.open(backupFileName, FILE_READ);
  if (!backupFile)
      {
        // If there's no backup file, then no worries.
        return;
      }
      // SAMPLE FILE : This is an example; note the \r \n as the line terminators.
      //               Ignore all chars after the second line terminators.
       // 0000000   /   M   I   D   N   I   G   H   T   /   I   S   H   A   L   L
       // 0000020   ~   3   .   M   P   3  \r  \n   7   2   4   8  \r  \n   1   9
       // 0000040   2   1   5  \r  \n
      // -------------------------------------------
      // Read the currentOpenDirectory
      // -------------------------------------------
      tempValue=0;
      myChar = backupFile.read();
      while (myChar != '\r' && tempValue < 40)
        {
        currentOpenDirectory[tempValue++] = myChar;
        myChar = backupFile.read();
        }
      currentOpenDirectory[tempValue] = '\0';    // Null terminate the filename
      myChar = backupFile.read();    // Read the following /n character
        
      // Given the currentOpenDirectory, go and read it - this will reset the fileNumber
    //  Serial.println(currentOpenDirectory);
      populateFileArray();

      // -------------------------------------------
      // Read the File Number
      // -------------------------------------------
      fileNumber = 0;
      myChar = backupFile.read();      
      while (myChar != '\r' )
        {
        fileNumber = (fileNumber * 10) + (myChar - 48);
        myChar = backupFile.read();
        }  
      myChar = backupFile.read();    // Read the following /n character
    
     // Serial.println(fileNumber);
      // -------------------------------------------
      // Read the blocksPlayed
      // -------------------------------------------
      blocksPlayed = 0;
      myChar = backupFile.read();      
      while (myChar != '\r' )
        {
          blocksPlayed = (blocksPlayed * 10) + (myChar - 48);
          myChar = backupFile.read();
        }
   // Serial.println(blocksPlayed);
      backupFile.close();

    lcd.clear();
    lcd.setCursor(0,0);
    tempValue = 0;
    myChar = pgm_read_byte(restoring); // Print a message "Restoring :" on the LCD. 
    while (myChar != '\0')
        {
        lcd.print(myChar);
        myChar = pgm_read_byte_near(restoring + (++tempValue));
        }
    lcd.setCursor(0,1);
    lcd.print(fileName[fileNumber - 1]);
    
//    lcd.print(blocksPlayed);
    delay(2000); 
//    Serial.println(thisFile);
//    Serial.println(blocksPlayed, DEC);   
  
      // -------------------------------------------
      // Now set up the other vars needed
      // -------------------------------------------

  actionStatus = ACTION_SELECT_MP3;
  actionRestore = 1;
  // To fully restore, we need the currentOpenDirectory, and the fileNumber of the current file
}