DFPlayer File Playing Delay Allowance: SOLVED

Hi Guys,

Really struggling with this!

I have a DFPlayer containing an SD card with two files on it. I want to be able to select which file to play using a single pushbutton, i.e. one press plays track one, second press plays track 2 and a third press stops all playing ready to cycle through again. I have a working button press function which sets a flag "track" to 0, 1 or 2, but the problem comes when passing the button choice to the DFPlayer.

To add to the problem, I need for whichever track is playing to loop until another button press is detected and then, in the case of track one, stop and commence track 2. In the case of track 2 playing, the button press would silence the playing, ready for the next press which would start track 1 again, if you see what I mean. I don't see how to stop the timer in order to stop the track playing and to read the pushbutton.

Its the delay that has to be added to allow the tracks to be played that are causing me problems. I know I should use millis, but I have so much trouble getting my head around the principle - despite using millis in my button press function!. Will I have to delve into the black magic of interrupts??

I'll also be controlling some leds at the same time, so I know I'll have to use millis to do the timing there, but that's something I have done before and so can lift that code from another project.

Below I've attached the code I have come up with so far - but it obviously doesn't work as it stops after 30 seconds of playing track 1 and because I can't change the value of "track" whilst it is playing, it just loops track 1.

#include <DFPlayerMini_Fast.h>

#include "SoftwareSerial.h"

// constants won't change
const int buttonPin = 12;   // the number of the pushbutton pin
// Use pins 8 and 9 to communicate with DFPlayer Mini
const int Tx = 8; // Connects to module's RX
const int Rx = 9; // Connects to module's TX

// Variables will change:
int track = 0;                // track number determined by number of switch presses
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

SoftwareSerial mySerial(Rx, Tx);  // assign the softwareSerial Rx and Tx pins

DFPlayerMini_Fast myMP3;

void setup() {

  Serial.begin (9600);
  pinMode(buttonPin, INPUT_PULLUP);  // set the pushbutton as input with pullup
  mySerial.begin(9600); // Init serial port for DFPlayer Mini
  myMP3.begin(mySerial);
  myMP3.volume(30);
  delay(25);
}

void loop() {
  selector();
  Serial.println (track);
  //if (track == 1){
  if (track > 0) {
    myMP3.play(track);
    // delay (30000);  // NEED TO CREATE AN INTERRUPTIBLE TIMER HERE
  }
  //else if (track == 2){
  // myMP3.play(2);
  // delay (30000);}// NEED TO CREATE AN INTERRUPTIBLE TIMER HERE

}

int selector() {
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == LOW) {
        track++;
        if (track >= 3 || track < 0)
          track = 0;
      }
    }
  }
  lastButtonState = reading;
  return track;
}

]

You can't have delays to handle this. What you describe is a state machine where you need to react to events such as a timeout event or button event.

here is a sample code that you can run with the Serial Console opened at 115200 bauds that would show you what's going on.

The code is totally not optimized as I spelled out all the events (two different functions) and states just for you to understand better what's going on.

The basic idea is to define the state of your system: muted, playing audio#1 or #2
the you check

  • if a button has been pressed and if so you handle the action needed and that moves you to the next stage
  • if a timeout has occurred you handle it the right way given your stage (relaunch the audio)
const byte buttonPin = 12;

enum : byte {MUTED, TRACK1, TRACK2} currentPlayerState = MUTED;
const uint32_t track1Duration = 5000; // 5 seconds
const uint32_t track2Duration = 7000; // 7 seconds
uint32_t startTime;

void userAction()
{
  static byte lastButtonState = HIGH;
  byte currentButtonState = digitalRead(buttonPin);
  if (currentButtonState == LOW && currentButtonState != lastButtonState) {
    switch (currentPlayerState) {
      case MUTED: // we were MUTED, we get a button press event, so next stage is playing track1
        Serial.println(F("Launching Track 1"));
        // ... to do ...
        startTime = millis();
        currentPlayerState = TRACK1;
        break;
      case TRACK1: // we were playing track 1, we get a button press event, so next stage is playing track 2. Need to stop track 1 first.
        Serial.println(F("Stopping Track1, Launching Track 2"));
        // ... to do ...
        startTime = millis();
        currentPlayerState = TRACK2;
        break;
      case TRACK2: // you got the idea by now :)
        Serial.println(F("Stopping Track2, Muting Player"));
        // ... to do ...
        currentPlayerState = MUTED;
        break;
    }
    delay(15); // cheap anti-bounce
  }
  lastButtonState = currentButtonState;
}

void timerAction()
{
  switch (currentPlayerState) {
    case TRACK1: // we are currently playing track1, check if it has ended and if so, relaunch it
      if (millis() - startTime >= track1Duration) {
        Serial.println(F("Track1 ended, relaunching Track 1"));
        // ... to do ...
        startTime = millis();
      }
      break;
    case TRACK2:
      if (millis() - startTime >= track2Duration) {
        Serial.println(F("Track2 ended, relaunching Track 2"));
        // ... to do ...
        startTime = millis();
      }
      break;
    case MUTED: break; // don't worry about it
  }
}

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(115200);
}

void loop() {
  userAction();
  timerAction();
}

I let you modify this to integrate the DFPlayer commands to start/stop audio when necessary

That DF library has a loop() function which will continuously play a given track. Why not use that? Then, you only have to react to the button press...

Even easier then if auto loop exists !

Many thanks J-M-L and blh64, you've given me plenty of food for thought there.

Its midnight here as I write this, so I'll tackle it in the morning, but I did manage to cobble together something that's very ugly - but it sort of works. Not as well as I want it to, but I did manage to find out that you can do away with the delay for the length of the file by making sure the next time through the loop it looks for a non-existent file!

This is what I came up with (which doesn't work with the loop function), but I prefer your ideas, I'll let you know how it goes!!

Very Ugly code:

#include <DFPlayerMini_Fast.h>

#include "SoftwareSerial.h"

// constants won't change
const int buttonPin = 12;   // the number of the pushbutton pin
// Use pins 8 and 9 to communicate with DFPlayer Mini
const int Tx = 8; // Connects to module's RX
const int Rx = 9; // Connects to module's TX

// Variables will change:
int track = -1;                // track number determined by number of switch presses
int choose = 0;              // number of presses
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

SoftwareSerial mySerial(Rx, Tx);  // assign the softwareSerial Rx and Tx pins

DFPlayerMini_Fast myMP3;

void setup() {

  Serial.begin (9600);
  pinMode(buttonPin, INPUT_PULLUP);  // set the pushbutton as input with pullup
  mySerial.begin(9600); // Init serial port for DFPlayer Mini
  myMP3.begin(mySerial);
  myMP3.volume(30);
  delay(25);
}

void loop() {
  selector();
  Serial.print ("Track = ");
  Serial.print (track);
  Serial.print ("    Choose = ");
  Serial.println (choose);
 
  if (track > 0) {
    myMP3.play(track);
    track = 0; 
  }
}

int selector() {
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == LOW) {
        choose++;
        track = (track + 2);
        if (track >= 4 || choose >= 3) {
          track = -1;
          choose = 0;
        }
      }
    }
  }
  lastButtonState = reading;
  return track;
}

simplified:

#include <DFPlayerMini_Fast.h>

#include "SoftwareSerial.h"

// constants won't change
const int buttonPin = 12;   // the number of the pushbutton pin
// Use pins 8 and 9 to communicate with DFPlayer Mini
const int Tx = 8; // Connects to module's RX
const int Rx = 9; // Connects to module's TX

// Variables will change:
int track = 0;                // track number determined by number of switch presses

const unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

SoftwareSerial mySerial(Rx, Tx);  // assign the softwareSerial Rx and Tx pins

DFPlayerMini_Fast myMP3;

void setup() {

  Serial.begin (9600);
  pinMode(buttonPin, INPUT_PULLUP);  // set the pushbutton as input with pullup
  mySerial.begin(9600); // Init serial port for DFPlayer Mini
  myMP3.begin(mySerial);
  myMP3.volume(30);
  delay(25);
  lastButtonState = ditialRead(buttonPin);
}

void loop() {
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState) {
    if (buttonState == LOW) {
      track++;
      if (track >= 3 ) track = 0;

      if (track > 0) {
        myMP3.loop(track);
        Serial.print("Track = ");
        Serial.println(track);
      }
      else {
        myMP3.stop();
        Serial.println("Off");
      }
    }
    delay( debounceDelay );
  }
  lastButtonState = reading;
}

Hi blh64, Many thanks! I tried your simplified code (had to add a declaration for lastButtonState and correct a typo). I changed the "buttonState" for "reading" in the third line of loop() to get it to run. See modified code:

#include <DFPlayerMini_Fast.h>

#include "SoftwareSerial.h"

// constants won't change
const int buttonPin = 12;   // the number of the pushbutton pin
// Use pins 8 and 9 to communicate with DFPlayer Mini
const int Tx = 8; // Connects to module's RX
const int Rx = 9; // Connects to module's TX

// Variables will change:
int track = 0;                // track number determined by number of switch presses
int lastButtonState;
const unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

SoftwareSerial mySerial(Rx, Tx);  // assign the softwareSerial Rx and Tx pins

DFPlayerMini_Fast myMP3;

void setup() {

  Serial.begin (9600);
  pinMode(buttonPin, INPUT_PULLUP);  // set the pushbutton as input with pullup
  mySerial.begin(9600); // Init serial port for DFPlayer Mini
  myMP3.begin(mySerial);
  myMP3.volume(30);
  delay(25);
  lastButtonState = digitalRead(buttonPin);
}

void loop() {
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState) {
    if (reading == LOW) {
      track++;
      if (track >= 3 ) track = 0;

      if (track > 0) {
        myMP3.loop(track);
        Serial.print("Track = ");
        Serial.println(track);
      }
      else {
        myMP3.stop();
        Serial.println("Off");
      }
    }
    delay( debounceDelay );
  }
  lastButtonState = reading;
}

Unfortunately it introduces a delay(), ie the debounce, which I need to avoid as I have to handle some led flashing at the same time. Also the first track starts ok with 'Track = 1' on the serial monitor, but the next button press displays 'Track = 2' but stops everything. Next button press displays 'Off'. Next button press starts track 1 again and so on. Track 2 doesn't get played. Any ideas?

you had some non blocking debouncing code in your first version, just use it to detect button press

  if ((millis() - lastDebounceTime) > debounceDelay) {...

that being said, the debounce is only called when the user is messing around with the system, so is it such a big deal if you miss a few ms in your blinking at that point ?

You're quite correct, that small delay won't actually cause any noticeable deviations in the led operation, I'll leave that in.

Do you have any idea as to why the first track starts ok with 'Track = 1' on the serial monitor, but the next button press displays 'Track = 2' but stops everything rather than playing track 2? The next button press displays 'Off' and the next button press after that starts track 1 again and so on. Track 2 doesn't get played. Any ideas?

@ J-M-L

I've actually had time to experiment with the code you suggested J-M-L as well and it works great! I have only found one problem, and that is that sometimes (but not consistently) the track change is triggered by the switch making and also triggered immediately the switch breaks. I must have a really rubbish pushbutton switch as I had to increase the debounce time to 150ms to overcome the problem. I'll have to build in my antibounce without delay to get over that - or buy a better switch!!

Further @ J-M-L

OK, using the principles in your reply #1 and now including the ezButton library to take care of the debouncing it's all working perfectly 8) . I hope now that it ports across to an ATtiny841 without too many headaches!

Thanks once again to J-M-L and blh64 for your help - greatly appreciated :smiley:

great to hear

in my code you really can get rid of the timerAction() stuff if you loop() the audio.

post your result may be for the benefit of the community?

You may have to do something like MyMP3.stop() before you start looping track #2. I don't have a DF player handy so I can't debug it.

J-M-L:
great to hear

in my code you really can get rid of the timerAction() stuff if you loop() the audio.

post your result may be for the benefit of the community?

That's a good idea. I'll modify the code to use the DFPlayer loop() function and if it all still checks out ok I'll post the final code here.

Well, the DFPlayer loop() function inclusion didn't work for me, even in conjunction with DFPlayer stop(), so as the timerAction() function that J-M-L crafted works just fine, I'm calling the audio part of my project done!

If anyone else is interested in how to control the playback of just two (or more....) tracks using a DFPlayer, an Arduino and a single pushbutton, you could do worse than have a look at this code:

Grateful thanks to J-M-L and blh64 :slight_smile:

#include <DFPlayerMini_Fast.h>
#include <ezButton.h>
#include "SoftwareSerial.h"

ezButton button(12);  // create ezButton object that attach to pin 12;

enum : byte {MUTED, TRACK1, TRACK2} currentPlayerState = MUTED;
const uint32_t track1Duration = 154000; // 154 seconds
const uint32_t track2Duration = 11000; // 11 seconds
uint32_t startTime;

const int Tx = 8; // Connects to module's RX
const int Rx = 9; // Connects to module's TX

SoftwareSerial mySerial(Rx, Tx);  // assign the softwareSerial Rx and Tx pins

DFPlayerMini_Fast myMP3; //

void userAction() {
  if (button.isPressed()) {
    switch (currentPlayerState) {

      case MUTED: // we were MUTED, we get a button press event, so next stage is playing track1
        Serial.println(F("Launching Track 1"));
        myMP3.play(1);
        startTime = millis();
        currentPlayerState = TRACK1;
        break;

      case TRACK1: // we were playing track 1, we get a button press event, so next stage is playing track 2. Track 1 is stopped by track 2 starting.
        Serial.println(F("Stopping Track1, Launching Track 2"));
        myMP3.play(2);
        startTime = millis();
        currentPlayerState = TRACK2;
        break;

      case TRACK2: // we were playing track 2, we get a button press event, so next stage is stopping track 2 and so we become MUTED.
        Serial.println(F("Stopping Track2, Muting Player"));
        myMP3.stop();
        currentPlayerState = MUTED;
        break;
    }
  }
}

void timerAction()
{
  switch (currentPlayerState) {
    case TRACK1: // we are currently playing track1, check if it has ended and if so, relaunch it
      if (millis() - startTime >= track1Duration) {
        Serial.println(F("Track1 ended, relaunching Track 1"));
        myMP3.play(1);
        startTime = millis();
      }
      break;

    case TRACK2:
      if (millis() - startTime >= track2Duration) {
        Serial.println(F("Track2 ended, relaunching Track 2"));
        myMP3.play(2);
        startTime = millis();
      }
      break;

    case MUTED: break; // don't worry about it
  }
}

void setup() {
  Serial.begin(9600);
  button.setDebounceTime(50); // set debounce time to 50 milliseconds
  mySerial.begin(9600); 
  myMP3.begin(mySerial); // Initialise the Softwareserial port for DFPlayer Mini
  myMP3.volume(30);  //Set the playback volume
  delay(25); // Give things a chance to settle
}

void loop() {
  button.loop(); // MUST call the loop() function first
  // LED handling function will go here
  userAction();
  timerAction();
}

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.