Professional Documents
Culture Documents
In the last week’s part 1 of my FAT and SD tutorial, we got as far as reading the file entries in
root directory, and peeking into a file with hex editor. Now we’ll cover the file allocation table
itself to enable reading longer files, and adapt the code into a small footprint FAT16 library!
Copy HAMLET.TXT (193 Clusters 2-13 allocated for the file (196 608
082 bytes) to disk bytes)
But what if we now copy a second HAMLET2.TXT to the disk? It would be a waste of space to
just continue from cluster 17, leaving the free cluster 14 unused. Instead of just allocating
clusters 17-28 for HAMLET2.TXT, the operating system can actually reuse cluster 14,
allocating clusters 14 + 17-27 for HAMLET2.TXT.
Obviously, after N file operations, the files can be spread all over the clusters, and if we allow
appending to existing files, later parts of a file may even be stored on clusters that are
physically before the earlier parts. We obviously need a map of some kind to keep track of the
clusters occupied a single file. And that’s exactly what a file allocation table is – a map with
one slot for each of the data clusters, each slot telling where to go next when reading a file. In
FAT16, each slot takes 2 bytes (i.e. one word), and the first two slots (#0 and #1) are reserved
for disk type and FAT type data – in case of FAT16 on a SD card or hard drive, these four
bytes will always be F8 FF FF FF. So for a 100-cluster disk, the FAT would be 102 words (204
bytes) long. If a file starts at cluster 2, fat[2] would tell the next cluster, e.g.
3, fat[3] would in turn contain the next cluster and so on, forming a linked list.
If that sounds simple enough in theory, it’s also that in practice. Here’s the FAT from
our test.img offset 0x10600:
In the above screen capture, fat[2] is marked on red (note that because we’re talking words,
fat[0] is the first two bytes, fat[1] the next two, and so on). Least significant byte is first, so the
value is 0x0003, i.e. sector 3. What’s after sector 3? Just look up fat[3] – it’s 0x0004 (the
two bytes right after fat[2]). We can continue this right until fat[0xD] (cluster 13 in
decimal), which reads 0xFFFF, meaning that is the last cluster of this file. For files occupying
only a single cluster, 0xFFFF is the only FAT entry, as can be seen for all files after our
HAMLET.TXT.
Congratulations, if you understood the above, you now understand everything there is to know
about FAT16. It’s time to transform it into code form!
// go to first cluster
fseek(in, data_start + (cluster-2) * cluster_size, SEEK_SET);
fread(buffer, 1, sizeof(buffer), in);
// do something with data
// go to fat[cluster]
fseek(in, fat_start + cluster * 2, SEEK_SET);
fread(&cluster, 2, 1, in);
if(cluster == 0xFFFF)
return;
...
The code above is obviously missing some important details, but I trust you get the basic idea:
// if we have read the whole cluster, read next cluster # from FAT
if(cluster_left == 0) {
fseek(in, fat_start + cluster*2, SEEK_SET);
fread(&cluster, 2, 1, in);
You should also try reading the HAMLET.TXT. You can download the UTF-8 Hamlet from
Project Gutenberg and use fc HAMLET.TXT pg1524.txt see if the extracted file matches
– mine did :)
To deal with the memory issue, I decided to set a little less strict limits than Petit FatFS (44
bytes + some stack) to avoid excess code optimization for size, but still stay under 64 bytes. For
reading data, I decided to use a 32 byte buffer, which is enough to store a full Fat16 file entry in
one go. For state variables, I chose the following 19 byte structure that is enough to navigate
around the file system and read files after initialization, resulting in memory footprint of 51
bytes and some stack:
// State data required by FAT16 library
typedef struct {
unsigned long fat_start; // FAT start position
unsigned long data_start; // data start position
unsigned char sectors_per_cluster; // cluster size in sectors
unsigned short cluster; // current cluster being read
unsigned long cluster_left; // bytes left in current cluster
unsigned long file_left; // bytes left in the file being read
} __attribute((packed)) Fat16State;
#define FAT16_BUFFER_SIZE 32
while(fat16_state.file_left) {
bytes_read = fat16_read_file(FAT16_BUFFER_SIZE);
fwrite(fat16_buffer, 1, bytes_read, out); // Write read bytes
}
return 0;
}
In addition to the fat16_seek(offset) and fat16_read(bytes) shown above, this is
really all that is needed to use the library. Also, if you read the above notes carefully, you’ll
notice that subdirectories can be accessed. To open SUBDIR\1.TXT, just replace
the fat16_open_file() statement in above code with the following – open_file basically
just positions the read pointer to the beginning of a given file, and subdirectories are essentially
just files with file entries as their contents:
fat16_open_file("SUBDIR ", " ");
It’s important to remember that filenames are case-sensitive and need to be padded with spaces
to 8+3 characters, otherwise the library won’t find your files. Furthermore, using mixed case
letters when creating file will create dummy file entries that are used to store “long filenames”
for the files, so “Hamlet.txt” becomes some dummy entries and the actual file might be called
HAMLET~1.TXT (I’m sure that will bring back memories for many of you :).
Closing words
We’ve now created a rather nice, although read-only library for reading FAT16 file system. I
recommend that you now grab the updated project zip and spend some time going through
the fat16.h, fat16.c, and test_lib.c in order to understand how the library works and
how it is used (I included a makefile so you can just use “make” to compile test_lib.exe.
That is the only way you’ll truly benefit from this (after all, if all you are looking for are ready-
made routines, there are more functionally complete libraries available). As a bonus, you’ll
then be able to make changes if you want to achieve something not covered in this tutorial!
Here are some examples that you could try as exercises:
Add a new function fat16_eof() that will return 1 if file has been completely read
Instead of fat16_open_file(), make a method fat16_next_file() that advances
file entries one by one, allowing you to for example print out the file structure and navigate
directories
Add fat16_skip_bytes() method to enable “forward seeking” in a file
Open the image file read/write and make fat16_write() user provided function
and fat16_write_file() that can overwrite bits of currently read file
In the next parts, we’ll start interfacing with the SD card and port the libraries to ATtiny2313.
Until then, take care!
PUBLISHED BY
Joonas Pihlajamaa
Coding since 1990 in Basic, C/C++, Perl, Java, PHP, Ruby and Python, to name a few. Also
interested in math, movies, anime, and the occasional slashdot now and then. Oh, and I also
have a real life, but lets not talk about it! View all posts by Joonas Pihlajamaa
Posted onApril 7, 2012AuthorJoonas PihlajamaaCategoriesElectronicsTagsfat, fat16, fatfs, file allocation
table, library, petit fatfs, tutorial
6. Saravanansays:
October 27, 2013 at 13:56
1. Joonas Pihlajamaasays:
October 27, 2013 at 21:22
It begins from the sector after file allocation tables, i.e. cluster 0 = start of partition data. At
least if my memory serves me correctly, it’s been a while since I wrote this.
REPLY
7. Neeraj Deshpandesays:
July 18, 2014 at 15:28
Neeraj
REPLY
1. Joonas Pihlajamaasays:
July 18, 2014 at 16:21
Wow, it seems I have at some point started talking about FAT32 when I meant FAT16. It’s just
a set of typos, everything here should be about FAT16. FAT32 is a (slightly) more complex
topic I haven’t tackled, and is left as an excercise to to the reader, too. :)
REPLY
8. joshsays:
August 14, 2014 at 03:08
hi is there a way i can read boot from the sd without the .img file?
REPLY
1. Joonas Pihlajamaasays:
August 14, 2014 at 12:45
I’m not sure I understand, but starting HxD with Administrator rights enables you to read the
SD directly, thus getting to boot sector without creating an .img first.
REPLY
1. joshsays:
August 14, 2014 at 16:12
Hi mate thank you for your reply just wants to say that your tutorial is amazing.Ahh I see. Ya I
can read the files from the SD directly, without the .img file. Am stuck with that point now I
don’t want to use the .img file any directions someone can pleas point me to ?
REPLY
1. Joonas Pihlajamaasays:
August 14, 2014 at 20:59
I am not sure what you are trying to accomplish? Reading the boot sector with PC? Looking at
the boot sector with hex editor? Reading the SD with embedded device like AVR?
The tutorial is structured so that the first two parts introduce the FAT16 file system structure
and code examples read an .img file. Later parts of the tutorial then use that same code on AVR
to read files from SD card directly.
REPLY
1. joshsays:
August 15, 2014 at 01:07
Hi joonas I went through all ur tutorial I am impressed with it. Yes I am implementing the code
on an embedded device, and I am reading from files from the SD card perfectly fine such as
if(ret = fat16_open_file(“TEST “, “TXT”)) that works I can read the file and see what’s inside
it and that is not in the .img file, so basically all I want to do is now is to know what files are
inside the sd card then read them such as test1 test2 test3 and use them for my project but I
don’t want to use the .img file more or less what ur doing with read_root.c but without .img or
even more to know if there are mor then one .img files (example test1.img and test2.img ) I
have a display on the project so I can display the text on my LCD (I can display all the files on
the LCD so the user chooses something) do you know what I mean sir ?
Thank you very much for all your time
9. jamessays:
March 30, 2015 at 02:12
1. Joonas Pihlajamaasays:
March 30, 2015 at 10:54
That is a more complex matter. I have the functionality in the works and if I manage to do it,
I’ll write about it, but your current option is to check out some more extensive FAT/SD
libraries to see how they do it.
REPLY
10. blakesays:
August 16, 2016 at 09:22
1. Joonas Pihlajamaasays:
August 16, 2016 at 23:22
I’ve mostly forgotten about this code, but probably it is because each item is 16-bit number, i.e.
two bytes long…
REPLY
1. blakesays:
August 17, 2016 at 06:57
I am glad i found this, i am actually implementing FAT32 for a college project and this
information has been great for me.
Thank you!