This little €4 module filled my entire weekend, mainly spent on finding the right documentation and a library that will work on Arduino, ESP8266 and ESP32. It’s a DFPlayer Mini MP3 Player from DFRobot (or, more likely, one of its many clones).
It can play MP3 and WAV files, stored on an SD card in the onboard card reader or from an attached USB drive. So far, I only tested MP3 files from SD.
You can connect one (4 Ω) speaker to the (quite impressive) onboard mono amplifier. Stereo line output is said to be provided on the DAC_R and DAC_L pins.
The main control method of this player is via commands over the module’s 9600 baud serial port. A subset of most commonly used commands can also be executed by means of two GPIO pins and/or two resistor-sensitive ‘ADKEY’ pins.
I made a false start by testing it on an ESP32 over hardware serial. That didn’t seem to work, so I tried an ESP8266 over software serial. That didn’t work either, and I had reached the point of giving up, assuming it was just another Chinese fake, when I gave it one more try on my good old Arduino Uno. With a quick & dirty 1K resistor for level shifting the module’s RX pin, the example sketch of one of many libraries finally made the little guy play me a song! In the end, it was the power supply that made the difference.
Some important things that I learned so far:
- The module works best when powered by a stable 5 Volt. My Arduino Uno and Wemos D1 R2 could deliver it from their 5V pin, smaller ESP8266 and ESP32 boards couldn’t. In that case: use a separate 5 Volt power supply.
- Wiring as suggested here caused a shutdown of my laptop’s usb port! I’m still searching for a way to connect the module to ESP32.
- The module operates on 3.3 V logic level, so make sure to level shift the connection to the module’s RX pin when using a 5 V microcontoller.
- Startup noise disappeared after I connected both GND pins from the player to ground.
- Although it’s for a Flyron FN-M16P module, this excellent manual/datasheet has more and better written information than DFRobot’s own summary. Apparently both modules use the same commands and probably have the same chip.
- Getting stereo ouput from the DAC pins is still a problem. According to the manual, DAC is enabled on startup, but is it? Explicitely enabling it by sending 0x0000 to memory address 0x1A made no difference while sending 0x0001 instead (supposed to turn it off) started a terrible noise…
- I tested quite some libraries from github, most of which have issues [update: starting sketches with delay(1000); solved most of them] and seem unnecessarily complicated to me. An exception is this simple and transparent library that is basically just a wrapper for sending serial commands. It can easily be extended with additional methods, even by unexperienced C++ programmers. You just have to lookup the command- and parameter bytes for the desired function in the datasheet and create a new function to send them (see example at the bottom of this page). [update: the author of the library has meanwhile added more commands]
- The module has an SPK_1 and an SPK_2 pin, with a GND pin in between, which could suggest that it has a stereo amplifier. Although the documentation clearly says it’s mono, someone actually published a wiring diagram with two speakers connected. This person may own a different version of the module, but mine definitely has a mono amplifier. So the two wires of a single speaker should go to the SPK pins.
[ Wiring for Arduino Uno, with SoftSerial on pins 10 and 11; don’t forget the 1K resistor! ]
The location of files and their names is also very important because of the way commands address them. All information can be found in the datasheet, but here’s a summary:
- The SD card’s maximum size is 32 GB. It must be FAT or FAT32 formatted.
- There can be up to 3000 .mp3 (or .wav) files in the root directory of the SD card, named 0001.mp3 to 3000.mp3.
- The root can have up to 99 subdirectories, named 01, 02, … 99. Each of these subdirectories can hold up to 255 files. Their names must start with 001, 002, … 255, but you can append decriptive titles to these files, e.g. 007_James_Bond_Tune.mp3.
- You can have a special subdirectory in the root named MP3. This can hold up to 3000 files, named 0001.mp3 to 3000.mp3.
- You can also have a subdirecory ADVERT in the root. Special commands are available for making tracks in this folder interrupt currently playing tracks from the MP3 folder (for advertisement puposes). The rules for the MP3 subdirectory apply here as well.
- Don’t create subdirectories inside subdirectories.
- The module does not actually use file- or directory names, but the order of files and directories in the SD card’s FAT. The previously mentioned naming conventions just try to give you control over that order. If you properly organize your audio files in a folder on your PC first, and then copy that entire structure to a freshly formatted SD card, then (in theory) the FAT order should match the file- and directory numbering. See also this tool
Apart from adding some extra functions to the above mentioned library, I still have to find out how to reliably receive and read the module’s response to commands and queries. Once that I’m in full control over this (for its price) remarkably versatile module, it will be useful for a couple of ‘talking’ projects that I have in mind. Probably just mono and not ESP32 driven, but anyway: lots of fun for just €4.
Here’s an example of a function that I added to the DFPlayerMini_Fast library. Calling play_in_dir(2, 5) will play file 005.mp3 in subdirectory 02.
Using an existing function as a template, I first added my new function to the DFPlayerMini_Fast.cpp file:
void DFPlayerMini_Fast::play_in_dir(uint16_t dirNum,uint16_t fileNum)
commandValue = PLAY_IN_DIR_COMMAND;
paramMSB = dirNum;
paramLSB = fileNum;
Then I added these two accompanying lines to the DFPlayerMini_Fast.h header file:
#define PLAY_IN_DIR_COMMAND 0x0F
and (inside the code for the class):
void play_in_dir(uint16_t dirNum,uint16_t fileNum);
Explanation: according to the datasheet, the 10 (hexadecimal) bytes that have to be sent (by the sendData() function) to play track 005.mp3 in directory 02 are:
7E FF 06 0F 00 02 05 xx xx EF
7E FF 06 are the first three bytes for every command. The following 0F is de code for ‘play a file in a direcory’, the following 00 means ‘no confirmation feedback needed’ and is followed by the two parameters for the desired directory- and file number: 02 and 05. Then comes xx xx which are two checksum bytes that will be calculated and replaced by the findChecksum() function of the library before the command is sent by sendData(). Finally, every command has to be closed by EF.
As you can see, my newly added function play_in_dir() takes two parameters only, corresponding with two bytes in the command sequence. All other bytes of the sequence are either fixed or calculated by the findChecksum() function.