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:

  1. Read the Master Boot Record
    1. 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.
    2. Read byte 0x1C6 containing the gap in bytes between the MBR sector and the first sector of the first partition.
  2. 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.
    1. 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.
    2. 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.
  3. 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.
    1. 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.
    2. 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.
    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.
    4. 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.
    5. 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.
    6. 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.
  4. 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:
    1. Copy the value of dir in two lower bytes of arg.
    2. 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).
    3. 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).
    4. 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.
  5. 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:
    1. 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.
    2. 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).
    3. Check if fileLen <= 512. If so, read the entire sector and subtract 512 from fileLen. Proceed with step 5b.
    4. 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.

10618