
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.
- #BB5 Moving your Arduino to a multi-tasking State Machine – Easy Intro
- #BB6 Arduino State😲Machine – Part 2: Implementation 🥳🥂
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.
I love this idea. I built it, but only the last clip plays when I load this sketch. Any possible way could get some help? I am trying to build sentences for a talking defect detector for my model railroad. Any help would be greatly appreciated!
Chris
LikeLike
Hi Chris. While I like these modules, I find they can be a bit frustrating at times. While all the modules appear to be similar there seems to be lots of different ones built on different ICs. While they all seem to be ok for playing files, some seem to require more time before beginning to play a file. Some of the most recent ones that I have bought use the MH2024K-24SS IC and I found it needs around 2 seconds after sending a command for it to start playing and change the status of the busy pin to change. To check if that is the problem try changing this line from 400 milliseconds to 2000 (2 seconds).
const int busyCheckDelay = 400; // Milliseconds to wait after playing before checking the busy pin
If it now plays all the tracks, then I think this is what the issue is. Then you could try to reduce the number. If this is the issue and your tracks are short, then this may mean your module is not suitable as you will have gaps of silence between tracks. I’ve got some more information about this in this post https://garrysblog.com/2022/06/12/mp3-dfplayer-notes-clones-noise-speakers-wrong-file-plays-and-no-library/.
LikeLike
Thank’s Gary, 300 seemed to be the magic number.
LikeLike
Hi Chris. Glad you got it going. I was expecting that you may need to increase the delay, but if it works with 300 then go with that. Maybe you have a fast module and at least some of your audio files are really short.
LikeLike