This project started life a long time ago, with the intention to build an iPod clone, back when personal MP3 players were an expensive luxury and long before you could buy them from China on ebay for less than a light bulb.
The plan for the MP3 player was to use a PIC microcontroller connected to a PATA hard disc drive (They were just called ATA back then, before SATA came along), the PIC would read MP3 files from a FAT32 partition on the drive and send them to an MP3 decoder chip through an audio DAC to earphones/speaker. A monochrome LCD with buttons would have provided the usual player controls and track selection, with a laptop hard drive giving plenty of song storage. I was going to mount all of this in a head unit in the dashboard of my car.
Unfortunately the project got shelved for about 10 years, until recently when I wanted my breadboard back. By now my car stereo has a USB socket to plug in mass storage and an SD card slot. So in order to bring this project to some sort of useful end, I finished off the library code I had written that provides access to files from a FAT32 filesystem on an ATA hard disc or CompactFlash with a PIC.
So the ATA+FAT32 library is detailed here. It is a bit special, in that it is strongly suited to low RAM systems, and I mean looooowwwww RAM. I’ve had the ATA access code running on a PIC16F877 which only has 368 bytes of RAM. To get the FAT32 access in, I did have to go to a PIC18F452, which has about 1600 bytes or so of data RAM. If 368 bytes of RAM sounds like a typo (It isn’t), bear in mind that PICs are based on a Harvard architecture, so the program memory, where the .text section of your program is stored, is counted seperately from the data RAM.
The astute reader will be wondering at this point how a 512 byte sector can be read from a hard disc into only 368 bytes of RAM. The answer is that it isn’t. The API for the library is written in such a way that no complete sector is ever read at once. The caller of the library is able to set the LBA address for a read, the bytes of the sector are then read and examined, or skipped, as is needed by the calling code. Using this technique, even the FAT32 code, which provides read only access to files and directory entries, never needs to read a full sector.
Get the source code: from github.
The rest of this page is extra detail, which may come in useful if you happen to decide to use the library in your own project.
The circuit to connect the PIC micro to the ATA hard disc signals is based on a design used by Paul Stoffregen in his Using an IDE Hard Drive with a 8051 Board and 82C55 Chip project.
I captured the schematic in Eagle format, available here and shown below.
As can be seen in the circuit, an 8255A port expander is used to get the multitude of ATA signals down to a more sensible amount, in order to leave enough I/O on the micro to hopefully perform other functions. The 74HC04 inverters used on some of the ATA control lines between the 8255 and the drive are very much necessary. This is because when any of the 8255 port directions are changed via its one internal control register, all bits on the 3 ports are reset to zero. Some of the ATA signals are active low, so without the inversion, setting them low at the drive would cause an inadvertent read or write to the currently addressed ATA register in the drive, or reset the drive entirely.
Because I was holding off laying out a board until the other MP3 player components were added, the circuit was built up using jumper wires on a breadboard.
The library is written in C and can be built with either Microchips current XC8 compiler, or the now obsolete and quite old Hi-Tech PICC or PICC18 (I was using version 8, which is even older). Note that the code needs no changes between these two compiler suites apart from including a different header file. This is because Microchip bought Hi-Tech and if you look deeper into the header files, PICC is referenced a lot and the compilers are pretty much the same.
The ATA routines in the library are written for the 8255 circuit shown above, but there is some flexibility in which pins and ports are used on the PIC for the various signals. Simply change the #defines in the clearly commented sections within picata.c to account for any differences in pin assignment.
The ATA API described in picata.h provides the expected init routine, a get info function which reads off only the model/serial and drive geometry values, more as a check that the drive access is working than for any useful function, hence that can be disabled with a #define. As mentioned above, data access is performed by seperate API functions, one to set the LBA address of the required sector, another to read the next byte in the sector straight from the drive, and another to skip a given number of bytes in the sector.
Beneath the API functions, the next layer down deals with accessing ATA registers (See the ATA spec for details), and the bottom layer of functions deals with twiddling with the 8255 in order to waggle the ATA control signals on port C, whilst outputting register data or reading it back from ports A and B, being sure to reverse the port directions in the 8255 mode register at the appropriate points.
Initially, I had some 8255 port read/write routines which would automatically switch port directions in the control word as they went along. A single access to an ATA register requires numerous port accesses through the 8255, to toggle control and address signals to the drive on port C, read the register data through port A etc. Eventually I realised it was the 8255 mode register changes clearing all outputs, as explained above, which were negating the ATA chip select halfway through the ATA register access cycle.
Whilst trying to get the ATA register accesses working correctly, I wired up a logic analyser I had to hand, to see what the signals were doing.
The code was then re-written with the explicitly layered approach, the ATA register access routines now call down a layer to set the 8255 port directions at the beginning of the ATA access.
Once the waveforms for accesses of the low byte of the ATA data bus were looking good, I found that all the high bytes of the 16 bit data word accessed during sector data reads had consistent bit errors. Some of the logic analyser connections were moved to those data[8:15] signals, which then revealed a lot of ringing or oscillation of the logic level on one bit or another.
This was solved in a highly technical fashion by having the demo code discussed below looping repeatedly on the PIC, then while that was gunning away, reaching over and poking the wires on the breadboard with my finger. With enough prodding suddenly good data would get read out. Must have been a poor ground or some crosstalk, after that every so often the IDE cable would need a good nudge, having discovered the sweet spot.
Whilst trying to diagnose the signal noise issue, I suspected there may be a wiring fault, and so a walking ones test for all pins on the 8255 ports was added. The logic analyser probes were then connected to the same rails as the ATA hard drive IDC header on the breadboard. It was only by moving the probe wires to the rest of the ATA data pins to see the test working, that the noise was caught.
There is some demonstration code in main.c, which does the following:
*Perform the wiring test if ATA_USE_WIRINGTEST is defined *Print contents of drive sector 0 *Print contents of FAT32 BPB sector *Read drive info if ATA_USE_ID is defined *Print all drive info bytes if HD_INFO_DBG is defined *Print a summary of drive model/serial/revision strings, drive geometry (Meaningless nowadays) *Initialise FAT32 if FAT32_ENABLED is defined *Parse the entries of the root directory, printing the files and directories found *Open a file named HELLO.TXT if present, and print the contents
For more detail: PIC microcontroller ATA library