Many FAT file system implementations are, by necessity, too large to take a place in micro-controller applications. Long file names, fragmented files, the FAT tables - no wonder a 'proper' solution weighs in at many K of code and buffers. One solution is to carry out raw access to the card, but this then involves compromises when getting the data off the card onto the desktop.
My solution is to reduce weight of code by applying stringent use restrictions:
Only 16 files (potentially), named in 8.3 format, pre-allocated and contiguous.
This enables us to locate the start sector of the file and read/write to it as we wish. The files can be freely swapped between PC and micro controller application using standard operating system commands. To reach this state of nirvana all that needs to be done is format the card before copying the pre-allocated files to it.
The only external requirement for servicing the code is a function that reads sectors, using logical block addressing, from your chosen FAT device. If you look in the ACE post previously the code zip there includes a complete MMC solution.
I found a schematic on the arduino site that is almost identical to my own and comes very highly recommended. If you haven't got a card reader that you can cannibalise then edge connector works well too. It's a common way of doing it and there are many good examples around - you can find one here.
Here's the header which shows just how micro micro can be!
/ Developed by Charlie Robson in 2008.
/ This is the bare minimum* functionality required to read and write data
/ to a FAT formatted device. In this case, an MMC card.
/ Rather than build a fully-featured FAT implimentation this will allow
/ the user to locate sectors associated with a file on the device, and
/ read and write to them in a brute force manner. No error checking, no
/ long filenames, not even any support for fragmented files :) This is
/ raw access, we only deal with existing files.
/ What you do get is a very lightweight module for extremely limited
/ environments. Arduino has 2k of SRAM. Holding the sector of FAT data
/ needed to traverse a fragged file would instantly gobble up 1/4
/ of that. Enough said. Another benefit to not using the FAT is that
/ there's no need to worry about whether a device is FAT12 or 16.
/ To use:
/ Format a card.
/ Make a file big enough to hold all the data you expect to write.
/ Copy to the card.
/ With the MMC module started up, call initialise. This will cache some
/ relevant information about the card such as the sector location of the
/ root directory and data area.
/ Call locateFileStart, passing the name of the file which is providing
/ the backing store for your transfers, and assuming the file is found
/ in the directory you'll receive its start sector and file size.
/ Now feel free to read and write data to and from the sectors allocated
/ to the file. Code carefully though - stray writes can twat your card!
/ Enjoy :)
/ *Massive understatement!
// some data structures we'll be encountering on our travels.
// Cache some relevant info about the card.
bool initialise(byte* buffer);
// Get start sector, file size for given filename. Returns false if the file
// is not found in the directory.
bool locateFileStart(const char* filename, unsigned long& sector, unsigned long& size);
#endif // __MICROFAT_H__
Now for the meat:
// Data which remains constant over one session. Re-initialise if
// the card is changed.
uint32_t rootDirSect, cluster2;
bool microfat::initialise(byte* buffer)
vars.buffer = buffer;
if (RES_OK != mmc::readSectors(vars.buffer, 0, 1))
partition_record* p = (partition_record*)&vars.buffer[0x1be];
long bootSector = p->lbaAddrOfFirstSector;
if (RES_OK != mmc::readSectors(vars.buffer, bootSector, 1))
boot_sector* b = (boot_sector*)vars.buffer;
if (BYTESPERSECTOR != b->bytesPerSector)
vars.sectorsPerCluster = b->sectorsPerCluster;
vars.rootDirSect = bootSector + b->reservedSectors + (b->fatCopies * b->sectorsPerFAT);
long dirBytes = b->rootDirectoryEntries * 32;
long dirSects = dirBytes / BYTESPERSECTOR;
if (dirBytes % BYTESPERSECTOR != 0)
vars.cluster2 = vars.rootDirSect + dirSects;
// Get start sector for given filename.
// Returns false if the specified file wasn't found in the directory.
// Short filenames only. Be aware that the directory fills up with 'deleted'
// filenames. Rather than deleting too many files try a quick reformat.
bool microfat::locateFileStart(const char* filename, uint32_t& sector, uint32_t& size)
if (RES_OK == mmc::readSectors(vars.buffer, vars.rootDirSect, 1))
// The filenames are stored in  format. No dot, all upper case.
// Names shorter than 8 chars are padded with spaces.
// Cook the supplied name. fred.txt -> FRED----TXT
for(int i = 0; i < 12; ++i)
cookedName[i] = 0x20;
for (int i = 0, j = 0; i < 12 && filename[i]; ++i)
if (filename[i] != '.')
// Force char to uppercase. Cheesy I know :)
cookedName[j] = filename[i] >= 96 ? filename[i] - 32 : filename[i];
// Continue cooking chars at the extension position
j = 8;
for (int i = 0; i < BYTESPERSECTOR; i += 32)
directory_entry* de = (directory_entry*)&vars.buffer[i];
// Don't match with deleted or system/volname/subdir/hidden files
if (de->filespec != 0xe5 && (de->attributes & 0x1e) == 0 && memcmp(cookedName, de->filespec, 11) == 0)
sector = vars.cluster2 + ((de->startCluster-2) * vars.sectorsPerCluster);
size = de->fileSize;
Which as you can see is smaller than the header - due in no small measure to the size of the FAT structure definitions.
As it stands this code could be improved by using non-static file info structures - only one is supported right now but the change would be trivial. Also allowing the filename search to traverse the entire root directory space would allow access to more files. However I don't really see the utility in ths -wherever I've used this code I've never needed more than one file :)
As usual any Qs are welcome.