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.
MP3 player components :
- 5 switches
- Arduino Uno
- 16x2 LCD screen
- SD card reader
- MP3 codec decoder
- 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
gizlenmis kod
//delayMicroseconds(10);// MP3 player ...
// 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
// 1.22 - Switch off after n hours
// 1.23 - Record voltage every minute to a file, to observe voltage falling.
// 1.24 - Memory saving: backup.dat and voltage.dat renamed to b.dat and v.dat
// - voltageStr and blocksPlayedStr replaced with one string, tempStr
// 1.25 - Recover from rebuild disaster : random stopping.
// - Remove SD_POWER
// - Included printFreeMemory(); on setup and before play.
// - Removed checkMemory()
// - removed code to shut the arduino off; it wasn't saving power anyway
// - If its time for sleep, then stop playing the current track.
// 1.26 - Display voltage on screen during play !!
// - lastVoltageCheck renamed nextVoltageCheck.
// - Display bytes free on screen as well !
// - If SD card error, display error and delay a while ... (was 'return'ing).
// - MAXINARRAY works at 15, but not at 20. - changed
// 1.27 - pull MP3_HARDRESET low again in setup. // NO REAL CHANGE
// 1.28 - Display error msg if unable to backup
// - No longer write voltage to voltage file. Keep printing it to the screen for the time being, until we
// - complete the investigation into it's usefulness.
// - replace lastBackupBlocks with nextBackupBocks; avoid adding 5000 every cycle.
// - Change switch off time from 60 to 90 mins.
// 1.29 - ABANDONED - never released
// 1.30 - ACTION_STOP_PLAYING, not ACTION_END_MP3. BUG. At the end of switch-off-time, the wrong status was
// being set.
// - In the menu (eg change volume), after 10 seconds of inactivity, return to browsing/playing menu.
// 1.31 - Abandon the voltage check, because it doesn't provide a reliable amount of battery life remaining.
// Instead, start counting the minutes used both in playmode and in browse mode.
// 1.32 - Removed 90 minute time limit, found mAh during play is 85, mAh during pause is 65.
// 1.33 - took v1.31, put back in the ShutDown routine after 90 minutes.
// 1.34 - reset button goes to 1 instead of 0
// - button 5 prints a first message "button pressed" before waiting for button 1 to chhose between restoring
// and resetting battery
// - Instead of displaying minutes paused and played, display mAh used.
// 1.36 - b.dat was being written too often, and it destroyed the SD card first block. SD card reformatted to ignore first 8Mb;
// but program must be changed to reduce number of times b.dat is writtem to disk.
//
// TODO
// display how many minutes of playtime is remaining ?
// NO IDEA ! Why does decode_time contain random ??
// After 5 secs of inactivity on volume, return to play.
//
#include
#include
#include
#include
#include
//#include
#include
// Framework headers
// Library headers
#include
//#include
//#include
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[] = "/b.dat";
byte doNotBackup = 0;
#define PLAY_MSB 1 // The 2 integers (4 bytes) for how much time has been spent in
#define PLAY_LSB 2 // play mode or pause mode are stored in EEPROM
#define PAUSE_MSB 3
#define PAUSE_LSB 4
uint16_t minutesPlaying;
uint16_t minutesPaused;
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 switchOffTime; // Used to determine when to shut the mp3 player off; eg 2 hours later
unsigned long currentTime; // Used to get millis() once per loop instead of 5+ times in each function.
unsigned long addMinuteCounter; // Used to add a minute of usage to the appropriate counters.
// ----------------------------------------------------------
// 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 15
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 = 100;
unsigned long resetMillis;
byte actuallyPlaying;
byte failureToPlay = 0;
unsigned long blocksPlayed;
// unsigned long nextBackupBlocks;
unsigned long blocksInFile;
char tempStr[10];
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 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_LAYER12 = 1;
const uint8_t SM_RESET = 2;
const uint8_t SM_STREAM = 6;
const uint8_t SM_SDINEW = 11;
// For space reasons, we only define those mode bits that are actually used - the rest are
// commented out.
// const uint8_t SM_DIFF = 0;
// const uint8_t SM_OUTOFWAV = 3;
// const uint8_t SM_EARSPEAKER_LO = 4;
// const uint8_t SM_TESTS = 5;
// 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_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 = "MP3 player V1.36";
const prog_uchar welcomeLine2[] PROGMEM = " ";
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 backupError[] PROGMEM = "Backup ERROR.";
const prog_uchar resetUsage[] PROGMEM = "Reset Battery.";
const prog_uchar sleeping[] PROGMEM = "Good night...";
const prog_uchar fifthButton[] PROGMEM = "Menu pressed ...";
//const prog_uchar lowMemory[] PROGMEM = "LOW MEMORY !!!";
//const prog_uchar freeMemoryStr[] PROGMEM = "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 ------------------------------------------------------------------------------="" -----------------------------------------------------------------="" -="" ...="" 0="" 0x04="" 0x0804="" 0x08="" 0x9c60="" 1.30="" 1.8="" 1000ms="" 10="" 12.288="" 1="" 22000="" 31="" 32="" 44.1khz="" 5000l="" 5="" 60="" 60l="" 64="" 8khz="" :="" _bv="" a="" abandoned="" action_browsing_mp3="" action_select_mp3="" action_stop_playing="" actionrestore="0;" actionstatus="ACTION_BROWSING_MP3;" actually="" actuallyplaying="0;" add="" addminute="" addminutecounter="currentTime" after="" an="" analog="" and="" another="" any="" appropriate="" approximate="" are="" around="" as="" at="" awaitdatarequest="" back="" backup="" basic="" battery="" be="" because="" been="" before="" begin="" being="" blocksinfile="playFile.size()" blocksplayed="0;" boot="" browse="" browsing="" buffer="" bus="" button="" byte="" calling="" can="" card="" casts="" changes="" check="" checkforsleep="" checkvoltage="" chip="" class="" clear="" clock="" close="" completed="" copy.="" correct="" count="" counters="" current="" currentdisplaystatus="0;" currentopendirectory="" currenttime="millis();" cycles="" data.="" data="" declick:="" decoding.="" default="" delay="" delaymicroseconds="" dereferencing="" deselectsd="" did="" digitalread="" digitalwrite="" dim="" dimdisplay="" display.="" display="" do="" don="" down="" dreq="" dummy="" e="" eeprom="" elay="" else="" end="" erial.begin="" erial.println="" error="" even="" every="" experimenting="" failed="" fails="" failuretoplay="" fast="" file.="" file="" filename="" filenumber-1="" filepos.="" for="" from="" function="" functions="" get="" getstarttimes="" go="" hard="" hardware="" have="" here:="" high="" higher="" hour="" hours="" how="" hrow="" hz="" i="" if="" in="" inactivity="" init="" initialize="" input="" int="" introduce="" is="" it="" just="" keep="" know="" l1-2="" l="" last="" lastbuttonpress="currentTime;" lastdecodetime="currentTime;" lcd.backlight="" lcd.clear="" lcd.init="" lcd.print="" lcd.setcursor="" lcd="" lcdstatus="lcdLit;" library="" life="" long="" loop.="" loop="" lot="" low="" many="" means="" messages="" mhz.="" midi="" millis="" mins="1" minute.="" minute="" minutes.="" mode="" monitor="" mp3...="" mp3="" mp3deselectcontrol="" mp3deselectdata="" mp3selectcontrol="" mp3selectdata="" mp3writeregister="" ms="" msgdownpin="" msgfifthpin="" msgmenupin="" msgselectpin="" msguppin="" must="" mychar="pgm_read_byte_near(cardError" n="" name="" necessary="" need="" needed="" newfilepos="" newmode="" next="" nextbackupblocks="5000l;" nextvoltagecheck="currentTime;" nitialize="" no="" not="" of="" off="" on="" once="" only="" open="" or="" order="" out.="" out="" output="" p3setvolume="" p3writeregister="" part="" parts="" paul="" paused="" pen="" per="" pin="" pinmode="" play.="" play="" player.begin="" player.setvolume="" player="" playfile.close="" playfile.seek="" playfile="" playing:="" playing="" please="" plus="" populatefilearray="" power-up="" press="" printwelcome="" provide="" rate="" read="" reading="" reason="" register="" registers="" remaining="" reset="" resetmillis="currentTime+1000;" restored="" return="" riteregister="" run="" s="" sample="" save="" sci_bass="" sci_clockf="" sci_mode="" sci_vol="" sd="" sec="" second="" seconds="" secs="" see="" selected="" selectsd="" sending="" serial.flush="" serial.print="" set="" settings="" setup.="" setup="" setupvs="" should="" show="" showdisplay="" slow="" so="" software="" some="" speed="" spi.begin="" spi.setbitorder="" spi.setclockdivider="" spi.setdatamode="" spi.transfer="" spi="" sprintf="" start="" starting="" startup="" stay="" stereo="" stop="" stored="" such="" suitable="" switch="" switched="" switchofftime="currentTime" t="" tarting="" tempvalue="" the="" then="" this="" thisfile="" thisvolume="" time="" times="" to="" transfer="" transferendoffile="" trying="" uint16_t="" unable="" unknown="" until="" unused="" up="" usage="" user="" utterly="" v1.27="" v1.30="" values="" variables="" ve="" view="" void="" vol="" voltage="" vs1053="" vs1053b="" wait="" want="" wanteddisplaystatus="DISPLAY_BROWSING;" we="" well="" which="" while="" wil="" will="" wire.begin="" with="" work="" write="" writeregister="" wrong="" xb800="" xfe="" xfefe="" xff="" xffff="" yet="" you="" your=""> 5)2048>
{
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 = currentTime+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) )
{
fifthButtonPressed();
lastButtonPress = millis(); // Ensure we don't interpret a single human press as several presses...
}
// -----------------------------------------------------------------------
// Check the SELECT button
// -----------------------------------------------------------------------
tempValue = digitalRead(msgSelectPin);
if (tempValue == HIGH && (millis() > lastButtonPress + 600) )
{
selectButtonPressed();
lastButtonPress = millis(); // Ensure we don't interpret a single human press as several presses...
}
// --------------------------------------------------------------------------------
// 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))
{
menuButtonPressed();
lastButtonPress = millis(); // Ensure we don't interpret a single human press as several presses...
}
// -----------------------------------------------------------------------
// 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)*/ )
{
downButtonPressed();
lastButtonPress = millis(); // Ensure we don't interpret a single human press as several presses...
}
// -----------------------------------------------------------------------
// 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)*/ )
{
upButtonPressed();
lastButtonPress = millis(); // Ensure we don't interpret a single human press as several presses...
}
//
// 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;
}
// V 1.34 Print a screen message to ack button press before waiting
lcd.clear();
lcd.backlight();
lcd.setCursor(0, 0);
tempValue = 0;
myChar = pgm_read_byte(fifthButton);
while (myChar != '\0')
{
lcd.print(myChar);
myChar = pgm_read_byte_near(fifthButton + (++tempValue));
}
// V1.31: Before restoring the backup, check to see if MsgDownPin is ALSO being pressed:
// If so, then reset the battery usage times; else restore the backup.
delay(1000);
tempValue = digitalRead(msgDownPin);
if (tempValue == HIGH)
{
resetUsageTimes();
}
else
{
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;
}
long 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();
long 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");
backup(); // V1.36, write current file position to the backup.
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]
// V1.25 we're going to play a file, so show how much RAM we have left.
// V1.26 Move this to print all the time on the screen
// printFreeMemory();
// delay(4000);
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]);
}
printTimeUsage();
//printFreeMemory();
// Serial.println(fileName[fileNumber-1]); Serial.println(fileName[fileNumber]);
currentDisplayStatus = DISPLAY_BROWSING;
}
// --------------------------------------------------------------
// 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;
printTimeUsage();
//printFreeMemory();
}
// --------------------------------------------------------------
// Dim the LCD after n minutes of no button presses
// V1.30 - 15 seconds to dim display ...
// --------------------------------------------------------------
void dimDisplay()
{
if (currentTime > (lastButtonPress + 10000) ) // 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)
{
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;
}
// V1.30 If we're showing the volume, and no button has been pressed for 10 seconds, return to browsing mode.
if (currentDisplayStatus == DISPLAY_MENU && (currentTime > lastButtonPress + 5000l ) )
{
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;
}
return;
}
// End of V1.30 change.
}
// ------------------------------------------------------------------------
// The DOWN button has been pressed
// ------------------------------------------------------------------------
void downButtonPressed()
{
lastButtonPress = millis();
// If the display was DIM, then turn on the display and ignore the button press.
if (lcdStatus == lcdDimmed)
{
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()
{
lastButtonPress = millis();
// If the display was DIM, then turn on the display and ignore the button press.
if (lcdStatus == lcdDimmed)
{
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()
{
// V1.30 - ALWAYS note the time a button was pressed.
lastButtonPress = millis();
// If the display was DIM, then turn on the display and ignore the button press.
if (lcdStatus == lcdDimmed)
{
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(1,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.
// PAUL V 1.36 too many backups destroyed the SD card. Don't backup every 5000 blocks pls.
//if (blocksPlayed > nextBackupBlocks)
// {
// nextBackupBlocks = blocksPlayed + 5000l;
// backup();
// }
// Serial.println(temp16);
lastDecodeTime = millis();
}
// -----------------------------------------------------------------------------------
// Write out our current playfile and time
// -----------------------------------------------------------------------------------
void backup()
{
// If the doNotBackup flag is set (its set below on an error) then do Not Backup.
// perhaps the SD card lock is in place ?
if (doNotBackup == 1)
{
return;
}
Mp3DeselectControl();
Mp3DeselectData();
SelectSD();
//Serial.println("backup");
backupFile = SD.open(backupFileName, FILE_WRITE);
if (!backupFile)
{
// PAUL : Print error to screen, wait for Button 5 to be pressed, then set doNotBackup flag
lcd.clear();
lcd.setCursor(0,0);
tempValue = 0;
myChar = pgm_read_byte(backupError); // Print a message "Backup Error :" on the LCD.
while (myChar != '\0')
{
lcd.print(myChar);
myChar = pgm_read_byte_near(backupError + (++tempValue));
}
doNotBackup = 1;
delay(7000);
//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(tempStr, "%ld", blocksPlayed);
backupFile.println(currentOpenDirectory);
backupFile.println(fileNumber);
backupFile.println(tempStr);
backupFile.close(); // NOTE: This does not shorten the backup file !!!
// Serial.println("backup OK");
}
}
// -----------------------------------------------------------------------------------
// On restore button, go to the same file and approx the same place
// 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
}
// -----------------------------------------------------------------------------------
// checkForSleep: Put the arduino into sleep mode after n hours of usage
// -----------------------------------------------------------------------------------
void checkForSleep()
{
if (currentTime < switchOffTime )
{
return;
}
// Save file position, if we were playing a file.
if (actionStatus == ACTION_PLAYING_MP3)
{
backup();
}
// Write a message to the LCD
lcd.backlight();
lcd.clear();
lcd.setCursor(0,0);
tempValue = 0;
myChar = pgm_read_byte(sleeping); // Print a message "Going to sleep :" on the LCD.
while (myChar != '\0')
{
lcd.print(myChar);
myChar = pgm_read_byte_near(sleeping + (++tempValue));
}
delay(3000);
// This leaves the LCD backlit (if was lit at the time of switch off)
lcd.noBacklight();
Serial.end();
ADCSRA = 0; // disable ADC
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();
MCUCR = _BV (BODS) | _BV (BODSE); // turn on brown-out enable select
MCUCR = _BV (BODS); // this must be done within 4 clock cycles of above
sleep_cpu ();
// Until you get your switch working, to switch the battery off to save power and waste
// the music, about the only thing you can do is stop the play ...
// PAUL: V 1.33: The action to stop playing is actionStatus = ACTION_STOP_PLAYING;
return;
}
// ------------------------------------------------------------------------------------
// How many minutes that the battery has been running for are stored in EEPROM memory.
// We record the number of minutes in pause mode, and the number of minutes in play mode.
// The numbers are reset manually, hopefully when you recharge the battery !
// ------------------------------------------------------------------------------------
void getStartTimes()
{
// Get the minutes so far spent in PLAY mode
minutesPlaying = EEPROM.read(PLAY_MSB); // hi byte
minutesPlaying = minutesPlaying << 8;
minutesPlaying = minutesPlaying + EEPROM.read(PLAY_LSB); // add low byte
// Get the minutes so far spent in PAUSE mode
minutesPaused = EEPROM.read(PAUSE_MSB); // hi byte
minutesPaused = minutesPaused << 8;
minutesPaused = minutesPaused + EEPROM.read(PAUSE_LSB); // add low byte
return;
}
// ------------------------------------------------------------------------------------
// Write the counters to EEPROM
// ------------------------------------------------------------------------------------
void writeMinutesPlaying()
{
byte b1 = minutesPlaying >> 8;
EEPROM.write(PLAY_MSB, b1);
b1 = minutesPlaying & 0xff;
EEPROM.write(PLAY_LSB, b1);
return;
}
void writeMinutesPaused()
{
byte b1 = minutesPaused >> 8;
EEPROM.write(PAUSE_MSB, b1);
b1 = minutesPaused & 0xff;
EEPROM.write(PAUSE_LSB, b1);
return;
}
// ------------------------------------------------------------------------------------
// Add a minute to the correct counter, and write to EEPROM
// ------------------------------------------------------------------------------------
void addMinute()
{
if (currentTime < addMinuteCounter)
{
return;
}
addMinuteCounter = currentTime + (1000l * 60l); // Ensure we a add a minute every 60 seconds..
if (actionStatus == ACTION_PLAYING_MP3)
{
minutesPlaying++;
writeMinutesPlaying();
}
else
{
minutesPaused++;
writeMinutesPaused();
}
// Update the screen, if we're browsing or playing ...
if (actionStatus == ACTION_PLAYING_MP3 || actionStatus == ACTION_BROWSING_MP3)
{
printTimeUsage();
}
return;
}
// ------------------------------------------------------------------------------------
// Reset the time used to zero.
// ------------------------------------------------------------------------------------
void resetUsageTimes()
{
lcd.clear();
lcd.setCursor(0,0);
tempValue = 0;
myChar = pgm_read_byte(resetUsage); // Print a message "Resetting Battery :" on the LCD.
while (myChar != '\0')
{
lcd.print(myChar);
myChar = pgm_read_byte_near(resetUsage + (++tempValue));
}
EEPROM.write(PLAY_MSB, 0);
EEPROM.write(PLAY_LSB, 1);
EEPROM.write(PAUSE_MSB, 0);
EEPROM.write(PAUSE_LSB, 1);
getStartTimes();
delay(2000);
currentDisplayStatus = MENU_NOTSET; // Ensure that we reprint the correct screen, after printing the message above.
return;
}
// ------------------------------------------------------------------------------------
// Print the time spent playing and paused on the screen.
// ------------------------------------------------------------------------------------
void printTimeUsage()
{
// 1.34: Convert the time spent playing and paused into mAh used and print it.
// testing has show pause uses 65 mAh and play uses 85mAh
// limits with floating point arithmetic suggest uising integers is more sensible.
//
// We convert 85 mAh into minutes - 1.417 mA per minute -
// convert 65 mAh into minutes - 1.083 mA per minute - multiply by 1000 to do some maths then divide by 1000 again.
long mahUsed = ( (minutesPlaying * 1417l) + (minutesPaused * 1083)) / 1000l;
//lcd.setCursor(12,0);
//lcd.print(minutesPaused);
lcd.setCursor(12,1);
lcd.print(mahUsed);
return;
}
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 :
SDA -> A4
SCL -> A5
Obvious Vcc (5v) and GND.
No comments:
Post a Comment