当前位置:网站首页>【FatFs】手动移植FatFs,将SRAM虚拟U盘
【FatFs】手动移植FatFs,将SRAM虚拟U盘
2022-07-24 05:20:00 【喵喵锤锤你小可爱】
【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移植步骤
- 得到FatFs源码,全部添加.c到工程中,并包含头文件路径
- 配置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格式化存储区。
- 实现diskio.c中的disk_ioctl、disk_write、disk_read、disk_initialize、disk_status几个函数即可。
- 测试。
4. STM32工程配置
直接使用STM32CubeMX生成一个工程,时钟和一些其他外设配置使用STM32CubeMX的NUCLEO默认的模板,这里配置了一个USB MSC用来方便在电脑上看到模拟出来的FAT存储器。




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

5. 实际实现代码
- 使用分散加载配置文件来管理内存,如图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};
- 因为内部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;
}
- 实现读写函数,记住的是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
- 然后是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;
}
- 实现过去时间的函数,不想真的实现就返回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 */
}
- 为了方便查看实现了一下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。


8. 一些不理解的问题
实验中总的分配了120KB的内存。下面是一些不太理解的问题,打算仔细读一下FAT文件系统的specs在来看看。
实验中发现修改GET_BLOCK_SIZE,使用f_mkfs格式化得到的模拟U盘在电脑上显示的大小不一样,越小得到的越大。但是总的有120K,实际最大显示88.5KB。
直接使用MSC(Mass Storage Class)来映射到电脑上管理显示的大小是正确的,格式化以后有100KB。
GET_BLOCK_SIZE 得到 256时f_mkfs格式化失败。

边栏推荐
- OSError: [WinError 127] 找不到指定的程序。Error loading “caffe2_detectron_ops.dll“ or one of its dependencies
- Unknown collation: ‘utf8mb4_ 0900_ ai_ Solution of CI '
- 《信号与系统》(吴京)部分课后习题答案与解析
- labelme转voc代码中的一个小问题
- 数据库连接数过大
- Numpy数组广播规则记忆方法 array broadcast 广播原理 广播机制
- Logical structure of Oracle Database
- Watermelon book / Pumpkin book -- Chapter 1 and 2 Summary
- ThreadLocal存储当前登录用户信息
- 推荐一款完全开源,功能丰富,界面精美的商城系统
猜你喜欢

如何解决训练集和测试集的分布差距过大问题

【activiti】流程实例

PLSQL query data garbled

《统计学习方法(第2版)》李航 第22章 无监督学习方法总结 思维导图笔记

【activiti】个人任务

Flink sql-client.sh use

"Statistical learning methods (2nd Edition)" Li Hang Chapter 14 clustering method mind map notes and after-school exercise answers (detailed steps) K-means hierarchical clustering Chapter 14

Multi merchant mall system function disassembly lecture 06 - platform side merchant settlement agreement

多商户商城系统功能拆解10讲-平台端商品单位

多商户商城系统功能拆解12讲-平台端商品评价
随机推荐
《机器学习》(周志华)第2章 模型选择与评估 笔记 学习心得
Likeshop100%开源无加密-B2B2C多商户商城系统
找ArrayList<ArrayList<Double>>中出现次数最多的ArrayList<Double>
Brief introduction of [data mining] cluster analysis
详谈数据同步工具ETL、ELT,反向ETL
Too many database connections
读取csv文件的满足条件的行并写入另一个csv中
The SaaS mall system of likeshop single merchant is built, and the code is open source without encryption.
多商户商城系统功能拆解09讲-平台端商品品牌
OpenWRT快速配置Samba
Authorized access to MySQL database
Loss after cosine annealing decay of learning rate
ThreadLocal stores the current login user information
SSM项目配置中问题,各种依赖等(个人使用)
Could not load library cudnn_cnn_infer64_8.dll. Error code 126Please make sure cudnn_cnn_infer64_8.
Multi merchant mall system function disassembly Lecture 14 - platform side member level
《统计学习方法(第2版)》李航 第22章 无监督学习方法总结 思维导图笔记
Recommend a fully open source, feature rich, beautiful interface mall system
《机器学习》(周志华) 第4章 决策树 学习心得 笔记
Xshell远程访问工具