top of page
Search
  • Writer's pictureIfrim Ciprian

DFPlayer Mini - Voice Output Implementation

Updated: Apr 12, 2022

As I've specified in my older posts, for the voice output I am using a DFPlayer Mini, that will play specific MP3/WAV files depending on the situation representing the voice assistant.


The DFPlayer Mini MP3 Player For Arduino is a small and low price MP3 module with an simplified output directly to the speaker. The module can be used as a stand alone module with attached battery, speaker and push buttons or used in combination with an Arduino or any other with RX/TX capable devices.


Specification

  • supported sampling rates (kHz): 8/11.025/12/16/22.05/24/32/44.1/48

  • 24 -bit DAC output, support for dynamic range 90dB , SNR support 85dB

  • fully supports FAT16 , FAT32 file system, maximum support 32G of the TF card, support 32G of U disk, 64M bytes NORFLASH

  • a variety of control modes, I/O control mode, serial mode, AD button control mode

  • advertising sound waiting function, the music can be suspended. when advertising is over in the music continue to play

  • audio data sorted by folder, supports up to 100 folders, every folder can hold up to 255 songs

  • 30 level adjustable volume, 6 -level EQ adjustable


Now, the code on the official website: https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299, is quite complex for just playing an MP3 file, so I simplified it here:

#include "DFRobotDFPlayerMini.h"
#include "SoftwareSerial.h"

// Create the Player object
SoftwareSerial mySoftwareSerial(2, 1);
DFRobotDFPlayerMini player;

void setup() 
{
  mySoftwareSerial.begin(9600);
  player.begin(mySoftwareSerial);
}


void loop() 
{
  player.volume(30);
  player.playMp3Folder(1);
  delay(5000);
  player.playMp3Folder(2);
  delay(2000);
}

I have performed all the tests on the Arduino Mega 2560 as it removes the need for a step-up convertor to 5v in order to get it to work with the Nicla Sense ME. So since the Nicla Sense ME only has 1 hardware serial available, I decided to apply that to the Mega as well with Serial 1:


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

// Create the Player object
DFRobotDFPlayerMini player;

void setup() 
{
  Serial.begin(9600);
  Serial1.begin(9600);
  player.begin(Serial1, true, false);
}

By testing it, it works wonderfully, however, the audio is being played indipendently from the code itself, so I am using delays that match the length of the audio file, however, it would be better to have a system that can detect when the file has finished and play the next one.



Now, the DFPlayer library does have this implemented, in the form of the following 2 methods:

 Serial.println(myDFPlayer.readState()); //read PLAYER state

The state 512 means the player is playing!


The second method is with:

if (myDFPlayer.readType() == DFPlayerPlayFinished) 
{ 
Serial.println("The file has finished playing");
}

However, after testing on my device, it can output when the file is not playing as "Not playing", but it does not work when it is playing,


So because this was not working, I decided to use the BUSY pin, which is HIGH when the file is not playing, and LOW when the file is playing.

So I have set-up the MEGA to check for the Digital Pin 5 when the file has stopped playing, with the following simple code to stop the code from running while the pin is low:

while(!digitalRead(5));

But the system still doesn't work accordingly, which was strange as the code was correct.

After about 2 hours of modifing the code and tring to fix it, I decideded to check any forums to see if there are other people that encountered the same issues.



It turns out, that there are fakes of the DFPlayer, which use a different chip, or the correct chip but with different resistors, which mess up the UART communication.


This is the important part from the forums:

The good "blues" use this chip : YX5200-24SS
The bad "reds" use this one : AA1746CJ1U6H1-94
Looks like the reds are a knock off using a cheaper main chip. I couldn't find any data on it where as the YX5200 data sheet is available.
I also found it's the loop functions that do not work correctly. It may be a timing issue or even a different command set, but without a data sheet for the knockoff device it's impossible to tell.
The only clue to tell which one you're ordering seems to be to look closely at the picture of the device. The "blues" (good) have "MP3-TF-16P" written on the top and screen printed white squares around the components, while the "reds" (bad) have "DFPlayer Mini" written on the top and no screen print around the components.

So it turns out that my player is the fake clone, and I needed to buy the original.

Thankfully on Amazon they were available for very little with 1 day delivery.

Here's how the original looks:

So after retesting the code, everything worked perfectly. Hooray!

In the same forums I also found a lot of people that spend hundreds of hours to find all the differences between the fake and original, and how to get the best performance.


Here is a simple function to play any file with any volume from any folder, one after the other, taken from the forums:

void loop() 
{
  playVoice(2, 1, 30);
  playVoice(15, 2, 30); 
}

int playVoice(int folder, int file, int volume) {
  Serial.println(String(folder) + "," +  String(file) + "," +  String(volume));
  player.volume(volume);
  player.playFolder(folder, file);
  delay(100);
  int playerState = 0;
  while (playerState != 512) {
    delay(500);         
    playerState = player.readState();
  }
  //player.volume(0);
  return playerState;
}

So after testing it, with it working perfectly, I decided to simplify the code, in my case the volume is always the maximum, 30, and I can simply the loop and tighten the delays:

void playVoice(int folder, int file) {
  player.playFolder(folder, file);
  delay(200);
  while(!digitalRead(5));
}

With the following void loop to simulate the selection of outputs, which the Nicla Sense will do:

void loop()
{
  while (Serial.available() > 0) {
    char incomingCharacter = Serial.read();
    if (incomingCharacter == '1') {
      playVoice(1, 2);
      playVoice(2, 2);
    }
    if (incomingCharacter == '2') {
      playVoice(2, 3);
      playVoice(1, 1);
    }
  }
}

Now, there are 2 more issues to solve, firstly there is a little static noise in the background while the DFPlayer plays the file and secondly, there is an initial pop when the library is initialised because of the 0AMP getting reset.


Firstly, by moving from the fake to the original, the static and pop were heavily reduced.

However, to remove the static completetly, the solution is on the DFPlayer website, and it can be solved by adding a 1Kohm resistor on the TX connection.


As for the POP sound, it cannot be fully removed, but it can be heavily reduced.

It turns out that the library resets the amp every time power is connected.

When initialising the DFPlayer with:

DFPlayer.begin(Serial1);

It can be initialised as follows:

DFPlayer.begin(serialVoice, true, false)

The false parameter removes the reset of the AMP.


But the code can be further improved, as 5 years after the release of the device, someone optimisised the library to have faster processing times and easier to use API.

And will be used from this point forward.


And this is how the device works:


In order to have a more natural voice, for the purpose of testing I have used https://www.naturalreaders.com/ TTS, and created different files for the outputs.


Once a voice line is put together it sounds as follows:


Here, we can see how much smoother the output is when we only use 2 voice lines instead of 3:


And here we have a full proper voice output, which would be the answer for "What are the environment conditions" command.


Here is the same output but on the device:


It can be notted that the switch between files on-device is a bit slower than on Audacity( of course), which reduces the smoothes a bit, but overall, I feel like it is still well exectuted.


Here is the final code to execute what happened in the previous video:


#include <DFPlayerMini_Fast.h>


DFPlayerMini_Fast myMP3;

void setup()
{
  Serial.begin(9600);
  Serial1.begin(9600);
  myMP3.begin(Serial1, true, false);
  delay(1000);

  Serial.println("Setting volume to max");
  myMP3.volume(20);
}

void loop()
{
  while (Serial.available() > 0) {
    char incomingCharacter = Serial.read();
    float temperature = 25.0;
    int humidity = 35;
    int relative_hum = 50;
    int absolute_hum = 70;
    
    if (incomingCharacter == '1')  //1 = "what are the environment conditions?" command
    {
      delay(100);
      playVoice(1, 1);
      playVoice(2, int(temperature);
      playVoice(1, 2);
      playVoice(2, humidity);
      playVoice(1, 3);
      playVoice(2, relative_hum);
      playVoice(1, 4);
      playVoice(2, absolute_hum);
    
    }
  }
}
void playVoice(int folder, int file) {
  myMP3.playFolder(folder, file);
  delay(150);
  while(!digitalRead(5));
  delay(50);
}

It is important to note that the file played for the output is different depending on the sensor readings, in this case with flase values. And it works perfectly!


Here is the module implemented with the Nicla Sense ME (moving away from the Mega 2560) just to make sure it works accordingly on the platform I will be using at the end:



8 views0 comments

Recent Posts

See All
bottom of page