Warning: This is a very long and newbie-ish (because I’m a noob myself) post but I wanted to keep everything as understandable as possible and also cover everything needed no matter the readers previous knowledge.

I’ve been trying to make my Arduino Duemilanove control my VS1002 on a breakout board for a while now and I’ve finally got it to work properly.

This post will document the things I learnt while making it work so that other people might benefit and not fall into the same problems as I did.

Also note that I’m working with an Arduino Duemilanove and a VS1002 chip from VLSI but afaik anything mentioned here is valid for most other versions of Arduino and many of the other products from VLSI including VS1053.

Ok, first the wiring. Arduino operates on 5V but the VS1xxx chips are operating on 3.3V. The newer Arduino models has a power out pin which supplies 3.3V and although this can use this to power general stuff we still need to convert anything coming out of the digital pins from 5V to 3.3V.

But first let’s connect these basic pins to supply power and attach output headphones or small speakers:

  • Arduino 3.3V to VS1002 +Vin
  • Arduino 3.3V to VS1002 RST
  • Arduino GND to VS1002 GND
  • VS1002 RIGHT to speaker/headphone right channel
  • VS1002 BUF to speaker/headphone GND
  • VS1002 LEFT to speaker/headphone left channel

Now onto converting the signals to lower voltage. I learned an easy way of doing this using 2 resistors from when I hooked up my Arduino to an micro SD socket from the first post in this thread. The math behind it looks like this:

Where R1 = 1800 and R2 = 3300
Vout = Vin x R2/(R2+R1)
Vout = 5 x 3300/(3300 + 1800)
Vout = 3.235

And 3.235V should be close enough to 3.3V so it should work fine. But it’s only necessary to convert OUTPUT pins on the Arduino because for INPUT pins 3.3V should be enough for the Arduino to detect a high signal.

If you’re not 100% clear now how to convert Arduinos 5V output signals into ~3.3V please read through the the first post on the thread again and pay close attention to the figure in there as an example.

This is what it looks like on my project. Note the 1.8K resistors connected directly to the Arduino, then jumper cables going to the VS1002 in between the 3.3K resistors leading eventually to GND.

My wiring for converting the signals to ~3.3V

Ok, now which pins do we need to hook up on the VS1002 then? Since VS1002 communicates with any microcontroller through an SPI bus it makes sense to utilize that ATmegas have built in hardware SPI. I found this tutorial invaluable in understanding how to set up SPI on the Arduino.

So I set up the following fixed connections:

  • Arduino digital pin 13 (converted to 3.3V) to VS1002 SCLK
  • Arduino digital pin 12 (no need to convert voltage) to VS1002 SO (Slave Out)
  • Arduino digital pin 11 (converted to 3.3V) to VS1002 SI (Slave In)

All the remaining digital pins can be wired to any pin that’s preferable/free. But we need to connect at least 1 more pin if there is only have the VS1002 on your SPI bus or 2 more pins if any more devices sharing the SPI bus. This is because the VS1002 operate in 2 different modes, or rather that 2 different types of data are sent. If we only have the VS1002 on the SPI bus we only need to connect the CS pin and the enable SM_SDISHARE in the code. If we have more than one device on SPI we need to connect both CS and BSYNC and disable SM_SDISHARE in the code. Basically these pins control which device is active currently on the SPI bus.

Section 7.2.1 in the data sheet for VS1002 gives a very short and detailed understanding of how the pins work and also how BSYNC turns into XDCS.

In my example I use pin 8 and 9 for XDCS and CS:

  • Arduino digital pin 8 (converted to 3.3V) to VS1002 BSYNC/XDCS
  • Arduino digital pin 9 (converted to 3.3V) to VS1002 CS

Finally there is an optional pin that can be connected which where the VS1002 signals that no more data should be sent to it right now because it can’t keep up. This is considered optional since we can “know” that we’ll never overflow the VS1002 if we send data slowly enough. However, I strongly suggest to wire this pin because .mp3 files can have variable bit-rate and this means it’s tricky to determine how fast is too fast for the VS1002. In this example we wire this one to analog pin 0 like this:

  • Arduino analog pin 0 (no need to convert voltage) to VS1002 DREQ

This is a test code that I reworked slightly from superjazz example code should work on any normal Arduino. The changes I did was just to use normal Arduino way of setting up the ports and also important to add a delay to when setting the the CS pin so that VS1002 has time to notice. Basically everything else is the same except condensed into one single sketch.

Just copy and paste into an empty new sketch. Double check that the wiring matches the definitions at the top of the code. If run on a correctly wired setup, this code will produce a play a tone until device is power off. Note: In this code pin BSYNC/XDCS and DREQ are unused and the device operates in SM_SDISHARE mode.

#include <avr/io.h>

#define MP3_XDCS 8
#define MP3_CS   9
#define SPI_MOSI 11
#define SPI_MISO 12
#define SPI_CLK  13
#define MP3_DREQ 14

//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

void spi_init()
{
  pinMode(MP3_XDCS, OUTPUT);
  pinMode(MP3_CS, OUTPUT);
  pinMode(SPI_MOSI, OUTPUT);
  pinMode(SPI_MISO, INPUT);
  pinMode(SPI_CLK, OUTPUT);
  
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0); // set spi clock rate to fck/4
}
 
void cs_low()
{
  digitalWrite(MP3_CS, LOW);
  delay(1); //this delay is important to give vs1002 enough time to react
}
 
void cs_high()
{
  digitalWrite(MP3_CS, HIGH);
  delay(1); //this delay is important to give vs1002 enough time to react
}
 
char spi_send(volatile char byte)
{
  SPDR = byte; //send byte
 
  while(!(SPSR & (1 << SPIF))); //wait until its done
  return SPDR; //return the rx byte
}

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  <pitch>
    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();
}

void setup(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);
}

void loop(void) 
{
}