Having successfully found the root directory on our sd card, we now need to start actually reading the data back from it. This is the last little link in a chain of
- initialise the sd card
- find the lba (logical block address) by reading sector zero
- read the mbr (master boot record)
- calculate where the root directory starts
- find the file we’re interested in
- read the file
- The first eight bytes are the file name – FAT16/MSDOS only supported up to eight characters in a filename.
- The next three bytes are the file extension
- Then there’s an attributes byte
- Followed by time stamps for the file creation/modified date.
- The next two bytes are the starting cluster for the data (note, cluster not sector)
- The last four bytes are the total file length (in bytes)
- Every root directory entry uses exactly 32 bytes
If we know which sector the actual data begins from, and which cluster to begin reading from, we can calculate at which sector the cluster begins.
// a cluster is a complicated thing:
// first there’s an ofset to the master boot record
// then some reserved sectors
// then a fat table (maybe two)
// then the root directory (fixed length)
// THEN there’s the first data block,
// which is where we start reading our clusters from
// BUT there’s a catch – clusters start from zero not 2
// so whichever cluster number we’ve been given, subtract 2
Convert the cluster number (in this case, cluster 0x0002) into a sector (in this case, because clusters start from 2 – it’s an anomaly of the FAT16 format – our first cluster is also the first sector where the data starts. We’ve already calculated this value)
“The actual data starts at sector 615 + 32 = 647”
If we jump to sector 647 and start reading back the data, we find that there is, indeed, data in there!
But with a file that’s 613,955 bytes long, it’s not all going to fit into a single cluster (one cluster is 32 sectors and each sector is 512 bytes, so that’s only 32×512 = 16,384 bytes – 16kb)
So where’s the rest of the data?
That’s where the FAT table comes in!
Firstly, take the cluster number and double it. That’s the same as bit-shifting the entire value one place to the left. In our example, our starting cluster was 0x02 so we double this and end up with 0x04
This tells us where in our FAT table to find the next (two-byte) cluster number.
Since the FAT tables themselves are written across a number of sectors, we need to convert this cluster_doubled value into a sector and byte offset, to read the actual “next cluster value” back.
Divide the cluster_doubled value by 512 to get the sector number
The remainder is the byte offset.
In our example, this gives us sectors zero, byte offset 4
So we want the first sector (sector zero) in our FAT table, fourth and fifth byte
Since the FAT table begins at sector 143, we add zero to this, open the sector (using our earlier functions) and read back all 512 bytes. When we get byte four, this makes up the least significant byte of a two-byte (16-bit) value. Byte five is the most significant byte.
In fact, when we open our sd card, read back bytes four and five from sector 143, we get
Byte 4 = 0x03
Byte 5 = 0x00
This tells us that the file continues at cluster 0x0003.
Using the same technique, we open and read the data from cluster 0x0003 (sector 647 + (3-2)*sectorsPerCluster = 647 + 32 = 679)
We continue reading data from the sector(s) and calculating the next cluster where the file continues until we’ve either read back the entire file (total bytes read > file size in bytes) or the FAT table returns 0xFF as the next cluster (this is a special response to say “no more clusters for this file”).
This is summarised in the following function (remove comments around UART calls to see what the microcontroller is actually doing when calculating next FAT clusters).
// close the currently open sector
nextFatClusterDoubled=nextFatCluster<<1; // this is the same as multiplying by two!
//UARTPrint(“next FAT cluster “);
// if we’re at the end of the block, send the end of block marker
//UARTPrintLn(“no futher fat clusters to follow”);
// open the next sector
//UARTPrint(“file continues at sector “);