历史上的今天
返回首页

历史上的今天

今天是:2025年02月09日(星期日)

正在发生

2021年02月09日 | STM32 文件系统 fatfs 移植笔记详解

2021-02-09 来源:eefocus

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 */

推荐阅读

史海拾趣

Ecera Comtek Corp公司的发展小趣事

Ecera Comtek Corp公司成立于XX世纪初,由几位热衷于电子通信技术的工程师共同创立。初创时期,公司面临着资金短缺、技术挑战和市场竞争等多重压力。然而,创始人们凭借对技术的热情和执着,不断研发创新产品,逐渐在行业内树立了口碑。他们经常通宵达旦地工作,试验新的设计方案,努力提升产品的性能和稳定性。经过数年的努力,Ecera Comtek Corp公司终于推出了首款具有竞争力的无线通信设备,为公司的发展奠定了坚实的基础。

Engelking Elektronik GmbH公司的发展小趣事

Engelking Elektronik一直将品质管理作为公司的核心竞争力之一。公司建立了完善的质量管理体系,从原材料采购到产品制造、测试和包装等各个环节都进行严格的质量控制。此外,公司还定期邀请第三方机构进行质量审核和评估,确保产品符合国际标准和客户要求。通过持续改进和优化生产流程,Engelking Elektronik的产品质量得到了客户的高度认可和信赖。

High Voltage Semiconductor Inc公司的发展小趣事

面对日益激烈的市场竞争和不断变化的市场需求,美高测始终保持着对技术创新的执着追求。公司不断投入研发资源,探索新的测试技术和应用场景,如微电网、新能源汽车等领域的高压半导体测试。同时,美高测还积极构建开放式的创新生态体系,与众多合作伙伴共同推动行业的进步和发展。通过这些努力,美高测在高压半导体测试领域持续保持着领先地位,为电子行业的未来发展贡献着重要力量。

DAPAudio公司的发展小趣事

DAPAudio公司自成立以来,一直将技术创新作为公司发展的核心动力。在音频处理领域,DAPAudio率先推出了基于先进算法的数字音频处理器,其独特的音质优化技术迅速赢得了市场的认可。随着技术的不断迭代和更新,DAPAudio的产品逐渐在高端音频市场占据了一席之地。

优先(苏州)半导体公司的发展小趣事

1994年,三星电子(苏州)半导体有限公司在苏州工业园区成立,成为园区首批入驻的外资企业之一。公司初期主要生产配套、低附加值产品,但随着市场的变化和竞争的加剧,公司开始寻求转型。2003年,苏州工厂开始转型生产核心产品,标志着公司从低端生产向高端制造的转变。此后,公司不断加大在智能制造方面的投入,采用先进的半导体自动化生产管理系统和ERP管理系统,实现了“无人化”自动化生产,生产自动化率可达90%以上。同时,公司始终坚持“绿色经营”的可持续发展理念,致力于保护及改善周边环境,履行企业的环保社会责任。

Glow-Lite Corp公司的发展小趣事

东微半导是一家在苏州扎根14余年的半导体公司,致力于自主研发和生产功率半导体核心器件。在充电桩产业快速发展的背景下,东微半导率先量产国内首款自主研发充电桩用功率半导体核心器件,打破了国外厂商的垄断地位。公司经过多年的自主研发,获得了创新结构的高压超级结技术的专利,使MOSFET场效晶体管的电能转换效率提升,具有动态损耗小、发热量低的优点。经客户端实测,整体性能达到了国际一流水平,现已出口至韩国、日本和德国等国际市场。

问答坊 | AI 解惑

什么是ESD?

本帖最后由 jameswangsynnex 于 2015-3-3 20:01 编辑 简言之,ESD就是电荷的快速中和,电子工业每年花在这上面的费用有数十亿美元之多。我们知道所有的物质都由原子构成,原子中有电子和质子。当物质获得或失去电子时,它将失去电平衡而变成带负电 ...…

查看全部问答>

哪位大哥能给发个rapi.lib

想在pc和device之间通过rapi进行通信,但是找不到rapi.lib 哪位兄弟有给发一个到邮箱guopeixin@yahoo.com.cn中, 先行谢过了…

查看全部问答>

嵌入式操作系统 ecos

应届毕业生,公司要求学ecos,但是从来没有接触过,身边也没有人懂这个,在网上转了一大圈也没找到多少资料,有懂行的朋友能帮忙推荐一两本好点的书吗? 我在淘宝找了下找到了下面3本书: 《嵌入式可配置实时操作系统eCos技术及实现机制》 《嵌 ...…

查看全部问答>

启动程序与引导程序有什么区别呀????

启动程序与引导程序有什么区别呀????…

查看全部问答>

CCS 编译错误

fatal error: file \"C:\\\\CCStudio_v3.3\\\\MyProjects\\\\Motor\\\\rts2800.lib<boot.obj>\"   has a Tag_Memory_Model attribute value of \"1\" that is different than one   previously seen (\"2\"); combining in ...…

查看全部问答>

LauchPad 收到了,为什么没有图片上的那个触摸小板?严重不符啊!

LauchPad 收到了,为什么没有图片上的那个触摸小板?严重不符啊!…

查看全部问答>

MSP430经典讲解,从入门到精通

没什么可说的,最近研究MSP430,在下自己收集的东西。分享下。…

查看全部问答>

USB3.0几大优势

USB可以称得上是目前最为成功,最有优势的外设接口规范,随着时代的发展,外设的进步,USB接口规范也需要有相应的配套升级,于是所谓的“SuperSpeed USB”(超高速USB)出现了,也就是我们今天要重点介绍的USB 3.0的优势。 USB 3.0有两大最能拿得 ...…

查看全部问答>

语言和编程是两码事---从语言进步到编程

从语言进步到编程 1. 语言和编程本是两码事 分不清语言和编程的人,估计很能学得好编程。 1.1 盲从技术只能让你晕的更久 和其它事物一样,软件编程的发展也经历了从低级到高级的各个阶段。从机器代码到汇编语言,从汇编语言到高级 ...…

查看全部问答>

大家编程都用什么仿真器?又一个 Jlink 固件升级后变成 unknow device

在淘宝上买了两个 J-Link V8,现在两个都已经因为固件升级变成了 unknown device. J-Link 功能强大,确实是非常不错的仿真器。 大家有没有比 J-Link 觉得更好用的仿真器呢? …

查看全部问答>