OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 93|回复: 0

《STM32H7R7开发指南 V1.1 》第五十四章 FATFS实验

[复制链接]

1344

主题

1360

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5737
金钱
5737
注册时间
2019-5-8
在线时间
1554 小时
发表于 前天 09:38 | 显示全部楼层 |阅读模式
第五十四章 FATFS实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

上两章,我们学习了SD卡和SD NAND的使用,不过仅仅是简单的实现读写扇区而已,真正要好好应用它们,必须使用文件系统管理。本章,我们将使用FATFS来管理SD卡(同时也管理SD NAND和NOR FLASH,不过仅以SD卡为例讲解),实现SD卡文件的读写等基本功能。
本章分为如下几个部分:
54.1 FATFS简介
54.2 硬件设计
54.3 程序设计
54.4 下载验证


54.1 FATFS简介
FATFS是一个完全免费开源的FAT/exFAT文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准C语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到8051、PIC、AVR、ARM、Z80、RX等系列单片机上。它支持FATl2、FATl6和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。FATFS的特点有:
Windows/dos系统兼容的FAT/exFAT文件系统
独立于硬件平台,方便跨硬件平台移植
代码量少、效率高
多种配置选项
支持多卷(物理驱动器或分区,最多10个卷)
多个ANSI/OEM代码页包括DBCS
支持长文件名、ANSI/OEM或Unicode
支持RTOS
支持多种扇区大小
只读、最小化的API和I/O缓冲区等
新版的exFAT文件系统,突破了原来FAT32对容量管理32Gb上限,可支持更大的存储
FATFS的这些特点,加上免费、开源的原则,使得FATFS应用非常广泛。FATFS模块的层次结构如图54.1.1所示:


第五十四章 FATFS实验728.png
图54.1.1 FATFS层次结构图

最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,就可以像在PC上读/写文件那样简单。
中间层FATFS模块,实现了FAT文件读/写协议。FATFS模块提供的是ff.c和ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
需要我们编写移植代码的是FATFS模块提供的底层接口,它包括存储媒介读/写接口(diskI/O)和供给文件创建修改时间的实时时钟。
FATFS的源码及英文详述,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html这个网站下载到,教程目前使用的版本为R0.14。本章我们就使用最新版本的FATFS作为介绍,下载最新版本的FATFS软件包,解压后可以得到两个文件夹:doc和src。doc里面主要是对FATFS的介绍,而src里面才是我们需要的源码。source文件夹详情表,如表54.1.1.1所示:


1.png
表54.1.1 FATFS的源码文件介绍

FATFS模块在移植的时候,我们一般只需要修改2个文件,即ffconf.h和diskio.c。FATFS模块的所有配置项都是存放在ffconf.h里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
1)FF_FS_TINY。这个选项在R0.07版本中开始出现,之前的版本都是以独立的C文件出现(FATFS和TinyFATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用FATFS,所以把这个选项定义为0即可。
2)FF _FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为0即可。
3)FF _USE_STRFUNC。这个用来设置是否支持字符串类操作,比如f_putc,f_puts等,本章我们需要用到,故设置这里为1。
4)FF _USE_MKFS。用来定时是否使能格式化,本章需要用到,所以设置这里为1。
5)FF _USE_FASTSEEK。这个用来使能快速定位,我们设置为1,使能快速定位。
6)FF _USE_LABEL。这个用来设置是否支持磁盘盘符(磁盘名字)读取与设置。我们设置为1,使能,就可以通过相关函数读取或者设置磁盘的名字了。
7)FF _CODE_PAGE。这个用于设置语言类型,包括很多选项(见FATFS官网说明),我们这里设置为936,即简体中文(GBK码,同一个文件夹下的ffunicode.c根据这个宏选择对应的语言设置)。
8)FF_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE支持),取值范围为0~3。0,表示不支持长文件名,1~3是支持长文件名,但是存储地方不一样,我们选择使用3,通过ff_memalloc函数来动态分配长文件名的存储区域。
9)FF_VOLUMES。用于设置FATFS支持的逻辑设备数目,我们设置为3,即支持3个设备。
10)FF_MAX_SS。扇区缓冲的最大值,一般设置为512。
11)FF_FS_EXFAT。新版本增加的功能,使用exFAT文件系统,用于支持超过32Gb的超大存储。它们使用的是exFAT文件系统,使用它时必须要根据设置FF_USE_LFN这个参数的值以决定exFATs系统使用的内存来自堆栈还是静态数组。
其他配置项,我们这里就不一一介绍了,FATFS的说明文档里面有很详细的介绍,大家自己阅读http://elm-chan.org/fsw/ff/doc/config.html即可。下面我们来讲讲FATFS的移植,FATFS的移植主要分为3步:
①数据类型:在integer.h里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。
②配置:通过ffconf.h配置FATFS的相关功能,以满足你的需要。
③函数编写:打开diskio.c,进行底层驱动编写,需要编写5个接口函数,如下图所示:


第五十四章 FATFS实验2984.png
图54.1.2 diskio需要实现的函数

通过以下三步,我们即可完成对FATFS的移植。
第一步,我们使用的是MDK5.38a编译器,数据类型和integer.h里面定义的一致,所以此步,我们不需要做任何改动。
第二步,关于ffconf.h里面的相关配置,我们在前面已经有介绍(之前介绍的11个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为FATFS模块完全与磁盘I/O层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘I/O模块并不是FATFS的一部分,并且必须由用户提供。这些函数一般有5个,在diskio.c里面。
首先是disk_initialize函数,该函数介绍如表54.1.2所示:


2.png
表54.1.2 disk_initialize函数介绍

第二个函数是disk_status函数,该函数介绍如表54.1.3所示:

3.png
表54.1.3 disk_status函数介绍

第三个函数是disk_read函数,该函数介绍如表54.1.4所示:

4.png
表54.1.4 disk_read函数介绍

第四个函数是disk_write函数,该函数介绍如表54.1.5所示:

5.png
表54.1.5 disk_write函数介绍

第五个函数是disk_ioctl函数,该函数介绍如表54.1.6所示:

6.png
表54.1.6 disk_ioctl函数介绍

以上五个函数,我们将在软件设计部分一一实现。通过以上3个步骤,我们就完成了对FATFS的移植,就可以在我们的代码里面使用FATFS了。
FATFS提供了很多API函数,这些函数FATFS的自带介绍文件里面都有详细的介绍(包括参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续API的使用,关于FATFS的介绍,我们就介绍到这里。大家可以通过FATFS自带的介绍文件进一步了解和熟悉FATFS的使用。


54.2 硬件设计
1. 例程功能
本章实验功能简介:开机的时候先初始化SD卡,初始化成功之后,注册三个磁盘:一个给SD卡用,一个给NOR FLASH用,一个给SD NAND用,之所以把NOR FLASH也当成磁盘来用,一方面是为了演示大容量的NOR FLASH也可以用FATFS管理,说明FATFS的灵活性;另一方面可以展示FATFS方式比原来直接按地址管理数据便利性,使板载NOR FLASH的使用更具灵活性。挂载成功后获取SD卡的容量和剩余空间,并显示在LCD模块上,最后定义USMART输入指令进行各项测试。本实验通过LED0指示程序运行状态。

2. 硬件资源
1)LED灯
       LED:LED0 – PD14
2)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
3)串口1 (PB14/PB15连接在板载USB转串口芯片CH340上面)
4)SD卡,通过SDMMC(SDMMC_D0~D4(PC8~PC11), SDMMC_SCK(PC12),
       SDMMC_CMD(PD2))连接
1)SD NAND
2)NOR FLASH
这几个外设原理图我们在之前的章节已经介绍过了,这里就不重复介绍了,不清楚的话可以查看本文之前章节的描述或对光盘资料提供的开发板原理图。


54.3 程序设计
FATFS的驱动为一个硬件独立的组件,因此我们把FATFS的移植代码放到《Middlewares》文件夹下。
本章,我们在《SD卡实验》的基础上进行拓展。在《Middlewares》下新建一个 FATFS 的文件夹,然后将 FATFS R0.14 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建一个exfuns的文件夹,用于存放我们针对FATFS做的一些扩展代码(这个我们后面再讲)。操作结果如图 54.3.1 所示:


第五十四章 FATFS实验6835.png
图54.3.1 FATFS 文件夹子目录

54.3.1 程序解析

1. FATFS驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
diskio.c/.h为我们提供了规定好的底层驱动接口的返回值。这个函数需要使用到我们的硬件接口,所以需要把使用到的硬件驱动的头文件包进来。

  1. #include "./MALLOC/malloc.h"
  2. #include "./SYSTEM/usart/usart.h"
  3. #include "./FATFS/source/ff.h"
  4. #include "./FATFS/source/diskio.h"
  5. #include "./BSP/NORFLASH/norflash_ex.h"
  6. #include "./BSP/SDNAND/spi_sdnand.h"
  7. #include "./BSP/SDMMC/sdmmc_sdcard.h"
复制代码
按照54.1的描述,接下来我们来对这几个接口进行补充实现。本章,我们用FATFS管理了3个磁盘:SD卡、NOR FLASH和SD NAND,我们设置SD_CARD为0,NOR_FLASH为1,SD_NAND为2,对应到disk_read/disk_write函数里面。SD卡比较好说,但是NOR FLASH,因为其扇区是4K字节大小,我们为了方便设计,强制将其扇区定义为512字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次数大增,所以不要随便往NOR FLASH里面写数据,非必要最好别写,如果频繁写的话,很容易将NOR FLASH写坏。
  1. #define SD_CARD     0   /* SD卡,卷标为0 */
  2. #define NOR_FLASH   1   /* NOR Flash,卷标为1 */
  3. #define SD_NAND     2   /* SD NAND,卷标为2 */

  4. /**
  5. * NOR Flash区域划分(最大扇区大小扇区对齐)
  6. *     名称      起始地址    大小
  7. *     代码区 0x00000000 0x00100000
  8. * 文件系统区 0x00100000 0x01800000
  9. *     字库区 0x01900000 0x00604000
  10. *     用户区 0x01F04000 0x000FC000
  11. */
  12. #define NORFLASH_SECTOR_SIZE    (512)
  13. #define NORFLASH_SECTOR_COUNT   (0x01800000 / NORFLASH_SECTOR_SIZE)
  14. #define NORFLASH_BLOCK_SIZE     (8)
  15. #define NORFLASH_FATFS_BASE     (0x00100000)
复制代码
另外,diskio.c里面的函数,直接决定了磁盘编号(盘符/卷标)所对应的具体设备,比如,以上代码中,我们就通过switch来判断,到底要操作SD卡,是NOR FLASH,还是SD NAND,然后,分别执行对应设备的相关操作。以此实现磁盘编号和磁盘的关联。

1. disk_initialize函数
要使用一个外设首先要对它进行初始化,所以先看sd卡的初始化函数,其声明如下:
DSTATUS disk_initialize ( BYTE pdrv)
函数描述:
初始化指定编号的磁盘,磁盘所指定的存储区。使用每个磁盘前进行初始化,那在代码中直接根据编号调用硬件的初始化接口即可,这样也能保证代码的扩展性,硬件的顺序可以根据自己的喜好定义。
函数形参:
形参1是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为3来支持三个磁盘,因此可选值为0、1和2。
代码实现如下:

  1. /**
  2. * @brief       初始化磁盘
  3. * [url=home.php?mod=space&uid=271674]@param[/url]       pdrv : 磁盘编号0~9
  4. * @retval      无
  5. */
  6. DSTATUS disk_initialize (BYTE pdrv)
  7. {
  8.     uint8_t res = 0;

  9.     switch (pdrv)
  10.     {
  11.         case SD_CARD:           /* SD卡 */
  12.             res = sd_init();    /* SD卡初始化 */
  13.             break;

  14.         case NOR_FLASH:         /* 外部flash */
  15.             norflash_ex_init();   
  16.             break;

  17.         case SD_NAND:           /* SD NAND */
  18.             res = sdnand_init();
  19.             break;
  20.         default:
  21.             res = 1;
  22.     }

  23.     if (res)
  24.     {
  25.         return  STA_NOINIT;
  26.     }
  27.     else
  28.     {
  29.         return 0;              /* 初始化成功*/
  30.     }
  31. }
复制代码
函数返回值:
DSTATUS枚举类型的值,FATFS规定了自己的返回值来管理各接口函数的操作结果,方便后续函数的操作和判断,我们看看它的定义:

  1. /* Status of Disk Functions */
  2. typedef BYTE    DSTATUS;

  3. /* Disk Status Bits (DSTATUS) */
  4. #define STA_NOINIT      0x01            /* Drive not initialized */
  5. #define STA_NODISK      0x02            /* No medium in the drive */
  6. #define STA_PROTECT     0x04            /* Write protected */
复制代码
定义时也写出了各个参数的含义,根据ff.c中的调用实例可知操作返回0才是正常的状态,其它情况发生的话就需要结合硬件进行分析了。

2. disk_status函数
要使用一个外设首先要对它进行初始化,所以先看sdio的初始化函数,其声明如下:

  1. DSTATUS disk_status (BYTE pdrv)
复制代码
函数描述:
返回当前磁盘驱动器的状态。
函数形参:
形参1是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为3来支持三个磁盘,因此可选值为0、1和2。
函数返回值:
直接返回RES_OK。

3. disk_read函数
disk_read实现直接从硬件接口读取数据,这个为接口FATFS的其它读操作接口函数调用,其声明如下:

  1. DRESULT disk_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
复制代码
函数描述:
从磁盘驱动器上读取扇区。
函数形参:
形参1:是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为3来支持三个磁盘,因此可选值为0、1和2。
形参2:buff指向要保存数据的内存区域指针,为字节类型。
形参3:sector为实际物理操作时要访问的扇区地址。
形参4:count为本次要读取的数据量,最长为unsigned int,读到的数量为字节数。
代码实现如下,同样要根据我们定义的设备标号,在swich-case中添加对应硬件的驱动:

  1. /**
  2. * @brief       读扇区
  3. * @param       pdrv   : 磁盘编号0~9
  4. * @param       buff   : 数据接收缓冲首地址
  5. * @param       sector : 扇区地址
  6. * @param       count  : 需要读取的扇区数
  7. * @retval      无
  8. */
  9. DRESULT disk_read (
  10.     BYTE pdrv,             /* Physical drive nmuber to identify the drive */
  11.     BYTE *buff,            /* Data buffer to store read data */
  12.     DWORD sector,          /* Sector address in LBA */
  13.     UINT count             /* Number of sectors to read */
  14. )
  15. {
  16.     uint8_t res = 0;

  17.     if (!count) return RES_PARERR;  /* count不能等于0,否则返回参数错误 */

  18.     switch (pdrv)
  19.     {
  20.         case SD_CARD:   /* SD卡 */
  21.             res = sd_read_disk(buff, sector, count);

  22.             while (res) /* 读出错 */
  23.             {
  24.                 if (res != 2) sd_init();    /* 重新初始化SD卡 */

  25.                 res = sd_read_disk(buff, sector, count);
  26.                 //printf("sd rd error:%d\r\n", res);
  27.             }

  28.             break;

  29.         case NOR_FLASH:  /* NOR Flash */
  30.             for (; count > 0; count--)
  31.             {
  32.                 norflash_ex_read(NORFLASH_FATFS_BASE + sector *
  33.                 NORFLASH_SECTOR_SIZE, buff, NORFLASH_SECTOR_SIZE);
  34.                 sector++;
  35.                 buff += NORFLASH_SECTOR_SIZE;
  36.             }

  37.             res = 0;
  38.             break;

  39.         case SD_NAND:      /* SD NAND */
  40.             res = sdnand_read_sectors(buff, sector, count);
  41.             break;

  42.         default:
  43.             res = 1;
  44.     }

  45.     /* 处理返回值,将返回值转成ff.c的返回值 */
  46.     if (res == 0x00)
  47.     {
  48.         return RES_OK;
  49.     }
  50.     else
  51.     {
  52.         return RES_ERROR;
  53.     }
  54. }
复制代码
函数返回值:
DRESULT为枚举类型,diskio.h中有其定义,我们也引用如下:

  1. /* Results of Disk Functions */
  2. typedef enum
  3. {
  4.     RES_OK = 0,             /* 0: 操作成功 */
  5.     RES_ERROR,              /* 1: 读/写错误 */
  6.     RES_WRPRT,              /* 2: 写保护状态 */
  7.     RES_NOTRDY,             /* 3: 设备忙 */
  8.     RES_PARERR              /* 4: 其它情形 */
  9. } DRESULT;
复制代码
根据返回值的含义确认操作结果即可。

4. disk_write函数
disk_write实现直接从硬件接口读取数据,这个接口为FATFS的其它写操作接口函数调用,其声明如下:

  1. DRESULT disk_write ( BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
复制代码
函数描述:
向磁盘写入一个或多个扇区。
函数形参:
形参1是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为3来支持三个磁盘,因此可选值为0、1和2。
形参2:buff指向要保存数据的内存区域指针,为字节类型。
形参3:sector为实际物理操作时要访问的扇区地址。
形参4:count为本次要读取的数据量,最长为unsigned int,读到的数量为字节数。
代码实现如下,同样要根据我们定义的设备标号,在swich-case中添加对应硬件的驱动:

  1. /**
  2. * @brief       写扇区
  3. * @param       pdrv   : 磁盘编号0~9
  4. * @param       buff   : 发送数据缓存区首地址
  5. * @param       sector : 扇区地址
  6. * @param       count  : 需要写入的扇区数
  7. * @retval      无
  8. */
  9. DRESULT disk_write (
  10.     BYTE pdrv,                   /* Physical drive nmuber to identify the drive */
  11.     const BYTE *buff,              /* Data to be written */
  12.     DWORD sector,               /* Sector address in LBA */
  13.     UINT count                  /* Number of sectors to write */
  14. )
  15. {
  16.     uint8_t res = 0;

  17.     if (!count) return RES_PARERR;  /* count不能等于0,否则返回参数错误 */

  18.     switch (pdrv)
  19.     {
  20.         case SD_CARD:       /* SD卡 */
  21.             res = sd_write_disk((uint8_t *)buff, sector, count);

  22.             while (res)     /* 写出错 */
  23.             {
  24.                 sd_init();  /* 重新初始化SD卡 */
  25.                 res = sd_write_disk((uint8_t *)buff, sector, count);
  26.                 //printf("sd wr error:%d\r\n", res);
  27.             }

  28.             break;

  29.         case NOR_FLASH:      /* NOR Flash */
  30.             for (; count > 0; count--)
  31.             {
  32.                 norflash_ex_write(NORFLASH_FATFS_BASE + sector *
  33.                 NORFLASH_SECTOR_SIZE, (uint8_t *)buff, NORFLASH_SECTOR_SIZE);
  34.                 sector++;
  35.                 buff += NORFLASH_SECTOR_SIZE;
  36.             }

  37.             res = 0;
  38.             break;

  39.         case SD_NAND:      /* SD NAND */
  40.             res = sdnand_write_sectors((uint8_t *)buff, sector, count);
  41.             break;

  42.         default:
  43.             res = 1;
  44.     }

  45.     /* 处理返回值,将返回值转成ff.c的返回值 */
  46.     if (res == 0x00)
  47.     {
  48.         return RES_OK;
  49.     }
  50.     else
  51.     {
  52.         return RES_ERROR;
  53.     }
  54. }
复制代码
函数返回值:
DRESULT为枚举类型,diskio.h中有其定义,编写读函数时已经介绍了,注意要把返回值转成这个枚举类型的参数。

5. disk_ioctl函数
disk_ioctl实现一些控制命令,这个接口为FATFS提供一些硬件操作信息,其声明如下:

  1. DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void *buff)
复制代码
函数描述:
获取其他控制参数。
函数形参:
形参1是FATFS管理的磁盘编号pdrv :磁盘编号0~9,我们配置FF_VOLUMES为3来支持三个磁盘,因此可选值为0、1和2。
形参2:cmd是FATFS定义好的一些宏,用于访问硬盘设备的一些状态。我们实现几个简单的操作接口,用于获取磁盘容量这些基础信息(diskio.h中已经定义好了),为方便,我们先只实现几个标准的应用接口,关于iSDIO/MMC的一些扩展命令我们再根据需要进行支持。

  1. /* Command code for disk_ioctrl fucntion */
  2. /* Generic command (Used by FatFs) */
  3. #define CTRL_SYNC          0  /* 完成挂起的写入过程(当FF_FS_READONLY == 0) */
  4. #define GET_SECTOR_COUNT   1  /* 获取磁盘扇区数(当FF_USE_MKFS == 1) */
  5. #define GET_SECTOR_SIZE    2  /* 获取磁盘存储空间大小 (当FF_MAX_SS != FF_MIN_SS) */
  6. #define GET_BLOCK_SIZE     3  /* 每个扇区块的大小 (当FF_USE_MKFS == 1) */
  7. #define CTRL_TRIM          4  /* 告知设备哪些块的数据不再使用(当FF_USE_TRIM==1) */
复制代码
下面是从http://elm-chan.org/fsw/ff/doc/dioctl.html得到的参数实现效果,我们也可以参考原有的disk_ioctl的实现来理解这几个参数。

7.png

形参3 buff为void形指针,根据命令的格式和需要,我们把对应的值转成对应的形式传给它。
参考原有的disk_ioctl的实现,我们的函数实现如下:

  1. /**
  2. * @brief       获取其他控制参数
  3. * @param       pdrv   : 磁盘编号0~9
  4. * @param       ctrl   : 控制代码
  5. * @param       buff   : 发送/接收缓冲区指针
  6. * @retval      RES_OK,支持;
  7. *              RES_ERROR,不支持
  8. */
  9. DRESULT disk_ioctl (
  10.     BYTE pdrv,      /* Physical drive nmuber (0..) */
  11.     BYTE cmd,       /* Control code */
  12.     void *buff      /* Buffer to send/receive control data */
  13. )
  14. {
  15.     DRESULT res;

  16.     if (pdrv == SD_CARD)    /* SD卡 */
  17.     {
  18.         HAL_SD_CardInfoTypeDef sd_card_info_struct;
  19.         
  20.         HAL_SD_GetCardInfo(&g_sd_handle, &sd_card_info_struct);
  21.         
  22.         switch (cmd)
  23.         {
  24.             case CTRL_SYNC:
  25.                 res = RES_OK;
  26.                 break;

  27.             case GET_SECTOR_SIZE:
  28.                 *(DWORD *)buff = 512;
  29.                 res = RES_OK;
  30.                 break;

  31.             case GET_BLOCK_SIZE:
  32.                 *(WORD *)buff = sd_card_info_struct.LogBlockSize;
  33.                 res = RES_OK;
  34.                 break;

  35.             case GET_SECTOR_COUNT:
  36.                 *(DWORD *)buff = sd_card_info_struct.LogBlockNbr;
  37.                 res = RES_OK;
  38.                 break;

  39.             default:
  40.                 res = RES_PARERR;
  41.                 break;
  42.         }
  43.     }
  44.     else if (pdrv == NOR_FLASH)  /* NOR FLASH */
  45.     {
  46.         switch (cmd)
  47.         {
  48.             case CTRL_SYNC:
  49.                 res = RES_OK;
  50.                 break;

  51.             case GET_SECTOR_SIZE:
  52.                 *(WORD *)buff = SPI_FLASH_SECTOR_SIZE;
  53.                 res = RES_OK;
  54.                 break;

  55.             case GET_BLOCK_SIZE:
  56.                 *(WORD *)buff = SPI_FLASH_BLOCK_SIZE;
  57.                 res = RES_OK;
  58.                 break;

  59.             case GET_SECTOR_COUNT:
  60.                 *(DWORD *)buff = SPI_FLASH_SECTOR_COUNT;
  61.                 res = RES_OK;
  62.                 break;

  63.             default:
  64.                 res = RES_PARERR;
  65.                 break;
  66.         }
  67.     }
  68.     else if (pdrv == SD_NAND)  /* SD NAND */
  69.     {
  70.         switch (cmd)
  71.         {
  72.             case CTRL_SYNC:
  73.                 res = RES_OK;
  74.                 break;

  75.             case GET_SECTOR_SIZE:
  76.                 *(WORD *)buff = 512;       /* SD NAND扇区强制为512字节大小 */
  77.                 res = RES_OK;
  78.                 break;

  79.             case GET_BLOCK_SIZE:
  80.                 *(WORD *)buff = sdnand_info.logic_block_size;   
  81.                 res = RES_OK;
  82.                 break;

  83.             case GET_SECTOR_COUNT:
  84.                 *(DWORD *)buff = sdnand_info.logic_block_num;
  85.                 res = RES_OK;
  86.                 break;

  87.             default:
  88.                 res = RES_PARERR;
  89.                 break;
  90.         }
  91.     }
  92.     else
  93.     {
  94.         res = RES_ERROR;    /* 其他的不支持 */
  95.     }
  96.    
  97.     return res;
  98. }
复制代码
函数返回值:
DRESULT为枚举类型,diskio.h中有其定义,编写读函数时已经介绍了,注意要把返回值转成这个枚举类型的参数。
以上实现了我们54.1节提到的5个函数,ff.c中需要实现get_fattime (void),同时因为在ffconf.h里面设置对长文件名的支持为方法3,所以必须在ffsystem.c中实现get_fattime、ff_memalloc和ff_memfree这三个函数。这部分比较简单,直接参考我们修改后的ffsystem.c的源码。
至此,我们已经可以直接使用FATFS下的ff.c下的f_mount的接口挂载磁盘,然后使用类似标准C的文件操作函数,就可以实现文件操作。但f_mount还需要一些文件操作的内存,为了方便操作,我们在FATFS文件夹下还新建了一个exfuns的文件夹,该文件夹用于保存一些针对FATFS的扩展代码,如刚才提到的FATFS相关函数的内存申请方法等。
本章,我们编写了4个文件,分别是:exfuns.c、exfuns.h、fattester.c和fattester.h。其中exfuns.c主要定义了一些全局变量,方便FATFS的使用,同时实现了磁盘容量获取等函数。而fattester.c文件则主要是为了测试FATFS用,因为FATFS的很多函数无法直接通过USMART调用,所以我们在fattester.c里面对这些函数进行了一次再封装,使得可以通过USMART调用。
这几个文件的代码,大家可以直接使用本例程源码,我们将exfuns.c/.h和fattester.c/.h加入FATFS组下的exfuns文件下,直接使用即可。

6. exfuns_init函数
我们在使用文件操作前,需要用f_mount函数挂载磁盘,我们在挂载SD卡前需要一些文件系统的内存,为了方便管理,我们定义一个全局的fs[FF_VOLUMES]指针,定成数组是我们要管理多个磁盘,而f_mount也需要一个FATFS类型的指针,定义如下:

  1. /* 逻辑磁盘工作区(在调用任何FATFS相关函数之前,必须先给fs申请内存) */
  2. FATFS *fs[FF_VOLUMES];  
复制代码

接下来只要用内存管理部分的知识来实现对fs指针的内存申请即可:
  1. /**
  2. * @brief       为exfuns申请内存
  3. * @param       无
  4. * @retval      0, 成功; 1, 失败.
  5. */
  6. uint8_t exfuns_init(void)
  7. {
  8.     uint8_t i;
  9.     uint8_t res = 0;

  10.     for (i = 0; i < FF_VOLUMES; i++)
  11.     {
  12.         fs[i] = (FATFS *)mymalloc(SRAMIN, sizeof(FATFS));   
  13.         /* 为磁盘i工作区申请内存 */

  14.         if (!fs[i])break;
  15.     }
  16.    
  17. #if USE_FATTESTER == 1  /* 如果使能了文件系统测试 */
  18.     res = mf_init();    /* 初始化文件系统测试(申请内存) */
  19. #endif
  20.    
  21.     if (i == FF_VOLUMES && res == 0)
  22.     {
  23.         return 0;   /* 申请有一个失败,即失败. */
  24.     }
  25.     else
  26.     {
  27.         return 1;
  28.     }
  29. }
复制代码
其它的函数我们暂时没有使用到,这里先不介绍了,大家使用时查阅源码即可。

2. mian.c代码
在main.c就比较简单了,按照我们的流程图的思路编写即可,在成功初始化后,我们通过LCD显示文件操作的结果。
最后,我们编写的main函数如下:

  1. int main(void)
  2. {
  3.     FRESULT res;
  4.     uint32_t total;
  5.     uint32_t free;
  6.     uint8_t t;
  7.    
  8.     sys_mpu_config();                   /* 配置MPU */
  9.     sys_cache_enable();                 /* 使能Cache */
  10.     HAL_Init();                         /* 初始化HAL库 */
  11.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  12.     delay_init(600);                    /* 初始化延时 */
  13.     usart_init(115200);                 /* 初始化串口 */
  14.     led_init();                         /* 初始化LED */
  15.     hyperram_init();                    /* 初始化HyperRAM */
  16.     lcd_init();                         /* 初始化LCD */
  17.     usmart_dev.init(300);               /* 初始化USMART */
  18.     my_mem_init(SRAMIN);                /* 初始化AXI-SRAM1~4内存池 */
  19.     my_mem_init(SRAMEX);                /* 初始化XSPI2 HyperRAM内存池 */
  20.     my_mem_init(SRAM12);                /* 初始化AHB-SRAM1~2内存池 */
  21.     my_mem_init(SRAMDTCM);              /* 初始化DTCM内存池 */
  22.     my_mem_init(SRAMITCM);              /* 初始化ITCM内存池 */
  23.    
  24.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  25.     lcd_show_string(30, 70, 200, 16, 16, "FATFS TEST", RED);
  26.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  27.     lcd_show_string(30, 110, 200, 16, 16, "Use USMART for test", RED);
  28.    
  29.     while (sd_init() != 0)
  30.     {
  31.         lcd_show_string(30, 130, 200, 16, 16, "SD Card Error!", RED);
  32.         delay_ms(500);
  33.         lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
  34.         delay_ms(500);
  35.         LED0_TOGGLE();
  36.     }
  37.    
  38.     exfuns_init();
  39.    
  40.     /* 挂载SD卡 */
  41.     f_mount(fs[0], "0:", 1);
  42.    
  43.     /* 挂载NOR Flash,如果NOR Flash没有文件系统则需要进行格式化 */
  44.     res = f_mount(fs[1], "1:", 1);
  45.     if (res == FR_NO_FILESYSTEM)
  46.     {
  47.         lcd_show_string(30, 130, 200, 16, 16, "NOR Flash Disk Formatting...", RED);
  48.         res = f_mkfs("1:", NULL, NULL, FF_MAX_SS);
  49.         if (res == FR_OK)
  50.         {
  51.             f_setlabel((const TCHAR *)"1:ALIENTEK");
  52.             lcd_show_string(30, 130, 200, 16, 16, "NOR Flash Disk Format   Finish", RED);
  53.         }
  54.         else
  55.         {
  56.             lcd_show_string(30, 130, 200, 16, 16,"NOR Flash Disk Format Error",
  57. RED);
  58.         }
  59.         delay_ms(1000);
  60.     }
  61.    
  62.     /* 挂载SD NAND,如果SD NAND没有文件系统则需要进行格式化 */
  63.     res = f_mount(fs[2], "2:", 1);
  64.     if (res == FR_NO_FILESYSTEM)
  65.     {
  66.         lcd_show_string(30, 130, 200, 16, 16, "SD NAND Disk Formatting...",
  67. RED);
  68.         res = f_mkfs("2:", NULL, NULL, FF_MAX_SS);
  69.         if (res == FR_OK)
  70.         {
  71.             f_setlabel((const TCHAR *)"2:SDNAND");
  72.             lcd_show_string(30, 130, 200, 16, 16, "SD NAND Disk Format Finish",
  73.             RED);
  74.         }
  75.         else
  76.         {
  77.             lcd_show_string(30, 130, 200, 16, 16, "SD NAND Disk Format Error ",
  78.             RED);
  79.         }
  80.         delay_ms(1000);
  81.     }
  82.    
  83.     /* 获取SD卡容量 */
  84.     while (exfuns_get_free("0", &total, &free) != 0)
  85.     {
  86.         lcd_show_string(30, 130, 200, 16, 16, "SD Card FatFs Error!", RED);
  87.         delay_ms(200);
  88.         lcd_fill(30, 130, 240, 150 + 16, WHITE);
  89.         delay_ms(200);
  90.         LED0_TOGGLE();
  91.     }
  92.    
  93.     lcd_show_string(30, 130, 200, 16, 16, "FATFS OK!           ", BLUE);
  94.     lcd_show_string(30, 150, 200, 16, 16, "SD Total Size:     MB", BLUE);
  95.     lcd_show_string(30, 170, 200, 16, 16, "SD Free Size:     MB", BLUE);
  96.     lcd_show_num(142, 150, total >> 10, 5, 16, BLUE);
  97.     lcd_show_num(134, 170, free >> 10, 5, 16, BLUE);
  98.    
  99.     while (1)
  100.     {
  101.         usmart_dev.scan();
  102.         
  103.         delay_ms(10);
  104.         
  105.         if (++t == 20)
  106.         {
  107.             t = 0;
  108.             LED0_TOGGLE();
  109.         }
  110.     }
  111. }
复制代码
main函数初始化了LED和LCD用于显示效果,初始化按键和ADC用于辅助显示ADC。在usmart_config.c里面的usmart_nametab数组添加如下内容:
  1. /* 函数名列表初始化(用户自己添加)
  2. * 用户直接在这里输入要执行的函数名及其查找串
  3. */
  4. struct _m_usmart_nametab usmart_nametab[] =
  5. {
  6. #if USMART_USE_WRFUNS == 1      /* 如果使能了读写操作 */
  7.     (void *)read_addr, "uint32_t read_addr(uint32_t addr)",
  8.     (void *)write_addr, "void write_addr(uint32_t addr, uint32_t val)",
  9. #endif
  10.     (void *)delay_ms, "void delay_ms(uint16_t nms)",
  11.     (void *)delay_us, "void delay_us(uint32_t nus)",

  12.     (void *)mf_mount, "uint8_t mf_mount(uint8_t* path,uint8_t mt)",
  13.     (void *)mf_open, "uint8_t mf_open(uint8_t*path,uint8_t mode)",
  14.     (void *)mf_close, "uint8_t mf_close(void)",
  15.     (void *)mf_read, "uint8_t mf_read(uint16_t len)",
  16.     (void *)mf_write, "uint8_t mf_write(uint8_t*dat,uint16_t len)",
  17.     (void *)mf_opendir, "uint8_t mf_opendir(uint8_t* path)",
  18.     (void *)mf_closedir, "uint8_t mf_closedir(void)",
  19.     (void *)mf_readdir, "uint8_t mf_readdir(void)",
  20.     (void *)mf_scan_files, "uint8_t mf_scan_files(uint8_t * path)",
  21.     (void *)mf_showfree, "uint32_t mf_showfree(uint8_t *path)",
  22.     (void *)mf_lseek, "uint8_t mf_lseek(uint32_t offset)",
  23.     (void *)mf_tell, "uint32_t mf_tell(void)",
  24.     (void *)mf_size, "uint32_t mf_size(void)",
  25.     (void *)mf_mkdir, "uint8_t mf_mkdir(uint8_t*path)",
  26.     (void *)mf_fmkfs,"uint8_t mf_fmkfs(uint8_t* path,uint8_t opt,uint16_t au)",
  27.     (void *)mf_unlink, "uint8_t mf_unlink(uint8_t *path)",
  28.     (void *)mf_rename, "uint8_t mf_rename(uint8_t *oldname,uint8_t* newname)",
  29.     (void *)mf_getlabel, "void mf_getlabel(uint8_t *path)",
  30.     (void *)mf_setlabel, "void mf_setlabel(uint8_t *path)",
  31.     (void *)mf_gets, "void mf_gets(uint16_t size)",
  32.     (void *)mf_putc, "uint8_t mf_putc(uint8_t c)",
  33. (void *)mf_puts, "uint8_t mf_puts(uint8_t *str)",
  34. };
复制代码

54.4 下载验证
将程序下载到开发板后,我们使用的是16Gb标有“SDHC”标志的micorSD卡,可以看到LCD显示如图54.4.1所示:

第五十四章 FATFS实验25114.png
图54.4.1程序运行效果图

打开串口调试助手,我们就可以串口调用前面添加的各种FATFS测试函数了,比如我们输入mf_scan_files("0:"),即可扫描SD卡根目录的所有文件,如图54.4.2所示:

第五十四章 FATFS实验25221.png
图54.4.2扫描SD卡根目录所有文件

其他函数的测试,用类似的办法即可实现。注意这里0代表SD卡,1代表NOR FLASH,2代表SD NAND。另外,提醒大家,mf_unlink函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-6-3 05:29

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表