Queuing files to the DFPlayer mp3 player module

In a previous post I presented how I use the DFPlayer module for playing audio files. In this one I’m going to share the code I use for the Arduino and DFPlayer to queue files. This allows files to be queued and played in sequence while the processor is doing other tasks. I’ve used this for playing short bits of speech that together form a sentence, for example in a speaking clock it queues files for when it announces the time – “It is” “twelve o’clock”.

This method doesn’t use a library which has some disadvantages. It relies on determining the play status of the DFPlayer only by monitoring the busy pin. It does not use serial communication to receive data back from the player. This means it does not receive error codes or other info back. It also can’t return info about tracks on the micro SD card. It sends a play request and then monitors the status pin. So far this has not been an issue for the projects I have used it with.

As the popular Arduinos are not multitasking and don’t have multiple cores, I used the opportunity for my first attempt at writing a state machine routine. In each loop of the loop routine the managePlayer funtion is called. It checks if there is anything in the queue, if something is playing or has finished playing and manages it. A downside or at least something to be aware of is the use of delays in other parts of a sketch can delay tracks from beginning to play because they will delay managePlayer being called.

I wrote this after learning about state machines from watching a couple of videos by Ralph Bacon. If you are not familiar with the concept, I recommend watching the videos.

Using the sketch

This is how I use the sketch.

Filenames

As discussed in my previous post MP3 DFPlayer notes: clones, noise, speakers, wrong file plays and no library. This sketch requires the same method of folder structure and filenames. Folders must use a 2 digit number between 01 – 99 and files require 3 digit filenames with prefixes that are in the range 001 – 255.

Managing the queue

managePlayer(); needs to be called regularly. I have it included in the loop routine. This is what manages play.

Setting the volume and equalization

The variable playerVol sets the volume using values from 0 to 30. Volume is set when the track begins to play so changing it will not change the volume of current playing tracks.

The variable playerEq sets the equalizer setting. There are six options to choose from:

  • Normal = 0
  • Pop = 1
  • Rock = 2
  • Jazz = 3
  • Classic = 4
  • Bass = 5

To queue a file to play

I call queueTrack. This add a file to the queue if there is space available. If the queue is full the track is not added.
Format:
queueTrack(, )

Stop files from playing

While I have not used it yet, I included a way of stopping the current track from playing and clearing everything from the queue. I thought this may be useful where a track is required to be played immediately rather than waiting for all the tracks in the queue to complete.
Example:
clearQueue();

The demo sketch

The sketch attempts to play 3 files on startup that are in a folder called 01 with the filenames starting with 001, 002 and 003. I’ve added this to the setup routine. In normal use they would be in other parts of the sketch. If you want to give it a go you can modify the code to play whatever you want. If you want some short test files you can use these ones I made for a talking clock.

/*
  Demo sketch for MP3 DFPlayer module with track buffer
  =====================================================  
  Provided under MIT license terms and conditions https://opensource.org/licenses/MIT
  
  Example sketch for the MP3 DFPlayer that queues tracks to a buffer to get them to play one after 
  the other without delaying or pausing the sketch. This demo uses polling to monitor the track 
  queue and play status of the DFPlayer using the DFPlayer status pin.
  
  No library is used for the DFPlayer

  NOTE: Avoid using delays in your code or it may delay tracks playing.

  Connections
  ===========
   DFPlayer VCC 3.2 - 5.0V to Pin 1
   DFPlayer ground pin 7 and/or 10 to GND
   Arduino TX (pin 9) to DFPlayer RX (pin 2) - 3.3V level - 1k resistor between modules. 2k resistor on Mp3 side to ground
   Arduino RX (pin 10) to DFPlayer TX (pin 3) - 3.3V level - 1k resistor between modules. Not used in 
    this sketch - Only needed to receive responses back from player
   Arduino D8 to DFPlayer BUSY (pin 16) - Monitors if track is playing. Low level when playing. High when in standby. 
   DFPlayer Speaker pin 6 and 8 - For driving a speaker
    OR (Audio output right channel to pin 4
    AND Audio output left channel to pin 5)

  Functions
  =========
  checkPlayer() - Check the queue and manages playback 
  queueTrack(<foldernumber>, <tracknumber>) - Adds a track to the queue to be played
  clearQueue() - Clears all files from the queue and stops current file playing

  File management
  ===============
  This sketch uses this method of folders and file naming convention
   - Folders named: 01 - 99
   - File names: Use 3 digit file names prefixes 001 - 255
   - Command - sendCommand(0x0F, <foldernumber>, <filenumber>);

  DFPlayer command values
  =======================
  Some command values are:
   - 0x06 - Set volume: 0 - 30
   - 0x07 - Set Equalizer: Normal = 0, Pop = 1, Rock = 2, Jazz = 3, Classic = 4, Bass = 5
   - 0x0F - Play track: 001 - 255
   - 0x0E - Pause track
*/

//#include "Arduino.h"              // Only needed if not using Arduino IDE
#include <SoftwareSerial.h>  // For cummunication with DFPlayer

// Player pins
const int mp3BusyPin = 8;  // MP3 Playback monitor pin
const int mp3TxPin = 9;    // Pin for sending commands to the player (Player pin 2)
const int mp3RxPin = 10;   // Pin for receiving commands - Not used or connected in this sketch

// Player buffer
uint8_t itemsInBuffer = 0;          // Number of tracks to play in buffer
const uint8_t bufferSize = 10;      // Number of tracks that can be stored in the buffer
int trackBuffer[2][bufferSize] = {  // Track buffer - folder, track
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

// Player settings
uint8_t playerVol = 10;  // Player volume: 0 - 30
uint8_t playerEq = 0;    // Player equalizer: Normal = 0, Pop = 1, Rock = 2, Jazz = 3, Classic = 4, Bass = 5

SoftwareSerial mp3(mp3RxPin, mp3TxPin);  // Using software serial instead of serial to allow serial monitor to be used

// ------------------------------------------------------------------
//                                SETUP
// ------------------------------------------------------------------
void setup() {

  // Set DFplayer pins
  pinMode(mp3BusyPin, INPUT);  // Monitor status

  // Set up comms for MP3 player
  mp3.begin(9600);

  // Wait for player to initialize
  delay(1000);

  // Add a few tracks to the queue for testing
  queueTrack(01, 1);  // Add track 1 in folder 1 to the queue
  queueTrack(01, 2);  // Add track 2 in folder 1 to the queue
  queueTrack(01, 3);  // Add track 3 in folder 1 to the queue
}

// ------------------------------------------------------------------
//                             MAIN LOOP
// ------------------------------------------------------------------
void loop() {

  // Your code goes here

  // Check and manage track playing
  managePlayer();
}

// ------------------------------------------------------------------
//                             DFPLAYER
// ------------------------------------------------------------------

// --------------------- Add files to the queue ---------------------
void queueTrack(int folderNumber, int trackNumber) {
  // Add track to the queue to play when ready

  // Check if there is space in the buffer and if so add the track
  if (itemsInBuffer >= bufferSize) {
    // Buffer is full
  } else {

    // Update track buffer
    itemsInBuffer++;
    trackBuffer[0][itemsInBuffer - 1] = folderNumber;
    trackBuffer[1][itemsInBuffer - 1] = trackNumber;
  }
}

// ----------------- Clear all items from the queue -----------------
void clearQueue() {

  itemsInBuffer = 0;

  for (byte i = 0; i < bufferSize - 1; i++) {
    trackBuffer[0][i] = 0;
    trackBuffer[1][i] = 0;
  }
}

// ------------ Monitor player queue and control player -------------
void managePlayer() {
  // Check status of player and manage playing tracks
  const int busyCheckDelay = 400;         // Milliseconds to wait after playing before checking the busy pin
  static unsigned long lastPlayTime = 0;  // For storing the time the last track was started

  // Player states
  enum class playerState : uint8_t {
    OFF,
    STARTPLAY,
    PLAYING,
  };

  // Keep track of the current State
  static playerState currState = playerState::OFF;

  // Process according to state
  switch (currState) {

    // Initial state - Player is powered down
    case playerState::OFF:

      // If there is something to play change to play state
      if (itemsInBuffer > 0) {
        currState = playerState::STARTPLAY;
      }
      break;

    // Start playing a file
    case playerState::STARTPLAY:

      if (itemsInBuffer > 0) {                                    // Confirm there is something in the buffer
        sendCommand(0x06, 0, playerVol);                          // Set volume
        sendCommand(0x07, 0, playerEq);                           // Set equaliser setting
        sendCommand(0x0F, trackBuffer[0][0], trackBuffer[1][0]);  // Play - folder, track

        // Remember the start time
        lastPlayTime = millis();
        currState = playerState::PLAYING;
      } else {
        currState = playerState::OFF;
      }
      break;

    // Playing file
    case playerState::PLAYING:

      // Check enough time has elapsed before checking busy pin
      if (millis() - lastPlayTime < busyCheckDelay) {
        break;
      }

      if (digitalRead(mp3BusyPin) == HIGH) {
        // It's stopped playing.
        if (itemsInBuffer == 0) {  // Should never occur
          currState = playerState::OFF;

        } else {
          itemsInBuffer--;

          // Shuffle tracks down the buffer
          for (byte i = 0; i < bufferSize - 1; i++) {
            trackBuffer[0][i] = trackBuffer[0][i + 1];
            trackBuffer[1][i] = trackBuffer[1][i + 1];
          }
          // Set end of buffer to 0
          trackBuffer[0][bufferSize - 1] = 0;
          trackBuffer[1][bufferSize - 1] = 0;

          if (itemsInBuffer > 0) {
            currState = playerState::STARTPLAY;
          } else {
            currState = playerState::OFF;
          }
        }
        break;

        default:
          // Nothing to do here
          break;
      }
  }
}

// --------------- Format and send commands to player ---------------
void sendCommand(byte Command, byte Param1, byte Param2) {
  // Format command including checksum and send to DFPlayer

  const byte startByte = 0x7E;
  const byte endByte = 0xEF;
  const byte versionByte = 0xFF;
  const byte dataLength = 0x06;
  const byte infoReq = 0x00;   // Get info back from module - 0x00 = no info, 0x01 = return info
  const bool isDebug = false;  // Debug mode. Not used

  // Calculate the checksum
  unsigned int checkSum = -(versionByte + dataLength + Command + infoReq + Param1 + Param2);

  // Construct the command line
  byte commandBuffer[10] = { startByte, versionByte, dataLength, Command, infoReq, Param1, Param2,
                             highByte(checkSum), lowByte(checkSum), endByte };

  for (int cnt = 0; cnt < 10; cnt++) {
    mp3.write(commandBuffer[cnt]);
  }

  // Delay needed between successive commands
  delay(30);
}

A couple of more notes

Setting the delay before checking player state

There is lag between sending a command to play a track and for the busy pin on the DFPlayer to change status indicating it is playing. busyCheckDelay is used to set the delay to wait. The amount of lag depends on the chip used and for the players I have it ranges from 199 to 1600 ms. More about that in this post. I’ve set it for 400 because I’ve been using this code where I string a lot of short files of less than 1 second each together. That means that the DFPlayer with the 1600ms lag can’t be used for these, but if the shortest file to be played is longer than really the busyCheckDelay can be set to a much bigger value.

Number of slots in the queue

I’ve set it up with 10 slots in the queue. Tracks are removed once they have been played, but it it exceeds 10 in the queue attempts to add more are ignored.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: