FatFs源码剖析

2019-07-21 05:25发布

一、介绍: 本文以网上开源文件系统FatFs 0.01为研究对象,剖析FatFs文件系统的核心操作。FatFs目前最新版本已更新到0.10a版本,而我之所以选择0.01版本,是因为这是最早的发布版本,与最新的版本相比,去掉了很多高级应用,且代码量相对较小,宏开关也少了许多,易于阅读和理解,用来研究它的雏形再合适不过了,所以笔者选择0.01版本进行剖析。当大家了解了0.01的核心思想后,再回去看最新的版本,也就容易理解多了。 FatFs历史版本下载:http://elm-chan.org/fsw/ff/00index_e.html    在官网的最下面,能找到所有版本的下载链接。   二、重点: 1. FatFs中有两个重要的缓冲区:win[]和buffer。win[]在FATFS结构体中,buffer在FIL结构体中。     win[]:系统缓冲区。当操作MBR,DBR,FAT表,根目录区时,使用该缓冲区;     buffer:文件缓冲区。当对文件的内容进行操作时,如读、写、修改时,才使用该缓冲区。     2. 在对文件的f_read和f_write过程中(不考虑f_lseek的情况),只有需要读写的最后一个扇区(内容小于512字节)才会被暂存到buffer中,而前面的扇区内容是直接通过磁盘驱动disk_read/disk_write在用户缓冲区和物理磁盘之间进行交互的。   注意:FatFs 0.01与最新的版本还是不少差异的,如在0.01中,buffer只是个指针,需要用户自己定义文件缓冲区,并将buffer指向该缓冲区。而最新版本中,FIL已经有自己的buf[_MAX_SS]。在0.01中没有f_mount()函数,所以需要手动将全局指针FatFs指向自己定义的变量fs。具体的操作流程可参考官网上的示例代码。   三、关键函数总结: 1. f_open  f_open() {     1. 初始化SD卡,初始化FATFS对象;     2. 查找文件的目录项;     3. 填充文件结构体FIL。 }   2. f_read f_read() {     1. 从物理磁盘直接读取所需扇区到用户缓冲区,这个过程中未使用buffer缓冲区;     2. 如果最后一扇区要读取的字节数少于512,则先将最后一扇区读到buffer中,然后再从buffer拷贝需要的字节到用户缓冲区中。 }       3. f_write f_write() {     1. 从用户缓冲区直接写入到物理磁盘,这个过程中未使用buffer缓冲区;     2. 如果要写入的最后一扇区内容少于512字节,则先从物理磁盘读取最后一扇区的内容到buffer中,然后修改buffer中的相应内容,并设置回写标记,这样下次调用f_close/f_write时,就会将buffer回写到物理磁盘中。 }       4. f_close 关键是调用了f_sync函数。   5. f_sync f_sync() {     1. 将buffer回写到物理磁盘中;     2. 读取文件对应的目录项到win[]中,更新参数,并回写。 }         5. move_window move_window专用于操作win[]系统缓冲区。   move_window(B); 调用前:                        执行中:     调用后:                            四、源码注释 本人在不破坏源码逻辑的前提下,对FatFs 0.01源代码进行了中文注释,个别函数重新修改了排版布局,以方便阅读。结合以上示意图即伪代码,相信大家会很快理解FatFs 0.01的核心思想及架构。 [cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <string.h>   
  2. #include "ff.h"         /* FatFs declarations */   
  3. #include "diskio.h"     /* Include file for user provided functions */   
  4.   
  5.   
  6. FATFS *FatFs;           /* ointer to the file system object */  
  7.   
  8.   
  9. /*------------------------------------------------------------------------- 
  10.   Module rivate Functions 
  11. -------------------------------------------------------------------------*/  
  12.   
  13. /*----------------------*/  
  14. /* Change Window Offset */  
  15.   
  16. //读取新的扇区内容到临时缓冲区win[],   
  17. //如果sector为0,则只需要把win[]的内容写入对应的物理扇区即可   
  18. static  
  19. BOOL move_window (  
  20.     DWORD sector        /* Sector number to make apperance in the FatFs->win */  
  21. )                       /* Move to zero only writes back dirty window */  
  22. {  
  23.     DWORD wsect;        //wsect用于检索FAT备份表的相应扇区   
  24.     FATFS *fs = FatFs;  
  25.   
  26.   
  27.     wsect = FatFs->winsect;  
  28.     /*首先检查目标扇区号是否与win[]对应的扇区号相同,如果相同则不进行任何操作。*/  
  29.     if (wsect != sector) {  /* Changed current window */  
  30. #ifndef _FS_READONLY   
  31.         BYTE n;  
  32.   
  33.         //首先检测win[]的内容是否做过修改,如果修改过,则需要先将其写入SD卡中。   
  34.         if (FatFs->dirtyflag) {  /* Write back dirty window if needed */  
  35.             if (disk_write(FatFs->win, wsect, 1) != RES_OK) return FALSE;  
  36.   
  37.             //清除修改标记   
  38.             FatFs->dirtyflag = 0;  
  39.               
  40.             /*如果当前操作的是FAT表,那么需要将修改后的FAT表拷贝到对应的FAT备份中*/  
  41.             if (wsect < (FatFs->fatbase + FatFs->sects_fat)) { /* In FAT area */  
  42.                 for (n = FatFs->n_fats; n >= 2; n--) {    /* Refrect the change to all FAT copies */  
  43.                     wsect += FatFs->sects_fat;  
  44.                     if (disk_write(FatFs->win, wsect, 1) != RES_OK) break;  
  45.                 }  
  46.             }  
  47.         }  
  48. #endif   
  49.   
  50.         //然后再读取新的扇区内容到win[]中   
  51.         if (sector) {  
  52.             if (disk_read(FatFs->win, sector, 1) != RES_OK) return FALSE;  
  53.             FatFs->winsect = sector; //更新当前缓冲区的扇区号   
  54.         }  
  55.     }  
  56.     return TRUE;  
  57. }  
  58.   
  59.   
  60.   
  61.   
  62.   
  63.   
  64.   
  65. /*--------------------------------------------------------------------------*/  
  66. /* ublic Funciotns                                                         */  
  67. /*--------------------------------------------------------------------------*/  
  68.   
  69.   
  70. /*----------------------------------------------------------*/  
  71. /* Load File System Information and Initialize FatFs Module */  
  72. //本函数做三件事:   
  73. // 1.初始化SD卡   
  74. // 2.检查文件系统类型,FAT16还是FAT32   
  75. // 3.填充FatFs对象,即记录物理磁盘的相关参数   
  76. FRESULT f_mountdrv ()  
  77. {  
  78.     BYTE fat;  
  79.     DWORD sect, fatend;  
  80.     FATFS *fs = FatFs;  
  81.   
  82.   
  83.     if (!fs) return FR_NOT_ENABLED;  
  84.   
  85.     //首先对文件系统对象清空   
  86.     /* Initialize file system object */  
  87.     memset(fs, 0, sizeof(FATFS));  
  88.   
  89.     //然后初始化SD卡   
  90.     /* Initialize disk drive */  
  91.     if (disk_initialize() & STA_NOINIT) return FR_NOT_READY;  
  92.   
  93.     //接着收搜索DBR系统引导记录,先检查第0扇区是否就是DBR(无MBR的SD卡),如果是则检查文件系统的类型;   
  94.     //如果不是则说明第0扇区是MBR,则根据MBR中的信息定位到DBR所在扇区,并检查该文件系统的类型   
  95.     /* Search FAT partition */  
  96.     fat = check_fs(sect = 0);       /* Check sector 0 as an SFD format */  
  97.     if (!fat) {                     /* Not a FAT boot record, it will be an FDISK format */  
  98.         /* Check a pri-partition listed in top of the partition table */  
  99.         if (fs->win[0x1C2]) {                    /* Is the partition existing? */  
  100.             sect = LD_DWORD(&(fs->win[0x1C6]));  /* artition offset in LBA */  
  101.             fat = check_fs(sect);               /* Check the partition */  
  102.         }  
  103.     }  
  104.     if (!fat) return FR_NO_FILESYSTEM;  /* No FAT patition */  
  105.   
  106.     //初始化文件系统对象,根据DBR参数信息对Fs成员赋值   
  107.     /* Initialize file system object */  
  108.   
  109.     //文件系统类型:FAT16/FAT32   
  110.     fs->fs_type = fat;                               /* FAT type */  
  111.   
  112.     //单个FAT表所占的扇区数   
  113.     fs->sects_fat =                              /* Sectors per FAT */  
  114.         (fat == FS_FAT32) ? LD_DWORD(&(fs->win[0x24])): LD_WORD(&(fs->win[0x16]));  
  115.   
  116.     //单个簇所占的扇区数   
  117.     fs->sects_clust = fs->win[0x0D];              /* Sectors per cluster */  
  118.   
  119.     //FAT表总数   
  120.     fs->n_fats = fs->win[0x10];                       /* Number of FAT copies */  
  121.   
  122.     //FAT表起始扇区(物理扇区)   
  123.     fs->fatbase = sect + LD_WORD(&(fs->win[0x0E]));   /* FAT start sector (physical) */  
  124.   
  125.     //根目录项数   
  126.     fs->n_rootdir = LD_WORD(&(fs->win[0x11]));        /* Nmuber of root directory entries */  
  127.   
  128.     //计算根目录起始扇区、数据起始扇区(物理扇区地址)   
  129.     fatend = fs->sects_fat * fs->n_fats + fs->fatbase;  
  130.     if (fat == FS_FAT32) {  
  131.         fs->dirbase = LD_DWORD(&(fs->win[0x2C])); /* Directory start cluster */  
  132.         fs->database = fatend;                       /* Data start sector (physical) */  
  133.     } else {  
  134.         fs->dirbase = fatend;                        /* Directory start sector (physical) */  
  135.         fs->database = fs->n_rootdir / 16 + fatend;   /* Data start sector (physical) */  
  136.     }  
  137.   
  138.     //最大簇号   
  139.     fs->max_clust =                              /* Maximum cluster number */  
  140.         (LD_DWORD(&(fs->win[0x20])) - fs->database + sect) / fs->sects_clust + 2;  
  141.   
  142.     return FR_OK;  
  143. }  
  144.   
  145.   
  146.   
  147.   
  148.   
  149. /*-----------------------*/  
  150. /* Open or Create a File */  
  151.   
  152. FRESULT f_open (  
  153.     FIL *fp,            /* ointer to the buffer of new file object to create */  
  154.     const char *path,   /* ointer to the file path */  
  155.     BYTE mode           /* Access mode and file open mode flags */  
  156. )  
  157. {  
  158.     FRESULT res;  
  159.     BYTE *dir;  
  160.     DIR dirscan;  
  161.     char fn[8+3+1];         //8.3 DOS文件名   
  162.     FATFS *fs = FatFs;  
  163.   
  164.     /*首先初始化SD卡,检测文件系统类型,初始化FATFS对象*/  
  165.     if ((res = check_mounted()) != FR_OK) return res;  
  166.       
  167. #ifndef _FS_READONLY   
  168.   
  169.     //如果磁盘设置为写保护,则返回错误码:FR_WRITE_PROTECTED   
  170.     if ((mode & (FA_WRITE|FA_CREATE_ALWAYS)) && (disk_status() & STA_PROTECT))  
  171.         return FR_WRITE_PROTECTED;  
  172. #endif   
  173.   
  174.     //根据用户提供的文件路径path,将文件名对应的目录项及其整个扇区读取到win[]中,   
  175.     //并填充目录项dirscan、标准格式的文件名fn,以及目录项在win[]中的字节偏移量dir   
  176.     res = trace_path(&dirscan, fn, path, &dir); /* Trace the file path */  
  177.   
  178. #ifndef _FS_READONLY   
  179.     /* Create or Open a File */  
  180.     if (mode & (FA_CREATE_ALWAYS|FA_OPEN_ALWAYS)) {  
  181.         DWORD dw;  
  182.   
  183.         //如果文件不存在,则强制新建文件   
  184.         if (res != FR_OK) {     /* No file, create new */  
  185.             mode |= FA_CREATE_ALWAYS;  
  186.             if (res != FR_NO_FILE) return res;  
  187.             dir = reserve_direntry(&dirscan);   /* Reserve a directory entry */  
  188.             if (dir == NULL) return FR_DENIED;  
  189.             memcpy(dir, fn, 8+3);       /* Initialize the new entry */  
  190.             *(dir+12) = fn[11];  
  191.             memset(dir+13, 0, 32-13);  
  192.         }   
  193.         else {              /* File already exists */  
  194.             if ((dir == NULL) || (*(dir+11) & (AM_RDO|AM_DIR))) /* Could not overwrite (R/O or DIR) */  
  195.                 return FR_DENIED;  
  196.               
  197.             //如果文件存在,但又以FA_CREATE_ALWAYS方式打开文件,则重写文件   
  198.             if (mode & FA_CREATE_ALWAYS) {  /* Resize it to zero */  
  199.                 dw = fs->winsect;            /* Remove the cluster chain */  
  200.                 if (!remove_chain(((DWORD)LD_WORD(dir+20) << 16) | LD_WORD(dir+26))  
  201.                     || !move_window(dw) )  
  202.                     return FR_RW_ERROR;  
  203.                 ST_WORD(dir+20, 0); ST_WORD(dir+26, 0); /* cluster = 0 */  
  204.                 ST_DWORD(dir+28, 0);                    /* size = 0 */  
  205.             }  
  206.         }  
  207.   
  208.         //如果是强制新建文件操作,则还需更新时间和日期   
  209.         if (mode & FA_CREATE_ALWAYS) {  
  210.             *(dir+11) = AM_ARC;  
  211.             dw = get_fattime();  
  212.             ST_DWORD(dir+14, dw);   /* Created time */  
  213.             ST_DWORD(dir+22, dw);   /* Updated time */  
  214.             fs->dirtyflag = 1;  
  215.         }  
  216.     }  
  217.     /* Open a File */  
  218.     else {  
  219. #endif   
  220.         if (res != FR_OK) return res;       /* Trace failed */  
  221.   
  222.         //如果打开的是一个目录文件,则返回错误码:FR_NO_FILE   
  223.         if ((dir == NULL) || (*(dir+11) & AM_DIR))  /* It is a directory */  
  224.             return FR_NO_FILE;  
  225.           
  226. #ifndef _FS_READONLY   
  227.   
  228.         //如果以FA_WRITE方式打开Read-Only属性的文件,则返回错误码:FR_DENIED   
  229.         if ((mode & FA_WRITE) && (*(dir+11) & AM_RDO)) /* R/O violation */  
  230.             return FR_DENIED;  
  231.     }  
  232. #endif   
  233.   
  234.     //填充FIL文件结构体参数   
  235. #ifdef _FS_READONLY   
  236.     fp->flag = mode & (FA_UNBUFFERED|FA_READ);  
  237. #else   
  238.     fp->flag = mode & (FA_UNBUFFERED|FA_WRITE|FA_READ);  
  239.     fp->dir_sect = fs->winsect;               /* ointer to the directory entry */  
  240.     fp->dir_ptr = dir;  
  241. #endif   
  242.     fp->org_clust =  ((DWORD)LD_WORD(dir+20) << 16) | LD_WORD(dir+26); /* File start cluster */  
  243.     fp->fsize = LD_DWORD(dir+28);        /* File size */  
  244.     fp->fptr = 0;                        /* File ptr */  
  245.   
  246.     //这一步很重要,它将直接导致f_read和f_write操作中的逻辑顺序   
  247.     fp->sect_clust = 1;                  /* Sector counter */  
  248.       
  249.     fs->files++;  
  250.     return FR_OK;  
  251. }  
  252.   
  253.  
/***************************************************************************************************** 文件名 :main.c 文件描述:程序执行的主要文件,不用说也明白 创建人 :何小龙 博客:http://blog.csdn.net/hexiaolong2009 创建时间:2012.05.16 更改历史:2012.05.29 *****************************************************************************************************/ #include "stm32f10x.h" #include "delay.h" #include "LED.h" #include "diskio.h" #include "ff.h" #include "lcd.h" #define FLASH_APP_ADDR 0x08010000 //第一个应用程序起始地址(存放在FLASH) #define STM_PAGE_SIZE 2048 //注意:STM32F103ZET6的FLASH页大小为2K //**************************************************************************************************** //全局变量声明 FATFS Fs; FIL file; BYTE buffer[STM_PAGE_SIZE]; FRESULT res; UINT br; typedef void (*fun)(void); //定义一个函数类型的参数. fun AppStart; /***************************************************************************************************** 函数名 :Jump2App 功 能:从Bootloader跳转到用户APP程序地址空间 入口参数:Addr,用户APP的起始执行地址 出口参数:无 返回值 :无 *****************************************************************************************************/ void Jump2App(u32 Addr) { if(((*(vu32*)Addr)&0x2FFE0000) == 0x20000000) //检查栈顶地址是否合法. { AppStart = (fun)(*(vu32*)(Addr+4)); //用户代码区第二个字为程序开始地址(复位地址) AppStart(); //跳转到APP. } } /***************************************************************************************************** 函数名 :FirmwareUpdate 功 能:固件升级函数 入口参数:无 出口参数:无 返回值 :无 *****************************************************************************************************/ void FirmwareUpdate(void) { int PageOffest = 0; //页偏移,从APP的基地址到当前页起始位置的字节总数 int ByteOffest; //当前页内的字节偏移,从当前操作页的起始位置到正在写入位置的字节偏移 int a, b; u8 i = 0; /*首先初始化SD卡*/ if(0 != disk_initialize(0)) return; /*接着挂载文件系统对象*/ f_mount(0, &Fs); /*查找是否存在要升级的BIN文件*/ res = f_open(&file, "RTC.bin", FA_OPEN_EXISTING | FA_READ); if(FR_OK != res) return; /*绘制进度条边框*/ LCD_DrawRectangle(50, 225, 250, 255); /*初始化临时变量*/ a = file.fsize / 100; //100表示将进度条平均分成100份,由于进度条长度为200个像素,所以1份占用2个像素 a &= 0xfffffffe; //将文件平均分成100份,所以a表示一份文件所占的字节数,为确保该字节数为偶数,故做此转换 b = 0; //b表示当前已经更新了多少字节 /*执行主要的IAP功能*/ while(1) { /*每次读取一个页的数据到内存缓冲区,注意:STM32F103ZE的页大小为2K*/ res = f_read(&file, buffer, STM_PAGE_SIZE, &br); if (res || br == 0) break; /*然后就是永恒的4步骤:解锁、擦除、更新、上锁*/ FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_ErasePage(FLASH_APP_ADDR + PageOffest); for(ByteOffest = 0; ByteOffest < STM_PAGE_SIZE; ByteOffest += 2) { /*更新FLASH,注意当前操作的实际位置:APP基地址FLASH_APP_ADDR+页偏移字节PageOffest+当前页内的字节偏移ByteOffest*/ FLASH_ProgramHalfWord(FLASH_APP_ADDR + PageOffest + ByteOffest, *(u16*)(buffer + ByteOffest)); b += 2; /*更新显示进度条,(b % a == 0)的目的是确保当前正好写完1份文件*/ if(b % a == 0) { LCD_Fill(50, 225, 50 + 2 * (b / a), 255, 0x7e0); //(b / a)表示已经写了几份文件 } } FLASH_Lock(); PageOffest += STM_PAGE_SIZE; /*每更新完1页,让LED状态翻转一次*/ i = !i; if(i) GPIO_SetBits(GPIOB, GPIO_Pin_5); else GPIO_ResetBits(GPIOB, GPIO_Pin_5); } /*关闭文件,卸载文件系统*/ f_close(&file); f_mount(0, 0); } /***************************************************************************************************** 函数名 :main 功 能:主程序入口函数 入口参数:无 出口参数:无 返回值 :int *****************************************************************************************************/ int main(void) { SystemInit(); delay_init(72); LED_Init(); LCD_Init(); FirmwareUpdate(); Jump2App(FLASH_APP_ADDR); while(1); }[/mw_shl_code]
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
49条回答
The_Professo_Le
1楼-- · 2019-07-26 00:15
 精彩回答 2  元偷偷看……
techxuan
2楼-- · 2019-07-26 02:06
请问楼主,对SD卡格式化的函数是否有使用过,OK吗?谢谢!
hexiaolong2008
3楼-- · 2019-07-26 02:17
回复【38楼】The_Professo_Lee:
---------------------------------
Visio 2003
hexiaolong2008
4楼-- · 2019-07-26 04:36
回复【39楼】techxuan:
---------------------------------
没用过。
The_Professo_Le
5楼-- · 2019-07-26 05:42
回复【40楼】hexiaolong2008:
---------------------------------
楼主靠自己理解画的图哦  强!
kingchunhai
6楼-- · 2019-07-26 11:13
谢谢,学习一下

一周热门 更多>