目录
一 准备工作
二 FatFs源码移植
三 FatFs源码适配
四 文件系统测试
(1)FatFs驱动源码(CSDN下载:https://download.csdn.net/download/sinat_33408502/16091729)(官网下载:http://elm-chan.org/fsw/ff/00index_e.html);
(2)芯片及其周边最小电路、烧写器等等(我这边用的芯片是LPC1857);
(3)SPI FLASH芯片(我这边用的是S25FL256S);
文件系统对于嵌入式系统的重要性是不言而喻的,有了文件系统管理数据和外设变得方便许多,同时简化了应用的开发。今天我们来以在SPI_FLASH上建立文件系统为例,看看FATFS文件系统怎么移植和使用。
FatFs是一个通用的嵌入式文件系统,对不同的平台支持很好,大到硬盘、U盘、存储卡,小到spi_flash芯片甚至单片机内部FLASH都可以使用FATFS。今天我们就在一个4M大小的SPI_FLASH( S25FL256S )上建立一个文件系统,主控制器是LPC1857。在做文件系统移植前,你需要把操作SPI_FLASH的驱动调通,能读写SPI_FLASH就可以了。
下面是源码移植的步骤:
(1)官网或者到我提供的资源路径下载FatFs的驱动源码;
(2)文件解释如下图所示:
其中,
diskio.c个diskio.h是和存储器读写控制相关的驱动接口,比如SPI_FLASH的读写函数接口,都要映射到这里面。必须的文件
ff.h和ff.h是FATFS的核心文件,必须的文件
ffconf.h是FATFS的配置文件,用来裁剪FATFS,必须的文件
integer.h是FATFS所用到的数据类型定义,用以兼容不同字长CPU,必须的文件
ffsystem.c是一些关于在带操作系统平台中,使用的示例,可选文件
ffunicode.c是万国码编码文件,文件里主要是大数组定义,假如你需要让文件名支持中文就需要这个文件,这个文件会使代码空间急剧变大,可选文件
(3)将需要用到的文件添加到工程中,并链接头文件路径:
为了使得移植过来的文件系统在自己系统中可用,还需要做一下适配工作:
(1)第一步修改ffconf.h文件(文件中各种宏的解释:https://blog.csdn.net/xiayufeng520/article/details/8830157)
在ffconf.h文件中可以配置文件系统的参数和功能,因为一开始我们的flash是没有文件系统的,需要自己格式化一下,所以需要有创建文件系统的功能,将FF_USE_MKFS宏置1,允许格式化:
#define FF_USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
(2)在diskio.c中定义了几个磁盘设备,在对应的几个操作函数里面也有这几个设备对应的操作,但是我们只用一个spi-flash所以就只定义一个:
/* 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 */
#define SPI_FLASH 0 /* 外部FLASH */
(3)定义文件系统空间相关属性(扇区数、扇区大小、每个块包含多少扇区):
//SPI FLASH前8M用来存储图片,后面的24M用来作为文件系统
#define FLASH_SECTOR_COUNT (6*1024) //扇区个数6K,一共24M
#define FLASH_SECTOR_SIZE (4*1024) //每个扇区4K
#define FLASH_BLOCK_SIZE 16 //每个BLOCK有16个扇区
(4)第四步就是最重要的修改diskio.c中的几个标准接口,这边先做一个列举,下面一一详述:
序号 | 接口名 | 说明 |
【1】 | disk_status | 获得磁盘状态 |
【2】 | disk_initialize | 磁盘初始化 |
【3】 | disk_read | 写扇区 |
【4】 | disk_write | 读扇区 |
【5】 | disk_ioctl | 其他函数 |
【6】 | get_fattime | 获得时间 |
【1】disk_status:直接返回成功即可
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
if(pdrv == SPI_FLASH)
{
return RES_OK;
}
}
【2】disk_initialize:spi-flash初始化
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
if(pdrv == SPI_FLASH)
{
flashSSPInit(); //初始化 spi flash
return RES_OK;
}
else
{
return RES_PARERR;
}
}
【3】disk_read:以扇区为单位读,这边我对Flash的空间偏移了2048个扇区,即8M空间作为文件系统,前8M空间留作它用。
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT FS_SpiFlash_Read(BYTE *buff, LBA_t sector, UINT count)
{
int i,j;
uint32_t addr = sector * FLASH_SECTOR_SIZE;
for(i = 0;i < count;i ++) //count个扇区
{
for(j = 0;j < 16;j ++) //每次读256字节,分16次读完一个扇区4k
{
flash_read_data_uint8(addr, buff, 256);
addr += 256;
buff += 256;
}
sector ++;
}
/*for(i = 0;i < count;i ++) //count个扇区
{
//一次读完一个4K扇区
flash_read_data_uint8(sector*FLASH_SECTOR_SIZE, buff, FLASH_SECTOR_SIZE);
sector ++;
buff += FLASH_SECTOR_SIZE;
}*/
}
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;
if(pdrv == SPI_FLASH)
{
sector += 2048; //偏移8M
FS_SpiFlash_Read((uint8_t *)buff, sector, count);//spi flash的读接口,注意函数参数类型一致性
res = 0;
return res;
}
else
{
return RES_PARERR;
}
}
【4】disk_write:以扇区为单位写,这边我对Flash的空间偏移了2048个扇区,即8M空间作为文件系统,前8M空间留作它用。
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT FS_SpiFlash_Write(BYTE *buff, LBA_t sector, UINT count)
{
int i,j;
uint32_t addr = sector * FLASH_SECTOR_SIZE;
for(i = 0;i < count; i ++)
{
flash_sector_erase(sector*FLASH_SECTOR_SIZE); //写之前先擦除,否则挂载失败返回13
for(j = 0;j < 16;j ++) //每次写256字节,分16次读完一个扇区4k
{
flash_write_sector(addr, buff, 256);
addr += 256;
buff += 256;
}
sector ++;
}
/*for(i = 0;i < count; i ++)
{
//一次写完一个4K扇区
flash_write_sector(sector*FLASH_SECTOR_SIZE, buff, FLASH_SECTOR_SIZE);
sector ++;
buff += FLASH_SECTOR_SIZE;
}*/
}
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;
if(pdrv == SPI_FLASH)
{
sector += 2048; //偏移8M
FS_SpiFlash_Write((uint8_t *)buff, sector, count);//spi flash的写接口,注意函数参数类型一致性
res = 0;
return res;
}
else
{
return RES_PARERR;
}
}
【5】disk_ioctl:主要是获取一些磁盘的参数
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
if (pdrv == SPI_FLASH)
{
switch (cmd)
{
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT: //扇区数量
*(DWORD * )buff = FLASH_SECTOR_COUNT;
return RES_OK;
case GET_SECTOR_SIZE : //扇区大小
*(WORD * )buff = FLASH_SECTOR_SIZE;//spi flash的扇区大小是 4K
return RES_OK;
case GET_BLOCK_SIZE : //块大小
*(DWORD * )buff = FLASH_BLOCK_SIZE;
return RES_OK;
default:
return RES_PARERR;
}
}
else
{
return RES_PARERR;
}
}
【6】get_fattime:文件系统时间的接口,直接返回0即可;
DWORD get_fattime(void)
{
return 0;
}
至此,基本上FatFs文件系统在我自用的LPC平台上移植完毕。使用的是S25FL256S的后24M空间作为文件系统(总共32M,前8M有其他的用处),分为6K个扇区,每个扇区4K大小。
在上面移植完成的基础之上,下面测试一下该文件系统的可用性:
FATFS fs;
FRESULT res;
BYTE work[FF_MAX_SS];
FIL filp;
BYTE buff[100];
UINT fnum;
BYTE temp;
DWORD fre_clust;
FATFS *pfs;
DWORD TOT_SIZE;
DWORD FRE_SIZE;
int main (void)
{
int i = 0;
BaseDataInit();
SystemInit(); //系统存储为小端模式
/********************************SEGGER_RTT模块+单元测试模块********************************/
#ifdef IS_UNITY_TEST
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
printf("\r\n\r\n\r\n\r\n\r\n%s\t%s\r\n", __DATE__, __TIME__); //打印程序编译的日期时间
fflush(stdout);
printf("TEST Start:\r\n");
res=f_mount(&fs, "0:", 1);
printf("\r\n[1]mount ret=%d", res);
if (res == FR_NO_FILESYSTEM)
{
printf("\r\n[2]no file system, format");
res = f_mkfs("0:", 0, work, sizeof(work));
if (res == FR_OK)
{
printf("\r\n[2.1]format ok");
res = f_mount(NULL, "0:", 1);
res = f_mount(&fs, "0:", 1);
printf(",mount again ret=%d", res);
}
else
{
printf("\r\n[2.2]format fail");
}
}
else if (res != FR_OK)
{
printf("\r\n[1.1]file system mount fail");
}
else
{
printf("\r\n[1.2]file system mount ok");
}
if (res == FR_OK)
{
res = f_open(&filp, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
if (res == FR_OK)
{
printf("\r\n[3.1]f_open ok, now write");
memset(buff, 0x55, sizeof(buff));
res = f_write(&filp, buff, sizeof(buff), &fnum);
if (res == FR_OK)
{
printf("\r\n[4.1]write ok, real write len=%d, read...", fnum);
temp = f_eof(&filp);
printf("\r\nreach file end=%d", temp);
printf("\r\nfile pointer back to 0");
f_lseek(&filp, 0);
memset(buff, 0, sizeof(buff));
res = f_read(&filp, buff, sizeof(buff), &fnum);
if (res == FR_OK)
{
printf("\r\n[5.1]read ok, real read len=%d, data=%d %d", fnum, buff[0], buff[99]);
}
else
{
printf("\r\n[5.2]read fail, res=%d", res);
}
printf("\r\n[5.3]f_size = %d", (int)f_size(&filp));
}
else
{
printf("\r\n[4.2]write fail");
}
//new
pfs = &fs;
res = f_getfree("0:", &fre_clust, &pfs);
if(res == FR_OK)
{
TOT_SIZE = (pfs->n_fatent - 2) * pfs->csize; //总容量 单位byte;
FRE_SIZE = fre_clust * pfs->csize; // 可用容量 单位byte;
printf("\r\n[6.1]f_getfree ok, total=%d, free=%d", TOT_SIZE, FRE_SIZE);
}
else
{
printf("\r\n[6.2]f_getfree fail, res=%d", res);
}
//new end
printf("\r\n[7]close file");
f_close(&filp);
}
else
{
printf("\r\n[3.2]f_open fail");
}
}
else
{
printf("\r\n[1.3]mount or format ret fail");
}
f_unlink("0:/test.txt");
res = f_open(&filp, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
printf("\r\n[5.4]f_size = %d", (int)f_size(&filp));
f_close(&filp);
getchar();
Flash_SSP_Entry();
return 0;
#endif
}
其中,有几个注意点:
(i)诸如FATFS fs;FRESULT res;等变量的定义最好用全局变量,一开始用做局部变量太大了,导致程序跑死;
(ii)如果首次挂载失败,报错13(FR_NO_FILESYSTEM),可以尝试清空flash数据之后,重新格式化。格式化的时间可能会稍微长一点,但是格式化完成之后,在进行挂载,就会比较快了;
(iii)挂载失败,报错13(FR_NO_FILESYSTEM)的可能性,还在于写文件接口disk_write中没有事先清除扇区导致flash_sector_erase(sector*FLASH_SECTOR_SIZE);
(iv)我这边读写都提供了两种读写方式,一种是每次读256个字节,分16次读完一个4K扇区,第二种是一次读完4K扇区,从本质上讲,两种方式没有区别:
程序运行结果如下: