【FatFs】手动移植FatFs,将SRAM转化为文件系统

1. 实验环境

  • Keil5 MDK-ARM,编译器使用ARM Compiler V6.16
  • NUCLEO-H723ZG
  • STM32CubeMX 6.5.0
  • Apr 17, 2021 FatFs R0.14b

2. 理论部分

因为FatFs和硬件本身没有直接关系,可以将自己想要的任何可以读写的“存储”格式化为FAT文件系统(格式化本身其实就是一个读写过程,实际上就是往“存储”中写入一定的“规范”来让程序按照预定义的方法来读写内存),所以将内置Flash、SRAM等这些存储使用FAT文件系统管理也是可行的。

这里使用SRAM作为物理存储,需要SRAM比较大的单片机,正好手上有一块NUCLEO-H723ZG,主控为STM32H723ZGT6,有总共564 Kbytes SRAM,下图为其RAM的内存地址映射表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZEB4v6rp-1658303686916)(https://raw.githubusercontent.com/MisakaMikoto128/TyporaPic/main/typora/Untitled.png?token=AOLAY4Q52SBW6RM4JMMSNZLC26XV6)]

这里我希望尽量少占用些内存,避免可能的问题。因为FatFs的sector size只能取512, 1024, 2048 and 4096 bytes,所以选择最小的sector size : 512 bytes。其次为能够正确使用f_mkfs格式化,sdisk_ioctl的实现中GET_SECTOR_COUNT返回的大小至少要为191= 128 + N_SEC_TRACK(:=63),这个后面再说。这里实际上使用了512bytes * 240 的SRAM。

3. FatFs移植步骤

  1. 得到FatFs源码,全部添加.c到工程中,并包含头文件路径
  2. 配置ffconf.h,本例中其他部分不变,只配置了FF_USE_MKFS为1,F_MAX_SS=FF_MIN_SS=512不变,FF_USE_FASTSEEK为1,FF_USE_LABEL为1,其中FF_USE_MKFS、F_MAX_SS、FF_MIN_SS是最重要的,因为要使用f_mkfs格式化存储区。
  3. 实现diskio.c中的disk_ioctl、disk_write、disk_read、disk_initialize、disk_status几个函数即可。
  4. 测试。

4. STM32工程配置

直接使用STM32CubeMX生成一个工程,时钟和一些其他外设配置使用STM32CubeMX的NUCLEO默认的模板,这里配置了一个USB MSC用来方便在电脑上看到模拟出来的FAT存储器。

最够点击GENERATE CODE,打开得到的工程。添加FatFs源码到工程。工程目录如图。

5. 实际实现代码

  1. 使用分散加载配置文件来管理内存,如图RAM_D1用来分配内存给一个数组,用来模拟FatFs的物理存储。这个数组为ram_disk_buf,使用二维数组单纯就是为了好理解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4J8GKfNk-1658303686928)(https://raw.githubusercontent.com/MisakaMikoto128/TyporaPic/main/typora/Untitled%206.png?token=AOLAY4XKKTD6OA5DLMKENKTC26XVQ)]

下面的配置都在diskio.c中操作

/* Definitions of physical drive number for each drive */
#define DEV_RAM     0   /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC     1   /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB     2   /* Example: Map USB MSD to physical drive 2 */#include <stdio.h>
#define ram_disk_sector_num 240
__attribute__((section(".RAM_D1"))) uint8_t ram_disk_buf[ram_disk_sector_num][FF_MAX_SS] = {0};
  1. 因为内部SRAM没有必要初始化,状态也都是正常的,不会无法读写什么的这两个函数直接返回RES_OK。
/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/DSTATUS disk_status (BYTE pdrv       /* Physical drive nmuber to identify the drive */
)
{return RES_OK;
}/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/DSTATUS disk_initialize (BYTE pdrv               /* Physical drive nmuber to identify the drive */
)
{return RES_OK;
}
  1. 实现读写函数,记住的是FatFs寻址使用的是逻辑块寻址LBA (Logical Block Addressing),这里的块知道就是sector,也就是寻址的最小单位是sector,每次读需要读取一整个sector,写需要写一整个sector,擦除的大小待会再讨论。这个sector不一定必须要和实际使用的硬件如Flash相同,这取决于速度和存储效率的考量。要理解这个需要先了解一下物理存储设备。
    比如Flash因为每次写入都需要先擦除,导致了这个擦除块就是最小的写单元,这个擦除块的名字可能叫Page(如STM32F103C8T6内置Flash),可能叫Flash(如STM单片机的F4和H7系列),大小也各不相同,但是不能把硬件如Flash的sector痛FatFs的sector搞混,它们是在各自技术下的叫法罢了。
    比如说自己移植FatFs配置的sector size = 1024bytes,而实际存储的Flash最小擦除单位为一个sector = 512bytes,那么实际上FatFs再调用disk_read或者disk_write读写存储的时候应带读写两个Flash的sector。当然再读写Flash的时候也导致了一个问题,有的Falsh(如H723内置Flash Sector Size = 128KB)的擦除单位太大,导致不能直接移植。
    这里设置F_MAX_SS=FF_MIN_SS=512bytes,SRAM的话没有Flash需要擦除才能写的烦恼,直接模拟为一个sector为512bytes,存储效率最高。
void VirualRAMDiskRead(uint8_t *buff,uint32_t sector,uint32_t count){for(int i = sector; i < sector+count; i++){for(int j = 0; j < FF_MAX_SS; j++){buff[(i - sector)*FF_MAX_SS+j] = ram_disk_buf[i][j];}}
}void VirualRAMDiskWrite(const uint8_t *buff,uint32_t sector,uint32_t count){for(int i = sector; i < sector+count; i++){for(int j = 0; j < FF_MAX_SS; j++){ram_disk_buf[i][j] = buff[(i - sector)*FF_MAX_SS+j];}}
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/DRESULT disk_read (BYTE pdrv,        /* Physical drive nmuber to identify the drive */BYTE *buff,        /* Data buffer to store read data */LBA_t sector,   /* Start sector in LBA */UINT count     /* Number of sectors to read */
)
{VirualRAMDiskRead(buff,sector,count);return RES_OK;
}/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/#if FF_FS_READONLY == 0DRESULT disk_write (BYTE pdrv,          /* Physical drive nmuber to identify the drive */const BYTE *buff,  /* Data to be written */LBA_t sector,       /* Start sector in LBA */UINT count         /* Number of sectors to write */
)
{VirualRAMDiskWrite(buff,sector,count);return RES_OK;
}#endif
  1. 然后是GET_SECTOR_COUNT 用于f_mkfs格式化时获取可用的sector的数量,32bit-LBA的情况下至少为191,这个参数也决定了总的FAT文件系统管理的容量,只管来说就是虚拟出来的存储容量(This command is used by f_mkfs and f_fdisk function to determine the size of volume/partition to be created. It is required when FF_USE_MKFS == 1)。GET_SECTOR_SIZE 这个再F_MAX_SS=FF_MIN_SS的情况下没有作用。GET_BLOCK_SIZE 这个是最下的擦除大小,单位是sector,如果不知道多大或者使用非Flash存储媒介就返回1。
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/DRESULT disk_ioctl (BYTE pdrv,       /* Physical drive nmuber (0..) */BYTE cmd,      /* Control code */void *buff        /* Buffer to send/receive control data */
)
{DRESULT res = RES_ERROR;switch (cmd){/* Make sure that no pending write process */case CTRL_SYNC:res = RES_OK;break;/* Get number of sectors on the disk (DWORD) */case GET_SECTOR_COUNT :*(DWORD*)buff = ram_disk_sector_num;res = RES_OK;break;/* Get R/W sector size (WORD) */case GET_SECTOR_SIZE :*(DWORD*)buff = FF_MAX_SS;res = RES_OK;break;/* Get erase block size in unit of sector (DWORD) */case GET_BLOCK_SIZE :*(DWORD*)buff = 1;res = RES_OK;break;default:res = RES_PARERR;}return res;
}
  1. 实现过去时间的函数,不想真的实现就返回0,但是必须要有这个函数的实现,否则会报错。
/*** @brief  Gets Time from RTC* @param  None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return 0;/* USER CODE END get_fattime */
}
  1. 为了方便查看实现了一下USB MSC的接口。这里不详细讲解,其实和FatFs在移植上很像。这些操作都在usbd_storage_if.c中实现。
/** @defgroup USBD_STORAGE_Private_Defines* @brief Private defines.* @{*/#define STORAGE_LUN_NBR                  1    //lun数量
/*
STORAGE_BLK_NBR
使用FatFs的f_mkfs格式化时这个参数没有什么作用,因为已经格式化好了。
但是不能小于160 = 128+32,小于的话电脑直接无法载入存储。
在没有使用FatFs的f_mkfs格式化时,这个参数决定了电脑上你格式化时显示的容量。
*/
#define STORAGE_BLK_NBR                  240 //对应FatFs的sector counter
/*
STORAGE_BLK_SIZ
这个参数不能乱写,主要时底层实现的时候是按照512来计算的,写错的话电脑直接无法载入存储
*/
#define STORAGE_BLK_SIZ                  512 //对应FatFs的sector size

参数设置有问题的情况

然后实现一下STORAGE_Read_HS和STORAGE_Write_HS即可

/*** @brief  Reads data from the medium.* @param  lun: Logical unit number.* @param  buf: data buffer.* @param  blk_addr: Logical block address.* @param  blk_len: Blocks number.* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_Read_HS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 13 */UNUSED(lun);VirualRAMDiskRead(buf,blk_addr,blk_len);return (USBD_OK);/* USER CODE END 13 */
}/*** @brief  Writes data into the medium.* @param  lun: Logical unit number.* @param  buf: data buffer.* @param  blk_addr: Logical block address.* @param  blk_len: Blocks number.* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_Write_HS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 14 */UNUSED(lun);VirualRAMDiskWrite(buf,blk_addr,blk_len);return (USBD_OK);/* USER CODE END 14 */
}

6. 测试代码

main.c中实现

/* USER CODE BEGIN 2 */fatfs_register();fatfs_rw_test("0:2021.txt");fatfs_rw_test("0:2021.txt");fatfs_rw_test("0:2022.txt");fatfs_rw_test("0:2023.txt");fatfs_rw_test("0:2024.txt");fatfs_rw_test("0:2025.txt");fatfs_rw_test("0:2026.txt");fatfs_rw_test("0:2027.txt");//fatfs_unregister();/* USER CODE END 2 */
FRESULT  f_res;
FATFS fs;
FIL file;
BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
void fatfs_register()
{f_res = f_mount(&fs, "0:", 1);if(f_res == FR_NO_FILESYSTEM){printf("no file systems\r\n");f_res = f_mkfs("0:", 0, work, sizeof work);if(f_res != FR_OK)printf("mkfs failed err code = %d\r\n",f_res);elseprintf("init file systems ok\r\n");}else if(f_res != FR_OK){printf("f_mount failed err code = %d\r\n",f_res);}else{printf("have file systems\r\n");}
}
void fatfs_unregister()
{f_res = f_mount(0, "0:", 0); /* Unmount the default drive */if(f_res != FR_OK){printf("file systems unregister failed err code = %d\r\n",f_res);}else{printf("file systems unregister ok\r\n");}
}void fatfs_rw_test(const char * path)
{uint8_t w_buf[] = "this is a test txt\n now time is 2020-9-17 22:13\nend";uint8_t r_buf[200] = {0};UINT w_buf_len = 0;UINT r_buf_len = 0;f_res = f_open(&file, path, FA_OPEN_ALWAYS | FA_WRITE | FA_READ);if(f_res != FR_OK){printf("open %s failed err code = %d\r\n",path,f_res);}else{printf("open %s  ok\r\n",path);}f_res = f_write(&file, w_buf, sizeof(w_buf), &w_buf_len);if(f_res != FR_OK){printf("write %s failed  err code = %d\r\n",path,f_res);}else{printf("write %s  ok   w_buf_len = %d\r\n", path,w_buf_len);f_lseek(&file, 0);f_res = f_read(&file, r_buf, f_size(&file), &r_buf_len);if(f_res != FR_OK){printf("read %s failed f_res = %d\r\n", path,f_res);}else{printf("read %s  ok   r_buf_len = %d\r\n", path,r_buf_len);}}printf("file %s at sector = %d,cluster = %d\r\n",path,file.sect,file.clust);f_close(&file);
}

7. FatFs中sector数量的最小限制

查看ff.c中f_mkfs的实现可以看到关于卷大小的限制的代码,b_vol: basic vloume size, sz_vol: size of volume。

8. 一些不理解的问题

实验中总的分配了120KB的内存。下面是一些不太理解的问题,打算仔细读一下FAT文件系统的specs在来看看。

实验中发现修改GET_BLOCK_SIZE,使用f_mkfs格式化得到的模拟U盘在电脑上显示的大小不一样,越小得到的越大。但是总的有120K,实际最大显示88.5KB。

直接使用MSC(Mass Storage Class)来映射到电脑上管理显示的大小是正确的,格式化以后有100KB。

GET_BLOCK_SIZE 得到 256时f_mkfs格式化失败。

【FatFs】手动移植FatFs,将SRAM虚拟U盘相关推荐

  1. xv6移植Fatfs

    写在前面 这是一篇记录在xv6上移植Fatfs文件系统过程的日志,在写下这段话时,我还没有开始移植,不知道会遇到什么困难.因为不是移植成功后再回顾过程写的,做到哪里写到哪里,所以组织上会很乱.但是可以 ...

  2. 2021-08-11 TM32F103 Buffer FatFs 文件系统移植

    FatFs 本文展示了STM32 FatFs文件系统移植 内容涉及 : FatFs 文件系统移植 SPI函数移植过程 SPI字节数据模拟输出独写 缓存读写 USART串口的识别 IO口输入输出 按键的 ...

  3. elm FatFs文件系统移植总结

    1 前言 本文将根据我的一些理解,针对elm FatFs文件系统做一个初步总结. 2 elm FatFs文件系统介绍 顾名思义FatFs文件系统就是针对FAT文件系统来的,主要是应用于MCU中,STM ...

  4. 基于SD卡的FatFs文件系统(FatFs移植到STM32)

    平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线 工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c, ...

  5. 【STM32CubeMx你不知道的那些事】第十章:STM32CubeMx的SPI外置FLASH(W25Q128)+文件系统(FATFS)+虚拟U盘

      这一张我们主要讲解一下STM32CUBEMX新版本 片外FLASH(W25Q128)+FATFS文件系统+虚拟U盘. 一.准备工作 这里我们要想配置SPI和文件系统 并验证需要的准备工作如下: 1 ...

  6. STM32Cube MX USB虚拟U盘+FATFS+W25Q128

    第一次写CSDN,把这两天做的一个小实验记个笔记.写的不好请见谅,有错误欢迎指正,欢迎讨论.在做之前也参考其他博主的一些文章Carry_王的博客 USB基本概念不做介绍,不懂的可以先去了解,主要说明实 ...

  7. 文件系统FATFS的移植教程

    最新FatFs:http://elm-chan.org/fsw/ff/00index_e.html 一.FATFS文件系统简介 FATFS是面向小型嵌入式系统的一种通用的FAT文件系统.它完全是由C语 ...

  8. STM32CubeMX系列09——SDIO(SD卡读写、SD卡移植FATFS文件系统)

    文章目录 1. 准备工作 1.1. 简单扫盲 1.1.1. SD卡 1.1.2. TF卡 1.1.3. SDIO接口 1.2. 所用硬件及原理图 2. 创建工程 2.1. 选择主控 2.2. 系统配置 ...

  9. 模拟SPI进行TF卡操作+Fatfs文件系统移植

    FATFS版本:FATFS R0.13b SD卡容量:16G 概述 本文的重点是进行Fatfs文件系统的移植和初步的使用.TF卡的操作实际上是指令操作,即你想它发送固定的CMD指令,它接收到指令给你返 ...

最新文章

  1. 免费!!3天,吃透JVM!(限时领)
  2. python 习题集锦
  3. linux 手动释放内存
  4. SAP ui5 sap.ui.getCore().getUIArea
  5. Django-内置用户系统
  6. Android 调用原生API获取地理位置和经纬度,判断所在国家
  7. MathType公式编辑器的下载安装及导入Word
  8. c语言程序设计商品管理系统
  9. Linux系统的安装与配置
  10. 栈内存与堆内存的简单理解
  11. html图片缩小属性,CSS属性实现同比例缩小图片
  12. MOOS程序解析记录(1)
  13. C语言-打卡机(sqlite数据库、多线程)
  14. 联想收购方正,不是不可能
  15. bzoj 2330 / AcWing 368 银河 差分约束系统+tarjan缩点+拓扑排序
  16. 力扣 面试题 17.09. 第 k 个数
  17. 什么是微型计算机的字长,计算机的字长是指什么
  18. STC15单片机I/O口的四种模式
  19. Android 电池管理系统
  20. U盘有多个分区怎么合并

热门文章

  1. 用户注册后是如何进行激活的,为什么需要激活
  2. AlexNet分类Fashi-MNIST(Pytorch实现)
  3. #19ACM第一次招新补题赛de题解呐#
  4. 淘宝自动登录2.0,新增Cookies序列化
  5. 为什么强烈推荐 Java 程序员使用 Google Guava 编程!
  6. Python使用PuLP第三方库解决线性规划问题
  7. Easyrecovery教您如何一招恢复手机误删照片!
  8. 【线上问题】线上故障分析-故障分级,原因,分类,混沌工程,排除方法
  9. 51地图 点击时取经纬度
  10. vue——axios请求成功却进入catch的原因