经过上一节的分析,我们对文件系统有一定的理解了,这一节给大家介绍怎么把FatFs文件系统的这些代码移植到STM32S上,然后STM32利用这一些代码或者函数,以文件的格式对FLASH进行读写数据。
实则对diskio.c提供一些函数接口。
首先将 ff11a\src文件夹拷贝至user底下,重命名为fatFs,以方便我们后续操作
移植文件系统主要就是实现底层disk函数的具体功能
一、底层disk接口程序API配置
1、首先将diskio.c和ff.c包含到工程中,如图所示:
2、添加完成之后,点击编译,报错找不到头文件,我们需要将diskio.c官方测试样例的三个头文件注释掉
3、将diskio.c中的result = ATA_disk_status();、result = MMC_disk_status();和result = USB_disk_status();注释掉
4、注释完之后,再次编译,找不到get_fattime,我们需要手动将get_fattime()函数添加到文件中
//返回时间
DWORD get_fattime (void)
{
//这个我不用 所以没有实现
return 0;}
5、可以发现diskio.h中官方已经定义了三个物理存储介质编号ATA、MMC和USB,diskio中的所有存储介质操作函数通过传入参数判断区分具体操作哪个存储介质,我用的SD卡和FLASH就将之前定义的宏注释掉,重新定义自己的宏,并将所有底层操作函数中所有的switch case中的原存储介质替换掉
6、首先我们来看disk_initialize函数,只有一个参数用于传入存储设备编号,编号用于区分操作的存储器,返回值返回当前的存储器状态。该函数用于初始化存储设备,并使设备进入读写可用的状态,初始化完之后用于返回一个状态值来告诉上层的调用者。
该函数用于被FatFs module(中间层)调用,不能直接使用,需要使用中间层的f_mount挂载函数来调用(我们初始化文件系统都通过使用f_mount来实现)
(1)首先将SPI_FLASH_Init(),函数添加到 case SPI_FLASH :中。
(2)接着要返回一个状态,但我们仅仅通过SPI_FLASH_Init()不知道是否初始化FLASH成功。因此我们通过SPI_FLASH_ReadID()函数通过判断读取设备号是否成功来判断,将SPI_FLASH_ReadID()放在“ DSTATUS disk_status (BYTE pdrv){}”状态读取函数中
/*-----------------------------------------------------------------------*/
/* 获取设备状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE pdrv /* 物理编号 */
)
{DSTATUS status = STA_NOINIT;switch (pdrv) {case ATA: /* SD CARD */break;case SPI_FLASH: /* SPI Flash状态检测:读取SPI Flash 设备ID */if(sFLASH_ID == SPI_FLASH_ReadID()){/* 设备ID读取结果正确 */status &= ~STA_NOINIT;}else{/* 设备ID读取结果错误 */status = STA_NOINIT;;}break;default:status = STA_NOINIT;}return status;
}
(4)如果我们不确定FLASH是否处于低功耗模式,我们需要在init后面添加SPI_Flash_WAKEUP()函数来确保FLASH处于正常功耗模式(因为FLASH在低功耗状态下,无法正常工作),所以disk_initializ函数的写法如下:
*-----------------------------------------------------------------------*/
/* 设备初始化 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (BYTE pdrv /* 物理编号 */
)
{uint16_t i;DSTATUS status = STA_NOINIT; switch (pdrv) {case ATA: /* SD CARD */break;case SPI_FLASH: /* SPI Flash */ /* 初始化SPI Flash */SPI_FLASH_Init();/* 延时一小段时间 */i=500;while(--i); /* 唤醒SPI Flash */SPI_Flash_WAKEUP();/* 获取SPI Flash芯片状态 */status=disk_status(SPI_FLASH);break;default:status = STA_NOINIT;}return status;
}
7、继续我们来配置disk_read()函数
disk_read()函数如下:
用于读取扇区(一个或者多个),第一个参数为设备号;第二个参数为读取扇区的起始地址,读取完数据后通过指针返回给上层,具体传入数组或指针由上层定义;第三个参数为开始的扇区号;第四个个参数为读取的扇区个数。
通过调用void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);函数来实现扇区的读取。通过disk_read()中的sector扇区号来计算出ReadAddr起始地址,通过count扇区个数来算出NumByteToRead要读取多少个字节。一个扇区4096Byte(4KB),一个块有16个扇区,但是此处扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间
通过调用SPI_FLASH_BufferRead()函数,并算出对应参数。默认读取成功,返回值为RES_OK
/*-----------------------------------------------------------------------*/
/* 读扇区:读取扇区内容到指定存储区 */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (BYTE pdrv, /* 设备物理编号(0..) */BYTE *buff, /* 数据缓存区 */DWORD sector, /* 扇区首地址 */UINT count /* 扇区个数(1..128) */
)
{DRESULT status = RES_PARERR;switch (pdrv) {case ATA: /* SD CARD */break;case SPI_FLASH:/* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */sector+=512; SPI_FLASH_BufferRead(buff, sector <<12, count<<12);status = RES_OK;break;default:status = RES_PARERR;}return status;
}
8、接着配置disk_write()函数,注意使用写功能,必须将_FS_READONLY置0,否则只支持只读
/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上 */
/*-----------------------------------------------------------------------*/
#if _USE_WRITE
DRESULT disk_write (BYTE pdrv, /* 设备物理编号(0..) */const BYTE *buff, /* 欲写入数据的缓存区 */DWORD sector, /* 扇区首地址 */UINT count /* 扇区个数(1..128) */
)
{uint32_t write_addr; DRESULT status = RES_PARERR;if (!count) {return RES_PARERR; /* Check parameter */}switch (pdrv) {case ATA: /* SD CARD */ break;case SPI_FLASH:/* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */sector+=512;write_addr = sector<<12; SPI_FLASH_SectorErase(write_addr);SPI_FLASH_BufferWrite((u8 *)buff,write_addr,count<<12);status = RES_OK;break;default:status = RES_PARERR;}return status;
}
#endif
9、再来配置disk_ioctl函数,
在初始化设备前,必须将存储设备进行格式化,建立好文件信息结构,从而才能实现对设备存储内容以文件格式进行读写。
第一个参数为设备号;第二个参数为cmd命令号,文件系统通过命令号来告诉底层,比如获取当前设备的容量,某个文件的大小等等(底层通过switch...case...来确定不同的返回值);第三个参数buff既可以作为输入也可以作为输出
/*-----------------------------------------------------------------------*/
/* 其他控制 */
/*-----------------------------------------------------------------------*/#if _USE_IOCTL
DRESULT disk_ioctl (BYTE pdrv, /* 物理编号 */BYTE cmd, /* 控制指令 */void *buff /* 写入或者读取数据地址指针 */
)
{DRESULT status = RES_PARERR;switch (pdrv) {case ATA: /* SD CARD */break;case SPI_FLASH:switch (cmd) {/* 扇区数量:1536*4096/1024/1024=6(MB) */case GET_SECTOR_COUNT:*(DWORD * )buff = 1536; break;/* 扇区大小 */case GET_SECTOR_SIZE :*(WORD * )buff = 4096;break;/* 同时擦除扇区个数 */case GET_BLOCK_SIZE :*(DWORD * )buff = 1;break; }status = RES_OK;break;default:status = RES_PARERR;}return status;
}
#endif
二 、FatFs 功能配置
ffconf.h 文件是FatFs 功能配置文件,我们可以对文件内容进行修改,使得FatFs 更符合
我们的要求。ffconf.h 对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,
其他配置采用默认即可。
1 #define _USE_MKFS 1
2 #define _CODE_PAGE 936
3 #define _USE_LFN 2
4 #define _VOLUMES 2
5 #define _MIN_SS 512
6 #define _MAX_SS 4096
1) _USE_MKFS:格式化功能选择,为使用FatFs 格式化功能,需要把它设置为1。
2) _CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简
体中文文件名需要使用“936”,正如在图 26-7 的操作,我们已经把cc936.c 文件
添加到工程中。
3) _USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,
并指定使用栈空间为缓冲区。
4) _VOLUMES:指定物理设备数量,这里设置为2,包括预留SD 卡和SPI Flash 芯
片。
5) _MIN_SS 、_MAX_SS:指定扇区大小的最小值和最大值。SD 卡扇区大小一般都
为512 字节,SPI Flash 芯片扇区大小一般设置为4096 字节,所以需要把_MAX_SS
改为4096。
三、FatFs 功能测试
移植操作到此,就已经把FatFs 全部添加到我们的工程了,这时我们编译功能,顺利编
译通过,没有错误。接下来,我们就可以使用编写图 26-5 中用户应用程序了。
主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在
main.c 文件中实现。
*******************************************************************************/#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "ff.h"
#include "string.h"
/********************************************************************************* 定义变量*******************************************************************************/
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
char fpath[100]; /* 保存当前扫描路径 */
char readbuffer[512]; /********************************************************************************* 任务函数*******************************************************************************/
/* FatFs多项功能测试 */
static FRESULT miscellaneous(void)
{DIR dir;FATFS *pfs;DWORD fre_clust, fre_sect, tot_sect;printf("\n*************** 设备信息获取 ***************\r\n");/* 获取设备信息和空簇大小 */res_flash = f_getfree("1:", &fre_clust, &pfs);/* 计算得到总的扇区个数和空扇区个数 */tot_sect = (pfs->n_fatent - 2) * pfs->csize;fre_sect = fre_clust * pfs->csize;/* 打印信息(4096 字节/扇区 1个扇区为4KB) */printf("》设备总空间:%10lu KB。\n》可用空间: %10lu KB。\n", tot_sect *4, fre_sect *4);printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",FA_OPEN_ALWAYS|FA_WRITE|FA_READ );if ( res_flash == FR_OK ){/* 文件定位 */res_flash = f_lseek(&fnew,f_size(&fnew));if (res_flash == FR_OK){/* 格式化写入,参数格式类似printf函数 */f_printf(&fnew,"\n在原来文件新添加一行内容\n");f_printf(&fnew,"》设备总空间:%10lu KB。\n》可用空间: %10lu KB。\n", tot_sect *4, fre_sect *4);/* 文件定位到文件起始位置 */res_flash = f_lseek(&fnew,0);/* 读取文件所有内容到缓存区 */res_flash = f_read(&fnew,readbuffer,f_size(&fnew),&fnum);if(res_flash == FR_OK){printf("》文件内容:\n%s\n",readbuffer);}}f_close(&fnew); printf("\n********** 目录创建和重命名功能测试 **********\r\n");/* 尝试打开目录 */res_flash=f_opendir(&dir,"1:TestDir");if(res_flash!=FR_OK){/* 打开目录失败,就创建目录 */res_flash=f_mkdir("1:TestDir");}else{/* 如果目录已经存在,关闭它 */res_flash=f_closedir(&dir);/* 删除文件 */f_unlink("1:TestDir/testdir.txt");}if(res_flash==FR_OK){/* 重命名并移动文件 */res_flash=f_rename("1:FatFs读写测试文件.txt","1:TestDir/testdir.txt"); } }else{printf("!! 打开文件失败:%d\n",res_flash);printf("!! 或许需要再次运行“FatFs移植与读写测试”工程\n");}return res_flash;
}FILINFO fno;
/*** 文件信息获取*/
static FRESULT file_check(void)
{/* 获取文件信息 */res_flash=f_stat("1:TestDir/testdir.txt",&fno);if(res_flash==FR_OK){printf("“testdir.txt”文件信息:\n");printf("》文件大小: %ld(字节)\n", fno.fsize);printf("》时间戳: %u/%02u/%02u, %02u:%02u\n",(fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,fno.ftime >> 11, fno.ftime >> 5 & 63);printf("》属性: %c%c%c%c%c\n\n",(fno.fattrib & AM_DIR) ? 'D' : '-', // 是一个目录(fno.fattrib & AM_RDO) ? 'R' : '-', // 只读文件(fno.fattrib & AM_HID) ? 'H' : '-', // 隐藏文件(fno.fattrib & AM_SYS) ? 'S' : '-', // 系统文件(fno.fattrib & AM_ARC) ? 'A' : '-'); // 档案文件}return res_flash;
}/*** @brief scan_files 递归扫描FatFs内的文件* @param path:初始扫描路径* @retval result:文件系统的返回值*/
static FRESULT scan_files (char* path)
{ FRESULT res; //部分在递归过程被修改的变量,不用全局变量 FILINFO fno; DIR dir; int i; char *fn; // 文件名 #if _USE_LFN /* 长文件名支持 *//* 简体中文需要2个字节保存一个“字”*/static char lfn[_MAX_LFN*2 + 1]; fno.lfname = lfn; fno.lfsize = sizeof(lfn);
#endif //打开目录res = f_opendir(&dir, path); if (res == FR_OK) { i = strlen(path); for (;;) { //读取目录下的内容,再读会自动读下一个文件res = f_readdir(&dir, &fno); //为空时表示所有项目读取完毕,跳出if (res != FR_OK || fno.fname[0] == 0) break;
#if _USE_LFN fn = *fno.lfname ? fno.lfname : fno.fname;
#else fn = fno.fname;
#endif //点表示当前目录,跳过 if (*fn == '.') continue; //目录,递归读取 if (fno.fattrib & AM_DIR) { //合成完整目录名 sprintf(&path[i], "/%s", fn); //递归遍历 res = scan_files(path); path[i] = 0; //打开失败,跳出循环 if (res != FR_OK) break; } else { printf("%s/%s\r\n", path, fn); //输出文件名 /* 可以在这里提取特定格式的文件路径 */ }//else} //for} return res;
}
/*** @brief 主函数* @param 无* @retval 无*/
int main(void)
{ /* 初始化调试串口,一般为串口1 */USART_Config(); printf("******** 这是一个SPI FLASH 文件系统实验 *******\r\n");//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化res_flash = f_mount(&fs,"1:",1);if(res_flash!=FR_OK){printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);printf("!!可能原因:SPI Flash初始化不成功。\r\n");while(1);}else{printf("》文件系统挂载成功,可以进行测试\r\n"); }/* FatFs多项功能测试 */res_flash = miscellaneous();printf("\n*************** 文件信息获取测试 **************\r\n");res_flash = file_check();printf("***************** 文件扫描测试 ****************\r\n");strcpy(fpath,"1:");scan_files(fpath);/* 不再使用文件系统,取消挂载文件系统 */f_mount(NULL,"1:",1);/* 操作完成,停机 */while(1){}
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/