×
单片机 > 单片机程序设计 > 详情

STM32 文件系统 fatfs 移植笔记详解

发布时间:2020-06-06 发布时间:
|

1、内存和Flash介绍


stm32 的 flash 地址起始于 0x0800 0000,结束地址是 0x0800 0000 加上芯片实际的 flash 大小,不同的芯片 flash 大小不同。


RAM 起始地址是 0x2000 0000,结束地址是 0x2000 0000 加上芯片的 RAM 大小,不同的芯片RAM也不同。


Flash 中的内容一般用来存储代码和一些定义为 const 的数据,断电不丢失,RAM 可以理解为内存,用来存储代码运行时的数据,变量等等,掉电数据丢失。


stm32 将外设等都映射为地址的形式,对地址的操作就是对外设的操作。

 

stm32 的外设地址从 0x4000 0000 开始,可以看到在库文件中,是通过基于0x4000 0000 地址的偏移量来操作寄存器以及外设的。


一般情况下,程序文件是从 0x0800 0000 地址写入,这个是 STM32 开始执行的地方,0x0800 0004 是 STM32的中断向量表的起始地址。


在使用keil进行编写程序时,其编程地址的设置一般是这样的: 



程序的写入地址从 0x08000000 开始的,其大小为0x80000也就是512K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08080000;


RAM的地址从0x20000000开始,大小为0x10000也就是64K的RAM。


这与STM32的内存地址映射关系是对应的。


M3复位后,从0x08000004取出复位中断的地址,并且跳转到复位中断程序,中断执行完之后会跳到我们的main函数,main函数里边一般是一个死循环,进去后就不会再退出,当有中断发生的时候,M3将PC指针强制跳转回中断向量表,然后根据中断源进入对应的中断函数,执行完中断函数之后,再次返回main函数中。


1.1、内部 flash 构成



STM32F103的中容量内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域。


STM32F105VC的是有64K或128页x2K=256k字节的内置闪存存储器,用于存放程序和数据。



主存储器:


一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 1M FLASH、 2M FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除,


系统存储区:


系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。


OTP 区域:


OTP(One Time Program),指的是只能写入一次的存储区域,容量为 512 字节,写入后数据就无法再更改, OTP 常用于存储应用程序的加密密钥。


选项字节:


选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。


2、文件系统 fatfs 详解


2.1、移植FATFS必经的几个步骤

 

1) 移植好官方提供的.c和.h文件;

 

2) 编写diskio.c的硬件连接部分代码;


3) 配置好ffconf.h内部的控制用宏变量;


4) 编写好内存管理部分:


要把一个数据块写到SD卡中,你先得在内存中开辟一块空间来存放这些数据才行。


在f_mount挂载SD卡的时候,通过串口观察该函数的FREASULT型返回值,观察到的始终都是error 13: FR_NO_FILESYSTEM。


为了弄清楚这个BUG的来源,我们最好弄清楚SD卡的文件结构以及FATFS文件系统本身的工作原理。


SD卡的文件结构: 


对于SD卡等存储介质,我们需要了解,它一般都有两个地址,一个是物理地址一个是逻辑地址,逻辑地址往往是物理地址基础上加一个偏移量,即逻辑地址零 = 物理地址零 + 偏移。 


考虑已格式化为FAT32格式后,在物理地址零处的最初是的512个字节是MBR(Master Boot Record),即主引导记录。


其中的前446个字节为引导代码,我们这里不做详解。


接下来的64个字节为分区表,其中16个字节为一组总共四组,每一个组都描述了一个分区,最后两个字节为固定的末尾签名 0x55、0xAA。


我们可以看到在0x01BE处开始到0x01CD处的16个字节指示了一个分区。


对于这16个字节,我们所需要知道的主要是以该16个字节距起始处偏移0x08个字节处为起始的四个字节表示的是跳转量。


即从这个物理地址的0扇区到达逻辑地址的0扇区的偏移量(单位为扇区)。


这里我们可以看到这个偏移量为0x0000_00E3。于是我们到这个偏移量的扇区去看,就可以看到我们逻辑扇区0处的数据,也就是DBR(Dos Boot Record)Dos引导记录。


这一块的引导记录与我们FATFS文件系统能否正常工作密切相关。

第一张图是从物理扇区打开看到的效果,第二张图是从逻辑扇区打开看到的效果。


具体这一块数据的详细解释将在下面与FATFS内部的文件系统结构体FATFS struct结合在一起解释。


除此之外我们还需要知道的两大块是。


对于FAT32,它从DBR开始,间隔若干扇区后会有一个根目录(Directory),在该根目录后间隔若干扇区后会有一个存储数据起始处。


2.2、FATFS工作原理


FatFs Module是一种完全免费开源的FAT文件系统模块,专门为小型的嵌入式系统而设计。


它完全用标准C语言编写,所以具有良好的硬件平台独立性,可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列单片机上而只需做简单的修改。


它支持FATl2、FATl6和FAT32,支持多个存储媒介;


有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。


移植准备:


1) FATFS源代码的获取,可以到官网下载:http://elm-chan.org/fsw/ff/00index_e.html;


2) 解压文件会得到两个文件夹,一个是doc文件夹,这里是FATFS的一些使用文档和说明,以后在文件编程的时候可以查看该文档。


另一个是src文件夹,里面就是我们所要的源文件。


3) 建立一个STM32的工程,为方便调试,我们应重载printf()底层函数实现串口打印输出。


结构体FATFS,FIL和DIR的具体含义:


FATFS结构体记录的主要是和FATFS文件系统本身相关的一些参数;


FIL主要记录的是和特定文件有关的参数;


DIR记录的是和目录有关的参数;


FATFS的内部变量的含义:


typedef struct {

    BYTE    fs_type;        /* FAT sub-type (0:Not mounted) */

    BYTE    drv;            /* Physical drive number */

    BYTE    csize;          /* Sectors per cluster (1,2,4...128)簇的大小,一般一个簇 = 八个扇区;给文件分配簇的时候一次分配三个簇 */

    BYTE    n_fats;         /* Number of FAT copies (1 or 2) */

    BYTE    wflag;          /* win[] flag (b0:dirty) */

    BYTE    fsi_flag;       /* FSINFO flags (b7:disabled, b0:dirty) */

    WORD    id;             /* File system mount ID */

    WORD    n_rootdir;      /* Number of root directory entries (FAT12/16) */

#if _MAX_SS != _MIN_SS

    WORD    ssize;          /* Bytes per sector (512, 1024, 2048 or 4096) */

#endif

#if _FS_REENTRANT

    _SYNC_t sobj;           /* Identifier of sync object */

#endif

#if !_FS_READONLY

    DWORD   last_clust;     /* Last allocated cluster 最后写入的一个文件的最后一个簇的簇地址,这里标志的是下一个文件写进来的时候应该写在哪个位置上*/

    DWORD   free_clust;     /* Number of free clusters 这里标志的是该文件系统管理下还剩下多少个可用簇*/

#endif

#if _FS_RPATH

    DWORD   cdir;           /* Current directory start cluster (0:root) */

#endif

    DWORD   n_fatent;       /* Number of FAT entries, = number of clusters + 2 */

    DWORD   fsize;          /* Sectors per FAT 一个FAT分区共有多少个扇区*/

    DWORD   volbase;        /* Volume start sector 这里标志的是逻辑0扇区,即逻辑0扇区相对于物理0扇区偏移的扇区量,对于我的SD卡,这里应该是0xE3*/

    DWORD   fatbase;        /* FAT start sector FAT开始扇区,其实这里我不是特别懂,但是通过Winhex观察可以看到,这一块是用了一些FF作为标志的,对于我的SD卡,这里大小是0x11CF*/

    DWORD   dirbase;        /* Root directory start sector (FAT32:Cluster#) 这里记载的是根目录所在的簇,我的SD卡为0x02*/

    DWORD   database;       /* Data start sector 根目录所在的扇区数,我的SD卡为0x20E3*/

    DWORD   winsect;        /* Current sector appearing in the win[] 这里记载的是我们的窗口win[]看到的是哪个区域的数据*/

    BYTE    win[_MAX_SS];   /* Disk access window for Directory, FAT (and file data at tiny cfg) */

} FATFS;

对于f_mount的代码,将SD卡挂载起来,把硬件与软件的连接做好的工作是在find_volume函数内部完成的。对于该函数,首先需要注意的一个片段是:


bsect = 0;

    fmt = check_fs(fs, bsect);                  /* Load sector 0 and check if it is an FAT boot sector as SFD */

    if (fmt == 1 || (!fmt && (LD2PT(vol)))) {   /* Not an FAT boot sector or forced partition number */

        for (i = 0; i < 4; i++) {           /* Get partition offset */

            pt = fs->win + MBR_Table + i * SZ_PTE;

            br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0;

        }

        i = LD2PT(vol);                     /* Partition number: 0:auto, 1-4:forced */

        if (i) i--;

        do {                                /* Find an FAT volume */

            bsect = br[i];

            fmt = bsect ? check_fs(fs, bsect) : 2;  /* Check the partition */

        } while (!LD2PT(vol) && fmt && ++i < 4);

    }

    if (fmt == 3) return FR_DISK_ERR;       /* An error occured in the disk I/O layer */

    if (fmt) return FR_NO_FILESYSTEM;       /* No FAT volume is found */

    

这一段代码先检测了物理地址的零扇区处。对于部分SD卡,物理地址等于逻辑地址,即找不到MBR区域。


对于另外一部分物理地址和逻辑地址不重合的SD卡,这段代码先通过找物理地址零扇区处记录了逻辑扇区偏移量的那16个字节。


换句话说,这段代码的第一个功能是,找到逻辑扇区相对于物理扇区的偏移量,再把该偏移量赋值给bsect。 


当找到逻辑地址的零扇区处后,它需要做的事情就是,需要检测这究竟是什么文件系统。


因此,在check_fs里面,我们可以看到这样的代码:


BYTE check_fs ( /* 0:Valid FAT-BS, 1:Valid BS but not FAT, 2:Not a BS, 3:Disk error */

    FATFS* fs,  /* File system object */

    DWORD sect  /* Sector# (lba) to check if it is an FAT boot record or not */

)

{

    fs->wflag = 0; fs->winsect = 0xFFFFFFFF;    /* Invaidate window */

    if (move_window(fs, sect) != FR_OK)         /* Load boot record */

        return 3;

    if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55)   /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */

        return 2;

 

    if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146)     /* Check "FAT" string */

        return 0;

    if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146)   /* Check "FAT" string */

        return 0;

 

    return 1;

}

这段代码首先是把DBR区域的数据取出来,再进行判断这段代码是否合法。


判断分为两个部分,一个是检测末尾的标识符0x55、0xAA;另一个是检测文件系统是否为FAT。其中BS_FilSysType=54,BS_FilSysType32=82。


而从上面的图我们可以看到,从第0x52字节开始的四个字节正好指示了FAT的存在。 

最后根据check_fs的结果,来进行判断。而在调试中,博主恰恰是在这里返回了FR_NO_FILESYSTEM。


调试的时候出现了这样的情况,如果我单步调试进入check_fs内部一步一步执行,此时SD卡挂载成功,不会发生错误;


但是如果我调试的时候是一步执行过check_fs的话,则会出现没有文件系统的报错。 

理论上说,如果单步调试通得过但是直接执行通不过则是时序上出了问题。


由于SD卡读写速度比较慢而这些代码执行速度比较快。在move_window里面是有一段将SD卡里面的DBR读到内存中的执行步骤。这一个步骤是纯硬件的,因此在执行这个读操作的时候CPU早就去执行下面的语句了。


而下面的语句是什么呢?就是那几个判断。于是问题产生了。


在SD卡还没来得及把数据全都读进来的时候,我们的观察窗口win[]里的都是一片0x00或是上次观察到的数据。因此在判断语句中,既检测不到0x55AA也检测不到FAT的标志,于是程序便自然用R_NO_FILESYSTEM作为返回值了。


解决这个问题只需要在Readblock末尾加一个延迟函数即可。想想也是简单地有点惭愧啊。(:зゝ∠)


关于FATFS结构体内部参数的具体含义我们可以从后面的代码中得知,最重要的几个就是之前博主用中文解释的。大致思想就是,DBR内储存的是有关这个FAT系统的一些具体参数,因为这一块是作为管理区而存在,因此需要记录诸如一个簇多少扇区,总共还剩下多少个可用簇,我的文件目录要放在哪,下一个文件应该从哪里开始写之类的信息。


if (LD_WORD(fs->win + BPB_BytsPerSec) != SS(fs))    /* (BPB_BytsPerSec must be equal to the physical sector size) */

        return FR_NO_FILESYSTEM;

 

    fasize = LD_WORD(fs->win + BPB_FATSz16);            /* Number of sectors per FAT */

    if (!fasize) fasize = LD_DWORD(fs->win + BPB_FATSz32);

    fs->fsize = fasize;

 

    fs->n_fats = fs->win[BPB_NumFATs];                  /* Number of FAT copies */

    if (fs->n_fats != 1 && fs->n_fats != 2)             /* (Must be 1 or 2) */

        return FR_NO_FILESYSTEM;

    fasize *= fs->n_fats;                               /* Number of sectors for FAT area */

 

    fs->csize = fs->win[BPB_SecPerClus];                /* Number of sectors per cluster */

    if (!fs->csize || (fs->csize & (fs->csize - 1)))    /* (Must be power of 2) */

        return FR_NO_FILESYSTEM;

 

    fs->n_rootdir = LD_WORD(fs->win + BPB_RootEntCnt);  /* Number of root directory entries */

    if (fs->n_rootdir % (SS(fs) / SZ_DIRE))             /* (Must be sector aligned) */

        return FR_NO_FILESYSTEM;

 

    tsect = LD_WORD(fs->win + BPB_TotSec16);            /* Number of sectors on the volume */

    if (!tsect) tsect = LD_DWORD(fs->win + BPB_TotSec32);

 

    nrsv = LD_WORD(fs->win + BPB_RsvdSecCnt);           /* Number of reserved sectors */

    if (!nrsv) return FR_NO_FILESYSTEM;                 /* (Must not be 0) */

 

    /* Determine the FAT sub type */

    sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIRE);    /* RSV + FAT + DIR */

    if (tsect < sysect) return FR_NO_FILESYSTEM;        /* (Invalid volume size) */

    nclst = (tsect - sysect) / fs->csize;               /* Number of clusters */

    if (!nclst) return FR_NO_FILESYSTEM;                /* (Invalid volume size) */

    fmt = FS_FAT12;

    if (nclst >= MIN_FAT16) fmt = FS_FAT16;

    if (nclst >= MIN_FAT32) fmt = FS_FAT32;

 

    /* Boundaries and Limits */

    fs->n_fatent = nclst + 2;                           /* Number of FAT entries */

    fs->volbase = bsect;                                /* Volume start sector */

    fs->fatbase = bsect + nrsv;                         /* FAT start sector */

    fs->database = bsect + sysect;                      /* Data start sector */

    if (fmt == FS_FAT32) {

        if (fs->n_rootdir) return FR_NO_FILESYSTEM;     /* (BPB_RootEntCnt must be 0) */

        fs->dirbase = LD_DWORD(fs->win + BPB_RootClus); /* Root directory start cluster */

        szbfat = fs->n_fatent * 4;                      /* (Needed FAT size) */

    } else {

        if (!fs->n_rootdir) return FR_NO_FILESYSTEM;    /* (BPB_RootEntCnt must not be 0) */

        fs->dirbase = fs->fatbase + fasize;             /* Root directory start sector */

        szbfat = (fmt == FS_FAT16) ?                    /* (Needed FAT size) */

            fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);

    }

    if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs))   /* (BPB_FATSz must not be less than the size needed) */

        return FR_NO_FILESYSTEM;

 

#if !_FS_READONLY

    /* Initialize cluster allocation information */

    fs->last_clust = fs->free_clust = 0xFFFFFFFF;

 

    /* Get fsinfo if available */

    fs->fsi_flag = 0x80;

#if (_FS_NOFSINFO & 3) != 3

    if (fmt == FS_FAT32             /* Enable FSINFO only if FAT32 and BPB_FSInfo == 1 */

        && LD_WORD(fs->win + BPB_FSInfo) == 1

        && move_window(fs, bsect + 1) == FR_OK)

    {

        fs->fsi_flag = 0;

        if (LD_WORD(fs->win + BS_55AA) == 0xAA55    /* Load FSINFO data if available */

            && LD_DWORD(fs->win + FSI_LeadSig) == 0x41615252

            && LD_DWORD(fs->win + FSI_StrucSig) == 0x61417272)

        {

#if (_FS_NOFSINFO & 1) == 0

            fs->free_clust = LD_DWORD(fs->win + FSI_Free_Count);

#endif

#if (_FS_NOFSINFO & 2) == 0

            fs->last_clust = LD_DWORD(fs->win + FSI_Nxt_Free);

#endif

        }

    }

#endif

#endif

    fs->fs_type = fmt;  /* FAT sub-type */

    fs->id = ++Fsid;    /* File system mount ID */

#if _FS_RPATH

    fs->cdir = 0;       /* Set current directory to root */

#endif

3、片内Flash上使用FatFS


在STM32片内Flash最后100页(可宏定义设置区间和大小)使用FatFS。


模拟NAND Flash把片内Flash做为一个zone:


1)每页做为一块(block);


2)每页分成4份;


3)每份为一段sector, 512字节。


  FATFS fs;

  FIL file;

  FRESULT res;

  char array[512]={0}, *parray = array;

  

  res = f_mount(0, &fs);

  res = f_mkfs(0 , 0, _MAX_SS);

  res = f_open(&file, "data.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);

  f_printf(&file, "%sn", "Success");        /* "Success" */

  parray = f_gets(parray , 8, &file);

  LCMTextOutExt(  0, 0, parray );

  f_close(&file);

  f_mount(0, NULL);




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
TQ210天嵌开发板S5PV210 LED闪烁程序C语言代码记录