【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中操作
#define DEV_RAM 0
#define DEV_MMC 1
#define DEV_USB 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。
DSTATUS disk_status (
BYTE pdrv
)
{
return RES_OK;
}
DSTATUS disk_initialize (
BYTE pdrv
)
{
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];
}
}
}
DRESULT disk_read (
BYTE pdrv,
BYTE *buff,
LBA_t sector,
UINT count
)
{
VirualRAMDiskRead(buff,sector,count);
return RES_OK;
}
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv,
const BYTE *buff,
LBA_t sector,
UINT count
)
{
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。
DRESULT disk_ioctl (
BYTE pdrv,
BYTE cmd,
void *buff
)
{
DRESULT res = RES_ERROR;
switch (cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_COUNT :
*(DWORD*)buff = ram_disk_sector_num;
res = RES_OK;
break;
case GET_SECTOR_SIZE :
*(DWORD*)buff = FF_MAX_SS;
res = RES_OK;
break;
case GET_BLOCK_SIZE :
*(DWORD*)buff = 1;
res = RES_OK;
break;
default:
res = RES_PARERR;
}
return res;
}
- 实现过去时间的函数,不想真的实现就返回0,但是必须要有这个函数的实现,否则会报错。
DWORD get_fattime(void)
{
return 0;
}
- 为了方便查看实现了一下USB MSC的接口。这里不详细讲解,其实和FatFs在移植上很像。这些操作都在usbd_storage_if.c中实现。
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 240
#define STORAGE_BLK_SIZ 512
参数设置有问题的情况
然后实现一下STORAGE_Read_HS和STORAGE_Write_HS即可
int8_t STORAGE_Read_HS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
UNUSED(lun);
VirualRAMDiskRead(buf,blk_addr,blk_len);
return (USBD_OK);
}
int8_t STORAGE_Write_HS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
UNUSED(lun);
VirualRAMDiskWrite(buf,blk_addr,blk_len);
return (USBD_OK);
}
6. 测试代码
main.c中实现
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");
FRESULT f_res;
FATFS fs;
FIL file;
BYTE work[FF_MAX_SS];
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);
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格式化失败。
|