FAT32 Hard-drive-based MP3 Player with 512 bytes of SRAM
This project shows how an 8-bit Microcontroller can be interfaced to an IDE (ATA) Hard Drive with the minimum of external circuitry. A FAT32 File System with long filenames and multiple directories are supported with only 512 Bytes of SRAM. The user-selected file's data is streamed to an MP3 Decoder, which in turn streams the raw (decoded) data to a 16-bit DAC. The user-interface is RS232, but this can be adapted to any suitable protocol that interfaces with an MMI unit (text display and switches).
The project's aim was two-fold. First to increase my embedded knowledge and experience and secondly to prove that with careful embedded firmware design, a lot can be achieved with the minimum of resources (a requirement many embedded engineers are faced with every day).
This is the reason I challenged myself to design a circuit that uses the minimum of SRAM to interface to IDE and decode FAT32.
A more suitable microprocessor choice would have been the ATMEL ATMEGA161, which features 1024 Bytes of SRAM and 35 I/O pins (if external SRAM is not used). This would have enabled a whole IDE sector (512 bytes) to be buffered and then processed, which would have simplified the FAT32 decoding. It also features an internal brown-out reset circuit and the ability to upgrade it's firmware internally (i.e. Boot Loader scheme, etc.). Unfortunately, it will only start to be available in May 2000.
Here is the embedded firmware, targeted for the AT90S8515.
C Coding style and convention
I have tried to follow a convention and style that would (with a little bit of explanation :) make it easy for other developers to understand the firmware. A modular approach with a strain of Hungarian notation was favored.
An object-orientated style was followed where the firmware was separated into stand-alone modules, e.g. serial access is handled by the UART module. Each module has a well-defined interface or API, e.g. "uart.h" and the functionality is implemented in the ".c" file, e.g. "uart.c"
There are exceptions to the rule, e.g. although "printf" is actually a macro (as defined in "uart.h"), I felt that it is actually such a well-known name that it should supercede the convention.
ATA (AT Attachment) and IDE (Integrated Drive Electronics) are both terms for the same thing. The IDE interface has a 16-bit data bus, of which 8-bit transfers (D0...D7) are used to read and write to the controller's registers. I will concentrate on PIO (programmed Input/Output) mode 0 (the slowest transfer timing).
Convention: A dash character (-) at the end of a signal name indicates it is a n active low signal.
The two chip-select lines (CS0- and CS1-) are used to select one of the two register banks. If CS0- and CS1- is high (+5V, i.e. not selected) then a read or write cycle will not access the device. The three address lines (A0, A1 and A2) is used to specify the register in the selected bank.
CSEL is used by the IDE device to decide whether it is device 0 or 1. All devices have a pull-up resistor on the CSEL pin and the CSEL line is grounded at the host. The IDE cable's middle connecter is not connected to the CSEL line, thus the middle device will detect that it is device 1. The IDE cable's end connecters ARE connected to the CSEL line, thus the device at the end of the cable will detect that it is device 0.
DIOR- is used for a register read cycle and DIOW- is used for a register write cycle.
In words, the µC reads from a 16/8-bit data register by setting up a valid address (with CS0-, CS1- and DA0:2), waiting at least 70ns before taking DIOR- low, and then waiting at least 115ns, before reading the valid data from the IDE device. The µC then takes the DIOR- pin high and waits at least 30ns for the data bus to go back into it's Hi-Z state.
In words, the µC writes to a 16/8-bit data register by setting up a valid address (with CS0-, CS1- and DA0:2), waiting at least 70ns before taking DIOW- low, and then writing the 16/8-bit data on the data bus. It then waits at least 60ns before taking the DIOR- pin high and waits at least 30ns for the IDE device to read the data. The µC then puts the data bus back into it's Hi-Z state.
The I²C bus was developed by Philips, who also owns the copyright to this communication bus. I have included the level-shifters as recommended by Philips, so that the µC is able to interface with 3.3V and 5V I²C devices. I have used the I²C software module of Peter Fleury (firstname.lastname@example.org) and all credit should go to him.
(Copied schematic of Level-shifters as recommended by Philips Royal Electronics)
This bus is used to communicate with the STA013 decoder. After the FAT32 file system is initialized, the µC searches for a 8.3 filename called "STA013.BIN" in the root directory (in Windows terms "C:\STA013.BIN") and proceeds to stream the data to the STA013. The file consists of I²C address and Data pairs and is terminated with a 0xFF address and 0xFF data pair.
Here is a brief explanation of the content of the "STA013.BIN" file:
58 1 Start update of firmware
16 1 SOFT_RESET Generate soft reset of decoder.
84 3 PCM DIVIDER Set the frequency ratio between OCLK and SCKT to 256.
85 0 PCMCONF Set PCM order to MSB first, Set LRCKT polarity to high=left and low=right, Data are sent on the falling edge of SCKT, 16 bit mode
86 0 PCMCROSS Left channel is mapped on the left output. Right channel is mapped on the Right output.
The next few lines are for a 10MHz crystal and PLL set to 256 times oversampling clock:
6 18 PLLCTL [20:16] (MF[4:0]=M)
7 0 PLLCTL [15:12] (IDF[3:0]=N)
100 42 PLLFRAC_L
101 169 PLLFRAC_H
80 16 MFSDF_441
82 49 PLLFRAC_441_H
81 60 PLLFRAC_441_L
97 15 MFSDF (X)
5 161 PLLCTL [7:0]
13 4 SCLK_POL The data (SDI) are sent with the rising edge of SCKR and sampled on the falling edge.
24 4 DATA_REQ_ENABLE Configure pin 28 of STA013 as a data request signal.
12 1 REQ_POL The µC sends data to the STA013 when the DATA_REQ line is low.
114 1 RUN
19 1 PLAY
255 255 End marker
I will not attempt to divulge in an explanation of the FAT32 File System, but rather restrict myself to the tricks that I used to get away with the minimum of SRAM...
With only 512 bytes of SRAM to work with, not even a whole IDE sector can be buffered. To overcome this design restraint, I have used a STREAMING metaphor. In the firmware, the IDE module is called to start a read of one or more sectors. A function pointer is specified and this function is called with all the data as it is read. The function uses a counter to wait for the data that it is interested in and then proceeds to copy only that portion.
Note that data is streamed at two bytes at a time to minimize the number of times the function is called, i.e. 256 times per sector.
Definition of function pointer in "ide.h"
// Definition for a pointer to a function that will be called
// with the streaming data
typedef void (*tvIDE_OnData)(U8 U8Byte0, U8 U8Byte1);
Function declaration in "ide.h"
EXTERN PUBLIC void vIDE_ReadSectors (U32 U32LbaAddress,U8 U8SectorCount, tvIDE_OnData pfOnData);
Function that receives the streaming data in "fat.c"
PRIVATE void vFAT_OnBiosParameterBlockSectorData(U8 U8Byte0, U8 U8Byte1)
// Copy important part of sector containing BIOS Parameter Block
// Note: 16 bits are copied at a time
if ( (U8FatSectorIndex >= FAT_BPB_ENTRY_OFFSET)
&&(U8FatSectorIndex < FAT_BPB_ENTRY_OFFSET+sizeof(uFAT_Buffer.xBPB)/2) )
*((U8*)(&uFAT_Buffer.xBPB)+U8FAT_BufferIndex) = U8Byte0;
*((U8*)(&uFAT_Buffer.xBPB)+U8FAT_BufferIndex) = U8Byte1;
How the streaming operation is set up in "fat.c"
// Read first sector of Partition
U8FatSectorIndex = 0;
U8FAT_BufferIndex = 0;
Furthermore, I also used a Cluster-chaining metaphor. In simple terms, I first scan the FAT entries of a file's clusters to see if a chain of sequential numbers exist. For example, if a file starts at cluster 0x01234560 ends at cluster 0x01234567and continues from cluster 0x02000000, the µC can stream 8 clusters (starting at cluster 0x01234560) to the MP3 decoder. After 8 clusters, the µC goes back to the FAT table and scans the next chain.
This is a type of FAT-look-ahead scheme and works very well if the file is not too broken up into small cluster chains.
Automotive Power Supply
A 3.3V, 5V and 12V supply is required. There is a myriad of switch-mode supply circuits available, but in the end, easy-access was my prime concern. That is why I opted not to use a fly-back configuration for the 12V supply, because custom transformers are expensive and hard to get hold off. The datasheets have excellent example circuits:
Note that in a normal automotive environment, the supply may vary from 8V to 16V. A double-battery (24V+) situation may also occur if the vehicle is jump-started with two batteries in series. Furthermore a load dump may occur, and the power supply has to protect the rest of the circuitry against an input voltage of 80V for 300 milliseconds. The connected supply may also be reversed.
All of these requirements can be met if a reverse-polarity diode and a TRANZORB is added to the design and the switch mode supplies can tolerate an input of 30V.
Although this is not a complete design of a hard-drive based MP3 Player suitable for in-vehicle use, I hope that I have shared with you some of the techniques, tricks and methodology that an embedded engineer can use to overcome the inherent limitations of an embedded system.
Pieter J. Conradie