Home MP3 Player | |
The SDFat library has changed since I wrote this. The function void listLfn(SdBaseFile* dirFile) no longer seems to be used... I will investigate more some time...
/* MP3 Player built around the Waytronic Electronics WT5001M03-28P Module. * This module doesn't have a built-in SD card holder so allows the size of the SD card to be chosen. It also allows the Arduino to read the files on the card. (c)2015 vwlowen.co.uk */ // Use SdFat for long file names. #include <SdFat.h> // https://github.com/greiman/SdFat-beta SdFat SD; #include <IRremote.h> // https://github.com/shirriff/Arduino-IRremote #include <EEPROM.h> #include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library #include <Adafruit_QDTech.h> // https://github.com/zigwart/Adafruit_QDTech #include <SPI.h> #include <SoftwareSerial.h> SoftwareSerial mp3(0, 8); // Define Software Serial pins for 'mp3' RX, TX. #define sclk 13 // SPI pins (ATmega328) for LCD. #define mosi 11 #define cs 9 // Other control pins for LCD. #define dc 7 #define rst 6 Adafruit_QDTech tft = Adafruit_QDTech(cs, dc, rst); // Invoke LCD library object instance. int pinEnable_MP3 = 2; // HIGH connects SD card to MP3 module. LOW connects it to Arduino. int pinBusy = 3; // Busy signal from MP3 module. int pinReset_MP3 = 4; // Reset signal to MP3 module. int CARD_CS = 10; // SD Card_select from Arduino (SPI SS pin). int pinIR = 5; // Input from Infra-Red receiver. IRrecv irrecv(pinIR); // Start Infra-Red library. decode_results results; // Variable structure for results from IR receiver. // Defines for my IR remote. These are the raw values returned from *MY* spare IR remote. // Use the Arduino IRrecvDump example program to get the values from your remote. #define VolUp 0x81CEA05F #define VolDown 0x81CE609F #define PlayPause 0x14E10EF #define Stop 0x14E906F #define Enter 0x81CE52AD #define RandomPlay 0x14E807F #define Next 0x81CE00FF #define Prev 0x81CE20DF #define Zero 0x81CE02FD #define One 0x81CEE817 #define Two 0x81CE18E7 #define Three 0x81CE9867 #define Four 0x81CE58A7 #define Five 0x81CED827 #define Six 0x81CE38C7 #define Seven 0x81CEB847 #define Eight 0x81CE7887 #define Nine 0x81CEF807 #define Overrun 0xFFFFFFFF // Returned when IR signal code is not recognized. // Variables to build up a three-digit number with eah IR Remote press or after timeout. int digit = -1; int dig1 = -1; int dig10 = -1; int dig100 = -1; int digCount = 0; int number; // Flag shows a digit was received from the IR rather than boolean isNumber = true; // a code for Vol Up, Vol Down, Next, Stop etc. unsigned long int irTimer = 0; // Timer for IR timeout. A 3-digit number (with leading zeros) int irTimeout = 2000; // will be calculated from the digits already received. unsigned long int textTimer; // Timer for scrolling text int textTimeout = 0; // Higher number means slower scroll. unsigned long int volumeTimer; // Timer for length of time to display volume value on LCD. byte mp3Volume = 20; // Default volume setting for MP3 module. int MaxTracks = 99; // Arduino will read SD card to get actual number of tracks. // File-playing flags boolean mp3Changed = false; // A new track number has been selected. boolean isRandom = false; boolean isPaused = false; boolean isStopped = true; boolean firstTrack = true; // Variables for scrolling filename on display String longName; // Long File name obtained from reading the SD card. int displayWidth = 13; // Width of LCD with size 2 letters. int offset = 0; void setup() { pinMode(pinReset_MP3, OUTPUT); digitalWrite(pinReset_MP3, HIGH); pinMode(pinEnable_MP3, OUTPUT); digitalWrite(pinEnable_MP3, LOW); // Disconnect MP3 Module from SD card. pinMode(pinBusy, INPUT); randomSeed(analogRead(0)); // Initialize random numbers for 'Random Play' function. tft.init(); // Initialize display tft.setRotation(1); // LCD screen rotation: 0 - Portrait, 1 - Landscape tft.fillScreen(QDTech_BLACK); // Clear LCD. tft.setTextWrap(false); tft.setTextColor(QDTech_WHITE); while (!SD.begin(CARD_CS)) { // Initialize SPI bus for SD card. tft.setCursor(10,10); tft.print("Insert Card"); // Display while no card is inserted. } tft.fillScreen(QDTech_BLACK); tft.setCursor(10,10); tft.print("Reading Card"); listLfn(SD.vwd(), -1); // Long file names. Parameter -1 means get MaxTracks. tft.fillScreen(QDTech_BLACK); tft.setCursor(0,0); tft.setTextColor(QDTech_WHITE); tft.setTextSize (2); tft.print("MP3 Player "); tft.setTextSize(1); tft.setCursor(130, 10); tft.setTextColor(QDTech_BLUE); tft.print(MaxTracks); // Print total number of tracks found on SD card. irrecv.enableIRIn(); // Start the IR receiver mp3.begin(9600); // Initialize the serial communication to the MP3 Module. mp3Volume = EEPROM.read(0); // Read Volume value from EEPROM. if (mp3Volume > 31) mp3Volume = 15; // Keep Volume value if EEPROM hasn't been set yet. setStop(); // Make sure the MP3 Module isn't playing a track. setVolume(); // Set MP3 Module volume (0 - 31). number = 0; // Initial track number. showNumber(); // Display track number as three digits (000). } void getTrackName(int num) { digitalWrite(pinEnable_MP3, LOW); // Switch SD card over to Arduino SPI. delay(50); SD.begin(CARD_CS); listLfn(SD.vwd(), num); // List 'num' number of files & get long filename digitalWrite(pinEnable_MP3, HIGH); // Switch SD card back to MP3 Module. delay(50); digitalWrite(pinReset_MP3, LOW); // Reset MP3 Module. (Required after losing connection delay(10); // to SD card. digitalWrite(pinReset_MP3, HIGH); delay(800); // Delay to allow MP3 Module to initialize itself. setVolume(); // Restore Volume. (Defaults to maximum after reset). } // Routine to work out which code was received from the IR receiver. void irConvert() { digit = -1; switch (results.value) { case Next: if ((!isStopped) && (!isPaused)) { number++; mp3Changed = true; } break; case Prev: if ((!isStopped) && (!isPaused)) { number--; mp3Changed = true; } break; case Zero: digit = 0; break; case One: digit = 1; break; case Two: digit = 2; break; case Three: digit = 3; break; case Four: digit = 4; break; case Five: digit = 5; break; case Six: digit = 6; break; case Seven: digit = 7; break; case Eight: digit = 8; break; case Nine: digit = 9; break; case Overrun: break;//digit = -2; default: break;//digit = -1; case RandomPlay: isRandom = !isRandom; tft.setCursor(0, 90); tft.fillRect(0, 90, 100, 105, QDTech_BLACK); // Clear tft.setTextColor(QDTech_YELLOW); tft.setTextSize (1); if (isRandom) { tft.print("Random Play"); } else if (isStopped) { tft.print("Stop"); } else tft.print("Play"); break; case VolUp: if (mp3Volume < 31) { mp3Volume++; setVolume(); EEPROM.write(0, mp3Volume); } break; case VolDown: if (mp3Volume > 0) { mp3Volume--; setVolume(); EEPROM.write(0, mp3Volume); } break; case PlayPause: isPaused = !isPaused; setPlayPause(); break; case Stop: setStop(); isStopped = true; isPaused = true; break; } if ( digit > -1){ isNumber = true; tft.setTextSize(2); tft.setCursor(100+(digCount * 11), 100); tft.setTextColor(QDTech_BLUE); tft.print(digit); } else { isNumber = false; return; } // If a number is received, work out if it's the 1st, 2nd or 3rd of a sequence and // create a 3-digit number if it's the 3rd. if ((isNumber)) { irTimer = millis(); if (digCount == 0) { dig1 = digit; digCount = 1; } else if (digCount == 1) { dig10 = digit; digCount = 2; } else if (digCount = 2) { dig100 = digit; digCount = 3; number = (100*dig1) + (10*dig10) + dig100; showNumber(); isNumber = false; mp3Changed = true; } } } void resetCounters() { digCount = 0; dig1 = 0; dig10 = 0; dig100 = 0; digit = -1; isStopped = false; isPaused = false; } void showNumber() { resetCounters(); tft.fillRect(85, 100, 150, 100, QDTech_BLACK); // Clear input digits tft.fillRect(0, 50, 180, 20, QDTech_BLACK); // Clear title text tft.setCursor(0,30); tft.fillRect(0, 30, 50, 14, QDTech_BLACK); // Clear track number tft.setTextColor(QDTech_YELLOW); tft.setTextSize (2); if (number < 100) tft.print("0"); if (number < 10) tft.print("0"); tft.println(number); tft.setCursor(0, 90); tft.fillRect(0, 90, 100, 9, QDTech_BLACK); // Clear tft.setTextColor(QDTech_YELLOW); tft.setTextSize (1); if ((isStopped) || (number == 0)){ tft.print("Stop"); } else isRandom ? tft.print("Random") : tft.print("Play"); } void setVolume() { // Send the Volume value to the MP3 Module. mp3.write(0x7E); mp3.write(0x03); mp3.write(0xA7); mp3.write(mp3Volume); mp3.write(0x7E); tft.setCursor(0, 110); tft.fillRect(0, 110, 60, 9, QDTech_BLACK); tft.setTextColor(QDTech_BLUE); tft.setTextSize(1); tft.print("Volume: "); tft.print(mp3Volume); volumeTimer = millis(); // Set timer for displaying Volume on LCD. } void setPlayPause() { // Send Play/Pause to MP3 Module. if (number != 0) { mp3.write(0x7E); mp3.write(0x02); mp3.write(0xA3); mp3.write(0x7E); tft.setCursor(0, 90); tft.fillRect(0, 90, 100, 9, QDTech_BLACK); // Clear tft.setTextColor(QDTech_YELLOW); tft.setTextSize (1); if (isPaused){ tft.print("Pause"); } else { isRandom ? tft.print("Random") : tft.print("Play"); } } else { isRandom ? number = random(1, MaxTracks+1) : number = 1; setPlayTrack(number); } } void setPlayTrack(int trk) { // Tell MP3 Module to play specified track number. mp3.write(0x7E); mp3.write(0x04); mp3.write(0xA0); mp3.write((byte)0x00); // Track nUmber Hi Byte - always zero for us. mp3.write(trk); // Track number Lo Byte. mp3.write(0x7E); } void setStop() { // Tell MP3 Module to stop playing. mp3.write(0x7E); mp3.write(0x02); mp3.write(0xA4); mp3.write(0x7E); tft.setCursor(0, 90); tft.fillRect(0, 90, 100, 9, QDTech_BLACK); // Clear tft.setTextColor(QDTech_YELLOW); tft.setTextSize (1); tft.print("Stop"); } void loop() { if (millis() > (volumeTimer + 5000)) { // Check Volume display timer. tft.setCursor(0, 110); tft.fillRect(0, 110, 60, 9, QDTech_BLACK); } if (irrecv.decode(&results)) { // Check if IR receiver has any results. if (results.value != Overrun) { irConvert(); // Decypher results. } irrecv.resume(); // Ready to receive the next value. } delay(100); // If a number is received from the IR, determine if it's the 1st or 2nd of a sequence // AND, if waiting for the 2nd and/or 3rd has timed out, calculate 3-digit number based on the // digits that have already been received. if ((digCount == 1) || (digCount == 2)) { if (millis() > (irTimer + irTimeout)) { // Timed out so build number out of digits if (digCount == 1) { // that HAVE been received. number = dig1; } if (digCount == 2) { number = (10*dig1) + dig10; } if (digCount == 3) { number = (100*dig1) + (10*dig10) + dig100; } resetCounters(); // Number has been determined so clear temporary variables. isRandom = false; isNumber = false; // Reset isNumber flag ready for next sequence. mp3Changed = true; // New track number has been selected so set 'changed' flag. } } // If module isn't busy (playing) and it's not stopped or paused, increment track number unless... // ...a new track number was received from the IR. else if ((digitalRead(pinBusy) == LOW) && (!isPaused) && (!isStopped) && (!firstTrack)){ isRandom ? number = random(1, MaxTracks + 1) : number++; if (number > MaxTracks) number = 1; mp3Changed = true; } if ((number > 0) && (mp3Changed) && (!isPaused) && (!isStopped) ) { getTrackName(number); // Read the SD card to get the filename of the track number textTimer = millis(); showNumber(); digitalWrite(pinEnable_MP3, HIGH); // Player bus = on (should already be on anyway) setPlayTrack(number); // Tell MP3 Module to play track delay(2000); // Allow time for the MP3 Module to begin playing mp3Changed = false; firstTrack = false; } // Scroll track filename on LCD display. if ((digitalRead(pinBusy) == HIGH) && (millis() > (textTimer + textTimeout)) ){ tft.setCursor(180,50); tft.setTextColor(QDTech_GREEN); tft.setTextSize (2); String t = ""; for (int i = 0; i < displayWidth; i++) t += longName.charAt((i + offset) % longName.length()); tft.setCursor(0, 50); tft.fillRect(0, 50, 180, 20, QDTech_BLACK); tft.print(t); offset > longName.length() ? offset = 2 : offset++; textTimer = millis(); } } // List files on SD card with long filenames. If trackNumber = -1, then list all to // get number of tracks (MaxTracks). If trackNumber = > 0, list upto that number to get // long filename of that trackNumber. void listLfn(SdBaseFile* dirFile, int trackNumber) { uint8_t offset[] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30}; char name[13]; char lfn[131]; bool haveLong = false; dir_t dir; uint8_t i; uint8_t lfnIn = 130; uint8_t ndir; uint8_t sum; uint8_t test; boolean found = false; if (trackNumber == -1) MaxTracks = 0; int trackCount = 0; dirFile->rewind(); while ((dirFile->read(&dir, 32) == 32) && !found){ if (DIR_IS_LONG_NAME(&dir)) { if (!haveLong) { if ((dir.name[0] & 0XE0) != 0X40) continue; ndir = dir.name[0] & 0X1F; test = dir.creationTimeTenths; haveLong = true; lfnIn = 130; lfn[lfnIn] = 0; } else if (dir.name[0] != --ndir || test != dir.creationTimeTenths) { haveLong = false; continue; } char *p = (char*)&dir; if (lfnIn > 0) { lfnIn -= 13; for (i = 0; i < 13; i++) { lfn[lfnIn + i] = p[offset[i]]; } } } else if (DIR_IS_FILE_OR_SUBDIR(&dir) && dir.name[0] != DIR_NAME_DELETED && dir.name[0] != DIR_NAME_FREE) { if (haveLong) { for (sum = i = 0; i < 11; i++) { sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + dir.name[i]; } if (sum != test || ndir != 1) haveLong = false; } // SdFile::dirName(dir, name); if (dir.reservedNT) { bool dot = false; for (char *p = name; *p; p++) { if (*p == '.') { dot = true; continue; } if (dot && (dir.reservedNT & 0X8) || !dot && (dir.reservedNT & 0X10)) { *p = tolower(*p); } } } if (haveLong) { longName = lfn + lfnIn; if (trackCount == trackNumber) { longName = longName.substring(0, longName.lastIndexOf(".mp3")) + "... "; found = true; } if (trackNumber == -1) { if (longName != "System Volume Information") { MaxTracks++; } } } trackCount++; if (dir.name[0] == DIR_NAME_FREE) return; haveLong = false; } } } More to follow...
|