Fatfs 文件系统移植
开发环境基本介绍
本次开发建立在GD32F470开发板上,利用的语言是C语言,开发编译平台为keil4平台,利用的程序模板框架为GD32官网可下载的示例代码:🥸🥸🥸
GD32F4xx_Demo_Suites_V2.6.1\GD32470I_EVAL_Demo_Suites\
Projects\17_SDIO_SDCardTest
目标是完成利用Fatfs 文件系统,实现SD卡的文件的读取,写入等操作。
FatFs基本介绍😵💫😵💫
FatFs是一个轻量级的嵌入式文件系统,适用于嵌入式设备的闪存、磁盘和SD卡等存储介质。它可以支持FAT12、FAT16和FAT32格式的文件系统,并且具有非常高的移植性和可扩展性。
FatFs的主要特点包括:
-
代码精简:FatFs的代码量非常小,可以很容易地嵌入到嵌入式设备中。
-
高度可配置:FatFs支持FAT12、FAT16和FAT32格式的文件系统,可以根据设备的需要进行配置。
-
高度可移植:FatFs的核心代码与底层驱动分离,因此可以很容易地移植到不同的平台上。
-
支持长文件名:FatFs支持长文件名,可以在设备中存储具有更有描述性的文件名。
-
多任务支持:FatFs可以在多个任务之间共享,支持多个文件同时打开和读写操作。
-
支持目录:FatFs支持目录和子目录的创建和操作。
-
具有高效性能:FatFs具有很高的文件读写性能和小的存储占用,适合嵌入式系统的应用。
总之,FatFs是一个轻量级、可配置、可移植和高效的嵌入式文件系统,非常适合嵌入式设备的应用。
具体流程
下载FatFs官方代码
官网链接:链接
下载好的文件解压后,应该是如下图所示:
其中document告诉我们具体的详细信息,source是源代码,是我们重点需要关注的对象。😼😼
将source文件夹整个复制到工程文件夹下,在keil中添加项目文件,最后会得到如下图所示的工程文件结构:
其中各个文件的作用此处不再赘述。
补全函数
根据官网描述,想要移植文件系统,总共需要重写5个函数,关闭一个功能。
关闭get_fattime函数
修改:找到ffconf.h中的FF_FS_NORTC,将其改为1,如下:
#define FF_FS_NORTC 1
补全其余5个函数:
其余5个函数全在diskio.c文件中。
由于我们只需要读取SD卡,因此程序可以将其他的设备统一默认为SD卡读写,我个人写的函数如下所示:
- disk_status :
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
uint32_t *psdstatus;
sd_error_enum card_state;
if (pdrv == 0)
{
card_state = sd_sdstatus_get(psdstatus);
if (card_state == SD_OK) return RES_OK;
else return RES_ERROR;
}
return status;
}
此处有坑! 注意好,因为GD32的sd检查返回值和Fatfs函数期望的返回值之间有区别,不能直接返回sd_sdstatus_get的检测结果,要用一个if判断!!! 后面代码同理😿😿😿😿
- disk_initialize
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return 0;
}
这里解释一下为什么只要return 0就可以了,在fatfs文件系统中,每一个步骤没有问题返回的都是宏定义RES_OK,而这个宏定义对应的数字为0。
另外,而在该程序中,我们用的是GD32的官方用例,用例中的main函数部分就已经把初始化给完成了,因此只要在此返回0就可以了。
- disk_read
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 */
)
{
DRESULT res;
int result;
switch (pdrv) {
default:
result = sd_block_read((uint32_t*)buff, (uint32_t)(sector * SD_BLOCKSIZE), SD_BLOCKSIZE);
// //printf("flag22221 , result is %d\n",result); // result is 29 == SD_OK
if ( result == SD_OK )
return RES_OK;
}
return RES_PARERR;
}
- disk_write
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 */
)
{
DRESULT res;
int result;
switch (pdrv) {
default:
result = sd_block_write((uint32_t*)buff, (uint32_t)(sector * SD_BLOCKSIZE), SD_BLOCKSIZE);
// res = sd_block_write((uint32_t *)(&buff[0]),sector << 9 ,SECTOR_SIZE); // todo:
if ( result == SD_OK )
res = RES_OK;
return res;
}
return RES_PARERR;
}
- disk_ioctl
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
return RES_OK;
}
值得说明的是:如果你的文件系统出现了
f_mount无法挂载,f_read无法读写等情况,且返回值为
#define FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
请务必检查disk_read函数,因为fs系统想要挂载磁盘,必须先从磁盘中读取出分区信息,根据函数追踪如下所示,f_mount调用的函数栈如下:
static FRESULT mount_volume ()
|
find_volume(fs, LD2PT(vol))
||
||
fmt = check_fs(fs, 0);
|||
|||
if (move_window(fs, sect) != FR_OK) return 4; /* Load the boot sector */
||||
||||
if (disk_read(fs->pdrv, fs->win, sect, 1) != RES_OK)
}
|||||
|||||
// result = sd_block_read((uint32_t *)(&buff[0]),sector << 9 ,SECTOR_SIZE); // todo:
result = sd_block_read((uint32_t*)buff, (uint32_t)(sector * SD_BLOCKSIZE), SD_BLOCKSIZE);
//printf("flag22221 , result is %d\n",result); // result is 29 == SD_OK
return result;
// 问题出在,gd32返回的是SD_OK,
//而如果直接返回result,则返回值是29,
//而ff系统的resok返回的是RES_OK,对应的值为0
最后就是disk_read函数出错导致的问题。
文件系统实操
在完成上述操作后,就可以进行文件系统实操环节了,最后输出如下图:
(不要在意flag,懒得删了, )😼😼😼
最后贴出测试的代码:
void function(){
// disk_initialize(3);
res_sd=f_mount(&fs,"0:",1);
if (res_sd != FR_OK){
printf("mount fail , error code : %d " , res_sd);
while(1);
}else{
printf("mount success");
// while(1)
}
printf("\r\n****** file writing test... ******\r\n");
res_sd = f_open(&fnew, "0:new.txt", FA_CREATE_ALWAYS | FA_WRITE );
if ( res_sd == FR_OK )
{
/* 将指定存储区内容写入到文件内 */
res_sd=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
if(res_sd==FR_OK)
{
gd_eval_led_off(LED1);
gd_eval_led_off(LED3);
printf("writing success\n");
}
else
{
printf("writing fail\n");
gd_eval_led_off(LED1);
}
/* 不再读写,关闭文件 */
// f_sync(&fnew);
// f_close(&fnew);
// res_sd_write = f_sync(&fnew);
}
else
{
printf("failed to open file,error code:%d\r\n" , res_sd);
}
/*------------------- 文件系统测试:读测试 ------------------------------------*/
printf("****** start reading test... ******\r\n");
res_sd = f_open(&ftest, "0:test.txt", FA_OPEN_EXISTING | FA_READ);
if(res_sd == FR_OK)
{
printf("open success\r\n");
res_sd = f_read(&ftest, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(res_sd==FR_OK)
{
printf("manage to read the data: %d\r\n",fnum);
printf("the data is:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("fail to read file(%d)\n",res_sd);
}
}
else
{
printf("fail to open file, error code : %d\r\n" , res_sd);
}
/* 不再读写,关闭文件 */
f_close(&ftest);
printf("end test\n");
f_close(&fnew);
printf("end file\n");
/* 不再使用文件系统,取消挂载文件系统 */
f_mount ( NULL,"0:",1);
}
写在最后,还有一个f_close的坑😭😭😭
f_close() 函数会调用数据同步函数,此时会向sd卡真正写入数据,之前的f_write()函数并不会真正向sd卡写入数据,只是把数据放在缓存中;
我在完成前面所有操作后,猛然发现程序运行到 f_close()后,就会卡死。研究以后发现,问题出在:
由于开发板的传输速率和SD卡的写入速率不匹配,导致写入失败(或者写入时会占用所有CPU?具体原理我也不太懂)
此时需要调整一下SD卡程序的时钟频率,具体调整位置如下:
在GD32470I_EVAL_Demo_Suites\Projects\17_SDIO_SDCardTest\Soft_Drive\ sdcard.c 中:
#define SD_CLK_DIV_TRANS ((uint16_t)0x0008)
/* SD clock division in transmission phase */
调整上述宏定义的数值,把数值调大即可(上述代码是调过以后的,从2变为8)。
至此文件系统移植结束
感谢各位观看,有帮助的话点个赞吧❤️❤️❤️