NodeMCU and DFPlayer MP3 Player for Elderly/Toddlers: One Button Player

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

How to Play an MP3 File Using a NodeMCU (ESP8266) and DFPlayer Module

It only took about 12 hours of trial and error, but I finally got the NodeMCU to play an MP3 through the DFPlayer. It took 12 hours because I was reaching my tolerance when I went for a Hail Mary and swapped the Tx and Rx – and whaddayaknow – ABBA coming through loud and clear!

I don’t know if it’s a typo or what, but the GetStarted.ino helpfully describes the serial inputs in its code as:

SoftwareSerial softSerial(/rx =/4, /tx =/5);

Which corresponds with D2 and D1, respectively, on the NodeMCU:

However, as you can see from the diagram up above, the two are swapped, and D1 (BPIO5) should connect to Rx and D2 to Tx (GPIO4). OMFG!

The examples I’ve seen mostly use the Arduino UNO, so maybe the UNO has weird GPIO maps? Or maybe the knockoff DFPlayers sold on Amazon have their GPIO4/GPIO5 reversed (most likely scenario). I don’t know, but if you think you’ve wired your DFPlayer right, and your sketch looks good, then try swapping the Tx and Rx. It worked for me.

How to Do It, Step by Step

Select the NodeMCU board from the Tools > Board > ESP8266 Boards (3.1.2) > NodeMCU 0.9 (ESP 12 Module) menu.

If you don’t have the ESP8266 Boards installed, you need to do that first. I’m pretty new to the Arduino IDE, so I keep notes on how to set things up. For example:

  • Install Arduino IDE
  • Install Wemos driver EXE
  • Install ESP8266 boards to the IDE, if it isn’t already:
    File > Preferences > Additional Board Manager URLs: http://arduino.esp8266.com/stable/package_esp8266com_index.json
  • Click OK
  • Next, go to Tools > Board > Board Manager in your Arduino IDE.
  • Search for ESP8266 and install it.
  • Go to Tools > Board > ESP8266 Boards (3.1.2) > NodeMCU 0.9 (ESP 12 Module
  • Connect the board.
  • Go to Tools > Port and select the COM port.

While you’re at it, you might as well fire up the Serial Monitor in Tools > Serial Monitor. Set the Baud rate to 115200.

Wire up your boards like indicated at the top of this post. Note that I do NOT use a resistor that so many have recommended. It’s not necessary for this simple test.

Format your micro SD card for FAT32.

Choose a loud, obnoxious MP3 for your test, rename it to 0001.mp3, then copy it to the root directory. I used Waterloo by ABBA, because it has no intro; it starts shaking your booty right from the start. Yes – it will have just the one file and nothing else for this example.

This is for the simplest test, so I’m not going to go into how to add files, directories, etc.

Use the GetStarted.ino sketch. Here’s mine, but it’s exactly like the one on GitHub.

/***************************************************
DFPlayer - A Mini MP3 Player For Arduino
 <https://www.dfrobot.com/product-1121.html>
 
 ***************************************************
 This example shows the basic function of library for DFPlayer.
 
 Created 2016-12-07
 By [Angelo qiao](Angelo.qiao@dfrobot.com)
 
 GNU Lesser General Public License.
 See <http://www.gnu.org/licenses/> for details.
 All above must be included in any redistribution
 ****************************************************/

/***********Notice and Trouble shooting***************
 1.Connection and Diagram can be found here
 <https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299#Connection_Diagram>
 2.This code is tested on Arduino Uno, Leonardo, Mega boards.
 ****************************************************/

#include "Arduino.h"
#include "DFRobotDFPlayerMini.h"

#if (defined(ARDUINO_AVR_UNO) || defined(ESP8266))   // Using a soft serial port
#include <SoftwareSerial.h>
SoftwareSerial softSerial(/*rx =*/4, /*tx =*/5);
#define FPSerial softSerial
#else
#define FPSerial Serial1
#endif

DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);

void setup()
{
#if (defined ESP32)
  FPSerial.begin(9600, SERIAL_8N1, /*rx =*/D3, /*tx =*/D2);
#else
  FPSerial.begin(9600);
#endif

  Serial.begin(115200);

  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(FPSerial, /*isACK = */true, /*doReset = */true)) {  //Use serial 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.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(20);  //Set volume value. From 0 to 30
  myDFPlayer.play(1);  //Play the first mp3
}

void loop()
{
  static unsigned long timer = millis();
  
  if (millis() - timer > 60000) {
    timer = millis();
    myDFPlayer.next();  //Play next mp3 every 3 second.
  }
  
  if (myDFPlayer.available()) {
    printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.
  }
}

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!"));
      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;
  }
  
}

And the Serial Monitor displays:

I think that wraps it up. It took me so long for it to make sound that I’ve run out of steam for my actual project, which is to create a music player for babies or for old folks with Alzheimer’s Disease/Dementia or for playing soothing sleep sounds with giant buttons to hit while half asleep in the dark.