| 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...
| |