Using the vs1002 MP3 IC with an AVR

A MP3 decoding ICIn one of my various orders to Sparkfun, I picked up a vs1002 IC on a breakout board. Just yesterday I got round to having a good look at it, and I figured if I show my code then others will not have to do the boilerplate and just get it working. My setup is a ATMEGA88 running at 3.686MHz on a STK500 board, but it should work for most anyone with a decent clock speed. Just remember that all code is ATMEGA88 specific and may need to be changed for other devices.

Things that may be useful: the Sparkfun page, the datasheet.

Communication modes

(Note: The vs1002 has a backwards compatability mode with the vs1001. We will not be using it)

The vs1002 IC does all meaningful communication over the SPI port. It has two submodes if you will, one being SCI (Serial Command Interface) and the other being SDI (Serial Data Interface). SCI is for things like play control, reading and writing registers and small technical changes. Conversely, SDI is there for MP3 data and a few testing modes.

Since the AVR has only one SPI bus, we will be sharing the CS (Chip Select) line with both submodes.

Ok, enough. Get to the code!

Not quite yet, first we need to wire it up! Since it is a fairly simple circuit, I am going to put my wiring in list form (AVR -> VS1002):

  • SS -> CS
  • MOSI -> SI
  • MISO -> SO
  • SCK -> SCLK
On top of these, you will need to
  • Connect the GND pin to GND and the Vin pin to +3.3v
  • Wire the DREQ pin to an input on the AVR (if you intend to expand outside this tutorial)
Wait, what is DREQ?
Good question. DREQ is a data control pin on the vs1002. It is essentially an active-low busy signal. It should be checked once for every 32 bits of data sent, but since we will only be using simple commands, there is no use for it now.
Setting up SPI
The first thing that we need to do is set up SPI. If you have ever used SPI before, most of this will look familiar. The basic idea of SPI is that the master generates a clock (that is, a period of time where the clock line is high, and then an equal period of time where the clock line is low) and just after the change of levels on the clock line (either high to low or low to high but not both) the values will be read. The AVR will handle all this in the background, but it is important to remember one thing: sending a byte and recieving one is a single data packet. You do not just send a byte, and then ask for a byte. When the AVR sends a bit, it changes the MOSI line. At the same time, the slave will be changing the MISO line. Thus, after we write to the SPI data register and the byte is sent, we need to check the data register for our received byte.
This code is fairly self explanatory, look at your AVR datasheet or the comments to see what each line does.
//spi.h
// spi functions 
 
// pin setup
#define DDR_SPI 	DDRB
#define PORT_SPI	PORTB
 
#define DD_CS	2
#define DD_MOSI 3
#define DD_MISO 4
#define DD_SCK	5
 
void spi_init(); // init the SPI subsystem
 
void cs_low(); // helpers
void cs_high();
 
char spi_send(char byte); // send a byte; return rx byte
//spi.c
// spi helper functions
 
#include <avr/io.h>
 
#include "spi.h"
 
void spi_init()
{
	DDR_SPI = 0xFF; // set the whole port to output
 
	DDR_SPI &= ~(1 << DD_MISO); // change back to input
 
	SPCR = (1 << SPE) | (1 << MSTR); // set spi clock rate to fck/4
}
 
void cs_low()
{
	PORT_SPI &= ~(1 << DD_CS);
}
 
void cs_high()
{
	PORT_SPI |= (1 << DD_CS);
}
 
char spi_send(char byte)
{
	SPDR = byte; //send byte
 
	while(!(SPSR & (1 << SPIF))); //wait until its done
	return SPDR; //return the rx byte
}
VS1002 Nuances
The different command modes for the vs1002 means that there are two distinct ways of sending commands to the chip. To send an SCI byte, CS needs to be set to low. To send a SDI byte, CS needs to be set to high. The basic form that we are going to use for a SCI command is: set CS low, send read/write, send address, send data/get data, set CS high. For a SDI command, you just set CS to high and start sending bytes. Again, the code below is fairly self explanatory.
//vs1002.h
// chip control functions
 
//SCI_MODE register defines (p. 26 datasheet)
#define SM_DIFF			0
#define SM_SETTOZERO		1
#define SM_RESET		2
#define SM_OUTOFWAV		3
#define SM_PDOWN		4
#define SM_TESTS		5
#define	SM_STREAM		6
#define SM_PLUSV		7
#define	SM_DACT			8
#define	SM_SDIORD		9
#define	SM_SDISHARE		10
#define	SM_SDINEW		11
#define	SM_ADPCM		12
#define	SM_ADPCM_HP		13
 
int sci_read(char address); // serial command interface read/write funcs
void sci_write(char address, int data);
 
void send_sinewave(char pitch); // send the sinewave test
//vs1002.c
// chip control functions
 
#include <avr/io.h>
 
#include "spi.h"
#include "vs1002.h"
 
int sci_read(char address)
{
	unsigned int temp;
 
	cs_low();
 
	spi_send(0x03); //send read command, don't worry about return byte
	spi_send(address); //now send the address to read
 
	temp = spi_send(0x00); //dummy byte to get out data MSBs
	temp <<= 8; //shift it along so we can fit more data in
 
	temp += spi_send(0x00); // get the LSB
 
	cs_high();
 
	return temp;
}
 
void sci_write(char address, int data)
{
	cs_low();
 
	spi_send(0x02); //send write command
	spi_send(address); //send address we are going to write to
 
	spi_send((data >> 8) & 0xFF); //send the first 8 MSBs of the data
	spi_send(data & 0xFF); //send the LSBs
 
	cs_high();
}
 
void send_sinewave(char pitch)
{
	cs_high(); //this is different to SCI, it is data over SDI (see p.21 datasheet)
 
	/* we need to send the following bytes (see p.35)
		0x53	0xEF	0x6E
		0	0	0	0
	*/
 
	spi_send(0x53);
	spi_send(0xEF);
	spi_send(0x6E);
	spi_send(pitch);
 
	for (int i=0; i < 4; i++) spi_send(0x00); // send the filler bytes
 
	cs_low();
}
Let’s mix it all together!
The main code is the simplest out of the lot. It basically: sets up SPI, sets CS to high, sets the SCI_MODE register to tell the chip what we want to do and then sends the sinewave command. Without further ado, here it is:
//vs1002test.c
// this program will initialise the vs1002 ic and send it the sine wave test command
 
#include <avr/io.h>
 
#include "spi.h"
#include "vs1002.h"
 
int main(void)
{
	spi_init(); //set up SPI registers
	cs_high(); // probably a good idea
 
	sci_write(0x00, (1<<SM_TESTS)|(1<<SM_SDISHARE)|(1<<SM_STREAM)|(1<<SM_SDINEW));
 
	cs_low(); // for data interface
	send_sinewave(170);
}
Now hook up an oscilloscope (mine was graciously donated) and you should get a nice sine wave! I will try to put a picture of it up soon.
I will probably post some more code once I have the chip working better. I can get it to play MP3s, but they are not streaming fast enough over UART so they sound off key and distorted. If you have any questions, leave me a comment and I will endeavour to help but I’m new to this chip, so no promises ;) .
Update: The code had a bug which required you to use the BSYNC and DREQ lines. This is fixed by setting the SM_SDINEW bit in the initial setup. This bit was in my original code, but it appears that wordpress didn’t like my use of << so it messed the code up. Thanks to Paul and zeneslev for picking me up on it!

35 Responses to “Using the vs1002 MP3 IC with an AVR”


  1. 1 ToddV

    Great post – I was looking to use the VS1011 I picked up from Sparkfun as well. Since the VS1011 can work in 1002 mode this looks like a great primer.

    Do you have any working code for playing MP3s? Did you mount an SD card or connect it directly to a PC for testing? Have you tried streaming?

    If I understand your post correctly you used SCI for the initial test, but you’d switch to SDI if you were going to send actual MP3 data – correct?

    Thanks

  2. 2 jeremy

    Hey ToddV,

    I have recently had some computer troubles so I have lost a lot of things but I’ll have a look for my code. If I remember correctly, to play an MP3 you simply read the file from whatever source and send it one byte at a time over SDI (cs_high(); spi_send(byte);). You don’t need to bother with headers or anything, just stream the file and it will play. SCI is reserved specifically for *how* the MP3 is played back, not the MP3 itself.

    I was doing it over UART with a slow crystal so it sounded quite sad. But I imagine that if you can stream the file faster than it plays, it will fill the RAM on the chip and set the DREQ line to tell you to stop sending data. That way it would have consistent playback.

    Let me know when you finish your project!

  3. 3 Paul

    Nice little intro. I’m looking to make a “show control” board for a Halloween display this year to flicker lights in timing with sound and this is probably how i will do it.

  4. 4 Sam

    I tried to do the sine wave test, however, I only get the sound when I stop the JTAG debugger! If I run it without JTAG it does not even work. Do you have any idea?

  5. 5 jeremy

    That is quite an odd problem. Are you sure you get the proper sine wave when you run stop the debugger? I never used this chip with JTAG and it worked perfectly for me.

  6. 6 Sam

    I use the JTAG with ATMEGA128.

    I do not know what the sine wave sounds like. I just get a tone when i stop the debugger. Can you suggest any way to solve this problem.

  7. 7 Sam

    I figured out the problem. It was Bsync not asserted correctly. Now I can hear the mp3 files playing but in Slow Motion. Very Slow, I think it has to do with the sampling rate. Any suggestions?

  8. 8 jeremy

    Sam,

    Did you write your own code or use mine? If you set SM_SDISHARE the clock on BSYNC is automatically generated. See p16 of the datasheet: “If SM SDISHARE is 1, pin XDCS is not used, but the signal is generated internally by inverting XCS.”

    The sound was very slow for me also; my guess is because you aren’t sending data at the bitrate that it is playing; for example, if you don’t send >=128kbit/s for a 128kbit/s mp3, it will have to slow down. Try sending a very low bitrate mp3 (like 24 or 32kbit) and see if that works.

  9. 9 Sam

    I’m using slightly different code. But it is similar.

    After writting and reading back CLOCKF register, I found the return value always 0! my value (0×9800) was never written to the register to activate the clock doubler! I had to write it few times to correctly set the register! But after I did that, the audio sounded more normal since now it is activating the clock doubler.

    The audio still needs alot of improvment, for example, the audio sound have vibration effect to it and it is skreetching alot. Any ideas?

  10. 10 Shawn

    Is there no need to isolate the two components? The AVR is operating at 5V and the VS1002 at 3.3V. Is this ok?

  11. 11 jeremy

    You need to isolate the components. According to Section 4.1 in the datasheet, the maximum supply voltage is 3.6V. If you are using an STK500, you can just change the board voltage level to 3.3v.

  12. 12 nurwansyah

    hello, my avr atmega8535L not detecting when i connect my ISP (STK 200), why? i use regulator LM 2576 in my circuit for suppying AT8535L & VS1002, then my vs1002 is hot!!why?

  13. 13 mmusa

    Thank you..

  14. 14 kbryant

    Hey,

    I’m trying to get an MP3 file on an SD Card to play but am having troubles. The MP3 is playing in slow motion. My atmega328 is operating at 16Mhz, and I’m using the VS1002 breakout board from Sparkfun. I’m indeed setting the clock to 0×9800. I’ve tried all the possible SPI clock divisions on my atmega328.

    From what I’m reading from the datasheet and application notes, if I have more than one slave sharing the SPI bus, I shouldn’t use SDISHARE, so I’m not. I’m using BSYNC/xDCS to control the SDI bus. Every 32 bytes, I’m checking to see if the DREQ register is clear, and if not, I have it wait until it is clear.

    Is there anything else I should be setting?

  15. 15 jeremy

    kbryant,

    I’m honestly not sure. I’m in the middle of final exams right now but I’ve been thinking of rewiring the circuit and getting it working during my time off. I had that problem originally but I put that down to my slow uart connection.

    One thing you have to remember is to set a low enough bitrate on the mp3; I very much doubt a 320kbps mp3 will play well off an AVR through SPI. You didn’t mention what rate you were using, but try to use an mp3 that is as low as 8/16kbps and work up from there.

    Hope this helps.

  16. 16 kbryant

    Alright, thank you for the quick response. I’ll keep working on this, and if I figure anything out I’ll repost here. Good luck on finals!

  17. 17 Sean

    I just bought the Olimex Dev mod-mp3 board from spark fun, out the box the EEPROM will play/pause/fwd/rev mp3s no problem, i am stunned how great this little board performs. Iam not sure if you had a chance look at this board and how its wired up, I dont want to spend to much time trying to programing sense all I need is just basic play back functions, I saw the VS1002 has a UART onboard? is this for command/control of the chip? Iam not to sure how the EEPROM works with the toggle buttons to send the mp3 data to the decoder, Is it possible to use the UART to control playback of the mp3s??

    Or would it be better to just use a AVR and SD card?

  18. 18 zeneslev

    Hi. When running your sine test code, I got nothing at the output. Upon some debugging, I noticed that for me, the SM_SDINEW bit was reset initially in contrast to what the datasheet says. Setting this bit made it work correctly. Not sure why this happened, but I’d like to let others know to try this if they can’t get the sine test working.

  19. 19 Paul

    I figured out why this code wasn’t working for me (and some other people).

    You need to change the following line in vs1002test.c:
    sci_write(0×00, (1<<SM_TESTS)|(1<<SM_SDISHARE)|(1<<SM_STREAM));

    Have it include (re)setting SM_SDINEW, by changing the line to this:
    sci_write(0×00, (1<<SM_TESTS)|(1<<SM_SDISHARE)|(1<<SM_STREAM)|(1<<SM_SDINEW));

    Thanks for the code!

  20. 20 Paul

    oh lol zeneslev beat me to it. Why dident i reread these comments before i went at it again today? lol

    oh well good times.

  21. 21 jeremy

    Thanks guys. It does need the SM_SDINEW bit to stop it emulating the VS1001. Interestingly enough, my original code has that bit set; I must have accidentally deleted it when wordpress mangled my code.

    All fixed now.

  22. 22 Paul

    I believe if your using a 12.288Mhz crystal all you want to do is activate the clock doubler, not set any other bits in the CLOCKF register.

    The other bits are used to correct the value if the clock is other than 24.576Mhz. Once you double 12.288 it dosent need to be corrected at all. So setting the CLOCKF register to 0×8000 seems to make more sense to me than setting it to 0×9800.

    When I tried setting it to 0×9800 the sine test would not work. But when I set it to 0×8000 it worked again.

    Ive been working to get the vs1002 playing nice with an atmega644 and a SD card using FAT FS to play MP3s off the SD. I am somewhat successful, im just trying to figure out the speed issue like everyone else here.

    Im willing to shear to anyone who can shed some light on the subject! me@pauljmac.com

  23. 23 Paul

    I am wrong. You must set CLOCKF to 0×9800. I cant figure out how it works. But anyway I am going to just load the code on the VS1002 to act as a standalone player with direct connection to the SD and control via an AVR. Seems to be a lot simpler than going though the AVR with FAT.

  24. 24 jeremy

    Keep the updates coming Paul! I am going to set up my board again after mid semester exams and I will give it another shot!

  25. 25 Paul

    Its actually very easy. I more or less have it working now. It allows you to connect a SD card directly up to the vs1002 and just use a micro controller to control playback.

    I have it working on my bench right now. The audio is not the best quality and im not sure why just yet, but I intend to find out.

    10 times easier than using an AVR with FAT. The only down side is you need an AVR with something like 16k of program space, as this code takes up just under 8k, which im sure would be pushed over 8k when playback control is added. But 8k is a whole lot better as compared to the 50+k FAT FS was occupying on the AVR.

    Anyway I have some data here on my personal website: http://pauljmac.com/projects/index.php/Vs1002d_MP3_player regarding the way to make this work. Its defiantly a work in progress. If you have any questions or wana give me a hand figuring this out stop me a line at me@pauljmac.com. I will try and remember to come back here to check for comments on Jeremys site but cant make any promises.

  26. 26 jeremy

    Paul,

    I originally got the ic working the same way you describe: with poor quality sound. However I am not sure why your FAT code is taking 50k of space; I managed to fit sd + vs1002 on a mega32 just fine!

    Keep up the good work!

  27. 27 Paul

    I hadn’t gone though and timed the FAT code down yet.

    I wonder if the poor quality sound is expected. Humm… I have a vs1011e breakout from sf that I may try and do the same thing with. I thought this would be a very good solution but if the sound quality doesn’t get any better its a bust.

  28. 28 Paul

    Success! To correct the poor audio problem while in standalone mode all you must do is clear the SM_STREAM bit, or just don’t set it in the first place. After correcting that the audio is fine. There was a bit of “wiggle” when playing 320kbps mp3s. But when I switched to 128, 188, and 192 it sounded fine. I will update my site with the (slightly) modified code.

  29. 29 Peter

    I’m trying to run this on an Arduino. My understanding is that the code should work fine, just with different ports:

    #define DD_CS 10
    #define DD_MOSI 11
    #define DD_MISO 12
    #define DD_SCK 13

    I’m not able to get the sinewave output. Any pointers?

  30. 30 Jo Even

    Thanks, I just got a VS1002 breakout board from Sparkfun and your project looks like a very simple intro :-)

    Peter: Don’t confuse the Arduino pin numbering with the actual hardware pin numbering. The AVR has ports consisting of 8 pins, in this case bit 2-5 of PORT B is used. See http://arduino.cc/en/Reference/PortManipulation.

  31. 31 Bertrand

    Hello, I have test this code with a sparkfun VS1001E and an atmega8.

    tension is 3.3V

    the atmega8 is configured with 8 Mhz internal clock

    => it doesn’t work… no signal out

    When I see the transmission with electronic USB oscilloscope, all seems ok (sck,mosi,cs).

    I have tried different frequency…

    I tried also to read memory of vs1001E : but it doesn’t respond : nothing on miso.

    I don’t know what can i do ?

    If someone have any idea…

    thks

    Bert

  32. 32 Terrance Petrea

    Great site i love it

  1. 1 5voltdreams
  2. 2 MP3 preparations | DannyBlog
  3. 3 How to make VS1002 work with Arduino | DannyBlog

Leave a Reply