第四十四章 FATFS实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0
[/mw_shl_code]
上一章,我们学习了
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 等系列单片机上而只需做简单的修改。它支持
FATl2、
FATl6 和
FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对
8 位单片机和
16 位单片机做了优化。
FATFS的特点有:
l
Windows兼容的
FAT文件系统(支持
FAT12/FAT16/FAT32)
l 与平台无关,移植简单
l 代码量少、效率高
l 多种配置选项
? 支持多卷(物理驱动器或分区,最多
10个卷)
? 多个
ANSI/OEM代码页包括
DBCS
? 支持长文件名、
ANSI/OEM或
Unicode
? 支持
RTOS
? 支持多种扇区大小
? 只读、最小化的
API和
I/O缓冲区等
FATFS的这些特点,加上免费、开源的原则,使得
FATFS应用非常广泛。
FATFS模块的层次结构如图
44.1.1所示:
图
44.1.1 FATFS层次结构图
最顶层是应用层,使用者无需理会
FATFS的内部结构和复杂的
FAT 协议,只需要调用
FATFS模块提供给用户的一系列应用接口函数,如
f_open,
f_read,
f_write 和
f_close等,就可以像在
PC 上读/写文件那样简单。
中间层
FATFS模块,实现了
FAT 文件读/写协议。
FATFS模块提供的是
ff.c和
ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
需要我们编写移植代码的是
FATFS模块提供的底层接口,它包括存储媒介读/写接口(
disk I/O)和供给文件创建修改时间的实时时钟。
FATFS的源码,大家可以在:
http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载到,目前最新版本为
R0.10b。本章我们就使用最新版本的
FATFS作为介绍,下载最新版本的
FATFS软件包,解压后可以得到两个文件夹:
doc和
src。
doc里面主要是对
FATFS的介绍,而
src里面才是我们需要的源码。
其中,与平台无关的是:
ffconf.h FATFS模块配置文件
ff.h FATFS和应用模块公用的包含文件
ff.c FATFS模块
diskio.h FATFS和
disk I/O模块公用的包含文件
interger.h 数据类型定义
option 可选的外部功能(比如支持中文等)
与平台相关的代码(需要用户提供)是:
diskio.c FATFS和
disk I/O模块接口层文件
FATFS模块在移植的时候,我们一般只需要修改
2个文件,即
ffconf.h和
diskio.c。
FATFS模块的所有配置项都是存放在
ffconf.h里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
1)
_FS_TINY。这个选项在
R0.07版本中开始出现,之前的版本都是以独立的
C文件出现(
FATFS和
Tiny FATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用
FATFS,所以把这个选项定义为
0即可。
2)
_FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为
0即可。
3)
_USE_STRFUNC。这个用来设置是否支持字符串类操作,比如
f_putc,
f_puts等,本章我们需要用到,故设置这里为
1。
4)
_USE_MKFS。这个用来定时是否使能格式化,本章需要用到,所以设置这里为
1。
5)
_USE_FASTSEEK。这个用来使能快速定位,我们设置为
1,使能快速定位。
6)
_USE_LABEL。这个用来设置是否支持磁盘盘符(磁盘名字)读取与设置。我们设置为
1,使能,就可以通过相关函数读取或者设置磁盘的名字了。
7)
_CODE_PAGE。这个用于设置语言类型,包括很多选项(见
FATFS官网说明),我们这里设置为
936,即简体中文(
GBK码,需要
c936.c文件支持,该文件在
option文件夹)。
8)
_USE_LFN。该选项用于设置是否支持长文件名(还需要
_CODE_PAGE支持),取值范围为
0~3。
0,表示不支持长文件名,
1~3是支持长文件名,但是存储地方不一样,我们选择使用
3,通过
ff_memalloc函数来动态分配长文件名的存储区域。
9)
_VOLUMES。用于设置
FATFS支持的逻辑设备数目,我们设置为
2,即支持
2个设备。
10)
_MAX_SS。扇区缓冲的最大值,一般设置为
512。
其他配置项,我们这里就不一一介绍了,
FATFS的说明文档里面有很详细的介绍,大家自己阅读即可。下面我们来讲讲
FATFS的移植,
FATFS的移植主要分为
3步:
① 数据类型:在
integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数
据类型,并根据编译器定义好数据类型。
② 配置:通过
ffconf.h配置
FATFS的相关功能,以满足你的需要。
③ 函数编写:打开
diskio.c,进行底层驱动编写,一般需要编写
6 个接口函数,如
图
44.1.2 所示:
图
44.1.2 diskio 需要实现的函数
通过以上三步,我们即可完成对
FATFS的移植。
第一步,我们使用的是
MDK5.11a编译器,器数据类型和
integer.h里面定义的一致,所以此步,我们不需要做任何改动。
第二步,关于
ffconf.h里面的相关配置,我们在前面已经有介绍(之前介绍的
10个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为
FATFS模块完全与磁盘
I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘
I/O 模块并不是
FATFS的一部分,并且必须由用户提供。这些函数一般有
6个,在
diskio.c里面。
首先是
disk_initialize函数,该函数介绍如图
44.1.3所示:
图
44.1.3 disk_initialize函数介绍
第二个函数是
disk_status函数,该函数介绍如图
44.1.4所示:
图
44.1.4 disk_status函数介绍
第三个函数是
disk_read函数,该函数介绍如图
44.1.5所示:
图
44.1.5 disk_read函数介绍
第四个函数是
disk_write函数,该函数介绍如图
44.1.6所示:
图
44.1.6 disk_write函数介绍
第五个函数是
disk_ioctl函数,该函数介绍如图
44.1.7所示:
图
44.1.7 disk_ioctl函数介绍
最后一个函数是
get_fattime函数,该函数介绍如图
44.1.8所示:
图
44.1.8 get_fattime函数介绍
以上六个函数,我们将在软件设计部分一一实现。通过以上
3个步骤,我们就完成了对
FATFS的移植,就可以在我们的代码里面使用
FATFS了。
FATFS提供了很多
API函数,这些函数
FATFS的自带介绍文件里面都有详细的介绍
(包括参考代码
),我们这里就不多说了。这里需要注意的是,在使用
FATFS的时候,必须先通过
f_mount函数注册一个工作区,才能开始后续
API的使用,关于
FATFS的介绍,我们就介绍到这里。大家可以通过
FATFS自带的介绍文件进一步了解和熟悉
FATFS的使用。
44.2 硬件设计
本章实验功能简介:开机的时候先初始化SD卡,初始化成功之后,注册两个工作区(一个给SD卡用,一个给SPI FLASH用),然后获取SD卡的容量和剩余空间,并显示在LCD模块上,最后等待USMART输入指令进行各项测试。本实验通过DS0指示程序运行状态。
本实验用到的硬件资源有:
1) 指示灯
DS0
2) 串口
3) TFTLCD模块
4) SD卡
5) SPI FLASH
这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。
44.3 软件设计
打开本章实验目录可以看到,我们在工程目录下新建了一个
FATFS的文件夹,然后将
FATFS R0.10b程序包解压到该文件夹下。同时,我们在
FATFS文件夹里面新建了一个
exfuns的文件夹,用于存放我们针对
FATFS做的一些扩展代码。设计完如图
44.3.1所示:
图
44.3.1 FATFS文件夹子目录
然后打开我们实验工程可以看到,我们新建了
FATFS分组,将必要的源文件添加到了
FATFS分组之下。打开
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; //W25Q1218,前
12M字节给
FATFS占用
#define FLASH_BLOCK_SIZE 8 //每个
BLOCK有
8个扇区
//初始化磁盘
DSTATUS disk_initialize (
BYTE
pdrv /* Physical
drive nmuber (0..) */
)
{
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;//W25Q1218,前
12M字节给
FATFS占用
break;
default:
res=1;
}
if(res)return STA_NOINIT;
else
return 0; //初始化成功
}
//获得磁盘状态
DSTATUS disk_status (
BYTE
pdrv /* Physical drive nmuber
(0..) */
)
{
return
0;
}
//读扇区
//drv:磁盘编号
0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
DRESULT disk_read (
BYTE
pdrv, /* Physical drive nmuber
(0..) */
BYTE
*buff, /* Data buffer to store
read data */
DWORD
sector, /* Sector address (LBA) */
UINT
count /* Number of sectors to
read (1..128) */
)
{
u8
res=0;
if
(!count)return RES_PARERR;//count不能等于
0,否则返回参数错误
switch(pdrv)
{
case
SD_CARD://SD卡
res=SD_ReadDisk(buff,sector,count);
break;
case
EX_FLASH://外部
flash
for(;count>0;count--)
{
W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SI
ZE);
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;
}
//写扇区
//drv:磁盘编号
0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _USE_WRITE
DRESULT disk_write (
BYTE
pdrv, /* Physical drive
nmuber (0..) */
const
BYTE *buff, /* Data to be written */
DWORD
sector, /* Sector address (LBA)
*/
UINT
count /* Number of
sectors to write (1..128) */
)
{
u8
res=0;
if
(!count)return RES_PARERR;//count不能等于
0,否则返回参数错误
switch(pdrv)
{
case
SD_CARD://SD卡
res=SD_WriteDisk((u8*)buff,sector,count);
break;
case
EX_FLASH://外部
flash
for(;count>0;count--)
{
W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECT
OR_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
//其他表参数的获得
//drv:磁盘编号
0~9
//ctrl:控制代码
//*buff:发送
/接收缓冲区指针
#if _USE_IOCTL
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (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 */
//31-25: Year(0-127 org.1980), 24-21: Month(1-12),
20-16: Day(1-31) */
//15-11: Hour(0-23), 10-5: Minute(0-59), 4-0:
Second(0-29 *2) */
DWORD get_fattime (void)
{
return
0;
}
//动态分配内存
void *ff_memalloc (UINT size)
{
return
(void*)mymalloc(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)
{
myfree(SRAMIN,mf);
}
该函数实现了我们
44.1节提到的
6个函数,同时因为在
ffconf.h里面设置对长文件名的支持为方法
3,所以必须实现
ff_memalloc和
ff_memfree这两个函数。本章,我们用
FATFS管理了
2个磁盘:
SD卡和
SPI FLASH。
SD卡比较好说,但是
SPI FLASH,因为其扇区是
4K字节大小,我们为了方便设计,强制将其扇区定义为
512字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次数大增,所以不要随便往
SPI
FLASH里面写数据,非必要最好别写,如果频繁写的话,很容易将
SPI FLASH写坏。
打开
ffconf.h可以看到,我们根据前面讲解修改了相关配置,此部分就不贴代码了,请大家参考本例程源码。另外,
cc936.c主要提供
UNICODE到
GBK以及
GBK到
UNICODE的码表转换,里面就是两个大数组,并提供一个
ff_convert的转换函数,供
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函数如下:
int main(void)
{
u32 total,free;
u8
t=0; u8 res=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组
2
delay_init(168);
//初始化延时函数
uart_init(115200); //初始化串口波特率为
115200
LED_Init(); //初始化
LED
usmart_dev.init(84); //初始化
USMART
LCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
W25QXX_Init(); //初始化
W25Q128
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMCCM); //初始化
CCM内存池
POINT_COLOR=RED;//设置字体为红 {MOD}
LCD_ShowString(30,50,200,16,16,"Explorer
STM32F4");
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,"2014/5/15");
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");//设置磁盘的名字为:
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;//设置字体为蓝 {MOD}
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;
}
}
在
main函数里面,我们为
SD卡和
FLASH都注册了工作区(挂载),在初始化
SD卡并显示其容量信息后,进入死循环,等待
USMART测试。
最后,我们在
usmart_config.c里面的
usmart_nametab数组添加如下内容:
(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)",
这些函数均是在
fattester.c里面实现,通过调用这些函数,即可实现对
FATFS对应
API函数的测试。 至此,软件设计部分就结束了。
44.4 下载验证
在代码编译成功之后,我们通过下载代码到
ALIENTEK探索者
STM32F4开发板上,可以看到
LCD显示如图
44.4.1所示的内容(假定
SD卡已经插上了):
图
44.4.1 程序运行效果图
打开串口调试助手,我们就可以串口调用前面添加的各种
FATFS测试函数了,比如我们输入
mf_scan_files("0:"),即可扫描
SD卡根目录的所有文件,如图
44.4.2所示:
图
44.4.2 扫描
SD卡根目录所有文件
其他函数的测试,用类似的办法即可实现。注意这里
0代表
SD卡,
1代表
SPI FLASH。另外,提醒大家,
mf_unlink函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。
实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
图44.1.2 所示:
一周热门 更多>