PIC Projects
Working with SD/MMC cards
Working with FAT16 file system
For this experiment we use the same hardware as above.
The main goal of the experiment was to write a PIC assembly language
code for reading all files on an SD/MMC card in the order as they appear in
the file directory. After the last file is read, the program starts over
with reading the first file again, and so on. An application of this code
that I have in mind is playing a list of MP3 files in the cyclic order.
An important step is to get a good reference to the FAT16 structure.
I used the following ones:
The code (see source attached) only reads files from the first partition
on the card, assuming that it is FAT16. It starts with initialization of
the USART and SSP modules as above. After that it runs according to the
following algorithm:
- Read the Master Boot Record
- Read byte 0x1C2 from Sector 0 on the card, which contains the file
system type. This byte is just read and saved, no checking is done.
- Read byte 0x1C6 containing the gap in bytes between the MBR sector and
the first sector of the first partition.
- Read the Volume Boot Record (VBR) of the first partition. It is located
in the first sector of the partition, which is computed above. The byte at
offset 0xD specifies the number of sectors per cluster which we save in the
variable sectPerClust. The following word is the number of
reserved sectors after VBR. Adding this number to the current sector number
provides the number of the first sector of FAT, which we save in the
variable fat.
- Read the byte at offset 0x16 which specifies the number of sectors per
FAT. This number needs to be multiplied by 2 (for 2 copies of FAT). Adding
this number to the variable fat results in the starting sector of
the directory list. We save this number in the variable dir.
- The file directory is intended to store up to 512 entries for files,
each takes 32 bytes. So, the total number of sector occupied by the
directory list is 32·512 / 512 = 32. Adding 32 to dir
results in the starting sector of the data area, which we save in the
variable dat.
- At this point all preliminary setup for working with the file system is
done. We start scanning the directory entries (32-byte records) starting
from sector number stored in dir. The first record contains the
volume name, so we skip the first 32 bytes to get to the next record. In the
coarse of the algorithm we will need to skip certain amounts of bytes from
the beginning of the directory list to get to the next record. This number
is stored in the variable bytes2Skip, whose initial value for
working with the first sector is 32. The variable currDirSectNo
(range 0 .. 31) keeps track on the current working sector number of the
directory list.
- Check if bytes2Skip=512. In this case we reset it to 0 and
request reading a new sector from the card. Just go to the previous
step.
- Read the first byte of the next record. If this byte is 0, then the end
of the directory list is reached. In this case we read the rest of the bytes
form the same sector, reset bytes2Skip to 32 and proceed with Step
3.
- If the first byte is non-zero, we check if it is 0xFE. This corresponds
to a deleted file, which we intend to skip. In this case we skip the next 31
bytes of the same record, add 32 to bytes2Skip and go to step
3a.
- At this point the record corresponds to a non-deleted file. We skip
the next 25 bytes to get to the number of the first file cluster. If the
cluster number is 0, then this record does not correspond to a real file. It
contains some information on a long file name of some file of a card, so we
skip it and proceed with step 3a.
- At this point the cluster number is not 0. We read the next 4 bytes to
get the file length. If the file length is 0, we just skip this record by
going to step 3a.
- Finally, we have found an existing file of a non-zero length. We skip
the remaining bytes till the end of that directory sector and add 32 to
bytes2Skip. If this number becomes 512, the end of sector has
reached, so next time we should request another sector. The variable
currDirSectNo is incremented on 1 and bytes2Skip is reset
down to 0. If currDirSectNo becomes 32, this was the last record in
the directory list. To start next time from the beginning of the file
directory we reset currDirSectNo to 0 and bytes2Skip to
32.
- At this time we got the next file cluster number stored in a 16-bit
variable cluster, which we dump to the screen by using the USART
interface. An algorithm for this is specified below. In a real application
the data in this file cluster should be directed to an MP3 encoder. To get
the next file cluster number we read a word at offset cluster with
the value dir as the base as follows:
- Copy the value of dir in two lower bytes of arg.
- Compute the sector containing this offset by taking the high-order
byte of cluster. Each sector is 512 bytes and each cluster number
takes 2 bytes, so to get the offset the value in cluster has to be
divided by 256, which is equivalent to taking the high-order byte. Adding
the result to arg (a 16-bit operation).
- Compute the offset of the cluster number in the sector determined above
by taking the cluster (mod 256) (the lower-order byte of variable
cluster, multiplying it by 2 (the cluster number takes 2
bytes).
- Read a word at offset computed in step 4c in sector determined in step
2b. This is the next cluster number of the file. Skip the rest of the current
sector.
If the cluster number is 0xFFFF, this was the last cluster of the file
and we proceed with reading a new file by going to step 3.
- If the cluster number is not 0xFFFF, we first compute the read cluster
number by subtracting 2 from cluster (the first two bytes in FAT
are reserved, but the cluster numbering in dir starts with 0).
Otherwise, we proceed with reading the file cluster as follows:
- Compute the byte address of the cluster by multiplying cluster
with sectPerClust. Since sectPerClust is always a power of
2, left shifts (a 24-bit operation) can be used to perform the
multiplication. Add the obtained value to dir and store the result
in arg.
- Start a loop that runs sectPerClust times. At each iteration
of that loop we read a sector with the offset in arg and dump it to
the screen (or to MP3 decoder).
- Check if fileLen <= 512. If so, read the entire sector and
subtract 512 from fileLen. Proceed with step 5b.
- If fileLen > 512 we read only fileLen bytes from
the current sector and skip the rest. Break the loop running through the
sectors of the current file by proceed with step 3 (read a new file).
Well, it was a bit long description, but the code works fine and passed my
tests. This is just an experimental version of the file reading procedures.
We will see later how it works in a real application. Anyway, it is pretty
well commented, so there should be no problem in understanding the
implementation details.
Downloads
Last modified:Mon, Jan 23, 2023.



