Car MP3 Player

 

 

 

 

 

 

 

 

 

 

 

 

 

By:

Chris Dolen (cd247) and Matt Watkins (maw72)

Contents

 

Introduction

This project was designed to be an mp3 player that you can use in your car.

 

In this project we used an Atmel microcontroller, an LCD display, some pushbuttons, an mp3 decoder, and a digital to analog converter.  We also used an SD (Secure Digital) card for storage to hold the MP3’s.  An SD card can have MP3 files written to it using a PC.  The card is formatted using the FAT file system that Microsoft Windows uses.  The microcontroller is then used to read this SD card to get the mp3 data.  Song titles are displayed on the LCD screen.  Pushbuttons on the STK500 are used for functions such as skip to the next song, stop, and play.  A mini-RCA jack (3.5mm audio jack) is used as the output of the mp3 player, which can be connected to a car-cassette adapter or a wireless FM transmitter (not included in the project).  A 12V DC plug is the power source so that it can be plugged into the cigarette lighter of a car for power.  The volume will be controlled by the car’s audio system.  As the design only needs a 12V DC power source, no batteries are required.  The mp3 player will never skip on bumpy surfaces as its just reading flash memory and there’s no disk drive or CD spinning that’s being read.  Since the SD card is removable, the storage size can increase as new, larger SD cards are created, as opposed to being a fixed size like most mp3 players today. 

 

Back to top

 

 

High Level Design

           

Rationale:

The main reason for choosing an MP3 player as a project idea is that I’ve always wanted to build one.  I’ve always been curious how it all works and I’m into digital circuitry in general, and I thought this would be a good culmination of a lot of things I’ve learned over the years.  It involves C coding, building a complete circuit that involves digital and analog pieces, accessing external flash memory and a FAT file system, and the end result is something that you can actually use.

Background Math:

            There weren’t really any calculations that needed to be done to begin the design.  The most important calculation that could be done is to determine the maximum bit rate of the mp3 that we could support, for example, 128 kbps.  There are two transfers for every bit of data: we must first get the mp3 data from the SD card, and then send it to the mp3 decoder.  The fastest we can clock the transfer of data from the SD card is 4 MHz, and the fastest we can send a bit of data to the mp3 decoder is 8 MHz.  But, these values don’t take into account all of the setup of the calls and the checking of loop variables that also need to be done.  So therefore it really depends on how you implement the required functions.  Also, our project was all done in C, which means it also depends on how the assembler translates our C code.  You might have a function that you think only takes 1 cycle, but it could take more, and this would affect your calculation.  Therefore we decided to just start testing with small bit rate files and determine the maximum bit rate later on.  We did know, however, that it was possible to achieve some relatively decent bit rate (24 kbps or so) because others had done it with similar hardware and clock frequencies.  Had we not had these other designs as references, we would have had to estimate our maximum bit rate based on what we thought the code would look like, but as you can imagine, this could be quite difficult and potentially not very accurate.

Logical Structure:

There were a few different stages to the creation of the design.  First we needed to choose a microcontroller.  We wanted to stick as close to the Atmel Mega32, as that’s what we were used to.  But after researching other various designs, we found that a very popular choice for an mp3 decoder and DAC (digital to analog converter) were the STA013 and the CS4334.  These two were proven to work well together, and there were at least a few other designs that had used these two.  This gave us a lot of background information on how to use them, which made creating an mp3 player in a months time seem more feasible.  But, the STA013 was a 3V chip and required 3V communication lines with the microcontroller.  Since the Mega32 was a 5V chip, we would have needed to build level converter circuitry between these two chips.  To avoid this, we used the Mega32L, which is very similar to the Mega32, but can be run at 3V.  The downside to this is that it can only be clocked at a maximum of 8 MHz instead of 16 MHz.  As the bottleneck of our design is currently the speed of the microcontroller, we realize we could have achieved higher bitrates by using the Mega32 and creating the level conversion circuitry, but the Mega32L did keep our hardware design simpler.

The next step was to choose what type of flash memory to use to store the mp3 files.  We knew we wanted flash memory instead of a hard drive or cd-rom, as those are larger and require more energy to power them.  We chose the SD card because it was one of the popular flash memory standards, it used the SPI interface so we only needed 4 I/O lines from our microcontroller, and it ran at 3V.  The only other piece of hardware we needed was an LCD display.  We chose a 16X2 LCD display that is very similar to the ones we had been using in lab all year.  A schematic of the hardware is shown below (modified from http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects/s2002/jmd48/nll4 jmd48 - EE 476 Final Project Web Page/476_final_project_schematic.htm and http://www.pjrc.com/mp3/sta013.html )

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

As I mentioned above, the majority of the schematic was used from http://www.pjrc.com/mp3/sta013.html.  The SD card which used the SPI interface needed to be on port B as that’s what the SPI functions use.  So the I2C code for the STA013 needed to be modified to used port A.  Port C was connected to the LCD display, and port D was used for pushbuttons.  D.0 and D.1 weren’t used, so that we could continually use HyperTerminal for debugging with printf’s.  The CS4334 DAC didn’t require a connection to the microcontroller, as it was completely configured through the STA013.  As shown in the schematic, there are 3 pushbuttons.  Button 4 will play the song that is currently displayed on the LCD display.  Button 5 will stop playing the current song.  Button 2 will skip to the next song on the SD card and display its title on the LCD display.  There is one last tricky part to point out here: the 1K ohm resistors running to the LCD display.  The LCD display was a 5V display, while all the signals from the Mega32L were 3V.  So we connected a 5V supply to Vdd on the LCD.  But the LCD sends data signals back to the Mega32L, which we needed to limit to 3V.  This is the job of the resistors.  Also, the inputs of the Mega32L have protection diodes which will protect it from high voltages, so the resistors were all we needed.

Hardware/Software Tradeoffs:

  1. By choosing to use the Mega32L, it made the hardware simpler as we didn’t need any level conversion logic.  But, this limited our maximum frequency to 8 MHz, which meant the code itself had to be faster and more efficient in order to achieve the same bit rate we could have with a 16 MHz clock on the Mega32.
  2. All of the code was written in C, but it could have run much faster on the hardware if we had written it all on assembly, as the compiler doesn’t always do the best job.  But, due to our time constraints, programming everything in C was the only feasible option.
  3. The easiest way to program this code would have been to read an entire mp3 from flash memory and store it in RAM, then stream it from RAM to the mp3 decoder.  But, we could only have a 512 byte RAM block to hold data, which meant we had to write code which would read 512 bytes from the SD card, then stream this data out, then read 512 more bytes, etc.  This process works, but involves an overall slower data transfer rate from the microcontroller to the mp3 decoder.

Standards:

MP3, or MPEG-1 Audio Layer 3, is digital audio encoding.  It is a lossy compression format, meaning that if you compress the data and then uncompress it, the new uncompressed version will not exactly match the original version.  According to wikipedia.org, it uses psychoacoustic models to discard components less audible to human hearing.  This means that frequencies and noises that people can’t hear anyway are removed, making the amount of data that needs to be stored smaller.  There are various bit rates available: 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320 kbit/s.  There are also various sampling frequencies: 32, 44.1 and 48 KHz.  44.1 KHz and 128 or 192 kbits/s are said to be the most popular right now.  The mp3 file has a standard format of which is a frame consisting of 384, 576, or 1152 samples.  This value depends on the MPEG version.  Also, all the frames have header information consisting of 32 bits and side information consisting of 9, 17, or 32 bytes which help the decoder to decode the encoded data correctly.  An mp3 frame consists of an mp3 header and mp3 data.  Multiple frames create an mp3 file.  The details for the mp3 header are shown below, as taken from http://en.wikipedia.org/wiki/MP3.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Our mp3 decoder is smart enough that if it doesn’t see mp3 data, it doesn’t output anything to the DAC.  It can also determine the correct bit rate and frequency of an mp3 by reading the header itself.  Therefore, all you need to do is stream it the mp3 file, and it will do the rest for you.

 

Patents:

            According to wikipedia.org, many different organizations have claimed ownership of patents related to decoding and/or encoding of mp3 files.  There are open source encoders and decoders that patent holders allow to be distributed freely, but there has been legal action against certain companies selling mp3 devices without paying any licensing fees.  Regardless, mp3 was patented in 1991, which means that by 2011, no patent could apply to mp3 any longer.

            The SD card is also covered by patents, according to http://en.wikipedia.org/wiki/Secure_digital.  Using the SD interface requires expensive royalties to be paid to the SDCA, but Wikipedia states that a common workaround for this is to use the MMC/SPI interface, which this project uses.  While in this mode, you can not access the proprietary encryption features of the SD card, but this project only requires storing data, and encryption of the mp3’s would not really be useful anyway.  MMC is an open standard that anyone can use, but the specification must be purchased from the MMC Association if it is needed.

 

Back to top

 

 

Program Details

We programmed the design in different pieces, and put it all together at the end.  Step 1 was to access the SD card and be able to read and write data.  Step 2 was to interface with the mp3 decoder, configure it, and stream some sample mp3 data to it.  In step 3 we added code to support reading data from an SD card formatted with the FAT file system.

Step 1: The SD card

The information used to understand the SD card was taken from a few different sources: http://www.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf, http://www.maxim-ic.com/appnotes.cfm/an_pk/3969, http://www.captain.at/electronic-atmega-mmc.php, and http://elm-chan.org/docs/mmc/mmc_e.html.  The SD card interfaces with the microcontroller via SPI.  We used some of information from http://instruct1.cit.cornell.edu/courses/ee476/SPI/index.html to understand an implement SPI.  The base code that we began with came from http://www.captain.at/electronic-atmega-mmc.php.  The description below refers to code in a file called mp3_withsd48test.c.  This file is not part of the final project code, but was an important stepping stone on our way to the final result.

To start with, the code must include the spi.h file.  This file allows us to use the Atmel SPI functions.  We also make a note of which data lines are connected to which I/O pins:  MOSI (master out slave in) is connected to B.5, MISO (master in slave out) is connected to B.6, SCLK (slave clock) is connected to B.7, and chip select is connected to B.4.  In the initialize function we must set these I/O pins to the correct data direction.  We must also correctly set the SPI configuration registers.  This is shown below:

  

    //set up SPI

    //bit 7 SPIE=0 no ISR

    //bit 6 SPE=1 enable spi

    //bit 5 DORD=0 msb first

    //bit 4 MSTR=1 Mega32 is spi master

    //bit 3 CPLO=1 clock polarity

    //bit 2 CPHA=1 clock phase

    //bit 1,0 rate sel=10 along with SPSR=1 sets clk to f/32 = 250 kHz

    SPCR = 0b01011110 ;  //clock must not exceed 400 kHz for initialization                                                                           

    SPSR = 1; //turn on SPI2x clock

   

    //set up i/o data direction for SPI

    //connect MOSI B.5 to DI

    //connect MISO B.6 to DO

    //connect SCLK B.7 to CLK

    //connect B.4 to chip select

    DDRB.4 = 1; //output chip select

    DDRB.5 = 1; //output MOSI

    DDRB.6 = 0; //input MISO

    DDRB.7 = 1; //output SCLK

 

            It is important to set the SPI clock to be under 400 kHz for the initialization of SD card.  After initialization is complete, we can increase the clock to 4 MHz, the maximum allowed when the MCU clock is 8 MHz (SPI clock max of f/2).  After initialization we called the testSD() function.  This function tests the SD card to make sure that it’s connected properly.  First it calls MMC_Init().  MMC_Init follows an initialization flowchart from http://www.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf:

            Our initialization differs slightly from this chart.  For example, in the first step we set the clock to 250 KHz, not 400 KHz, due to the available SPI clock dividers and our 8 MHz base clock.  The next step is to assert the CS line by setting PORTB.4 to 1.  We then send 80 clock pulses, which just means writing 80 bits of data.  This is done with the simple loop:

for(i=0; i < 10; i++) spi(0xFF); // send 10*8=80 clock pulses

By calling spi(0xFF), the microcontroller takes care of setting all the appropriate signals in order to send the 8 bits of data specified in the parameter.  Since it’s a 1 bit serial line, each bit takes 1 clock pulse, so 80 bits of data will create 80 clock pulses.  Next, the CS line is deasserted by setting PORTB.4 to 0.  Sixteen more clock pulses are sent, similar to the code above except the loop is only performed twice.  Now we can start sending commands.  The first command is command 0, which is the reset command:

Command(0x40,0,0,0x95)

The Command function is pretty simple.  Every command that goes to the SD card has 6 bytes.  The format can be seen in this picture, from http://www.maxim-ic.com/appnotes.cfm/an_pk/3969:

 

 

 

 

 

 

 

 

 

The first byte always starts out with a 0 then a 1 in the upper bits.  Then the command you want to send is entered in the remain 6 bits of this first byte.  For command 0, these bits are just 0.  Then you pass arguments, depending on which command you send.  If the command doesn’t take any arguments, or if it takes fewer that 4 bytes of arguments, then those bytes are just ignored.  Lastly you send a 7 bit CRC, with a 1 in the least significant bit.  The Command function takes 4 parameters: the command byte, two 16 bit arguments, and a CRC byte.  The CRC byte is 0x95 or 0xFF for these commands, and for later commands the value of CRC is irrelevant.  The Command function then just sends all of these values, bit by bit, to the SD card, as shown below:

 

char Command(char command, unsigned int AdrH, unsigned int AdrL, char CRCbits )

{    // sends a command to the MMC

     spi(0xFF);

     spi(command);

     spi((unsigned char)(AdrH >> 8));

     spi((unsigned char)AdrH);

     spi((unsigned char)(AdrL >> 8));

     spi((unsigned char)AdrL);

     spi(CRCbits);

     spi(0xFF);

     return spi(0xFF);  // return the last received character

}

 

The return value of the function is the response from the SD card.  There are different formats of the response, depending on the command issued.  For all the commands we use, it responds in the R1 format, which is:

 

What we want to see is a return value of 1, which means the command completed and it’s in the idle state now.  Now are ready to send command 41 and command 55, which are the initialization commands.  These commands must be called until command 55 returns a 1 in the idle bit of the return value.  Once this completes, the initialization is complete.  If you want there are other checks that can be done, for example by issuing command 58 to read the operating conditions register, but it’s not necessary to do so.

            Now that the SD card is initialized, we can speed up the SPI clock.  The maximum speed you can run this clock at is the frequency of the clock of the microcontroller / 2, which is 4 MHz in our case.  To set this up, you need to set the two SPI registers as follows:

SPCR = 0b01011100;                                                                           

SPSR = 1; //turn on SPI2x clock

The default block size for a data transfer on an SD card should be 512 bytes.  To guarantee this we can use command 16 to set the block length to 512, as follows:

     Command(0x50,0,512,0xFF)

Now the SD card is properly configured.  In order to test it, we need something to write to it.  In this stage of our testing a function called fillram() is called, which simply writes a string over and over again into a block of ram (called sector[ ]) which is 512 bytes long.  The function looks like this:

 

void fillram(void) { // fill RAM sector with ASCII characters

     int i,c;

 

     c = 0;

     for (i=0;i<=512;i++) {

          sector[i] = mystring[c];

          c++;

          if (c > 17) { c = 0; }

     }

}

The string used here (called mystring) is simply:

char *mystring = "Chris was here! ";

So now we have 512 bytes of RAM filled with multiple copies of this string.  To write this data to the SD card, we call writeramtommc().  This function copies a string that is in RAM on the microcontroller to the SD card.  The function looks like this:

 

int writeramtommc(void) { // write RAM sector to MMC

     int i;

     unsigned char c;

    

     // 512 byte-write-mode (single block)

     response = Command(0x58,0,512,0xFF);

     if (response !=0) {  //CMD24, which is a single block write, addr is 512

          printf("MMC: write error 1 \n\r");

          printf("response = %d\n\r", response);

          return 1;

     }

     printf("response = %d\n\r", response);

     spi(0xFF);

     spi(0xFF);

     spi(0xFE); //start token

     // write ram sectors to MMC

     for (i=0;i<512;i++) {

          spi(sector[i]);

     }

    // at the end, send 2 dummy bytes (unused CRC bytes)

     spi(0xFF);

     spi(0xFF);

 

     c = spi(0xFF);

     printf("c1 = %d\n\r", c);

     c &= 0x1F;    // 0x1F = 0b.0001.1111;

     printf("c2 = %d\n\r", c);

     if (c != 0x05) { // 0x05 = 0b.0000.0101

                      //if c=0x05, data is accepted

          printf("MMC: write error 2 \n\r");

          return 1;

     }

     // wait until MMC is not busy anymore

     while(spi(0xFF) != (char)0xFF);

     return 0;

}

First, command 24 is used to tell the SD card we are going to perform a write.  This command has 1 argument: the address to which we would like to write to.  In this case you can see that we are writing to address 512.  The address has to be a multiple of 512, since the block size is 512 bytes.  Once the command is accepted, we need to send the start token, which is 0xFE.  Then we can write 512 bytes to the address we specified.  So we call spi() for each byte of sector[] which transfers the desired 512 bytes to the SD card.  We then send two dummy bytes at the end, and check to see what the response is.  If the response is 0x05, then the data was accepted.  Now we wait until the idle bit goes high by repeatedly sending 0xFF and looking at the idle bit.  Once it goes high, we have completed the write, and we can return.

            To really know if the write succeeded, we need to read from the SD card.  To do this, we call sendmmc().  This function reads the data we just wrote to the SD card, and writes it to HyperTerminal.  It looks like this:

 

int sendmmc(void) { // send 512 bytes from the MMC

     int i;

     // 512 byte-read-mode

     response = Command(0x51,0,512,0xFF);

     if (response != 0) { //CMD17 is read single block, addr is 512

          printf("MMC: read error 1 \n\r");

          printf("response = %d\n\r", response);

     }

     // wait for 0xFE - start of any transmission

     startToken = 0;

     while(startToken == 0)

     begin

       response = spi(0xFF);

       printf("response2 = %d\n\r", response);

       if (response == 0xFE) startToken = 1;

     end

 

     for(i=0; i < 512; i++) {

          recvd = spi(0xFF);  // get character

          printf("%c", recvd);

     }

     // at the end, send 2 dummy bytes

     spi(0xFF); // actually this returns the CRC/checksum byte

     spi(0xFF);

     return 0;

}

Here we use command 17 to tell the SD card that we are going to perform a read, and we send 512 as a parameter to indicate the address we want to read from.  Then we wait for the SD card to return the start token, which is 0xFE.  To do this we keep sending 0xFF until we get 0xFE returned to us.  At this point the read data is ready.  To get it, we loop 512 times calling spi(0xFF) each time and print the return value which contains the data returned from the card to HyperTerminal.  If the entire process is successful, we see “Chris was here! Chris was here! Chris was here!” printed over and over again on the screen.

 

Step 2: The mp3 decoder (STA013) and the DAC (CS4334):

            To begin with, we define the mp3 decoder I/O names to the pins they correspond to:

 

            // Define STA013 Pins

#define I2C_SDA_direction    DDRA.0

#define I2C_SDA_in      PINA.0

#define I2C_SDA_out     PORTA.0

#define I2C_SCL              PORTA.1

#define DATA            PORTA.2

#define CLOCK           PORTA.3

#define DATA_REQ             PINA.4

#define RESET           PORTA.5

 

As you can see, there are 4 outputs and 1 input.  SDA and SCL are the data and clock pins used for the I2C connection.  This is used for configuration purposes, such as to set up the mp3 decoder.  DATA, CLOCK, DATA_REQ, and RESET are used to actually send mp3 data to the mp3 decoder.  Note that there is an I2C library for the Atmel microcontrollers, but we didn’t use it here.

            To configure the mp3 decoder, we call config_sta013().  This function is displayed below:

 

unsigned int config_sta013(void) begin    

   // Read from address 0x01 on the STA013

   printf("Starting config_sta013\r\n");

   address = 0x01;                                        // Load the address into the address reg, r4

                                                    

   sta013_read();                                        // Read data from the STA013

   // Test if address 0x01 contains 0xAC

   printf("data = %x, errorFlag = %d\r\n", data, errorFlag);

   if((data != 0xac) || (errorFlag != 0x00)) begin

      printf("Error: STA013 Not Present, %d, \r\n", errorFlag);    // Print out the error flag

      return 1;                                           // Return 1 to indicate that the STA013 is not present

   end

   else

      printf("STA013 Present\r\n");

  

   printf("beginning to load STA013 config file\r\n");  

   // Send STA013_updatedata information to STA013 (i.e., STA013 config file)

   for (i = 0 ; i < max_config_index ;) begin

      address = STA013_UpdateData[i];                // Load the address into the address reg, r4

      data    = STA013_UpdateData[i+1];              // Load the data into the data reg, r5

      sta013_write();                                // Write data to STA013 

     

      // Increment i

      i += 2;

     

      // Check for an error in the write

      if (errorFlag != 0x00) begin

         printf("Error: STA013 Configuration Failed, %d\r\n", errorFlag);   // Print out the error flag

         return 2;                                   // Return 2 to indicate sta013 failed configuration

      end

     

      // Delay initialization for soft reboot

      if (address == 0x10) begin

          delay_ms(1000);

      end    

   end

  

   printf("finished loading STA013 config file\r\n"); 

  

   printf("beginning to load STA013 board data\r\n");  

   // Send STA013 board specific data

   for (i = 0; i < max_PLL_index; ) begin

      address = config_PLL[i];                            // Load the address into the address reg, r4

      data    = config_PLL[i+1];                          // Load the data into the data reg, r5

      sta013_write();                                     // Write data to STA013

     

      // Increment i

      i += 2;

     

      if (errorFlag != 0x00) begin

         printf("Error: STA013 PLL Configuration Failed, %d\r\n", errorFlag);   // Print out the error flag

         return 2;                                   // Return 2 to indicate sta013 failed configuration

      end

   end

  

   printf("finished loading STA013 board data\r\n"); 

              

   printf("STA013 Configuration Complete\r\n"); 

   return 0;                                         // Return 0 to indicate pass configuration

end

 

This function begins by making sure that the STA013 chip is present and wired properly.  In order to do this, it performs a read from address 0x01 on the chip.  This address contains 0xAC, so we need to make sure we can read this value from that address.  To perform a read, the sta013_read() function is called:

 

void sta013_read(void) begin

  printf("inside sta013_read\r\n");

   // Start Condition

   sta013_I2C_start();

   //i2c_start();

   printf("called I2C_start\r\n");

  

   // Send the device address with the R/W bit (i.e., the 8th bit) cleared

   printf("about to try sta013_I2C_write\r\n");

   I2C_byte = 0x86;

   sta013_I2C_write();

    if (errorFlag != 0) begin

       printf("Read Error: Error writing device address (W) to STA013.\r\n");

       return;

    end

    printf("finished calling sta013_I2C_write, errorFlag = %d\r\n", errorFlag);

  

   // Send the read address

   printf("about to try sta013_I2C_write again\r\n");

   I2C_byte = address;

   sta013_I2C_write();

   if (errorFlag != 0) begin

      printf("Read Error: Error writing read address to STA013.\r\n");

      return;

   end

   printf("finished calling sta013_I2C_write again, errorFlag = %d\r\n", errorFlag);

  

   // Start Condition

   sta013_I2C_start();

   printf("called I2C_start again\r\n"); 

 

   // Send the device address with the R/W bit (i.e., the 8th bit) set

   printf("about to try sta013_I2C_write 3rd time\r\n");

   I2C_byte = 0x87;

   sta013_I2C_write();

   if (errorFlag != 0) begin

      printf("Read Error: Error writing device address (R) to STA013.\r\n");

      return;

   end

   printf("finished calling sta013_I2C_write 3rd time, errorFlag = %d\r\n", errorFlag);

  

   // Read the data from the given address

   printf("about to try sta013_I2C_read\r\n");

   sta013_I2C_read();

   printf("finished calling sta013_I2C_read, data = %x\r\n", data);

  

   // Stop Condition

   sta013_I2C_stop();

end

 

To do any sort of I2C command, you first need to call the sta013_I2C_start() function:

 

void sta013_I2C_start(void) begin

   // High to low transition of I2C_SDA while I2C_SCL is high

   I2C_SDA_direction = 1;

   delay_us(5);

   I2C_SDA_out = 1;

   delay_us(5);

   I2C_SCL = 1;

   delay_us(5);

   I2C_SDA_out = 0;

   delay_us(5);

   I2C_SCL = 0;

   delay_us(5);

end

 

This function prepares the chip for a command.  First we set the direction to 1 to make it an output.  Data is sampled on the rising edge of the clock, so we first set the SDA line to 1 and then bring the clock line high.  Next we set the SDA line to 0 and drop the clock line.  This signifies a start condition.  There are 5 microsecond delays in between all of the I2C changes.  This is because I2C can’t be clocked very fast, and since this is just a 1 time configuration anyway, we chose a high delay between all I2C transactions to ensure correct operation.

            Now that the start condition is done, we send the value 0x86 to signify that we would like to perform a read.  We store 0x86 in I2C_byte, and call sta013_I2C_write():

 

void sta013_I2C_write(void) begin

   // Clock each bit onto the SDA bus (starting with the MSB)

   I2C_SDA_direction = 1;

   for(j = 7; j >= 0; j--) begin

      delay_us(5);   

      I2C_SCL = 0; //I2C_SCL is the clock (what is the min/max clock speed?)

      delay_us(5);

      I2C_SDA_out = (I2C_byte >> j) & 0x01; // Write each bit while I2C_SCL is low

      delay_us(5);

      I2C_SCL = 1;

      delay_us(5);

   end

  

   // Get the ack bit

   I2C_SCL = 0;

   delay_us(5);   

   I2C_SDA_direction = 0;

   delay_us(5);

   I2C_SCL = 1;

   delay_us(5);

   errorFlag = I2C_SDA_in; //should be a 0

   delay_us(5);

   I2C_SCL = 0;  

end     

 

To write data over the I2C bus, you set the SDA direction to output, lower the clock, write the data bit, and raise the clock.  You repeat this lowering of the clock, sending a bit, and raising the clock until you have sent your data byte.  To receive the ack bit, you set the SDA direction to input, lower and raise the clock, and then read SDA.  The value should be a 0 if everything went ok.  Finally, lower the clock again and exit the function.

            Now we need to perform another write to send the address that we want to read from.  The void sta013_I2C_write() function is called again, except this time the address to read from is stored in the I2C_byte variable.  Now the STA013 knows which address we want to read from, and we just need tell it that we’re going to do a read next.  To do that, we do another write with value 0x87, saying we’re ready to read.  Once this is complete, we can call sta013_I2C_read():

 

void sta013_I2C_read(void) begin

   data = 0x00;

  

   // Clock each bit off of the SDA bus

   I2C_SDA_direction = 0;

   delay_us(5);

   I2C_SCL = 0;

   delay_us(5);  

   for(j = 7; j >= 0; j--) begin   

      I2C_SCL = 1;

      delay_us(5);

      data = data | (I2C_SDA_in << j);     // Read the bit while I2C_SCL is high

      delay_us(5);

      I2C_SCL = 0;

      delay_us(5);

   end

end

This function just reads bit by bit while controlling the clock.  The data changes on a low to high transition, so after the clock is high a new data bit can be read.  We now have the data at address 0x01, and we just need to check to make sure it’s 0xAC.  If it is we can move on.  We also need to call the sta013_I2C_stop() function, which tells the STA013 that we are done with it for now:

 

void sta013_I2C_stop(void) begin

   // Low to high transition of I2C_SDA while I2C_SCL is high

   I2C_SDA_direction = 1;

   delay_us(5);

   I2C_SDA_out = 0;

   delay_us(5);

   I2C_SCL = 1;

   delay_us(5);

   I2C_SDA_out = 1;

   delay_us(5);

   I2C_SCL = 0;

   delay_us(5);

end

 

This function simply changes the SDA line from low to high which the clock line is high, which is the stop condition for the STA013.

            The next step is very important.  The STA013 requires some configuration data in order to function properly.  This data is available in a file from STMicroelectronics, here: http://www.st.com/stonline/products/families/audio/audiodecoders/mp3/sta013.htm.  The file is called p02_0609.bin, and contains many pairs of addresses and data that need to be written to the STA013.  This data is stored in an array called STA013_UpdateData.  These address/data pairs are loaded into the address and data variables, and sta013_write() is called:

 

void sta013_write(void) begin

   // Start Condition

   sta013_I2C_start();

 

   // Send the device address with the R/W bit (i.e., the 8th bit) cleared

   I2C_byte = 0x86;

   sta013_I2C_write();

   if (errorFlag != 0) begin

      printf("Write Error: Error writing device address (W) to STA013.\r\n");

      return;

   end

  

   // Send the write address

   I2C_byte = address;

   sta013_I2C_write();

   if (errorFlag != 0) begin

      printf("Write Error: Error writing write address to STA013.\r\n");

      return;

   end

  

   // Send the data

   I2C_byte = data;

   sta013_I2C_write();