FTPmicro Tutorial - FAT16.c
To store files in the SD card, a FAT Filesystem is used, so they can be written directly from a PC.
The FAT16 driver is implemented by the file fat16.c, using the same interface of the MPFS filesystem (read only for now). This means that the FAT16 methods have the same name, parameters and behaviour of the ones in MPFS.c.
To understand how the code works, we are going to see how the FAT16 is made up.
First of all, the memory is divided in sectors of 512 bytes; n sectors form a cluster, where n is computed and written in a certain position of the memory when this last is being formatted. The cluster is the base unit of FAT16; as an example, a file occupies an entire cluster even though it is empty.
The first absolute sector is the MBR (Master Boot Record), and it principally contains executable code to boot the OS. But we are interested in a little portion of that sector, that is the Partition Table, where there are informations of the partitions. Particurarly, there we found the starting sector of the filesystem.
| Offset | Description | Size |
| 0x000 | Executable Code | 446 Bytes |
| 0x1BE | First Partition Entry | 16 Bytes |
| 0x1CE | Second Partition Entry | 16 Bytes |
| 0x1DE | Third Partition Entry | 16 Bytes |
| 0x1EE | Fourth Partition Entry | 16 Bytes |
| 0x1FE | Boot sector signature | 2 Bytes |
Each partition entry contains:
| Offset | Description | Size |
| 0x00 | Partition state (active/inactive) | 1 Byte |
| 0x01 | Starting Head | 1 Byte |
| 0x02 | Starting Cylinder/Sector | 2 Bytes |
| 0x04 | Partition Type | 1 Byte |
| 0x05 | Ending Head | 1 Byte |
| 0x06 | Ending Cylinder/Sector | 2 Bytes |
| 0x08 | Starting address in sectors | 4 Bytes |
| 0x0C | Size of the partition in sectors | 4 Bytes |
So, the item we are interested in is at the address 0x1C6 of the MBR.
At this point, we are able to read the first sector of the FAT16 (the Boot Record), where there are many useful informations that we have to save somewhere.
| Offset | Description | Size |
| 0x000 | Jump Code & NOP | 3 Bytes |
| 0x003 | OEM Name | 8 Bytes |
| 0x00B | Bytes Per Sector | 2 Bytes |
| 0x00D | Sectors Per Cluster | 1 Byte |
| 0x00E | Reserved Sectors | 2 Bytes |
| 0x010 | Number of FATs | 1 Byte |
| 0x011 | Max entries in Root Directory | 2 Bytes |
| 0x013 | Partition size in sectors (< 32MB) | 2 Bytes |
| 0x015 | Media Descriptor (HardDisk, ...) | 1 Byte |
| 0x016 | Sectors Per FAT | 2 Bytes |
| 0x018 | Sectors Per Track | 2 Bytes |
| 0x01A | Number of Heads | 2 Bytes |
| 0x01C | Number of Hidden Sectors | 4 Bytes |
| 0x020 | Partition Size in Sectors | 4 Bytes |
| ... | ... | ... |
| 0x1FE | Boot Sector Signature: always 0x55AA | 2 Bytes |
The next drawing shows the organization of the memory (not in scale):

We already know the MBR and the FBR, while the other sections are:
- FAT Area (File Allocation Table): in a FAT file system, files are not necessarily contiguous, but the can be fragmented all over the memory;
for that reason, there is a FAT Area where there are cluster chains that are used to reassemble a file from its clusters. - FAT2: it is a security copy of the FAT Area.
- Root Directory: it contains the list of the files stored in the root directory.
- Data Area: the files are stored here (divided in clusters).
In the MPFS filesystem, all files are contiguous, so for simplicity in the Microchip stack, each file is identified by his linear address in the memory (that address is called handle). But in the FAT16 filesystem, this information is not enough; for that reason, there is a table (the handleTable) that contains some informations of each open file. The MAX_OPENED_FILES define is equal to the max number of entries of that table.
typedef struct {
BYTE attrib;
WORD cluster; // cluster in DataArea
BYTE sector; // sector in cluster
WORD byte; // byte in sector;
DWORD filesize;
} handleEntry;
handleEntry handleTable[MAX_OPENED_FILES];Now we can see how the FAT driver initialization is done:
/* partition data */
DWORD partitionStart;
BYTE clusterSize; // sectors per cluster
DWORD fatStart;
DWORD rootStart;
DWORD dataArea;
BYTE rootSize;
BOOL FATInit() {
char* buf_pt = &sd_buffer[0];
char i;
sdPresent = SDInit();
if (!sdPresent)
return FALSE;
SDReadSector(0);
partitionStart = *(DWORD*)(buf_pt+0x1C6);
SDReadSector(partitionStart);
clusterSize = buf_pt[0x0D];
fatStart = partitionStart + *(WORD*)(buf_pt+0x0E);
rootStart = fatStart + (*(WORD*)(buf_pt+0x16)) * buf_pt[0x10];
rootSize = *(WORD*)(buf_pt+0x11) * sizeof(dirEntry) / SECTOR_SIZE;
dataArea = rootStart + rootSize;
for (i = 0; i < MAX_OPENED_FILES; i++)
handleTable[i].attrib = FREE_HANDLE;
mpfsOpenCount = 0;
return TRUE;
}In this method the SD card and the Handle table are initialized and some addresses are computed:
| Variable | Description | Value |
| clusterSize | Number of sectors in a cluster | |
| fatStart | Address of the FAT Area | start of partition + reserved sectors |
| rootStart | Address of the Root Directory | Address of the FAT + (nuber of FAT copies * FAT size) |
| rootSize | Size of the Root Directory | Number of entries * size of an entry |
| dataArea | Address of the Data Area | Address of the Root Dir + Size of the Root Dir |
All these addresses and sizes are in sectors of 512 bytes.
A Directory is composed by entries of fixed size (32 bytes),
each of which contains the following fields (only for ShortFileName entries):
| Offset | Description | Size |
| 0x00 | Name | 8 Bytes |
| 0x08 | Extension | 3 Bytes |
| 0x0B | Attributes | 1 Byte |
| 0x0C | Reserved | 1 Byte |
| 0x0D | Creation Date & Time | 7 Bytes |
| 0x14 | Extended Attributes | 2 Bytes |
| 0x16 | Time | 2 Bytes |
| 0x18 | Date | 2 Bytes |
| 0x20 | First cluster of the File | 2 Bytes |
| 0x1C | Size of the file in bytes | 4 Byte |
The items Name and Extension are always written in upper case, and empy characters are filled with spaces (0x20 in hex). For example, Goofy.txt will be
GOOFY TXT 47 4F 4F 46 59 20 20 20 54 58 54
The MPFSOpen method searches for a file in the memory and if it find that file, it will create and return an handle:
MPFS MPFSOpen(BYTE* name){
BYTE scount, ecount;
dirEntry* entry;
char* buf_pt = &sd_buffer[0];
char i;
char name_ext[11];
if (!sdPresent) return MPFS_INVALID;
for (i=0; i<11; i++)
name_ext[i] = 0x20;
for (i=0; i<8 && *name!='.' && *name; i++)
name_ext[i] = toupper(*name++);
if (*name == '.') {
name++;
for (i=8; i<11 && *name; i++)
name_ext[i] = toupper(*name++);
}
scount = 0;
while (scount < rootSize) {
SDReadSector(rootStart + scount);
ecount = 16;
entry = (dirEntry*)buf_pt;
while (ecount--) {
if (entry->attrib == 0x0F) goto next;
// Long File Name
if (entry->name_ext[0] == 0x00 || entry->name_ext[0] == 0xE5) goto next;
// Empty Entry
if (memcmp((void*)name_ext, (void*)entry->name_ext, 11) == 0) { // found!
for (i=0; i < MAX_OPENED_FILES && handleTable[i].attrib!=FREE_HANDLE; i++);
if (i == MAX_OPENED_FILES) return MPFS_NOT_AVAILABLE;
handleTable[i].attrib = USED_HANDLE;
handleTable[i].cluster = entry->cluster;
handleTable[i].filesize = entry->size;
handleTable[i].sector = 0;
handleTable[i].byte = 0;
mpfsOpenCount++;
return i;
}
next:
entry++;
}
scount++;
}
return MPFS_INVALID;
}In the first section, the filename is written in an array in the 8+3 format. In this way, we can compare it with the one stored in a directory entry. At this point, all the entries are read, until we found the desired file.
When the file is found, the code will create a new handle and saves some informations:
- filesize: file size in bytes.
- cluster: first cluster of the file in Data Area.
- sector: initialized to 0; it indicates the current sector in the current cluster.
- byte: similar to the previous; it contains the current byte index in the current sector.
With MPFSGetBegin the driver makes the card ready to read the file opened using MPFSOpen, this is done loading
in the buffer the sector pointed by the variables in the HandleTable:
BOOL MPFSGetBegin(MPFS handle){
if (handle >= MAX_OPENED_FILES || handleTable[handle].attrib != USED_HANDLE)
return FALSE; // invalid handle
if (handleTable[handle].filesize == 0)
return TRUE;
_currentHandle = handle;
SDReadSector(dataArea + (handleTable[handle].cluster-2)*clusterSize + handleTable[handle].sector);
return TRUE;
}In the FAT16 filesystem, two cluster are reserved, so the first cluster of the Data Area is the number 2.
The MPFSGet method reads and return a byte at a time. If this byte is already present in the SD buffer,
we can read it from the buffer, otherwise we have to calculate the address of the next sector to load.
If this sector is located in the same cluster of the previous, it is the following, else the FAT Area must be read to know the nect cluster of the file.
BYTE MPFSGet() {
char* buf_pt = &sd_buffer[0];
BYTE b = 0;
WORD tmp;
if (_currentHandle == MPFS_INVALID)
return 0;
if (handleTable[_currentHandle].byte >= SECTOR_SIZE) {
handleTable[_currentHandle].byte = 0;
handleTable[_currentHandle].sector++;
if (handleTable[_currentHandle].sector >= clusterSize) {
tmp = handleTable[_currentHandle].cluster;
SDReadSector(fatStart + tmp / (SECTOR_SIZE /2) ); // read FAT Area
handleTable[_currentHandle].cluster = *(WORD*)(buf_pt + (tmp % (SECTOR_SIZE/2)) * 2);
// next cluster in chain
handleTable[_currentHandle].sector = 0;
}
SDReadSector(dataArea + (handleTable[_currentHandle].cluster-2)*clusterSize + handleTable
[_currentHandle].sector);
}
b = buf_pt[handleTable[_currentHandle].byte++];
handleTable[_currentHandle].filesize--;
return b;
}In the FAT Area, each word of 16 bit answer to a cluster. As an example, if we are reading a file starting at the cluster 4 (the fifth), to know were the file continues, we only have to read the corresponding word in the FAT, that is the fifth one.
So, to do that, we have to know where to read the value of the next cluster in the chain; so, first we must calculate the sector:
current_cluster / (SECTOR_SIZE /2)
At this point, the word to read is at address (in bytes)
(current_cluster % (SECTOR_SIZE/2)) * 2
in that sector.
Other used methods:
void MPFSClose() {
mpfsOpenCount--; // decrementa il numero di file aperti
handleTable[_currentHandle].attrib = FREE_HANDLE; // libera l'handle
}
BOOL MPFSIsEOF(void) {
return (handleTable[_currentHandle].filesize == -1); // se la dimensione è -1, il file è finito
}
- Emanuele's blog
- 3962 reads





Post new comment