一、介绍:
本文以网上开源文件系统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?
- #include <string.h>
- #include "ff.h" /* FatFs declarations */
- #include "diskio.h" /* Include file for user provided functions */
-
-
- FATFS *FatFs;
-
-
-
-
-
-
-
-
-
-
-
- static
- BOOL move_window (
- DWORD sector
- )
- {
- DWORD wsect;
- FATFS *fs = FatFs;
-
-
- wsect = FatFs->winsect;
-
- if (wsect != sector) {
- #ifndef _FS_READONLY
- BYTE n;
-
-
- if (FatFs->dirtyflag) {
- if (disk_write(FatFs->win, wsect, 1) != RES_OK) return FALSE;
-
-
- FatFs->dirtyflag = 0;
-
-
- if (wsect < (FatFs->fatbase + FatFs->sects_fat)) {
- for (n = FatFs->n_fats; n >= 2; n--) {
- wsect += FatFs->sects_fat;
- if (disk_write(FatFs->win, wsect, 1) != RES_OK) break;
- }
- }
- }
- #endif
-
-
- if (sector) {
- if (disk_read(FatFs->win, sector, 1) != RES_OK) return FALSE;
- FatFs->winsect = sector;
- }
- }
- return TRUE;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- FRESULT f_mountdrv ()
- {
- BYTE fat;
- DWORD sect, fatend;
- FATFS *fs = FatFs;
-
-
- if (!fs) return FR_NOT_ENABLED;
-
-
-
- memset(fs, 0, sizeof(FATFS));
-
-
-
- if (disk_initialize() & STA_NOINIT) return FR_NOT_READY;
-
-
-
-
- fat = check_fs(sect = 0);
- if (!fat) {
-
- if (fs->win[0x1C2]) {
- sect = LD_DWORD(&(fs->win[0x1C6]));
- fat = check_fs(sect);
- }
- }
- if (!fat) return FR_NO_FILESYSTEM;
-
-
-
-
-
- fs->fs_type = fat;
-
-
- fs->sects_fat =
- (fat == FS_FAT32) ? LD_DWORD(&(fs->win[0x24])): LD_WORD(&(fs->win[0x16]));
-
-
- fs->sects_clust = fs->win[0x0D];
-
-
- fs->n_fats = fs->win[0x10];
-
-
- fs->fatbase = sect + LD_WORD(&(fs->win[0x0E]));
-
-
- fs->n_rootdir = LD_WORD(&(fs->win[0x11]));
-
-
- fatend = fs->sects_fat * fs->n_fats + fs->fatbase;
- if (fat == FS_FAT32) {
- fs->dirbase = LD_DWORD(&(fs->win[0x2C]));
- fs->database = fatend;
- } else {
- fs->dirbase = fatend;
- fs->database = fs->n_rootdir / 16 + fatend;
- }
-
-
- fs->max_clust =
- (LD_DWORD(&(fs->win[0x20])) - fs->database + sect) / fs->sects_clust + 2;
-
- return FR_OK;
- }
-
-
-
-
-
-
-
-
- FRESULT f_open (
- FIL *fp,
- const char *path,
- BYTE mode
- )
- {
- FRESULT res;
- BYTE *dir;
- DIR dirscan;
- char fn[8+3+1];
- FATFS *fs = FatFs;
-
-
- if ((res = check_mounted()) != FR_OK) return res;
-
- #ifndef _FS_READONLY
-
-
- if ((mode & (FA_WRITE|FA_CREATE_ALWAYS)) && (disk_status() & STA_PROTECT))
- return FR_WRITE_PROTECTED;
- #endif
-
-
-
- res = trace_path(&dirscan, fn, path, &dir);
-
- #ifndef _FS_READONLY
-
- if (mode & (FA_CREATE_ALWAYS|FA_OPEN_ALWAYS)) {
- DWORD dw;
-
-
- if (res != FR_OK) {
- mode |= FA_CREATE_ALWAYS;
- if (res != FR_NO_FILE) return res;
- dir = reserve_direntry(&dirscan);
- if (dir == NULL) return FR_DENIED;
- memcpy(dir, fn, 8+3);
- *(dir+12) = fn[11];
- memset(dir+13, 0, 32-13);
- }
- else {
- if ((dir == NULL) || (*(dir+11) & (AM_RDO|AM_DIR)))
- return FR_DENIED;
-
-
- if (mode & FA_CREATE_ALWAYS) {
- dw = fs->winsect;
- if (!remove_chain(((DWORD)LD_WORD(dir+20) << 16) | LD_WORD(dir+26))
- || !move_window(dw) )
- return FR_RW_ERROR;
- ST_WORD(dir+20, 0); ST_WORD(dir+26, 0);
- ST_DWORD(dir+28, 0);
- }
- }
-
-
- if (mode & FA_CREATE_ALWAYS) {
- *(dir+11) = AM_ARC;
- dw = get_fattime();
- ST_DWORD(dir+14, dw);
- ST_DWORD(dir+22, dw);
- fs->dirtyflag = 1;
- }
- }
-
- else {
- #endif
- if (res != FR_OK) return res;
-
-
- if ((dir == NULL) || (*(dir+11) & AM_DIR))
- return FR_NO_FILE;
-
- #ifndef _FS_READONLY
-
-
- if ((mode & FA_WRITE) && (*(dir+11) & AM_RDO))
- return FR_DENIED;
- }
- #endif
-
-
- #ifdef _FS_READONLY
- fp->flag = mode & (FA_UNBUFFERED|FA_READ);
- #else
- fp->flag = mode & (FA_UNBUFFERED|FA_WRITE|FA_READ);
- fp->dir_sect = fs->winsect;
- fp->dir_ptr = dir;
- #endif
- fp->org_clust = ((DWORD)LD_WORD(dir+20) << 16) | LD_WORD(dir+26);
- fp->fsize = LD_DWORD(dir+28);
- fp->fptr = 0;
-
-
- fp->sect_clust = 1;
-
- fs->files++;
- return FR_OK;
- }
-
-
/*****************************************************************************************************
文件名 :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]
---------------------------------
哎,现在做东西就有这个瓶颈啊,度娘说了,NTFS没有开放源码,坑爹啊,有没有办法把大于4G的文件分割成小于4G的文件,导入U盘里,然后U盘读出后再形成一个大于4G的文件,请问可行吗?
---------------------------------
兄弟,你确定fatfs最大支持4g的sd卡啊?
怪不得前几天弄了好久的移植8g卡,运行到 f_open(&fdst, "0:/Demo.TXT", FA_OPEN_ALWAYS | FA_WRITE);这个函数的时候,总是返回 FR_NOT_ENABLED
看来我还是太差劲了。。。。
---------------------------------
是FAT32最大只能支持单个文件不超过4G,并不是说FatFs不支持8G的卡,移植时要注意不同容量的卡底层驱动是不一样的
---------------------------------
昨晚已经成功了。。。嘎嘎,不过只实现了创建、打开、写、读文件,还有创建文件夹
一周热门 更多>