MP3 (MPEG Layer III) is a ubiquitous audio encoding format.

Initially released in 1993 as a way to store compressed audio, it has since spread pretty much everywhere and established itself as one of the most widely used formats to store personal audio.

Offering a decent audio quality to file size ratio, it provides a simple way to store a large collection of music onto relatively small storage devices such as SD cards or other flash-based media.

While there was a time when usage of the format was protected by software licensing, it is now completely free to use since 2017.

The Aecho module

The Aecho module is a complete SPI MP3 player solution based on the famous VS1011 decoder chip from VLSI.

It features a standard 3.5mm stereo audio jack output and allows playing MP3 data with great simplicity.

We provide a library to make it even easier to use. Playing an MP3 file from the VFS requires only a single line of code!

It also allows controlling the output volume from 0 to 256.

This page will present both the wiring of the module as well as how to use the library to get it to play something.


The Aecho module needs 4V to 6V input supply. We can share the ioNode's 'VIN' for this since it has the same requirements.

It uses the SPI bus for both control and MP3 streaming. It has two distinct 'CS' (or chip-select) lines for this reason - 'CS' for mp3 data and 'DCS' for control commands.

The module also exposes a 'DREQ' pin which will tell us when the module's internal buffer is ready to receive more MP3 data.

Finally, a 'RESET' signal is also provided to perform a hardware reset of the decoder.

Aecho module pinout

The aecho library handles all of these internally so all we really need to care about is hooking up these lines to some DIO pins on our ioNode.

The animation below shows an example of how to wire the Aecho to an ioNode:

Using the aecho library

In order to use the Aecho we must first add the 'aecho' library to the list of dependencies in our dfe.conf:

# ...
  - aecho

Using substrate

Let's declare this Aecho module in our substrate (the pin numbers match the example wiring shown above):

# Aecho MP3 module
uses :aecho, name: 'audio0', cs_pin: 8, dcs_pin: 9, rst_pin: 10, dreq_pin: 11

This will create an aecho structure pointer 'audio0' (struct aecho *audio0;) which we can then use to play audio with the methods below:

// Play File
uint8_t aecho_play(struct aecho *a, char *file, void (*eof_callback)(void *user), void *user);

// Play File - Fixed Length
uint8_t aecho_play_n(struct aecho *a, char *file, uint8_t file_len, void (*eof_callback)(void *user), void *user);

When playing a file, we can set a callback function that will be automatically run when the end of file (EOF) is reached. We may also set a user pointer that will be passed to our callback whenever it is run.

 1 #include "substrate.h"
 3 void eof(void *user)
 4 {
 5     // Reach EOF while playing our file!
 6     scli_printf("Reached end of file!\n");
 8     // Stop playing
 9     aecho_stop(audio0);
10 }
12 void init()
13 {
14     // Initialize substrate
15     substrate_init();
17     // Play some file
18     if(aecho_play(audio0, "sdc0p0:/example/music.mp3", eof, 0))
19     {
20         // Failed to play file :(
21         scli_printf("Failed to play the audio file\n");
22     }
24     // ...
25 }

Manual initialization (without substrate)

For those who prefer to go without the substrate, we can still initialize the Aecho manually. This however will require us to setup the underlying VS1011 decoder as well, and take care of regularly updating the aecho. As always, this is not recommended as it creates unnecessary complexity.

 1 #include <vs1011/vs1011.h>
 2 #include <aecho/aecho.h>
 4 // I/O Pins
 5 #define PIN_CS      8
 6 #define PIN_DCS     9
 7 #define PIN_RST     10
 8 #define PIN_DREQ    11
10 // MP3 Decoder Driver
11 struct vs1011 dec;
12 struct aecho audio0_data;
13 struct aecho *audio0;
15 void eof(void *user)
16 {
17     // Reach EOF while playing our file!
18     scli_printf("Reached end of file!\n");
20     // Stop playing
21     aecho_stop(audio0);
22 }
24 void main()
25 {
26     // ...
28     // Initialize VS1011 Decoder
29     vs1011_init(&dec, PIN_RST, PIN_DREQ, PIN_CS, PIN_DCS);
31     // Initialize Aecho module around decoder
32     audio0 = &audio0_data;
33     aecho_init(audio0, &dec);
35     // Play some file
36     if(aecho_play(audio0, "sdc0p0:/example/music.mp3", eof, 0))
37     {
38         // Failed to play file :(
39         scli_printf("Failed to play the audio file\n");
40     }
42     // ...
44     // Main Loop
45     for(;;)
46     {
47         // Update decoder
48         aecho_update(audio0);
49     }
50 }

Play control

Some methods are provided to control the playing:

// Pause currently playing file
void aecho_pause(struct aecho *a);

// Resume playing
void aecho_resume(struct aecho *a);

// Completely stop playing
void aecho_stop(struct aecho *a);

The 'aecho_pause' and 'aecho_resume' methods will maintain the currently playing file handle, while the 'aecho_stop' method will stop playing and release the file handle.

Volume control

A few methods are provided to control the output volume:

// Set Volume
void aecho_set_vol(struct aecho *a, uint8_t vol);

// Get Volume
uint8_t aecho_get_vol(struct aecho *a);

// Get Volume as Percentage
uint8_t aecho_get_vol_pct(struct aecho *a);

// Volume Up
void aecho_vol_up(struct aecho *a);

// Volume Up - Multiple
void aecho_vol_up_x(struct aecho *a, uint8_t x);

// Volume Down
void aecho_vol_down(struct aecho *a);

// Volume Down - Multiple
void aecho_vol_down_x(struct aecho *a, uint8_t x);

The volume is an 8-bit unsigned integer from 0 (minimum volume) to AECHO_VOL_MAX (0xfe / 254).