第四十四章 FATFS实验
上一章学习了SD卡的使用,不过仅仅是简单的读扇区而已。真正要好好使用SD卡,必须使用文件系统管理。
本章将使用FATFS来管理SD卡,实现SD卡文件的读写等基本功能。
本章分为如下几部分:
44.1 FATFS简介
44.2 硬件设计
44.3 软件设计
44.4 下载验证
44.1 FATFS简介
FATFS是一个完全免费开源的FAT文件系统模块,专为小型的嵌入式系统而设计。
它完全用标准的
C语言编写,具有良好的硬件平台独立性,可以移植到8051、PIC、
AVR、SH、Z80、H8、
ARM等系列
单片机上而只需做简单的修改。
它支持FTA12 FAT16 和FAT32,支持多个存储媒介;
有独立的缓冲区,可以对多个文件进行读/写,并特别对8位和16位单片机做了优化。
FATFS的特点有:
① Windows兼容的FAT文件系统(支持FAT12/FAT16/FAT32);
② 与平台无关,移植简单;
③ 代码量少,效率高;
④ 多种配置选项
支持多卷(物理驱动器或分区,最多10个卷);
多个ANSI/OEM代码页,包括DBCS;
支持长文件名,ANSI/OEM或Unicode;
支持RTOS;
支持多种扇区大小;
只读、最小化的API和I/O缓冲区等。
FATFS的这些特点,加上免费、开源的原则,使得FATFS应用非常广泛。
FATFS模块的层次结构:
最顶层是应用层
,使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用
FATFS模块提供给用户的一系列应用接口即可,如f_open,f_read, f_write和f_close等,
就可以像在PC上读/写文件那样简单。
中间层FATFS模块
,实现了FAT文件读写协议。
FATFS模块提供的是ff.c和ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接
包含进去即可。
底层存储媒介接口,包括存储媒介读/写接口(disk I/O)和供文件创建修改时间的实时时钟。
这些接口需要我们编写移植。
FATFS源码:
下载地址:
http://elm-chan.org/fsw/ff/00index_e.html
文件解压后内容:doc和src。
doc里面是对FATFS的介绍;src里面是需要的源码。
FATFS模块的所有配置项都是存放在ffconf.h里面,我们可以通过配置里面的一些选项,来满足
自己的需求。下面是几个重要的配置选项。
FATFS移植:
FATFS移植主要分为3步:
① 数据类型:在integer.h里面去定义好数据的类型。这里需要了解你的编译器类型,并根据编译器定义好数据类型;
② 配置:通过ffconf.h配置FATFS的相关功能,以满足你的需求;
③ 函数编写:打开diskio.c,进行底层驱动编写,一般需要编写6个函数接口,如所示:
第一步:使用的是MDK5.13编译器,其数据类型和integer.h里面定义的一致,所以此步
不需要做任何改动。
第二步:根据上面讲过的需求,配置ffconf.h里面的配置,其他的用默认配置即可。
第三步:因为FATFS模块完全和磁盘I/O层分开,因此需要下面的函数来实现底层物理磁盘
的读写与获取当前时间。
底层磁盘I/O模块并不是FATFS的一部分,必须由用户提供。就是上面提到的6个函数。
注意:使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续的API的使用,
大家可以通过FATFS自带的介绍文件进一步了解和熟悉FATFS的使用。
44.2 硬件设计
本章实验功能:
开机时先初始化SD卡;
之后注册两个工作区(一个给SD卡使用,一个给SPI FLASH用);
然后获取SD卡的容量和剩余空间,并显示在LCD上;
最后,等待USMART输入指令进行各项测试。
本实验通过DS0指示程序运行状态。
本实验用到的硬件资源:
1)指示灯DS0;
2)串口;
3)TFTLCD模块;
4)SD卡;
5)SPI FLASH。
44.3 软件设计
介绍的文件:
diskio.c
ffconf.h
main.c
diskio.c内容:
实现了前面提到的6个函数,同时因为在ffconf.h里面设置对长文件名的支持为方法3,所以必须实现
ff_memalloc和ff_memfree这两个函数。
本章,我们用FATFS管理了两个磁盘:SD卡和SPI FLASH。SD卡比较好说,但是SPI FLASH,因为其扇区是4K字节大小,为了方便设计,强制将其扇区定义为512字节,这样好处是设计使用相对简单,坏处是擦除次数大增,所以不要随便向SPI FLASH里面写数据,非必要最好别写,如果频繁写的话,很容易将SPI FLASH写坏。
另外,diskio.c里面的函数,直接决定了磁盘编号(盘符/卷标)所对应的具体设备,比如,上面代码中,设置SD_CARD为0,EX_FLASH位为1,对应到disk_read/disk_write函数里面,
通过switch来判断,到底要操作SD卡,还是SPI FLASH,然后,分别执行对应设备的相关操作,以此实现磁盘编号和磁盘关联。
打开diskio.c,代码如下:
#define SD_CARD 0 //SD卡,卷标为0
#define EX_FLASH 1 //外部Flash,卷标为1
#define FLASH_SECTOR_SIZE 512
//对于W25Q128
//前12M字节给FatFs使用,12M字节后,用于存放字库,字库占3.09M,剩余的给客户自己使用
u16 FLASH_SECTOR_COUNT = 2048*12; //W25Q128前12M字节
#define FLASH_BLOCK_SIZE 8 //每个BLOCK有8个扇区
//获得磁盘状态
DSTATUS disk_status(BYTE pdrv) //Physical drive number to identify the drive
{
return RES_OK;
}
//初始化磁盘
DSTATUS disk_initialize(BYTE pdrv) //Physical drive number to identify the drive
{
u8 res = 0;
switch(pdrv)
{
case SD_CARD: //SD卡
res = SD_Init(); //SD卡初始化
break;
case EX_FLASH: //外部Flash
W25QXX_Init();
FLASH_SECTOR_COUNT = 2048*12; //前12M字节给FATFS占用
break;
default:
res = 1;
}
if(res)
return STA_NOINIT;
else
return 0; //初始化成功
}
//读扇区
//pdrv :磁盘编号0~9
//*buffer:数据接收缓冲首地址
//sector :扇区地址
//count :需要读取的扇区数
DRESULT disk_read(BYTE pdrv, //physical drive number to identify the drive
BYTE *buff, //Data buffer to store read data
DWORD sector, //Sector address in LBA
UINT count) //Number of sectors to read
{
u8 res = 0;
if(!count)
return RES_PARERR; //count不能等于0,否则返回参数错误
switch(pdrv)
{
case SD_CARD: //SD卡
res = SD_ReadDisk(buff, sector, count);
while(res) //读出错
{
SD_Init(); //重新初始化SD卡
res = SD_ReadDisk(buff, sector, count);
//printf("sd rd error:%d\r\n", res);
}
break;
case EX_FLASH: //外部Flash
for(; count > 0; count--)
{
W25QXX_Read(buff, sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE)
sector++;
buff += FLASH_SECTOR_SIZE;
}
res = 0;
break;
default:
res = 1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res == 0x00)
return RES_OK;
else
return RES_ERROR;
}
//写扇区
//pdrv :磁盘编号0~9
//*buffer:发送数据首地址
//sector :扇区地址
//count :需要写入的扇区数
#if _USE_WRITE
DRESULT disk_write(BYTE pdrv, //physical drive number to identify the drive
const BYTE *buff, //Data buffer to store read data
DWORD sector, //Sector address in LBA
UINT count) //Number of sectors to write
{
u8 res = 0;
if(!count)
return RES_PARERR; //count不能等于0,否则返回参数错误
switch(pdrv)
{
case SD_CARD: //SD卡
res = SD_WriteDisk((u8*)buff, sector, count);
while(res) //写出错
{
SD_Init(); //重新初始化SD卡
res = SD_WriteDisk((u8*)buff, sector, count);
//printf("sd wr error:%d\r\n", res);
}
break;
case EX_FLASH: //外部Flash
for(; count > 0; count--)
{
W25QXX_Write((u8*)buff, sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
sector++;
buff += FLASH_SECTOR_SIZE;
}
res = 0;
break;
default:
res = 1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res == 0x00)
return RES_OK;
else
return RES_ERROR;
}
#endif
//其他表参数的获取
//pdrv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
#if _USE_IOCTL
DRESULT disk_ioctl(BYTE pdrv, //Physical drive number(0...)
BYTE cmd, //Control code
void * buff) //Buffer to send/receive control data
{
DRESULT res;
if(pdrv == SD_CARD) //SD卡
{
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(DWORD*)buff = 512;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = SDCardInfo.CardBlockSize;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = SDCardInfo.CardCapacity/512;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}
else if(pdrv == EX_FLASH) //外部FLASH
{
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}
else
res = RES_ERROR; //其他不支持
return res;
}
#endif
//获取时间
//User defined function to give a current time to fatfs module
//当前时间以双字值封装返回。
//位域如下:
//bit31:25 年(0~127)(从1980开始)
//bit24:21 月(1~12)
//bit20:16 日(1~31)
//bit15:11 小时(0~23)
//bit10:5 分钟(0~59)
//bit4:0 秒(0~29)
DWORD get_fattime(void)
{
return 0;
}
//动态分配内存
void *ff_memcalloc(UINT size)
{
return (void*)mymalloc(SRAMIN, size);
}
//释放内存
void ff_memfree(void * mf)
{
myfree(SRAMIN, mf);
}
ffconf.h内容:
头文件可以看到我们还修改了fatfs相关配置,参考例程源码。
cc936.c主要提供
UNICODE到
GBK以及
GBK到
UNICODE的码表转换,里面就是两个大数组,
并提供一个ff_conver的转换函数,供UNICODE和GBK码互换,这个在中文长文件名支持的时
候必须用到。
FATFS文件夹下还有一个文件夹exfuns,里面保存一些FATFS针对FATFS的扩展代码,本章
编写了4个文件,分别是:exfuns.c、exfuns.h、fattester.c和fattester.h。
exfuns.c:主要定义了一些全局变量,方便FATFS使用,同时实现了磁盘容量获取等函数;
fattester.c:主要是为了测试FATFS用,因为FATFS的很多函数无法直接通过USMART调用,所
以
在
fattester.c里面对这些函数进行了一次再封装,使得可以通过USMART调用。
main.c内容:
在main函数里面,我们为SD卡和FLASH都注册了工作区(挂在),在初始化SD卡并显示器容
量信息后
,
进入死循环,等待USMART测试。
int main()
{
u32 total, free;
u8 t = 0; u8 res = 0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
usmart_dev.init(72); //初始化USMART
LED_init();
KEY_init();
LCD_init();
W25QXX_init(); //初始化W25Q128
my_mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR = RED;
LCD_ShowString(30,50,200,16,16,"WarShip STM32");
LCD_ShowString(30,70,200,16,16,"FATFS TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2015/1/20");
LCD_ShowString(30,130,200,16,16,"Use USMART for test");
while(SD_Init())//检测不到 SD 卡
{
LCD_ShowString(30,150,200,16,16,"SD Card Error!"); delay_ms(500);
LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500);
LED0=!LED0;//DS0 闪烁
}
exfuns_init(); //为fatfs相关变量申请内存
f_mount(fs[0], "0:", 1); //挂载SD卡
res = f_mount(fs[1], "1:", 1); //挂载FLASH
if(res == 0x0D) //FLASH磁盘,FAT文件系统错误,重新格式化FLASH
{
LCD_ShowString(30,150,200,16,16,"Flash Disk Formatting...");//格式化 FLASH
res = f_mkfs("1:", 1, 4096); //格式化FLASH, 1, 盘符;1,不需要引导区,8个扇区为1镞
if(res == 0)
{
f_setlabel((const TCHAR*)"1:ALIENTEK"); //设置Flash磁盘名:ALIENTEK
LCD_ShowString(30,150,200,16,16,"Flash Disk Format Finish");//格式化完成
}
else
LCD_ShowString(30,150,200,16,16,"Flash Disk Format Error ");//格式化失败
delay_ms(1000);
}
LCD_Fill(30,150,240,150+16,WHITE); //清除显示
while(exf_getfree("0", &total, &free)) //得到SD卡的总容量和剩余容量
{
LCD_ShowString(30,150,200,16,16,"SD Card Fatfs Error!");
delay_ms(200);
LCD_Fill(30,150,240,150+16,WHITE);//清除显示
delay_ms(200);
LED0=!LED0;//DS0 闪烁
}
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(30,150,200,16,16,"FATFS OK!");
LCD_ShowString(30,170,200,16,16,"SD Total Size: MB");
LCD_ShowString(30,190,200,16,16,"SD Free Size: MB");
LCD_ShowNum(30+8*14,170,total>>10,5,16); //显示 SD 卡总容量 MB
LCD_ShowNum(30+8*14,190,free>>10,5,16); //显示 SD 卡剩余容量 MB
while(1)
{
t++;
delay_ms(200);
LED0=!LED0;
}
}
最后,在usmart_conf.c里面的usmart_nametab数组添加如下内容:
这些函数均是在fattester.c里面实现的,通过调用这些函数,即可实现对FATFS对应API函数的测试。
(void*)mf_mount, "u8 mf_mount (u8* path,u8 mt)",
(void*)mf_open, "u8 mf_open (u8*path,u8 mode)",
(void*)mf_close, "u8 mf_close (void)",
(void*)mf_read, "u8 mf_read (u16 len)",
(void*)mf_write, "u8 mf_write (u8*dat,u16 len)",
(void*)mf_opendir, "u8 mf_opendir (u8* path)",
(void*)mf_closedir, "u8 mf_closedir (void)",
(void*)mf_readdir, "u8 mf_readdir (void)",
(void*)mf_scan_files,"u8 mf_scan_files (u8 * path)",
(void*)mf_showfree, "u32 mf_showfree (u8 *drv)",
(void*)mf_lseek, "u8 mf_lseek (u32 offset)",
(void*)mf_tell, "u32 mf_tell (void)",
(void*)mf_size, "u32 mf_size (void)",
(void*)mf_mkdir, "u8 mf_mkdir (u8*pname)",
(void*)mf_fmkfs, "u8 mf_fmkfs (u8* path,u8 mode,u16 au)",
(void*)mf_unlink, "u8 mf_unlink (u8 *pname)",
(void*)mf_rename, "u8 mf_rename (u8 *oldname,u8* newname)",
(void*)mf_getlabel, "void mf_getlabel (u8 *path)",
(void*)mf_setlabel, "void mf_setlabel (u8 *path)",
(void*)mf_gets, "void mf_gets (u16 size)",
(void*)mf_putc, "u8 mf_putc (u8 c)",
(void*)mf_puts, "u8 mf_puts (u8*c)",
44.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32F103上,可以看到
LCD显示下图所示内容(SD卡已经插上了):
打开串口调试助手,可以用串口调用前面添加的各种FATFS测试函数了。比如输入
mf_scan_files("0:"),即可扫描SD卡根目录下面的额所有文件,如下图所示:
注意:这里0代表SD卡,1代表SPI的FLASH。
mf_ulink函数,在删除文件夹时,必须保证文件夹是空的,才可以正常删除,否则不能删除。