In this article I will illustrate the building of your own DIY music player. The music player described can play almost any audio file including MP3's, OGG, AAC, FLAC, WMA. The sound quality is actually better than a ipod. Hopefully this article will provide a starting point for you to build your own player.
This humble arduino AVR based music player has no problem playing even 320kbp's MP3's or even 500kbp's OGG's, it can amazingly play FLAC files as well! This is no mean feat because FLAC decoding is at 1000Kbps !!!
The circuit will incorporate a Arduino as the micro controller, which will be reading files from a SD card and sending it to a music decoder which is the VS1053 chip which incorporates a mp3 decoder and earphone driver as well.
The VS1053 is very small and difficult to solder so it is advisable to get a breakout board, I used one avaiable on ebay built by ic-dev.com.cn which is also available at MDFly as well. It's a excellent breakout board featuring a microphone and line input as well should you want to use the VS1053 as a digital recorder as well. However we are going to use VS1053 in it's decoding mode, we will read the data from the SD card and feed it to the VS1053 which will give us music :) The breakout board can be seen below.
SD cards talk to the Arduino through a interface called SPI. It incorporates 3 lines, CLK – for the clock, MISO (Master In Slave Out) – The slave sends data through this and the master receives it, and MOSI (Master Out Slave In) – The master sends data to the slave through this line.
There is a additional line called CS (Chip select), if this line is LOW that means that that chip is active. This is important as the 3 lines mentioned (CLK, MISO, MOSI) are shared between the VS1053 and the SD card. If the Arduino wants to send data to the VS1053 chip it will pull the VS1053's CS (Chip select) line low thus activating it and at the same time it will make the SD card's CS line high thus disabling it. Now when the Arduino sends data through the SPI bus it will be going to the VS1053 and not the SD. Should the Arduino want to talk to the SD, it will reverse the process disabling the VS1053 and activating the SD. Remember when it comes to chip selects in SPI, they are active LOW, that means that they are active only if they are low.
The other issue is that VS1053 runs at 3.3v and the Arduino is at 5v, this we need to reduce the voltage fed to the VS through the SPI digital interface otherwise the chip can get damaged. So we will use a resistor based voltage divider. The MISO (master in slave out) can be directly connected as only the SD card or the VS1053 running at 3.3v will be activating this, the Arduino is just listening so this line will only see 3.3v maximum.
Here is a example of using resistor based shiftors to reduce the voltage i found on the web.......
Here is the schematic of our circuit...Both the SD card and the arduino share the same SPI bus.
Regarding the SD card one can either by a SD card breakout board, or use a microSD card, and directly solder the wires to the microSD card's adapter. Every microSD usually comes with a SD adapter which you can insert the microSD card.
I directly soldered the wires to the microSD adapter. Here is the pin descriptions for the microSD adapter...
It is strongly advised to put a 0.1uf capacitor between pin's 3 and 4 of the SD card to improve stability, and please use short wires (3 to 4 inches maximum) between the SD card/Arduino and VS1053 SPI interface as long wires cause stray capacitance leading to instability.
What we do is to open a file on the SD card, read data from it (32 bytes) to a buffer, wait till VS1053's DREQ pin (Data request) is high which indicates it needs to receive data then we will write the data to the VS. The VS has two inputs CS (which when is low indicates that the VS is active) , and DS(Data select) which are mutually exclusive. When DS is low any data send to the SPI bus is read by the VS as data to be decoded, whereas is CS is active that means VS will read that as command data, ie to increase volume etc, once we send data we will deselect the VS, read more data from the SD etc etc....
The source code is available below, let me illustrate some key functions.
For the arduino there are certain pins already marked for SPI which are
MOSI - pin 11
MISO - pin 12
CLK - pin 13
CS - pin 10
So here are the additional definitions,
/* Control Chip Select Pin (for accessing SPI Control/Status registers)
*/ #define MP3_XCS 2
This means that pin 2 of the arduino will be used to select the VS's command mode, if this pin is LOW then VS's command interface is selected.
/* Data Chip Select / BSYNC Pin */
#define MP3_XDCS 9
This means that pin 9 of the arduino will be used to select the VS's data mode, if this pin is LOW then VS's data interface is selected.
/* VS1053's Data select pin for sending data is Pin10 */
#define SD_CS 10
The SD card's chip select is pin 10, when this is LOW then data will be sent/read from the SD card instead of the VS. This means of course that when SD_CS is active LOW, then the other pins MP3_XDCS and MP3_XCS must be disabled (ie HIGH).
/ * Data Request Pin: Player asks for more data */
#define MP3_DREQ 8
This pin can be directly connected from the VS's DREQ to the arduino's pin 8. When VS needs more data to decode it will pull this pin HIGH.
Now for a few basic functions,
void vs1053_select_control ()
{
fastDigitalWrite (MP3_XCS, LOW);
}
When we want to select the VS control mode we call this function which will pull the XCS line low thus activating the VS's command mode.
/** Pull the VS10xx Control Chip Select line High */
void vs1053_deselect_control ()
{
fastDigitalWrite (MP3_XCS, HIGH);
}
This will deselect the VS's command mode, by making the pin LOW.
Similar functions are there for the VS's data interface which deal with pin MP3_XDCS, and the SD card's chip select, pin SD_CS.
Now we will initialize the SPI bus,
void init_spi ()
{
SPI.setBitOrder (MSBFIRST);
SPI.setDataMode (SPI_MODE0);
SPI.setClockDivider (SPI_CLOCK_DIV16); // slow SPI bus speed
SPI.transfer (0xFF);
}
The Arduino runs at 16Mhz, while the maximum SPI speed is 8mhz, it is important to use a low SPI bus speed to write to the VS command interface. The manual recommends VS_CLOCK/7 at the start. Since the VS starts off with 12mhz clock, we need to reduce the Arduino's SPI rate to below 12/7= 1.5mhz. SPI_CLOCK_DIV16 means Arduino's default clock speed of 16 divided by 16, which is 1 mhz which is quite ok. After initializing the VS we can increase the VS's clock speed to around 55mhz.
Now we will get to a few basic VS functions. To command the VS we need to write/read from/to a 16 bit register and we will get a return value of 16bits.
unsigned int vs1053_read_register (unsigned char addressbyte)
The next function,
void vs1053_write_register (unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte)
will write a 16bit value to the VS1053 register adreesbyte. For example if you want to write 0xff32 to SPI_CLOCKF, you would invoke this function as,
vs1053_write_register(SPI_CLOCKF, 0xff, 0x32);
The most important function is the initialization of the VS1053 which has to be done precisely....
/** Soft Reset of VS10xx with patching the code */
void vs1053_softreset_and_patch ()
{
unsigned int sci_status = 0;
init_spi ();// slow speed
//software reset the VS
vs1053_write_register (SPI_MODE, 0x08, 0x04);
delay (1);
while (!fastDigitalRead (MP3_DREQ));
// upload patch vs1053_load_patch ();
vs1053_write_register (SPI_CLOCKF, 0xc0, 0x00); // VS1053
delay (1);
vs1053_write_register (SPI_MODE, B00001000, B10000000);
vs1053_set_volume (0x00, 0x00);
// ram up the SPI speed now that the VS internal clock has been set
SPI.setClockDivider (SPI_DEFAULT_SPEED);
}
Let's analyze the function and see what it does.....
void vs1053_softreset_and_patch ()
{
unsigned int sci_status = 0;
init_spi (); // slow speed
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Set slow SPI speed for initialisation
Next software reset the VS by setting the necessary flags in the SPI_MODE register and we wait for the DREQ to become LOW to signify that the VS is now reset
// software reset the VS
vs1053_write_register (SPI_MODE, 0x08, 0x04);
delay (1);
while (!fastDigitalRead (MP3_DREQ));
// /upload patch
vs1053_load_patch ();
^^^^^^^^^^^^^^^^^^^^^^^
We upload any patch if needed, the current patch enables FLAC playback
vs1053_write_register (SPI_CLOCKF, 0xc0, 0x00); // VS1053
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This Set's the VS1053's clock speed to 4.5 x crystal speed, which is 4.5 * 12.28 Mhz
delay (1);
vs1053_write_register (SPI_MODE, B00001000, B10000000);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This will set the SPI_MODE bits to SM_SDISNEW which is the default mode and also the low byte “ B10000000” enables spatial processing to give a better surround sound feel for headphones, if this is not desired just pass 0x00 as the low byte.
vs1053_set_volume (0x00, 0x00);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Set's the volume to maximum
// ram up the SPI speed now that the VS internal clock has been set
SPI.setClockDivider (SPI_DEFAULT_SPEED);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now that we have the VS initialised and its clock set to high speed we can increase the Arduino's bus speed to the maximum which is 8Mhz. SPI_DEFAULT_SPEED is defined as SPI_CLOCK_DIV2, which is ½ of the arduino's clock speed, ie 16/2 = 8Mhz.
The next function will illustrate what we have to do to send data to the VS1053
void transfer_32bytes (unsigned char *buf)
{
deselect_sd ();
vs1053_deselect_control ();
vs1053_select_data ();
while (!fastDigitalRead (MP3_DREQ));
for (unsigned char i = 0; i < 32; i++)
{
SPI.transfer (buf[i]); //Send a single byte 32 times
}
vs1053_deselect_data ();
}
Let's go through the steps.........
// Transfers 32 bytes of data
void
transfer_32bytes (unsigned char *buf)
{
deselect_sd ();
^^^^^^^^^^^^^^^^^^^^^^
This will deselect the SD card from the SPI bus
vs1053_deselect_control ();
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since we want to send DATA we must disable the VS1053 control interface
vs1053_select_data ();
^^^^^^^^^^^^^^^^^^^
Enable the VS1053 Data select to tell the VS that we are sending data
while (!fastDigitalRead (MP3_DREQ));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Wait till the VS1053 makes the DREQ line low, which tells us that it needs to be sent data.....
for (unsigned char i = 0; i < 32; i++)
{
SPI.transfer (buf[i]); // Send out a single byte 32 times
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Send the 32bits of data in the buffer via SPI to the VS
vs1053_deselect_data ();
^^^^^^^^^^^^^^^^^^^^^^^^^
Now that we have sent data, deselect the VS's Data interface.
}
The above example's illustrate the critical functions and how to send data and control commands to the VS. The other functions in the code are documented and should be able to be easily understood. They contain additional VS1053 routines such as volume setting etc, and a routine to play a file, and terminate a file. They are based on the VS1053 reference for playing and stopping a file.
The resulting Music player is quite impressive, the Arduino's atmega328p at only 16mhz together with the VS1053 decoder has no problems playing mp3's at 320kbp's it can even play FLAC files at 1000kbp's but that is really pushing it. The enclosed sketch will play all the files in the SD cards root folder. Here is the prototype...
I hope this article will stimulate people to build there own mp3 players. This player only support's forward and next track with two buttons. Additional functionality like increasing and decreasing the volume can be easily implemented with the vs1053_set_volume functions and appropriate software code.
The source code is here..It's in a "doc" file, just open it and select-all and copy and paste the text to a arduino sketch...please note that this incorporates a optional 12kb patch to enable FLAC playback, if you are not interested you can just remove it, and not call the patching function at the start of the VS initiatlisation code. I actually incorporated to patch for testing purposes to verify that the arduino can actually handle high bitarates that FLAC requires. I plan to be writing a function to load this patch from a file stored on the SD instead which will free a lot of the AVR's memory.