【FatFs】手动移植FatFs,将SRAM虚拟U盘

【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存储器。

Untitled

在这里插入图片描述

Untitled

Untitled

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

Untitled

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 == 0

DRESULT 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);
        else
            printf("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。

Untitled

Untitled

8. 一些不理解的问题

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

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

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

GET_BLOCK_SIZE 得到 256时f_mkfs格式化失败。
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值