The intent of this project is to create a music player that uses only one button, so even a blind toddler with one foot could use it. It does have limitations, however; you can only set the volume once, because the one button can only Start and Stop the player. Technically, you could use it to Forward the music by pressing it once to Stop, then again to Start – it will start playing a different, random song.
Although I only added one button, I did also include leads to Volume Up (D4) and Volume Down (D6) – those are not included in the above wiring diagram (but you can see them poking up in the back). I added these leads so I can adjust volume as needed for the environment. Note that if you unplug it, it will revert to the initial volume level defined in the sketch.
I could also put the modules and wires inside the speaker and drill a hole through the top of this speaker, but I’ll wait until I get the bugs worked out. I also may add hidden “buttons” for Volume, Next, and Prev. I’d also like an option to play from other directories – maybe adding two buttons – for example: Music and Books.
Some things to keep in mind about DFPlayer:
- Micro SD size limit is 4GB – 32GB, formatted as FAT32.
- The official documentation says that you can have only 255 files in each directory, so I had to break up my MP3s into multiple directories.
- The order in which you copy files to the SD card matters.
- The Tx/Rx on NodeMCU is swapped, according to the GetStarted.ino sketch.
- The file structure on the SD card is wonky. If you put the MP3 files on root, the format is 4 digits, beginning with 0001.mp3. If you put them in a folder, the folders are 2 digits: 01 for example, but the files must begin with only three digits: 001.mp3.
- The order in which you copy the files over to the SD card matters – they may not play in order unless you individually copy them over.
- You can wire it for mono, stereo, or headphones.
- The serial print macro can use only literal values, so you can’t print variables. The literal values are defined when the sketch is compiled. For example, you can’t add the track number variable to the line: Serial.println(F(“Playing next track: ” ));
- The Mini MP3 player has the capability of holding 6,000 MP3 files in a folder on the SD chip when using the /MP3 folder name.
- If the same song keeps repeating, even if the IDE shows that it’s playing a different file number, it’s because the file for that number doesn’t exist. This happens when you play for example, myDFPlayer.play(2083) but you only have 900 files on the card.
This is a simple Arduino sketch I found that works. It’s a good starting point.
Working Sketch
Here’s a working sketch for the single button music player:
//Original source: https://community.dfrobot.com/makelog-312205.html #include "Arduino.h" #include "SoftwareSerial.h" #include "DFRobotDFPlayerMini.h" SoftwareSerial mySoftwareSerial(4, 5); // RX, TX DFRobotDFPlayerMini myDFPlayer; void printDetail(uint8_t type, int value); #define pp D3 //this is the only button I'll use for this project. #define vu D4 //added a lead but no button - use with Ground lead to Vol+ #define vd D6 //added a lead but no button - use with Ground lead to Vol- #define nxt D5 #define prv D7 int v=9, pv=0; int totalFileCount = 2673; int randNumber = random(1, totalFileCount); //Generate a random number from all mp3s void setup() { mySoftwareSerial.begin(9600); Serial.begin(115200); pinMode(pp,INPUT_PULLUP); pinMode(nxt,INPUT_PULLUP); pinMode(prv,INPUT_PULLUP); pinMode(vu,INPUT_PULLUP); pinMode(vd,INPUT_PULLUP); Serial.println(); Serial.println(F("DFRobot DFPlayer Mini Demo")); Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); delay(1000); if (!myDFPlayer.begin(mySoftwareSerial, true, false)) { //Use softwareSerial to communicate with mp3. Serial.println(F("Unable to begin:")); Serial.println(F("1.Please recheck the connection!")); Serial.println(F("2.Please insert the SD card!")); while(true){ delay(0); // Code to compatible with ESP8266 watch dog. } } delay(2000); //added a delay to prevent the stack error Serial.println(F("DFPlayer Mini online.")); myDFPlayer.setTimeOut(2000); //I had to up this to 2000 delay(1000); //I found a delay necessary for it to execute the following functions. myDFPlayer.volume(v); //Set volume value. From 0 to 30 delay(1000); playRandom(); delay(2000); Serial.println(F("Playing all random MP3s " )); delay(1000); } void loop() { //Serial.println(pv); //prints the zero if(digitalRead(pp)==0){ if(pv == 0){ myDFPlayer.volume(v); playRandom(); pv=1; Serial.println(F("D3 Play Pressed.")); Serial.println("Random. " + String(randNumber)); delay(200); //prevent double presses } else if(pv == 1){ myDFPlayer.pause(); pv=0; Serial.println(F("D3 Pause Pressed.")); delay(200); //prevent double presses } } if(digitalRead(nxt)==0){ myDFPlayer.volume(v); playRandom(); Serial.println(F("D5 Next Pressed.")); } if(digitalRead(prv)==0){ myDFPlayer.volume(v); myDFPlayer.previous(); Serial.println(F("xxx Previous Pressed.")); } if(digitalRead(vu)==0 && v<30){ v=v+1; myDFPlayer.volume(v); Serial.println("D4 Vol Up Pressed. Level: " + String(v)); } if(digitalRead(vd)==0 && v>0){ v=v-1; myDFPlayer.volume(v); Serial.println("D6 Vol Down Pressed. Level: " + String(v)); } delay(200); if (myDFPlayer.available()) { printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. } } void playRandom() { randNumber = random(1, totalFileCount); myDFPlayer.play(randNumber); } void printDetail(uint8_t type, int value){ switch (type) { case TimeOut: Serial.println(F("Time Out!")); break; case WrongStack: Serial.println(F("Stack Wrong!")); break; case DFPlayerCardInserted: Serial.println(F("Card Inserted!")); break; case DFPlayerCardRemoved: Serial.println(F("Card Removed!")); break; case DFPlayerCardOnline: Serial.println(F("Card Online!")); break; case DFPlayerUSBInserted: Serial.println("USB Inserted!"); break; case DFPlayerUSBRemoved: Serial.println("USB Removed!"); break; case DFPlayerPlayFinished: Serial.print(F("Number:")); Serial.print(value); Serial.println(F(" Play Finished!")); delay(500); playRandom(); Serial.println(F("Playing next track: " )); Serial.print(F(" ")); break; case DFPlayerError: Serial.print(F("DFPlayerError:")); switch (value) { case Busy: Serial.println(F("Card not found")); break; case Sleeping: Serial.println(F("Sleeping")); break; case SerialWrongStack: Serial.println(F("Get Wrong Stack")); break; case CheckSumNotMatch: Serial.println(F("Check Sum Not Match")); break; case FileIndexOut: Serial.println(F("File Index Out of Bound")); break; case FileMismatch: Serial.println(F("Cannot Find File")); break; case Advertise: Serial.println(F("In Advertise")); break; default: break; } break; default: break; } }
Note that I created my own “Shuffle” function playRandom() which generates a random number between 1 and the total number of files on the SD card. So every time it finishes playing a song, it plays another random song – and there’s a 1 in 2673 chance that it will play the same song twice. I did this mostly because for some reason, using the myDFPlayer.randomAll() function plays the same first song every time it starts. Future code will have a “played” array so it doesn’t repeat songs. Also, “random” has a tendency to pick favorite songs, so I may add a function for a better random generator.
I failed to dynamically set the totalFileCount using myDFPlayer.readFileCounts(). For expediency, I just counted the number of files and set it as a global variable. Ideally, I want the script to read the number of files on the SD card – which it CAN do – but I wasn’t able to get it to consistently work.
Troubleshooting
I’ve spent many hours troubleshooting this sketch. At first, I thought the SD card was the culprit, because it seemed to play fine with just a few folders in it, but then it was randomly failing – and I re-partitioned and reformatted my poor SD cards so many times, thinking it must be the problem.
It would work after an upload, but then I’d make a simple code change, and suddenly I’d get errors, or worse, it would stall on power on – it would get to “DFPlayer Mini online.” then not respond. Some Interwebbers suggested these two changes:
if (!myDFPlayer.begin(mySoftwareSerial, true, false)) {
and
myDFPlayer.setTimeOut(2000);
I’m convinced that the mySoftwareSerial, true, false fixed most of my trouble. It would run but I’d still get the “DFPlayerError:Get Wrong Stack” error on power up. So I added delays in the setup() and changed setTimeOut to 2000. So far, these changes have prevented the errors, and it starts as expected when powered on.
I also kept running into the problem where it would repeat the same song many times. Turns out the card was corrupted, and that file number wasn’t available, so DFPlayer played the first track it could find – track 1.
Setting Up the Micro SD
And the micro SD card, formatted for FAT32 contains 13 folders currently and 2673 MP3 files. Each directory is two digits, from 01-13. Each of the file names must start with 3 digits in each of the directories.
Make sure you decide how you want the files laid out before making a ton of changes. The best practice is to figure it all out on your HD then copy each folder – one at a time – to the SD.
Headphone Jack
I tested it with the headphone jack. Plugging in headphones does not automatically disable the speakers; I’ll need a switch for that.
Headphone plug wiring:
And wiring the jack to the DFPlayer:
The headphones signal works but is weak. Playing straight into headphones is only barely audible at 100% volume. So disappointing – because the built in amp for speakers is pretty loud. In order to use headphones, I’ll need a separate amplifier and run the DAC outputs into it.
References
Here are all the functions available, for reference:
//----Mp3 play---- myDFPlayer.next(); //Play next mp3 myDFPlayer.previous(); //Play previous mp3 myDFPlayer.play(1); //Play the first mp3 myDFPlayer.loop(1); //Loop the first mp3 myDFPlayer.pause(); //pause the mp3 myDFPlayer.start(); //start the mp3 from the pause myDFPlayer.playFolder(15, 4); //play specific mp3 in SD:/15/004.mp3; Folder Name(1~99); File Name(1~255) myDFPlayer.enableLoopAll(); //loop all mp3 files. myDFPlayer.disableLoopAll(); //stop loop all mp3 files. myDFPlayer.playMp3Folder(4); //play specific mp3 in SD:/MP3/0004.mp3; File Name(0~65535) myDFPlayer.advertise(3); //advertise specific mp3 in SD:/ADVERT/0003.mp3; File Name(0~65535) myDFPlayer.stopAdvertise(); //stop advertise myDFPlayer.playLargeFolder(2, 999); //play specific mp3 in SD:/02/004.mp3; Folder Name(1~10); File Name(1~1000) myDFPlayer.loopFolder(5); //loop all mp3 files in folder SD:/05. myDFPlayer.randomAll(); //Random play all the mp3. myDFPlayer.enableLoop(); //enable loop. myDFPlayer.disableLoop(); //disable loop. //----Read imformation---- Serial.println(myDFPlayer.readState()); //read mp3 state Serial.println(myDFPlayer.readVolume()); //read current volume Serial.println(myDFPlayer.readEQ()); //read EQ setting Serial.println(myDFPlayer.readFileCounts()); //read all file counts in SD card Serial.println(myDFPlayer.readCurrentFileNumber()); //read current play file number Serial.println(myDFPlayer.readFileCountsInFolder(3)); //read file counts in folder SD:/03 //----Mp3 control---- // myDFPlayer.sleep(); //sleep // myDFPlayer.reset(); //Reset the module // myDFPlayer.enableDAC(); //Enable On-chip DAC // myDFPlayer.disableDAC(); //Disable On-chip DAC // myDFPlayer.outputSetting(true, 15); //output setting, enable the output and set the gain to 15 // myDFPlayer.EQ(DFPLAYER_EQ_POP); // myDFPlayer.EQ(DFPLAYER_EQ_ROCK); // myDFPlayer.EQ(DFPLAYER_EQ_JAZZ); // myDFPlayer.EQ(DFPLAYER_EQ_CLASSIC); // myDFPlayer.EQ(DFPLAYER_EQ_BASS);
More articles, tutorials that I found useful:
- The Full Function sketch on Github
- How to wire headphones and writing a custom Play method – This method makes sure any existing tracks are stopped first. Then we make sure that the current file number that the DFPlayer has matches the one we supplied. Keep trying till these track numbers match. It was the only way that I could play a track 100% of the time on the DFPlayer mini.
- Walkthrough of functions and a tutorial/demo – Use a isPlaying boolean variable to indicate a file is currently playing. Also includes demo for using 3 buttons.
- How to Play Folders – and more – If you need to have more than a thousand mp3 files in a folder, you can use the “playLargeFolder” function. In that case, the file number has to start with a four-digit number.
- Monitor the Busy pin to play Next song – Pin 16 of the module is a busy signal with a LOW meaning that a track is currently being played. Perhaps monitoring this busy signal and waiting for it to go HIGH to trigger the next() function would work.
- ESPHome Tutorial – For best quality audio a powered stereo speaker can be connected to the modules
DAC_R
,DAC_L
(aka the Headphone Jack).