Mini MP3 Player

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.

Stereo line output can be taken from the DAC_R and DAC_L pins. You can also connect one (4 Ω) speaker to the (quite impressive) onboard mono amplifier.

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.
  • 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.
  • Although it’s for a Flyron FN-M16P module, this manual/datasheet has more and better information than DFRobot’s own documentation. Apparently both modules use the same commands and probably have the same chip.
  • I tested quite some libraries from github, most of which have issues 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).
  • 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 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, named 001.mp3 to 255.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 can be used to make 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. So much 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:

Then I added these two accompanying lines to the DFPlayerMini_Fast.h header file:

and (inside the code for the class):


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.



LOLIN TFT-2.4 Shield

Here’s a well designed new display from Wemos Electronics. They call it a (D1 Mini) Shield, but instead of going on top of a Wemos D1 Mini (Pro), it lets you plug in the Mini at the back of the 2.4″ TFT touch screen display, leaving all GPIO pins accessible, thanks to double header rows. Very useful!


But there’s more. All pins are also broken out to a breadboard-friendly header and to a TFT connector. Connecting the display to a D32 Pro (that has the same TFT connector) now requires only one (special) cable! The picture shows the connector’s pinout.


The 320×240 display uses the well-proven ili9341 chip, for which I already wrote a lot of sketches, both for esp8266 and esp32 boards. The XPT2046 library can be used to drive the touch screen.

I tested several sketches on a plugged-in Mini Pro, as well as a TFT cable-connected D32 Pro, and everything worked fine. Example sketches on the Wemos site use Adafruit’s ili9341 library, but I prefer the much faster TFT_eSPI library.

This is a well-thought product that makes wiring very convenient and stable. Recommended!



Quest for Fire

By no means a gamer myself, I’ve always been interested in the math behind the graphics in video games. A previous post showed how simple clouds can be produced by the Diamond-Square algorithm, running on an esp8266. That algorithm can generate maps and landscapes as well.

Another visual effect on my todo list was algorithmically simulated fire.


The video shows my first attempt on a small TFT display. I had to write a relatively simple algorithm, because it needs to run on a microcontroller. Some further experimenting with the color palette, weighting factors and randomization, as well as adding Perlin noise, will hopefully result in a more realistic fire. I also plan to add a rotary encoder for regulating the ‘fire’.


Visual memory

Soon after my start with Arduino, I bought an external 32KB EEPROM, just in case a future project would need more memory than the board’s modest SRAM. But when I finally needed it for a sketch, I realized that the 300 KByte RAM of my 480×320 TFT display could be used for this purpose as well.

I had used pixels as memory before, but only in visualisations of IFS fractals, where each pixel keeps track of the number of times it has been ‘hit’ by the iteration process, and is colored accordingly. Recent examples are this autumn inspired version of Barnsley’s Fern and this conifer-like fractal, based on an example on Ken Brakke’s IFS page. Both fractals were produced by an esp8266 on a 320×240 TFT display (ili9341).

Using a display as external memory for projects brings memory intensive algorithms, like the A* path search algorithm for my 15-Puzzle project, within reach of Arduino and ESP boards. The only prerequisite is that your display library lets you read single pixel values. Bonus: leaving the display’s backlight on will show a ‘brain scan’ of the sketch in progress.


Newton fractal

Here’s another famous fractal that I wanted to try on Arduino: the Newton fractal. It’s named after Isaac Newton’s iterative method for finding roots of functions. The classic example applies the method to function f(z) = z3 – 1 , where z is a complex number. This function has three roots in the complex plane.

classic newton fractal

The sketch that produced this picture loops over the pixels of a 480×320 display, mapping each of them to a complex number z0, that will serve as the initial value for Newton’s iteration process:

zn+1 = zn – f(zn) / f'(zn)

The basic color of a pixel (red, green or blue) depends on to which of the three roots the process converges. Its color intensity depends on the number of iterations that were needed to reach that root within a pre-defined precision. It’s just that simple!

Apart from playing with different color mappings (always essential for producing visually appealing fractals), I wanted to use modified versions of Newton’s method, as well as to apply them to different functions. The Arduino core has no support for complex calculus, and a library that I found didn’t even compile. So I wrote a couple of basic complex functions and put them in a functions.h file. There must better ways, but it works for me.

Once you have a basic sketch, the canvas of complexity is all yours!



f(z) = z4 – 1


This is my functions.h file. It must be in the same folder as the fractal sketch.


And here’s the Newton fractal sketch. Note the #include “functions.h” on the first line.



Snowflakes & Clouds

I haven’t been working on larger projects for a while, so here’s a couple of recent chips from the workbench.

In my ongoing quest for a faster HX8357D display driver, I was exited to read that the fast TFT_eSPI library now claims to support this beautiful but rather slow display. Unfortunately, the results were disappointing (unstable, readPixel freezes the screen). However, a nice bycatch of my experiments was the discovery that display updates become noticeably faster when the esp8266’s runs at 160MHz. Very useful for my fractal sketches, and for faster aircraft position updates on the Flight Radar‘s map.

Then I wondered how fast an esp8266 could actually drive my ili9341 driven 320×240 display. So I used my fast ‘offset-modulo’ Koch Snowflake algorithm for a snowflake animation speed test.

The result: an esp8266 at 160MHz can draw more than 100 (iteration depth 3) snowflakes per second on an ili9341 display, using the TFT_eSPI library. That’s about 5x faster than Adafruit’s HX8357 library can achieve on my 480×320 display. Hopefully, there will be a fast and reliable library for that display some day.



My renewed attention to the Koch Snowflake led to the discovery of a fractal that was new to me: the Plasma fractal. A commonly used algorithm to produce it is the Diamond-square algorithm. It’s apparently being used to generate nature-like backgrounds for video games.

I wrote a small demo sketch that produces random clouds (well, sort of…), but it can also be used to produce (non-musical) rock formations or landscapes.

6 samples of randomly generated clouds

(picture quality suffers from camera pixel interference with the TFT display)


The Diamond-square algorithm assigns initial values to the four corners of a (2n+1)x(2n+1) grid. Then it starts a process of calculating the remaining elements of the grid, based on neighbour values and a random noise of decreasing magnitude. The visual result depends on the four initial corner values, the random noise and the chosen color mapping.

The name Plasma fractal becomes clear when we show consecutive results in a loop. While still using random noise, the trick is to initialize the random generator with the same seed value for every new loop cycle. By slightly changing the initial corner values for each new cycle, the results will also be slightly different from the previous one, which is exactly what we want for a smooth animation.

The general formula that I used for initializing corner k with value h[k] is:

h[k] = d[k] + a[k]*sin( t*f[k] )   where t is a loop counter.

So the value of corner k will periodically swing around value d[k] with amplitude a[k]. The period of the swing is determined by f[k].

Now we can play with the d, a and f values for each corner, as well as with the color mapping, in order to find visually appealing animations. Here’s a first impression (esp8266 & ili9341 320×240 display). The sketch uses a 65×65 grid, with every point filling a corresponding 3×4 rectangle on the display with it’s color value. Since grid values are stored in 4-byte float variables, a 129×129 grid would become too large for the esp8266.


Used values:


Color mapping:



Small Talk

Two years ago, I used an Emic2 module for a couple of TTS (text-to-speech) projects. Despite its flexibility (someone even made it sing Bohemian Rhapsody here), the Emic2 sounds quite ‘robotic’ when used for direct TTS conversion. Not the best choice for a Talking Alarm Clock project, unless you like to wake up next to Stephen Hawking… (no offense).

So I considered to compose full sentences out of spoken language fragments, stored in mp3 format on my VS1053 breakout’s SD card. Then I discovered this google_tts library on github. It takes a text string as input and returns a link to a correspondig mp3 file in the Google cloud. You can specify one out of 30 languages, as well as a voice (both male and female).

I tried it, and the result is fully satisfying. The sketch starts with retrieving the local time for my location, provided by a simple api on my server. Then it fills a string variable with a natural language sentence like “The current time is <hr> hour and <min> minutes” (in Dutch in my sketch). Next, this string in processed by the library and the resulting link is sent to The returned mp3 data is streamed to the VS1053 chip and sent as analogue audio to the amplifier.

This project is definitely going to replace my alarm clock. From now on, a soft female voice will wake me up in the morning, reading the current time and weather forecast, before telling me to stay in bed for a while. So glad I didn’t use the Emic2…

Here’s a basic sketch (current time only). You can ignore warnings when compiling it for esp32: it’ll run just fine on esp32 boards.

VS1053 + Matrix keypad


The VS1053b is a popular chip, used in many MCU controlled audio projects. It surprises me that nobody seems to use its eight GPIO pins, controllable over SPI by the master MCU. These GPIO pins enabled me to control my esp8266 based Internet radio by means of a matrix keypad, despite the fact that the esp8266 had only one free pin left.

The cheap 12-key membrane keypad shown in the picure uses 7 of these pins, wheras a 16-key matrix keypad uses all of them.


Adafruit’s VS1053 library has basic functions for adressing the chip’s GPIO pins. I wrote a simple demo sketch that prints the pressed key. In a real project, this information would typically be used to control other functions of the VS1053, like changing audio source or adjusting volume.


Make sure that pin definitions in the demo sketch match your MCU and connect RST to your board’s RST.

The VS1053_GPIO wiring for the demo sketch is different from what the picture shows. This is what I used for my 12-key membrane matrix keypad:






The connector’s column/row mapping for these particular keypads is usually as follows (from left to right in the picture): row0 – row1 – row2 -row3 – col0 – col1 – col2. The sketch can easily be adapted for a 16-key matrix keypad.

The basic idea of the sketch is:

  • keypadListen() sets all four row pins to INPUT with value 0; it sets all three column pins to OUTPUT with value 1. In this idle state, GPIO_digitalRead() will return B00000111 (decimal 7).
  • In loop(), we check GPIO_digitalRead(). A value higher than 7 means that one of the row pins has changed from 0 to 1 because it got connected to one of the column pins. This happens if a key on that row was pressed (that’s how matrix keypads are wired). We call keypadRead() to find the responsible key.
  • keypadRead() searches the row that caused the GPIO_digitalRead() > 7 condition, by checking each row pin until it reads 1. This gives us the row of the pressed key: R.
  • Next, it finds the column pin that is connected to row pin R by setting all column pins to INPUT with value 0 and row pin R only to OUTPUT with value 1. Checking each column pin until it reads 1 gives the column of the pressed key: C.
  • Now, keymap[R][C] holds the value of the pressed key (10 and 11 for * and #).
  • A simple debouncer is added for, well, debouncing.



Instead of adapting an existing Internet Radio project to my needs (like support for air traffic control stations), I decided that it would be easier to write my own sketch from scratch. I’m satisfied with the first results, and I learned a lot from this project.

Keypad controlled prototype in radio mode (left) and ATC mode (right)

The radio runs without problems on a Wemos D1 R2, driving an Adafruit VS1053 breakout and a 2.4″ ili9341 TFT display. It can be controlled with a 16-key matrix keypad (the pictures show a Wemos D1 Mini Pro, but its wifi turned out to be too slow for some stations).

The prototype uses almost 500 lines of code for the radio. David Bird’s excellent METAR functions take yet another 500 lines. Too big to post on this page, I’m afraid…

It has the following features (more will be added later):

  • Can play mp3 encoded streams from internet radio stations (up to 320 Kbps).
  • Controlled by means of a 4×4 matrix keypad, using VS1053’s own GPIO pins.
  • Offers storage of 40 station presets (grouped in four  bands).
  • Displays station name and song title on a TFT display.
  • Displays a decoded weather report (METAR) when playing an air traffic control (ATC) channel. The first 10 stations in the preset list are reserved for ATC stations (I used David Bird’s excellent functions for METAR decoding).
  • A 30 Kbyte ring buffer is used for smooth playback.
  • Designed to handle chunked transfer encoded streams if necessary (I was unable to test this, because I couldn’t find any station that uses this encoding).
  • Stereo dB levels can be read directly from the chip and be used to drive VU meters from PWM pins.

Below is a flow chart of the initial connection, followed by the streaming process (basically a finite state machine). In practice, it all comes down to determining each incoming byte’s function within the stream by keeping track of some counters and states. Once the actual streaming has started, an incoming byte can be one of the next types:

  1. audio byte – will be sent to a ringbuffer that feeds the VS1053 decoder
  2. metadata byte – a human readable character from the song title
  3. metadata size – an integer; multiplied by 16, it is the following song title’s length
  4. CR or LF – used as separators
  5. chunk size (if station uses chunked transfer encoding) – size of the next chunk

Déjà VU


My nostalgic love for analog VU meters made me buy two of these little Chinese guys. They contain good old-fashioned incandescent bulbs and came in a kit with an analog control board. Build quality looks OK and everything works as expected, although the control board seems to be designed to power LEDs instead of incandescent bulbs.


While preparing to connect them to my VS1053 audio decoder breakout, I discovered that additional functionality can be added to the VS1053b chip by means of plugins. One of these extra plugin functions allows you to digitally read VU meter levels! This means that, instead of using the analog control board from the kit, I can let an Arduino (or other MCU) translate digital VU levels to PWM values for driving the meters directly from GPIO pins (these simple VU meters are basically just volt meters). This provides total programmatic control over the meters’ sensitivity, needle speed etc.

Plugin files for the VS1053b chip can be found on the VLSI Solution site. Documentation can be a bit confusing for beginners like me. It took some research before I understood the mechanism, but then it was easy to upload a plugin, enable the VU meter and actually read its values. Here’s some info that might help (when using Arduino or ESP boards):

  • Plugins are loaded into volatile (temporary) memory of the chip. This means that they won’t harm the chip in case you make mistakes. Just restart your VS1053.
  • Because of the above, plugins have to be loaded within sketches that use them.
  • From the VLSI site, get the ‘.plg’ file of the plugin that you want to use. This file contains a plugin array (RLE encoded) and a LoadUserCode function.
  • Put the plugin array from the .plg file on top of your sketch (or in a header file that you include on top of your sketch). You may add PROGMEM to the array’s declaration.
  • If not using Adafruit’s library: put the example LoadUserCode function from the .plg file inside your sketch. Note that this function contains two WriteVS10xxRegister calls. You’ll need to write your own WriteVS10xxRegister function for writing a 16-bit value to a VS1053 memory address.
  • If not using Adafruit’s library: After the line <your_player>.begin() in your sketch, add the line LoadUserCode();
  • In case you use the Adafruit VS1053 library, you don’t need to include and call a LoadUserCode function. Instead, add the following line after <your_player>.begin(): musicPlayer.applyPatch(plugin, PLUGIN_SIZE);
  • After writing the plugin to the VS1053’s memory, enable the VU meter by setting the 7th bit (from the left) of the VS1053’s status register (address 0x01) to 1.
  • After enabling the VU meter, you can read dB levels from the AICTRL3 register (0x0F). The high 8 bits represent the left channel, the low 8 bits the right channel.

After I posted an issue with the VU meter plugin on their forum, the VLSI support team was kind enough to release a new version that fixes it. Now I can read decibel levels directly from the VS1053b chip and control my stereo VU meters with two GPIO pins.

I’ll post a video of The Swinging Needles, after I got me a couple of reverse current protection diodes.