目录
1、Fatfs相关文件
fatfs的介绍这里就不过多赘述了,总之单片机上面用FatFS的比较多,已经趋于标准化了。
另外,本篇文章是基本是基于应用层的。对于ST的HAL库SDMMC部分代码,没有深入研究过(也是目前项目中SD卡的难点所在:在进行高速频繁的文件读写操作时,总是会出现失败现象,但是原因难以排查,只好在应用层加入了重试机制)。
如下图,分3部分:一是fatFs源码,二是STM32的sdio驱动层代码,三是STM32的HAL库文件。
常见的接口函数:
#define f_size(fp) ((fp)->obj.objsize)
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
文件系统的常用配置选型:
如下修改宏定义,(1)编码为437格式(通用英文格式编码),(2)修改长文件名支持方式3和最大长度,即堆内存方式。
2、驱动测试
如下做了一个简单的驱动测试函数,验证驱动有没有问题,思路很简单:
挂载文件系统->打开一个新文件->写入数据->关闭文件->卸载文件系统 。
uint8_t ToolingFatfsCommTest(void)
{
//测试fatfs文件读写功能
uint8_t byRev = 0;
uint32_t ulWriteNum;
FRESULT res;
FIL fd;
char test_file[] = "0:test.hex";
uint8_t test_data[256] = {0x00};
//1、挂载文件系统
if(f_mount(&SDFatFS,"0:",1) == FR_OK)
{
byRev++;//1-挂载成功
//log_output(LOG_TAG_POWER,LOG_LEVEL_NORMAL,"f_mount \r\n");
res = f_open(&fd,test_file,FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
if(res == FR_OK)
{
byRev++;//2-打开成功
//log_output(LOG_TAG_POWER,LOG_LEVEL_NORMAL,"f_open \r\n");
memset(test_data,0x7E,256);
res = f_write(&fd,test_data,256,&ulWriteNum);
if(res == FR_OK && ulWriteNum == 256)
{
byRev++;//3-写入成功
//log_output(LOG_TAG_POWER,LOG_LEVEL_NORMAL,"f_write \r\n");
}
f_close(&fd);
//log_output(LOG_TAG_POWER,LOG_LEVEL_NORMAL,"f_close \r\n");
}
f_mount(&SDFatFS,"0:",0);
}
if(byRev >= 3)
{
//log_output(LOG_TAG_POWER,LOG_LEVEL_NORMAL,"\r\nTooling FatFS Test OK\r\n");
return 1;
}
return 0;
}
3、重试机制
修改sd_diskio.c文件中的读、写函数,添加重试机制,解决偶发的读写错误时卡死的问题。
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
uint8_t i;
for(i=0;i<3;i++)
{
if(BSP_SD_ReadBlocks((uint32_t*)buff,
(uint32_t) (sector),
count, SD_TIMEOUT) == MSD_OK)
{
/* wait until the read operation is finished */
while(BSP_SD_GetCardState()!= MSD_OK)
{
}
res = RES_OK;
return res;
}
else
{
BSP_SD_Init();
}
}
return res;
}
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
uint8_t i;
for(i=0;i<3;i++)
{
res = (DRESULT)BSP_SD_WriteBlocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT);
if(res == MSD_OK)
{
/* wait until the Write operation is finished */
while(BSP_SD_GetCardState() != MSD_OK)
{
}
res = RES_OK;
return res;
}
else
{
BSP_SD_Init();
}
}
return res;
}
4、实现较为复杂的文件操作
4.1 获取磁盘容量
利用f_getfree函数,获取盘符的相关信息;
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);
其中nclst返回空闲集群的数量信息(空闲容量),fatfs返回文件系统的相关信息(使用容量)。
void GetSDCardFreeNum(uint32_t* freeSize,uint32_t* totalSize)
{
FRESULT res;
FATFS *fs = &SDFatFS;
DWORD freeClust = 0,freeNum = 0,totalNum = 0;
res = f_getfree("0:",&freeClust,&fs);
if(res == FR_OK)
{
totalNum = (fs->n_fatent-2)*fs->csize;//得到使用容量
freeNum = freeClust*fs->csize;//获取空闲容量
*freeSize = freeNum>>1;//单位是KB
*totalSize = totalNum>>1;//单位是KB
}
return ;
}
4.2 遍历目录,获取文件大小和数量,以及查找\删除等操作
//封装了一个小函数,获取完整的文件路径名
void GetFilePath(char *path,char *dirPath,char *fileName)
{
uint8_t byDirPathLen = 0,byFileNameLen = 0;
byDirPathLen = strlen(dirPath);
byFileNameLen = strlen(fileName);
memcpy(path,dirPath,byDirPathLen);
memcpy(path+byDirPathLen,"/",1);
memcpy(path+byDirPathLen+1,fileName,byFileNameLen);
return;
}
void GetDirectorySizeNum(char *dirPath,uint32_t* fileNum,uint32_t* totalSize)
{
DIR dir;
FRESULT res;
FILINFO FileInfo;
uint32_t ulFileNum = 0,ulTotalSize = 0;
char filepath[64] = {0};
res = f_opendir(&dir,dirPath);
if(res == FR_OK)
{
for(;;)
{
res = f_readdir(&dir,&FileInfo);
if(res != FR_OK || FileInfo.fname[0] == 0)
break;
else
{
//此处实现统计功能
ulFileNum++;//统计目录下面的文件个数
ulTotalSize += FileInfo.fsize;//统计文件的总大小
//此处实现删除指定文件功能
if(if(strncmp(FileInfo.fname,"0:test.hex",10) == 0))
{
GetFilePath(filepath,dirPath,FileInfo.fname);
res = f_unlink(filepath);
}
}
}
f_closedir(&dir);
}
*totalSize = ulTotalSize/1024;
*fileNum = ulFileNum;
}
4.3 认识文件权限和错误码
根据不同的权限,一般可以封装出来几种不同的函数用法:
1、以只读方式打开文件,读取数据(1、读取指定位置和长度的数据,2、读取全部数据);
2、创建一个新的文件或清除文件数据;
3、向文件中追加数据;
4、将数据写入文件中(1、持续写入,2、写入指定位置,3、整个写入);
5、对文件的数据计算校验值;
等等,根据自己的需要封装函数,实现起来都很简单。
错误码,如果出现FR_DISK_ERR磁盘错误,一般是因为驱动原因,需要定位底层驱动函数,或者检查是否存在冲突问题(比如高频率的使用SDIO和其它通讯接口时,往往会出现SDIO读写错误,简单的方法就是SDIO底层读写函数添加重试机制,同时调用Fatfs函数时屏蔽中断或者添加互斥机制),FR_OK就是无错误,其它错误码就是顾名思义的,按照含义来查找自己的程序为什么会出现这个错误即可。
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01 //只读权限
#define FA_WRITE 0x02 //只写权限
#define FA_OPEN_EXISTING 0x00 //打开已存在的文件
#define FA_CREATE_NEW 0x04 //创建新文件
#define FA_CREATE_ALWAYS 0x08 //创建新文件,若已存在则覆盖
#define FA_OPEN_ALWAYS 0x10 //打开一个新文件
#define FA_OPEN_APPEND 0x30 //打开一个文件,并将文件指针定位到末尾 即追加
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;