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


#21

Glad to hear you like it @Moors7 ! :smiley:
@ScruffR : I hope my envelope is not lost… :cold_sweat:


#22

@armor : Sorry for the wait, but I am also working on various urgent projects… :sweat:
I have not written all the code myself, but adapted it to my wishes… The timing is indeed decided by trial&error, by experience.
As you could see higher, we did not manage yet to access files in other folders than “01”…
@ScruffR is still waiting for a module I sent a couple of weeks ago, but post seems to take longer between EU countries than between China and EU. He was going to try finding a solution.
It would be great if you could share your code (or links to the source)!
Enjoy!


#23

@FiDel

I have included below the program I have currently. Folder and track selection works fine, however, previous and next only work if I send the command twice and getting the volume returns a random number! Just adding a point about why your folder and file selection does not work - it could be player device, I read on some arduino forum that some devices are a bit hit and miss - maybe that’s why the volume enquiry does not appear to work on mine?

#include "math.h"   //using constrain to ensure values in commands in range

#define BUFSIZE 20	//buf size
#define CMDNUM 8	//cmdbuf size
uint8_t send_buf[] = {0x7E, 0xFF, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEF};  //this is the standard command message template
uint8_t recv_buf[10];
char cmdbuf;
boolean paused = false;

//
void setup ()
{
    Serial1.begin (9600);
    Serial.begin (9600);
    while (!Serial.available());
    Serial.println("mini DFPlayer Test - Setup");
    mp3_set_volume (0x30);
    mp3_get_tf_sum ();
    print_info ();
}

void print_info ()
{
    Serial.println("");
    Serial.print ("you sent: ");
    Serial.printlnf ("%x %x %x %x %x %x %x %x %x %x",send_buf[0],send_buf[1],send_buf[2],send_buf[3],send_buf[4],send_buf[5],send_buf[6],send_buf[7],send_buf[8],send_buf[9]);
    delay (1000);
    for (int j=0; j<9; j++) recv_buf[j] = 0x00;
    int i=0;
    while(Serial1.available() && i <= 9)
    {
       recv_buf[i] = Serial1.read();
       i++;
    }
    if (i>0)
    {
        Serial.print ("you get: ");
        Serial.printlnf ("%x %x %x %x %x %x %x %x %x %x",recv_buf[0],recv_buf[1],recv_buf[2],recv_buf[3],recv_buf[4],recv_buf[5],recv_buf[6],recv_buf[7],recv_buf[8],recv_buf[9]);
    }
}
//
void loop ()
{
    if (Serial.available())
    {
        delay(100);
        cmdbuf = Serial.read();
        switch (cmdbuf)
        {
            case '>':
                mp3_next ();
                print_info();
            break;
            case '<':
                mp3_prev ();
                print_info();
            break;
            case 'N':   //play track 6 in MP3 folder
                mp3_play_physical_track (6);
                delay(100);
            case 'p':
                mp3_play ();
                delay(100);
            break;
            case '.':
                mp3_stop ();
            break;
            case '=':   //pause or hold
                mp3_pause_resume ();
            break;
            case '-':
                mp3_dec_volume ();
            break;
            case '+':
                mp3_inc_volume ();
            break;
            case 'g':
                Serial.println(mp3_get_volume (), DEC);
            break;
            case 'z':
                mp3_get_tf_current ();
                print_info ();
            break;
            case 'f':       //french hello
                mp3_seek (0x02,0x01);
                delay(100);
                mp3_play();
            break;
            case 'e':       //english hello
                mp3_seek (0x01,0x01);
                delay(100);
                mp3_play();
            break;
            case 'o':       //other folder
                mp3_seek (0x00,0xFF);
                delay(100);
                mp3_play();
            break;
            case '?':
                mp3_get_state ();
                print_info ();
            break;
            case '0':
                mp3_set_EQ (0x00);
                print_info ();
            break;
            case '1':
                mp3_set_EQ (0x01);
                print_info ();
            break;
            case '2':
                mp3_set_EQ (0x02);
                print_info ();
            break;
            case '3':
                mp3_set_EQ (0x03);
                print_info ();
            break;
            case '4':
                mp3_set_EQ (0x04);
                print_info ();
            break;
            case '5':
                mp3_set_EQ (0x05);
                print_info ();
            break;
            default:
                Serial.println ("error! unknown command");
            break;
        }
    }
}
//
void mp3_set_reply (boolean state)
{
	send_buf[4] = state ? 0x01: 0x00;
}
//
void fill_uint16_bigend (uint8_t *thebuf, uint16_t data)
{
	*thebuf =	(uint8_t)(data>>8);
	*(thebuf+1) =	(uint8_t)data;
}
//calc checksum (1~6 byte)
uint16_t mp3_get_checksum (uint8_t *thebuf)
{
	uint16_t sum = 0;
	for (int i=1; i<7; i++)
	{
		sum += thebuf[i];
	}
	return -sum;
}
//fill checksum to send_buf (7~8 byte)
void mp3_fill_checksum ()
{
	uint16_t checksum = mp3_get_checksum (send_buf);
	fill_uint16_bigend (send_buf+7, checksum);
}
//
void send_func ()
{
	for (int i=0; i<10; i++)
	{
		Serial1.write (send_buf[i]);    //assumed use of Serial1 port
	}
}
//package send buffer with command, 2 arguments, checksum and send
void mp3_send_cmd (uint8_t cmd, uint8_t dh, uint8_t dl)
{
	send_buf[3] = cmd;
	send_buf[5] = dh;
	send_buf[6] = dl;
	mp3_fill_checksum ();
	send_func ();
}
//package send buffer with command, 1 argument, checksum and send
void mp3_send_cmd (uint8_t cmd, uint16_t arg)
{
	send_buf[3] = cmd;
	fill_uint16_bigend ((send_buf+5), arg);
	mp3_fill_checksum ();
	send_func ();
}
//package send buffer with command, no arguments, checksum and send
void mp3_send_cmd (uint8_t cmd)
{
	send_buf[3] = cmd;
	fill_uint16_bigend ((send_buf+5), 0);
	mp3_fill_checksum ();
	send_func ();
}
//0x01 play next track in current folder
void mp3_next ()
{
	mp3_send_cmd (0x01);
	delay(10);
}
//0x02 play previous track in current folder
void mp3_prev ()
{
	mp3_send_cmd (0x02);
	delay(10);
}
//0x03 play logical/sequential track number
void mp3_play_track (uint16_t num)
{
	mp3_send_cmd (0x03, num);
}
//0x03 play from track
void mp3_play_track ()
{
	mp3_send_cmd (0x03);
}
//0x04 increase volume level by 1
void mp3_inc_volume ()
{
	mp3_send_cmd (0x04);
}
//0x05 decrease volume level by 1
void mp3_dec_volume ()
{
	mp3_send_cmd (0x05);
}
//0x06 set volume 0-30
void mp3_set_volume (uint16_t volume)
{
    volume = constrain(volume, 0, 0x30);
	mp3_send_cmd (0x06, volume);
}
//0x07 set EQ0/1/2/3/4/5    Normal/Pop/Rock/Jazz/Classic/Bass
void mp3_set_EQ (uint16_t eq)
{
    eq = constrain(eq,0,5);
	mp3_send_cmd (0x07, eq);
}
//0x09 set device 1/2/3/4/5 U/SD/AUX/SLEEP/FLASH
void mp3_set_device (uint16_t device)
{
    device = constrain(device,1,5);
	mp3_send_cmd (0x09, device);
}
//0x0A standby but no way to get out of standby
void mp3_sleep ()
{
	mp3_send_cmd (0x0a);
}
//0x0C reset
void mp3_reset ()
{
	mp3_send_cmd (0x0c);
}
//0x0D play current selected track
void mp3_play ()
{
	mp3_send_cmd (0x0d);
}
//0x0E pause - play to resume
void mp3_pause ()
{
	mp3_send_cmd (0x0e);
}
//0x12 Play mp3 file [NUM] in mp3 folder File format exact 4-digit number (0001~2999) e.g. 0235.mp3
void mp3_play_physical_track (uint16_t num)
{
    num = constrain(num,1,2999);
	mp3_send_cmd (0x0F, num);
}
//0x16 stop player
void mp3_stop ()
{
	mp3_send_cmd (0x16);
}
//0x17 Loop Folder 01 - Loops all tracks in folder named "01"
void mp3_loop_folder ()
{
	mp3_send_cmd (0x17);
}
// 0x18 Random play - Random all tracks, always starts at track 1
void mp3_random ()
{
	mp3_send_cmd (0x18);
}
// 0x19 Single loop - Loops the track that is playing
void mp3_repeat ()
{
	mp3_send_cmd (0x19, 0x00);
}
// 0x1A pause if playing, resume if paused
void mp3_pause_resume ()
{
    mp3_send_cmd (0x1A, 0x00, paused ? 0x00 : 0x01);
    paused = !paused;
}
//0x0F specify mp3 folder num (00-99) and within that file num (000-255) on SD card
void mp3_seek (uint8_t folder, uint8_t file)
{
    folder = constrain(folder,0,99);
    file = constrain(file,0,255);
	mp3_send_cmd (0x0F, folder, file);
}
//
void mp3_get_state ()
{
	mp3_send_cmd (0x42);
}
// return 
int mp3_get_volume ()
{
    int volume = 0;
	mp3_send_cmd (0x43);
	delay(1000);
    int i=0;
    while(Serial1.available() && i <= 9)
    {
       recv_buf[i] = Serial1.read();
       i++;
    }
    if (i=9) volume = (int) recv_buf[6];
    return volume;
}
//
void mp3_get_u_sum ()
{
	mp3_send_cmd (0x47);
}
//
void mp3_get_tf_sum ()
{
	mp3_send_cmd (0x48);
}
//
void mp3_get_flash_sum ()
{
	mp3_send_cmd (0x49);
}
//
void mp3_get_tf_current ()
{
	mp3_send_cmd (0x4c);
}
//
void mp3_get_u_current ()
{
	mp3_send_cmd (0x4b);
}
//
void mp3_get_flash_current ()
{
	mp3_send_cmd (0x4d);
}
//
void mp3_single_loop (boolean state)
{
	mp3_send_cmd (0x19, !state);
}
//
void mp3_single_play (uint16_t num)
{
	mp3_play_track (num);
	delay (10);
	mp3_single_loop (true);
}
//
void mp3_DAC (boolean state)
{
	mp3_send_cmd (0x1a, !state);
}
//
void mp3_random_play ()
{
	mp3_send_cmd (0x18);
}

#24

Thanks for sharing your code @armor !
I will try to find time tomorrow to check it out with the module.
Maybe I’ll get an idea…

:raising_hand:


#25

Checked out the possibilities of your code and i’m impressed how you made the whole process simpler!
I learned a lot… :+1:

But still, I didn’t manage what I want to do in my Juke-Box sketch!
Hoping to be able to select particular tracks in particular folders, I added the following possibilities to your code:

switch (cmdbuf)
{

        case 'a':
            mp3_seek (1, 1); // Play Folder 1, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'b':
            mp3_seek (2, 1); // Play Folder 2, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'c':
            mp3_seek (3, 1); // Play Folder 3, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'd':
            mp3_seek (4, 1); // Play Folder 4, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'e':
            mp3_seek (5, 1); // Play Folder 5, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'f':
            mp3_seek (6, 1); // Play Folder 6, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'g':
            mp3_seek (7, 1); // Play Folder 7, File 1
            delay(100);
            mp3_play ();
            delay(100);
        case 'h':
            mp3_seek (8, 1); // Play Folder 8, File 1
            delay(100);
            mp3_play ();
            delay(100);

The same song always restarts, no matter which folder I select.
Here is the result in the serial monitor when pressing a,b,c,d,e,f,g,h:

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 41 0 0 0 => Difference?

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe b5 ef 7e ff 6 40 0 0 6

Any more ideas to make that work?

Thanks!


#26

Hi @FiDel

Looking at the send buffer it appears that you are always sending the same command and not the right one. The Seek command is:

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

So the send string should be: 7e ff 6 f 5 1 cs cs ef where cs cs are the checksums to select folder 5 track 1.

Sorry to ask the obvious but how are your files structured on the SD card? This is how I have formatted the SD card and named the folders and files. Apparently having MP3 folder is necessary even though I have nothing in it.

Hope this gets you unstuck on track selection. Please let me know if get the volume enquiry or previous or next working.


#27

Well, @armor , as you can see, in the extra commands I made, based on your code functions, I did change the folder number, but still get the same serial lines on the screen…

Indeed, I organised folders and files on my SD card in almost the same structure like you show above.
Only, all my folders are inside the MP3 folder… That’s how I understood the manual.

???


#28

The numbered folders need to be outside of the MP3 folder. The MP3 folder uses the physical track call and a separate set of files! Maybe that’s one issue. Another issue might be that the function calls are expecting a type uint8_t, try with 0x05 instead of 5 and 0x01 instead of 1. Lastly, the constrain value range for folder and file appears to be returning 0. Are you sure you have copied the code I posted as was and not altered it? It is certainly not producing the correct output buffer.


#29

OK @armor , I’ll try again tonight with the modified folder structure.
It’s certainly a strange way of working…

Yes, I copied your code completely as it was, except for the “direct selection” commands I quoted yesterday.


Does that mean that this folder is reserved for this function only: :flushed:

//0x12 Play mp3 file [NUM] in mp3 folder File format exact 4-digit number (0001~2999) e.g. 0235.mp3
void mp3_play_physical_track (uint16_t num)
{
num = constrain(num,1,2999);
mp3_send_cmd (0x0F, num);
}

If that’s right, maybe I can use this method for my “Juke-Box” app?
You constrain the number between 1 and 2999…
Can I put 3000 files in that folder?

Chinese logic!
:weary:


#30

I’ve whipped up a very rudimentary test sketch that allows you to test the MP3 commands via Particle.function().
You’d e.g. trigger the loop for folder 2 with this command string 0x17 2 0 (command, data low byte, data high byte) and this seems to start playing file 001.mp3 in that folder and then carries on to 002.mp3 - I’m not yet through the whole folder if it will loop back to 001.mp3 once it finishes, but it doesn’t look too bad :wink:

uint32_t msCmdReceived;

void sendCmd(int cmd, int lb, int hb, bool reply = false)
{                 // standard format for module command stream
    uint8_t buf[] = {0x7E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF}; 
    int16_t chksum = 0;
    int idx = 3;                    // position of first command byte

    buf[idx++] = (uint8_t)cmd;      // inject command byte in buffer
    if (reply) buf[idx++] = 0x01;   // set if reply is needed/wanted
    if (hb >= 0)                    // if there is a high byte data field
        buf[idx++] = (uint8_t)hb;   // add it to the buffer
    if (lb >= 0)                    // if there is a low byte data field
        buf[idx++] = (uint8_t)lb;   // add it to the buffer
    buf[2] = idx - 1;               // inject command length into buffer
    
    for (int i=1; i < idx; i++)     // calculate check sum for the provided data
        chksum += buf[i];
    chksum *= -1;

    buf[idx++] = (chksum >> 8);     // inject high byte of checksum before
    buf[idx++] = chksum & 0xFF;     // low byte of checksum
    buf[idx++] = 0xEF;              // place end-of-command byte

    Serial1.write(buf, idx);        // send the command to module
    for (int i = 0; i < idx; i++)   // send command as hex string to MCU 
      Serial.printf("%02X ", buf[i]);
    Serial.println();
}

int mp3Command(String para)
{
    int cmd = -1;                   
    int lb = -1;
    int hb = -1;
                                    // parse the command string
    int count = sscanf(para.c_str(), "0x%x %d %d", &cmd, &lb, &hb);

    if (count > 0 && cmd >= 0)      // if we got a well formed parameter string
      sendCmd(cmd, lb, hb, true);   // do the module talking

    msCmdReceived = millis();       // set a non-blocking delay timer

    return cmd;                     // return what command we think we received
}

void setup()
{
    Serial.begin(115200);
    Serial1.begin(9600);
    
    Particle.function("MP3", mp3Command);
}

void loop()
{
    if ((millis() - msCmdReceived > 500))
    {
        if (Serial.available())
            Serial.println();
        while(Serial1.available())
            Serial.printf("0x%02x ", Serial1.read());
        msCmdReceived = 0;
    }
}

This code also allows for shorter commands (omitting DL and/or DH).


Update:
Yup, the folder looping works just as expected.


Quickest, easiest way of controlling the DFPlayer Mini
#31

Placing the music outside the MP3 folder definitely makes a difference. While I had them in there, it played some wrong songs when trying to choose them >9. Once moved into the root, they work just fine :slight_smile:


#32

Since I only RTFM :wink: I’d never even had guessed to put the numbered folders inside an MP3 folder - this idea was just introduced to me after looking at the Arduino library


#33

@FiDel, one thing I noticed tho’ with the folder loop is that the order in which the files are played is not according their file names but in the order they were placed into the folder.
I have not yet found a way to change the folder and then loop in numerical order.


#34

Haleluja! :sweat_smile:
Thanks for the tip @armor!

Now I get all the songs I want:
And this is now the serial monitor output with the 8 “cases” I added:

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

you sent: 7e ff 6 1 1 0 0 fe f9 ef
you get: fe ba ef 7e ff 6 41 0 0 0

I believe you will be happy that it has nothing to do with your code, @armor
The folder structure is indeed the key to enable direct addressing to a particular song in a particular folder!

Also the numbering 001, 002, 003… is very important. After the number, you can keep the descriptive title.
If files are not properly named, you can only play them sequentially.

When I find the time, I’d like to build a simple control with with a 16-button touch switch and a mini OLED display, showing a menu of folders with their genre (Classic, Rock, Reggae…)

Any other ideas for a simple, cheap “Juke-Box”?


#35

How about a touch screen - e.g. like this 2.4" Nextion

I’ve managed (with some tweaking and partly clumsy due to the scripting language on these displays) to control that MP3 module without another µC,

This pure proof of concept demo allows to enter folder and song number and start/pause/stop the playback - but could well be extended.


#36

OK @ScruffR , thanks!
I will try out your Particle function sketch also…


#37

That touch screen gives of course a lot of possibilities.
I could also use a 4D touch screen, but the reason I would like to use the touch keypad with the mini OLED display is that they are very cheap also (Keypad = $1.5, Display = $4)


#38

What a fantastic sketch! Universal! Any command with their parameters can be sent this way.
That’s of course great if you have a list handy or you memorized them…
Of course, simpler variations can be made with more user friendly commands.
:ok_hand:


#39

Yup, that is a price difference ($5.50 vs. $13.90) for display plus input, but (Particle block your ears :ear:) $19.90 for the Photon are only required for one (if you don’t need WiFi or other µC specifics).


That sketch is mainly for testing all sorts of combinations and finding unknown commands.
e.g. the baud rate can be set on the player, but the command seems to be nowhere to be found - so brute force requires “bare metal” code :wink:


#40

Hats off @ScruffR!
That sketch is beyond my understanding of C++ … :wink:

I thought that touchscreen display was more expensive. A 4D display is around €110, I believe…
Must look into it!

Enjoy your MP3 music (module)!