EE 281 Final Project
PORTABLE MP3 PLAYER
Building an MP3 player was a nice experience. It required a bit of research, fair amount of programming, and huge amount of debugging. But after I began hearing sounds coming from the headphones, it became much more fun.
I programmed the AVR chip using assembly language. The advantages were reduced programming space, which can be a problem using C, and better control on timing. As this project did not have too many computations, using assembly was not that hard.
The main parts of the circuit are:
Atmel AtMega163 processor
32MB CompactFlash card
STA013 MP3 decoder
CS4334 digital-to-analog converter
HD44780 40x2 LCD
During the development I used Atmel's STK500 board which made finishing the project much easier. I programmed and tested the player on STK500 and after I made sure that every part works correctly, I transferred the Mega163 processor to my own circuit board
Programming interface using PonyProg programmer software.
Plays MP3 files of up to 192 kbps. (I actually haven't checked for higher rates yet. It may as well support higher rates)
Reads Tag information from MP3 files and displays artist name and title on LCD.
Play, Stop, 16x Fast Forward, 16x Rewind, Track Skip to next and previous song.
When a song finishes, the LCD display updates to the next song information
UART interface for further development and debugging
Here is the top level schematic.
And here is the pinout table for the Mega163.
Check out the AVR basic system schematic.
Here is the mp3.asm source code, Mega163 definition file m163def.inc
The method I'm using to store songs into the CF card is pretty easy: through a CF card reader. I do not write any data to the card using the CPU. I make sure that the card is formatted to FAT-16 file system. After, I store the songs, I defragment the drive so that all song are stored linearly. The reason is that after I find out the start of the file, I read the sectors all sequentially until the end of the song. The details are in this link.
The CF cards can be operated in the True IDE mode, which is the interface used for harddisks. I chose this mode, because I later can add an harddisk to the player and make it much useful. The differences between using a harddisk and CF card are:
Harddisks have a 16-bit data bus while the CF card in this project operates in 8-bit mode (16-bit access is also possible with CF, but it would be harder to assign the CPU pins)
Memory access times
So, I will have to rearrange the processor's pin-out as well as add more addressing registers to be able to address a GB harddisk.
Here is the CF card pin-out. -CS1 (pin 32) can be tied to VCC, so that PortC pin-6 can be used some place else.
In True-IDE mode all read/write operations are performed over a set of registers. There is a setup time for the CF card at power-up. So, at reset, I read the status register to check whether the card is ready or not. The 'chbsy' function performs this. To put the card in 8-bit mode, the Feature Register is loaded with $01 and a set feature command is sent by writing into the command register.
All memory read operations are done by reading one sector at a time (Sector Count register = 1). Before reading a sector, Sector No, Cylinder Low, Cylinder High and Head registers are written to specify the sector to read. Then a Read Sector command is written to the command register. Then the CF is polled until it is ready. After that the Data register is read for 512 times. Clearly, before starting reading any data from the card, it is very useful to take a look at the Identify Drive information to get an idea of numbers like bytes per sector, or sectors per track etc.
One thing that stopped me for a long time was the late learned fact that tracks (32 sequential sectors for the card I used) are written into the next head each time. So, for example, the 32 sectors are written into Head-0 and than the next track is written into Head -1, and so on. After the last head is written the cylinder address is incremented by one. What I did was incrementing cylinder address at every 32 sectors and consequentially reading the drive by skipping (#_of_heads-1) tracks at a time.
The STA013 has a SPI bitstream input interface, I2C control interface and PCM output interface. The decoder output is connected to CS4334 DAC, which produces analog output which can directly be used by a speaker. Interfacing the decoder is very easy. You just connect the pins correctly and it works. You can order both parts from www.pjrc.com and also there are many useful information about how to use these chips. I borrowed the figures below from PJRC's website. I used the exact connections for both chips as shown below except for the inductor.
The CPU I/O is 5V and the decoder works at 3V. You can connect the decoder's 3V output to the CPU directly but the CPU output should be level shifted to 3V. In the figures below left hand side is the CPU and the right hand side is the decoder. Connections are:
PORTC1 - SDA
PORTC0 - SCL
PORTB5 - SDI
PORTB7 - SCKR
PORTD2 - DATA_REQ (Connect this pin directly without any level shifting)
RESET - RESET
SDA pin connection (bidirectional)
SCLK, SDI, SCKR and RESET pin connections (CPU to decoder)
The SPI interface is used to send data from the CPU to the decoder. The SPI clock runs at Fclk/16 = 500kHz. Refer to the assembly code for CPU's SPI interface initialization (SPCR and SPSR registers).
I2C interface is used for initializing and controlling the decoder. The decoder needs an initialization file called p02_0609.bin. The file consists of 2007 lines. Each line has a register number and its value. This file needs to be sent to the decoder just after reset before the decoder can start normal operation. I stored this file in the program memory using the .db directive. You can use the p02_0609.inc version of this file in your program. The first time I sent this file to the decoder, the decoder did not work. After couple hours of debugging, I found out that the 'DATA_REQ_ENABLE' command was causing problems. So, I sent this command before sending the file. The order of commands I send is:
Read IDENT register (0x01). The value read should be 0xAC
Write 0x04 to DATA_REQ_ENABLE register (0x18)
Read DATA_REQ_ENABLE register (0x18)
Send the 2007 words from the p02_0609.inc file. I waited for 30ms after sending the SOFT_RESET command (0x10) which is one of the commands in the file. This command is said to cause problems without this delay.
Write 0x01to RUN register (0x72)
Write 0x01to PLAY register (0x13)
Writing data is done in single byte write mode. The device address(1000011), register address and data is sent one after another. The I2C hardware in AtMega163 takes care of protocol timing, but you have to obey the rules and the state machine described in the AtMega163's user guide.
Reading data is more tricky. I first write the device address for a write operation and the register address, and then write the device address for read operation and read one byte. For specifics, you can refer to the I2C_write_byte and I2C_read_byte functions in the program.
Sending The Data
The decoder needs bursts of data of around 200 bytes in size. When it needs data it asserts the DATA_REQ pin. This signal is used as an external interrupt to the CPU and the CPU sends on byte after that. After the completion of the transfer the internal "SPI transfer completed" interrupt is executed. If the DATA_REQ pin is still asserted one more byte from the memory is sent and so on. A burst of transfer is started by either a positive edge on the DATA_REQ pin or by the start_spi_tx function for the first time. With an SPI clock frequency of 500 kHz (Fclk/16), there are 16x8 = 128 clock cycles for the main CPU execution until the next interrupt, which is quite enough.
The Y pointer is used for the location of the SRAM data to be sent and it is incremented after each transmission. The Z pointer holds the location in SRAM for the data that is being copied from the compact flash card. I used 512 bytes of the SRAM for storing CF data. This data portion is divided into two. When 256 bytes are being written to one half, data is sent from the other half so that no half is overwritten while it is being read. The algorithm is as below.
Make sector read request
If Y<256, don't copy from memory, just wait for Y>=256
If Y>=256, data is being sent from the second half. Copy the first half of the sector and ignore the second half.
Make a second sector read request for the same sector
If Y>=256, don't copy from memory, just wait for Y=0
If Y<256, data is being sent from the first half. Copy the second half of the sector and ignore the first half.
At initialization, a sector is read into the SRAM and then the SPI transmission is triggered by the start_spi_tx function.
I used a 40x2 HD44780 LCD for displaying the artist, the title and the track number. The LCD shares the 8-bit data bus with the compact flash card. It is initialized and controlled by writing to its data and command registers. Its contrast can be controlled by the PWM waveform obtained from the Mega163's Timer1.
I used Mega163's UART interface for debugging.