AddThis Social Bookmark Button

09 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
}