A great & very cheap MP3 sound module without need for a library!

Hello great community! :wave:

I have just learnt how to use a very promising and very cheap ($2.64/pce incl. shipping) MP3 module (TF Card U Disk Mini MP3 Player) to generate the “Alert sounds” for my projects:

Till now, I used a Text-to-speech module, but with good quality MP3 sounds it will be nicer and more efficient, I think…

I start this new topic as I could find posts about some other “MP3 shield” in this forum, but not yet about this one. And the Web IDE does not contain a ported library yet.
I’m sure many of you could be interested in using this great module…

Communication between the MP3 module and a Particle goes through the serial ports (Rx/Tx = Serial1 !!!)
It uses an SD card to store all MP3 files and connects directly to the speakers:

Some links to explore:
First manual
Second manual
A discovery project
Arduino forum discussion
Command discovery project
Video about previous link (Interesting approach without library!)

First I looked at the Arduino Library, but I don’t see me port this to Particle world myself! :sweat:

Below is a very interesting sketch I found here: Video
It has all the information you need in the header section and … it needs no library at all!
I hope many among you will try it out also and let us know how it all sounds like!

Success!


/* DF Player mini command discovery (Modified for Particle world by @FiDel - Feb 16, 2016)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This program is meant to discover all the possibilities of the command structure of the DFPlayer mini.
No special libraries are needed.
It's very easy to understand and can be the basis for your own mp3 player sketch.
Note: Commands are not always correctly described in the manual. I tried to fix it, but there is still a lot to do. The commands recoverd so far are listed below.

Use of sketch: Enter three (separated) decimal numbers in the Serial Monitor with no end of line character.
First number : Command
Second number: First (High Byte) parameter
Third number : Second (Low Byte) parameter
E.g.: 3,0,1 will play the first track on the TF card

Very important for 5V Arduinos: Use serial 1K resistors or a level shifter between module RX and TX and Arduino to suppress noise
Connect Sound module board RX to Arduino pin 11 (via 1K resistor)
Connect Sound module board TX to Arduino pin 10 (via 1K resistor)
Connect Sound module board Vcc to Arduino Vin when powered via USB (preferably 3.0) else use seperate 5V power supply
Connect Sound module board GND to Arduino GND

General DF Player mini command structure (only byte 3, 5 and 6 to be entered in the serial monitor):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Byte Function Value
==== ================ ====
(0) Start Byte 0x7E
(1) Version Info 0xFF (don't know why it's called Version Info)
(2) Number of bytes 0x06 (Always 6 bytes)
(3) Command 0x__
(4) Command feedback 0x__ If enabled returns info with command 0x41 [0x01: info, 0x00: no info]
(5) Parameter 1 [DH] 0x__
(6) Parameter 2 [DL] 0x__
(7) Checksum high 0x__ See explanation below. Is calculated in function: execute_CMD
(8) Checksum low 0x__ See explanation below. Is calculated in function: execute_CMD
(9) End command 0xEF

Checksum calculation.
~~~~~~~~~~~~~~~~~~~~
Checksum = -Sum(byte(1..6)) (2 bytes, notice minus sign!)

Commands without returned parameters (*=Confirmed command ?=Unknown, not clear or not validated)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CMD CMD
HEX Dec Function Description Parameters(2 x 8 bit)
==== === =================================== ========================================================================
0x01 1 Next * [DH]=X, [DL]=X Next file in current folder.Loops when last file played
0x02 2 Previous * [DH]=X, [DL]=X Previous file in current folder.Loops when last file played
0x03 3 Specify track(NUM) * [DH]=highByte(NUM), [DL]=lowByte(NUM)
1~2999 Playing order is order in which the numbers are stored.
Filename and foldername are arbitrary, but when named starting with
an increasing number and in one folder, files are played in
that order and with correct track number.
e.g. 0001-Joe Jackson.mp3...0348-Lets dance.mp3)
0x04 4 Increase volume * [DH]=X, [DL]=X Increase volume by 1
0x05 5 Decrease volume * [DH]=X, [DL]=X Decrease volume by 1
0x06 6 Specify volume * [DH]=X, [DL]= Volume (0-0x30) Default=0x30
0x07 7 Specify Equalizer * [DH]=X, [DL]= EQ(0/1/2/3/4/5) [Normal/Pop/Rock/Jazz/Classic/Base]
0x08 8 Specify repeat(NUM) * [DH]=highByte(NUM), [DL]=lowByte(NUM).Repeat the specified track number
0x09 9 Specify playback source (Datasheet) ? [DH]=X, [DL]= (0/1/2/3/4)Unknown. Seems to be overrided by automatic detection
(Datasheet: U/TF/AUX/SLEEP/FLASH)
0x0A 10 Enter into standby – low power loss * [DH]=X, [DL]=X Works, but no command found yet to end standby
(insert TF-card again will end standby mode)
0x0B 11 Normal working (Datasheet) ? Unknown. No error code, but no function found
0x0C 12 Reset module * [DH]=X, [DL]=X Resets all (Track = 0x01, Volume = 0x30)
Will return 0x3F initialization parameter (0x02 for TF-card)
"Clap" sound after excecuting command (no solution found)
0x0D 13 Play * [DH]=X, [DL]=X Play current selected track
0x0E 14 Pause * [DH]=X, [DL]=X Pause track
0x0F 15 Specify folder and file to playback * [DH]=Folder, [DL]=File
Important: Folders must be named 01~99, files must be named 001~255
0x10 16 Volume adjust set (Datasheet) ? Unknown. No error code. Does not change the volume gain.
0x11 17 Loop play * [DH]=X, [DL]=(0x01:play, 0x00:stop play)
Loop play all the tracks. Start at track 1.
0x12 18 Play mp3 file [NUM] in mp3 folder * [DH]=highByte(NUM), [DL]=lowByte(NUM)
Play mp3 file in folder named mp3 in your TF-card. File format exact
4-digit number (0001~2999) e.g. 0235.mp3
0x13 19 Unknown ? Unknown: Returns error code 0x07
0x14 20 Unknown ? Unknown: Returns error code 0x06
0x15 21 Unknown ? Unknown: Returns no error code, but no function found 
0x16 22 Stop * [DH]=X, [DL]=X, Stop playing current track
0x17 23 Loop Folder 01 * [DH]=x, [DL]=1~255, Loops all tracks in folder named "01"
0x18 24 Random play * [DH]=X, [DL]=X Random all tracks, always starts at track 1
0x19 25 Single loop * [DH]=0, [DL]=0 Loops the track that is playing
0x1A 26 Pause * [DH]=X, [DL]=(0x01:pause, 0x00:stop pause)

Commands with returned parameters (*=Confirmed command ?=Unknown, not clear or not validated)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CMD CMD
HEX Dec Function Description Parameters(2 x 8 bit)
==== === =================================== ===========================================================================
0x3A 58 Medium inserted * [DH]=0, [DL]=(1:U-disk, 2:TF-card)
0x3B 59 Medium ejected * [DH]=0, [DL]=(1:U-disk, 2:TF-card)
0x3C 60 Finished track on U-disk * [DH]=highByte(NUM), [DL]=lowByte(NUM)
Not validated. Returns track number when song is finished on U-Disk
0x3D 61 Finished track on TF-card * [DH]=highByte(NUM), [DL]=lowByte(NUM)
Returns track number when song is finished on TF
0x3E 62 Finished track on Flash * [DH]=highByte(NUM), [DL]=lowByte(NUM)
Not validated. Returns track number when song is finished on Flash
0x3F 63 Initialization parameters * [DH]=0, [DL]= 0 ~ 0x0F. Returned code when Reset (0x12) is used.
(each bit represent one device of the low-four bits)
See Datasheet. 0x02 is TF-card. Error 0x01 when no medium is inserted.
0x40 64 Error ? [DH]=0, [DL]= 0~7 Error code(Returned codes not yet analyzed)
0x41 65 Reply ? [DH]=0, [DL]= 0~? Return code when command feedback is high
0x00 no Error (Other returned code not known)
0x42 66 The current status * [DH] = Device number [DL] = 0 no play, 1 play
0x43 67 The current volume * [DH]=0, [DL]= Volume (0-30)
0x44 68 The current EQ * [DH]=0, [DL]= EQ(0/1/2/3/4/5) [Normal/Pop/Rock/Jazz/Classic/Base]
0x45 69 The current playback mode * [DH]=0, [DL]= (0x00: no CMD 0x08 used, 0x02: CMD 0x08 used, not usefull)
0x46 70 The current software version * [DH]=0, [DL]= Software version. (My version is 5)
0x47 71 The total number of U-disk files * [DH]=highByte(NUM), [DL]=lowByte(NUM). Not validated
0x48 72 The total number of TF-card files * [DH]=highByte(NUM), [DL]=lowByte(NUM)
0x49 73 The total number of flash files * [DH]=highByte(NUM), [DL]=lowByte(NUM). Not validated
0x4A 74 Keep on (Datasheet) ? Unknown. No returned parameter
0x4B 75 The current track of U-Disk * [DH]=highByte(NUM), [DL]=lowByte(NUM), Current track on all media
0x4C 76 The current track of TF card * [DH]=highByte(NUM), [DL]=lowByte(NUM), Current track on all media
0x4D 77 The current track of Flash * [DH]=highByte(NUM), [DL]=lowByte(NUM), Current track on all media
0x4E 78 Folder "01" [DH]=x, [DL]=1 * [DH]=0, [DL]=(NUM) Change to first track in folder "01"
Returns number of files in folder "01"
0x4F 79 The total number of folders * [DH]=0, [DL]=(NUM), Total number of folders, including root directory

Additional info can be found on DFRobot site, but is not very reliable
Additional info:http://www.dfrobot.com/index.php?route=product/product&product_id=1121

Ype Brada 2015-04-06
*/
    
# define Start_Byte 0x7E
# define Version_Byte 0xFF
# define Command_Length 0x06
# define End_Byte 0xEF
# define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info]

void setup ()
{
 Serial.begin(9600);
 Serial1.begin(9600);

 execute_CMD(0x3F, 0, 0); // Send request for initialization parameters

 while (Serial1.available()<10) // Wait until initialization parameters are received (10 bytes)
 delay(30); // Pretty long delays between succesive commands needed (not always the same)

 // Initialize sound to very low volume. Adapt according used speaker and wanted volume
 execute_CMD(0x06, 0, 5); // Set the volume (0x00~0x30)
}


void loop ()
{
 if (Serial.available())
 {
  // Input in the Serial monitor: Command and the two parameters in decimal numbers (NOT HEX)
  // E.g. 3,0,1 (or 3 0 1 or 3;0;1) will play first track on the TF-card
  byte Command = Serial.parseInt();
  byte Parameter1 = Serial.parseInt();
  byte Parameter2 = Serial.parseInt();
  
  // Write the input at the screen
  Serial.print("Command : 0x");if (Command < 16) Serial.print("0"); Serial.print(Command, HEX);
  Serial.print("("); Serial.print(Command, DEC);
  Serial.print("); Parameter: 0x");if (Parameter1 < 16) Serial.print("0");Serial.print(Parameter1, HEX);
  Serial.print("("); Serial.print(Parameter1, DEC);
  Serial.print("), 0x");if (Parameter2 < 16) Serial.print("0");Serial.print(Parameter2, HEX);
  Serial.print("("); Serial.print(Parameter2, DEC);Serial.println(")");

  // Excecute the entered command and parameters
  execute_CMD(Command, Parameter1, Parameter2);
 }

 if (Serial1.available()>=10) // There is at least 1 returned message (10 bytes each)
 {
  // Read the returned code
  byte Returned[10];
  for (byte k=0; k<10; k++)
  Returned[k] = Serial1.read();

  // Wtite the returned code to the screen
  Serial.print("Returned: 0x"); if (Returned[3] < 16) Serial.print("0"); Serial.print(Returned[3],HEX);
  Serial.print("("); Serial.print(Returned[3], DEC);
  Serial.print("); Parameter: 0x"); if (Returned[5] < 16) Serial.print("0"); Serial.print(Returned[5],HEX);
  Serial.print("("); Serial1.print(Returned[5], DEC);
  Serial.print("), 0x"); if (Returned[6] < 16) Serial.print("0"); Serial.print(Returned[6],HEX);
  Serial.print("("); Serial.print(Returned[6], DEC); Serial.println(")");
 }
}



void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters
{
 // Calculate the checksum (2 bytes)
 int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);

 // Build the command line
 byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};

 //Send the command line to the module
 for (byte k=0; k<10; k++)
 {
  Serial1.write( Command_line[k]);
 }
}

Originally, I had an error in this code and it did not compile.
Thanks to @ScruffR it was solved.

I modified the original sketch to use “serial1” for all communication between Particle and MP3 module and “serial” for all input/output on the (USB) serial monitor output.
Now it works completely and I managed to play all MP3 files on my memory card.

When you put files on the card, you should first create one “MP3” folder (@ root level) and put your MP3 files in that folder, numbered 001, 002, 003 etc… You can also put them in sub-folders. (see manual)


12 Likes

@FiDel, great find!! I just bought a couple to play with :yum:

1 Like

It’s my pleasure @peekay123 !
Glad that I can start contributing to this great community…

I have just adapted another sketch or this module to “Particle world”:
It allows to play all songs on the CF card with a PLAY, PREVIOUS and NEXT button.

For my first test, I used 3 pinwires to connect D2 (Next), D3 (Pause) or D4 (Prev) to GND:

Here is the working sketch for Particles:

/* MP3 PLAYER PROJECT
From project: http://educ8s.tv/arduino-mp3-player/
Modified for "Particle world" by @FiDel
*/

# define Start_Byte 0x7E
# define Version_Byte 0xFF
# define Command_Length 0x06
# define End_Byte 0xEF
# define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info]

# define ACTIVATED LOW

int buttonNext = D2;
int buttonPause = D3;
int buttonPrevious = D4;
boolean isPlaying = false;



void setup ()
{
  pinMode(buttonPause, INPUT_PULLUP);
  digitalWrite(buttonPause,HIGH);
  pinMode(buttonNext, INPUT_PULLUP);
  digitalWrite(buttonNext,HIGH);
  pinMode(buttonPrevious, INPUT_PULLUP);
  digitalWrite(buttonPrevious,HIGH);

  Serial1.begin(9600);
  delay(1000);
  playFirst();
  isPlaying = true;
}



void loop ()
{ 
 if (digitalRead(buttonPause) == ACTIVATED)
  {
    if(isPlaying)
    {
      pause();
      isPlaying = false;
    }else
    {
      isPlaying = true;
      play();
    }
  }


 if (digitalRead(buttonNext) == ACTIVATED)
  {
    if(isPlaying)
    {
      playNext();
    }
  }

   if (digitalRead(buttonPrevious) == ACTIVATED)
  {
    if(isPlaying)
    {
      playPrevious();
    }
  }
}


void playFirst()
{
  execute_CMD(0x3F, 0, 0);
  delay(500);
  setVolume(20);
  delay(500);
  execute_CMD(0x11,0,1); 
  delay(500);
}


void pause()
{
  execute_CMD(0x0E,0,0);
  delay(500);
}


void play()
{
  execute_CMD(0x0D,0,1); 
  delay(500);
}


void playNext()
{
  execute_CMD(0x01,0,1);
  delay(500);
}


void playPrevious()
{
  execute_CMD(0x02,0,1);
  delay(500);
}


void setVolume(int volume)
{
  execute_CMD(0x06, 0, volume); // Set the volume (0x00~0x30)
  delay(2000);
}


void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters
{
 // Calculate the checksum (2 bytes)
 int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);

 // Build the command line
 byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};

 //Send the command line to the module
 for (byte k=0; k<10; k++)
 {
  Serial1.write( Command_line[k]);
 }
}

It sounds great with good speakers!
Enjoy… :stuck_out_tongue:

ATTENTION!

I found out that the speaker terminals are not “decoupled”: DC current is flowing through the speaker! This sounds poor and is dangerous…
Put an electrolytic capacitor in line with your speaker(s): Bigger = better… (220 - 2.200 uF/10V)
Attention to the correct polarity! (SPK1 = +)

7 Likes

Remark:

With this module, you can actually make an MP3 player without a controller (Particle):
Put 2 switches between module pins IO1 and IO2 and GND, you can control PREVIOUS and NEXT (short press) as well as VOL+ and VOL- (Long press)

But the value of above sketch is to see how you can control this module from a Particle.
The limits are those of your imagination!

You can put 99 folders on the SD card, with each 255 MP3 files…

What I still want to find out is how to simply play a particular song with one command.
=> Can we develop a function to select a specific folder and a specific song?

Can you help me find this?

Here is a page from the manual describing the key to this: HEXADECIMAL Calculations…

Nice challenge!
:bow:

Hmm, where do you see the problem there?

If you do Serial.write(100) it’s just the same as Serial.write(0x64) (HEX 64 == DEC 100 == BIN 01100100).
Decimal or hexadecimal notation is just a way for us humans to easier comprehend the value, the controller only ever sees the binary anyway.

CMD=15; // = 0x0F (just for demo in decimal)
Par1=99; // e.g. folder 99 (decimal)
Par2=255; // e.g. song nr 255 (decimal)
int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);
byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};

// or with the function from your first code
execute_CMD(0x0F, folderNr, SongNr);
2 Likes

Thanks @ScruffR !
I’ll try it out tomorrow … (Bedtime here :zzz: )
Good thinking!

1 Like

@ScruffR There we are again…
I applied you wisdom, and I’m sure your thinking is correct, but I can still not make the module pick a particular song from a particular folder… :smirk:

To test it, I try to make a “Juke-box” sketch:
When it starts up, it should play the first song (001.mp3) from the first folder (001).
When I connect a pin to Gnd, (buttonNextFolder) the next folder up (002) should be selected and all songs should be played till the last one…

Currently, it always restarts from song 001 in folder 01 :flushed:

Here’s my (provisional) looping code:

void loop ()
{
    
 if (digitalRead(buttonNextFolder) == LOW)
 {
  folderNr = folderNr+1;
  songNr = 1;
  execute_CMD(0x0F, folderNr, songNr); // Maximum: Play folder 99, song 255
  delay(500);
  execute_CMD(0x11,0,1);    // Play mode: [DH]=0, [DL]=1:Loop play, 0:stop
  delay(500);
 }
 
 // While playing (busy) do nothing
 // When stopping (not busy): songNr ++; and return
 
 delay(1000);
}

I guess you have reused the code from the three button sketch further up.
So the button setup should work.

You could add some Serial.print() statements to check what data you actually send to the module.

Also try to remove the loop-play command, to see if at least the one selected file gets played correctly.

1 Like

Yes, that works fine: Here’s the monitor output when I connect the pin to Gnd
folderNr: 5 songNr: 1
folderNr: 6 songNr: 1
folderNr: 7 songNr: 1
folderNr: 8 songNr: 1

I guess the key of this issue is that we should not send one byte integers (1, 2, 3…) but 2 byte strings (01, 02, 03… for folders) and 3 byte strings (001, 002, 003…) for songs. Isn’t it?

Nope, you send one byte not the digits.
But I guess if you want to set the “base” folder for loop-play you’d need another CMD - 0x0F will only play that one song.

I found 0x17 to be the CMD you want to use.

execute_CMD(0x17, 0, folderNr); // the second parameter will be ignored.

Having one of these modules would rid me of the need to guess my way through the topic :wink:

1 Like

No problem, @ScruffR , I'll post you one if you send me your mail address.
(PS: I ordered them here, and it was cheaper than a post stamp to Belgium...)

Are you sure about this? I just tried it but the result is the same: The first song in the first folder starts playing.

The module manual says:

2).For example, specify "01" folder 100.MP3 file, serial port to send commands : 7E FF 06 0F 00 01 64 xx xx EF
DH: represents the name of the folder, the default support for 99 documents become 01 - 99 named
DL: on behalf of the tracks, the default maximum of 255 songs that 0x01 ~ 0xFF

That'd be nice, as the source you linked to doesn't seem to ship to Austria and the others take ages (or charge ridiculous prices)

I'll PM you my address :heart_eyes:


No not really, but this is what I understood from the comments in the code of your first post.
At the moment I'm reading the docs/datasheets.

0x17 23 Loop Folder 01 * [DH]=x, [DL]=1~255, Loops all tracks in folder named "01"

But I just stumbled over a sentence in manuals that suggests that [DH]=x might actually mean the byte has to be omitted rather than it being ignored.
This would require a slight (easy) rework of execute_CMD().

But then I realise that Manual 1 deals with a different module, so I'm puzzled :confused:

1 Like

There are a couple of them on Ebay.de, which might be interesting for those in the EU: http://www.ebay.de/sch/i.html?_from=R40&_sacat=0&_nkw=mp3%20modul%20arduino&rt=nc&LH_PrefLoc=3&_trksid=p2045573.m1684

2 Likes

Indeed @Moors7 ! And not expensive either…

@ScruffR : I’m sure we’ll get there!
Tonight I will find some more time to check this out.

Soon, you will be able to try it yourself :wink:

1 Like

@FiDel Thanks for starting this thread - I bought a DFPlayer Mini branded mp3 player a few weeks back. I am using BANGGOOD.COM - stuff arrives from China in 5-10 days and they have a European warehouse (but more expensive) mine was £3/€4 cheaper than eBay.de but not as cheap as $2.60.

I had downloaded a library written for Arduino but not ported it and tried your code which worked straight away and prompted me to port the arduino code. The person who had created the arduino library had played with the numerous commands. I have a version running based on this arduino code (if I remember created by DFROBOT). It uses the Serial Terminal to allow one to enter commands e.g. < previous track, > next track, + volume up, - volume down, etc.

I can also select a track in a folder and play once, or repeat. I want to have multiple language folders with spoken instruction sets language selectable. I like the control via the Serial1 as I have used up a lot of GPIO and Analog Pin with SPI and other things and that I can throw out a sound command alongside a display change. I am pretty much there but… I have noticed timing issues with sending commands and responses. A small wait after the command appears to improve the reliability. However, if I request the current volume level say, I do not get back an answer between 0 and 0x30 as expected. Volume Up and Down and setting a volume level all work. Other commands also seem to have variable results - tf card files 0x48. I notice from your code you have included fairly long delays. Is this done from experience? I also have seen posts on arduino forum where others have experienced varying responses from the mp3 player. When someone else gets one of these devices could you comment please? I would like to know if this behaviour is a feature or a function of a very cheap clone?

2 Likes

Just ordered two for myself as well. Looking forward to seeing more develop in this thread over the next few weeks (until mine arrives).

2 Likes

Just want to thank you for sharing this. Just got mine in yesterday (ordered the 21st from China :o!) and they seem to be working exactly as they should.
Time to add some sound to my wordclock :innocent:

2 Likes

I’m still waiting (Belgium -> Austria takes longer than China -> Netherlands ???)

Or are they going via Australia again (had several mails coming from UK this way !!!)

2 Likes