Portable MP3 Player
Final circuit with pushbuttons (front right), MCU (front left),
Multimedia card (middle left) and decoder/DAC board (gold board in back)
Introduction
In the recent years, the MPEG Layer III (MP3) music compression format has become an extremely popular choice for digital audio compression. Its high compression ratio, and near CD quality sound make it a logical choice for storing and distributing music - especially over the internet, where space and bandwidth are important considerations. As a result of the MP3 popularity, a variety of portable MP3 players have entered into the market in an attempt to capitalize on the demand for portable, high quality music. In 1999, over 1.5 million portable MP3 players were sold worldwide and research projects the sale of over 30 million devices per year by 2005. We decided to design and implement a portable MP3 player similar to products currently available (e.g. Diamond Rio, Creative Labs Nomad, etc.).
Our goal was to design a scaled down version of an otherwise marketable product with minimal cost. Our intent was to ensure that our end result would be expandable to include all of the functionality of the most popular players on the market.
High Level Design
We used the ATMEL AT90S8535 microcontroller to control the components and data flow.
Our design supports standard MP3 file formats with a bitrate of 128Kbps. The decoder chip that we used supports rates from 8Kbps-320Kbps, but we decided it would be simpler to support one bitrate, and then expand the design to support other rates. Our decoder required the incoming bitstream to be equivalent to the decoded bitstream (128Kbps). Because our memory card has a memory latency of 1.5ms and the decoder has a 256-byte buffer - we decided to send data in 256 byte blocks. At 128Kbps, 256 bytes will expire in 16ms, requiring a new data block. A major concern of this technique was buffer underrun/overrun. We prevented this by initially filling the buffer completely before starting to decode. Once running, a block is requested every 16ms, however the first request is sent approximately 8.5 ms after decode start to ensure that the buffer never contains more than 250 bytes and leaves us with just over 7ms to request and receive a block of data. These calculations are considered trivial and left as an exercise to the reader! :)
The pushbuttons are used to control the operation of the player. The four functions implemented with four buttons are play, stop, next track, previous track.
Program/Hardware Design
The parts used in this project were:
- ATMEL AT90S8535
- ST MicroElectronics STA013 (MP3 decoder)
- Crystal/Cirrus Logic CS4331 (18 bit serial DAC)
- SanDisk Multimedia Card (16MB serial flash card)
- 10 MHz crystal for decoder
- 4 MHz oscillator for MCU
- Function generator (replaceable by 11.2896MHz oscillator)
- resistors, capacitors, bipolar transistors, diodes, and connectors
Level shifting circuits
All the components used in the design are 3.3V except the ATMEL MCU that is 5V. As a result, every line that interfaced between the MCU and the other components needed to be level shifted. Lines that were unidirectional used the following circuits:
One line was bi-directional and used both the step-up and step-down circuits but were connected to two port pins on the MCU, one for input and one for output, to avoid incorrect level detection.
Interfaces
The Multimedia Card (MMC) uses an SPI interface to control the card and transfer data. It is a four-line interface: SS, SCLK, MOSI, and MISO. To send a command to the card, the user must lower SS, put data on the MOSI line, and toggle SCLK (data is sampled on the rising edge for the MMC). After receiving a command, the MMC will respond with a response byte, followed by any data that was requested. A specific initialization sequence must be followed on power-up to the card in order to put the MMC in SPI mode. On a data request, the MMC will send a 0x00 response byte, followed some time later by a data token (0xfe) and a block of data. In order to read a byte from the slave on the MISO line, the master must send a byte of 0xff on the MOSI line. The SPI interface is implemented using MCU hardware, but the SS line is toggled manually with a separate command. The MCU operates as the master, and the MMC is the slave.
***SPI Note*** The ATMEL STK200 development board has a resistor pack between the PORTB header (SPI pins) and the chip for ISP programming. As a result communication between the MCU and another device (especially another MCU as a slave) does not work correctly. The programming resistors disrupt the voltage enough to sabotage communication. The simple work-around that we came up with is connecting to the LED jumpers instead of the header. This effectively connects directly to the port pins on the chip.
The MP3 decoder chip has three interfaces: a serial interface for incoming data , an I2C interface for control, and a serial PCM interface for the decoded bitstream.
The serial input has three lines, SCLK, SDATA, and BIT_ENABLE. All data on the SDATA line is ignored while BIT_ENABLE is low. We connected the data and clock lines directly to the MMC SCLK and MISO lines and controlled the BIT_ENABLE line with the MCU. The bit enable line is raised when we receive the data token from the MMC, and is lowered after 256 bytes have exchanged.
The I2C interface is a standard two-line interface, SDA, and SCL. This is used to initialize and control the MP3 decoder. The MP3 decoder has a set of registers that must be written on start-up using I2C. The ATMEL web site has an implementation of the I2C interface which we were able to use with slight modification.
The PCM interface is used to send decoded data to the DAC for conversion to an audio signal. There is an over sampling clock which is supplied by an external oscillator (11.2896MHz - we did not realize this clock needed to be supplied, and ended up using a function generator because we did not have time to order another oscillator). There is an left-right channel clock that is operating at 44.1Khz (the sampling frequency of the MP3 files) that is obtained from the OCLK. There is also a serial data line and a serial clock line. The DAC used can operate in a variety of modes, configurable through the STA013.
Memory Format
Programming the MMC was done through a PC's parallel port. We wrote a programmer in C++ which initializes the MMC and programs it (Source code is included). The programmer is a DOS executable, and a file named tracks.lst must be included in the same directory along with all MP3 files that are to be written to the card. Tracks.lst contains the filenames of the MP3 files in the format:
track1.mp3
track2.mp3
.
.
.
lasttrack.mp3
There cannot be any extra characters at the end of a line (spaces, periods, etc.), filenames should be limited to 8 characters, and there is no bounds checking in the programmer. The user is responsible for ensuring the file size does not exceed the size of the MMC (we chose to do it this way so that larger memory cards could be programmed with the same programmer).
The programmer also builds and writes a table of contents for the MMC, which we designed to allow for simple addition of tracks without reprogramming the entire card (this feature was not included in the programmer). Address locations are 32 bits, so four bytes are allotted for each track in the TOC. Address location 0x0000 contains the number of tracks on the card. Address locations 0x0001, 0x0002, 0x0003, 0x0004 contain the address of track 1 with the MSB in 0x0001. The next track address is placed in the next 4 bytes. The 4 bytes following the address of the last track contains the address of the first non-valid memory location on the card. This allows us to stop decoding before reaching invalid data.
Address | Contents |
0x0000 | number of tracks |
0x0001 | MSB of track 1 address |
0x0002 | track 1 address |
0x0003 | track 1 address |
0x0004 | LSB of track 1 address |
. . . | . . . |
. . . | . . . |
. . . | . . . |
0x000? | MSB of invalid data addr |
+1 | invalid data address |
+1 | invalid data address |
+1 | LSB of invalid data addr |
On start-up of the MMC the TOC is read from the MMC and put into SRAM. This allows for quick and easy determination of the memory locations of any track. The formula for the location of the address of the next track is 4*current track + 1.
Program Structure
Setup the MCU:
Setup SPI
Setup I2C interface ports
Setup pushbutton ports
Setup timer1 for reset on compare match (do not enable timer yet) ~8.5ms
Setup UART (for testing and design only)
Initialize all variables
Enable interrupts
Initialize the MMC:
Power up sequence
Put in SPI mode
Set Read Block length to block size (256 bytes)
Read the TOC and put into SRAM
Load the address of track 1 into address registers
Load the end of card addresses into the endcard registers
Initialize the Decoder:
Initialize I2C
Write all registers
Send 1 Block to Decoder
Write the RUN register of the decoder to enable decoding
Write the PLAY register of the decoder to start decoding
Start timer1
State Machine:
MMC_read yet?
Send the next block - set the timer to interrupt on 16ms
Increment the address - check for end of card
Error checking for mp3 decoding ( there is a frame count register in the decoder that should be incrementing)
Check push buttons?
Alter play state, and address counters as appropriate - checking for end of card
Results of the Design
The player successfully played our test MP3s! We were able to add pushbutton functionality to control play, stop, next and prev. This allowed us to change tracks and stop at any time.
Sound quality was as good as could be expected from a protoboard and several feet of interconnecting wires. We did not achieve CD quality, but we were not expecting to get it. The decoder does drop an occasional MPEG frame, causing a crack or hiss. This was probably attributable to the inaccuracy of the function generator in supplying the oversampling clock. We did try several oscillator circuits to try to replace the function generator but could not get them to work at 11MHz.
We also were able to remove the ATMEL chip from the development board and use 4 AA cell batteries to draw power for the MCU, the decoder, and the MMC. The ATMEL chip uses all four batteries, while the other components are only using two cells to generate 3V. Moving the MCU to the protoboard allows the unit to become portable (with the exception of the function generator).
What would we do differently next time???
There are a couple of things we would do differently or add to the design if we had to do it again. The first thing would be to use an MCU that is capable of running at 3.3V. This would have eliminated the extraneous level shifting circuitry that took up so much space and time (not to mention Tylenol). We also believe that our sound quality would have been better if we were able to remove the extra wires and transistors.
Another issue in the design was the use of surface mount components - the STA013 was only available in surface mount, and the DAC recommended by STM was also surface mount. This was very time consuming, but really just an inconvenience and probably did not affect the result significantly.
Our implementation was not as portable as we had hoped. Given more time, or the opportunity to do this again, we would probably have used an oscillator instead of the function generator.
The last change we would have made to our design would be to support variable bitrates. We chose 128Kbps because we found it to be common and was easy to work with. In order to work at higher bitrates, we would have had to adjust the SPI interface to run at CLK/4 instead of CLK/16. We experimented with different SPI clock rates but could not effectively communicate with the MMC at the higher rate. This could have been a result of the level shifting circuitry adding delay, extra capacitance, or even as simple as a setting in the SPI register. Because we were only going to add variable bitrates as an add-on we did not explore the problem in depth.