cube sdio fatfs 初始化sd卡_「正点原子STM32Mini板资料连载」第三十四章 FATFS 实验...

1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)关注官方微信号公众号,获取更多资料:正点原子

9a136959a309f92d53fc2832024b4ac9.png

第三十四章 FATFS 实验

上一章,我们学习了 SD 卡的使用,不过仅仅是简单的实现读扇区而已,真正要好好应用

SD 卡,必须使用文件系统管理,本章,我们将使用 FATFS 来管理 SD 卡,实现 SD 卡文件的读

写等基本功能。本章分为如下几个部分:

34.1 FATFS 简介

34.2 硬件设计

34.3 软件设计

34.4 下载验证

34.1 FATFS 简介

FATFS 是一个完全免费开源的 FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完

全用标准 C 语言编写,所以具有良好的硬件平台独立性,可以移植到 8051、PIC、AVR、SH、

Z80、H8、ARM 等系列单片机上而只需做简单的修改。它支持 FATl2、FATl6 和 FAT32,支持

多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位

单片机做了优化。

FATFS 的特点有:

⚫ Windows 兼容的 FAT 文件系统(支持 FAT12/FAT16/FAT32)

⚫ 与平台无关,移植简单

⚫ 代码量少、效率高

⚫ 多种配置选项

 支持多卷(物理驱动器或分区,最多 10 个卷)

 多个 ANSI/OEM 代码页包括 DBCS

 支持长文件名、ANSI/OEM 或 Unicode

 支持 RTOS

 支持多种扇区大小

 只读、最小化的 API 和 I/O 缓冲区等

FATFS 的这些特点,加上免费、开源的原则,使得 FATFS 应用非常广泛。FATFS 模块的层

次结构如图 34.1.1 所示:

35def0a711575c1f7461745925141990.png

图 34.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.10a。本章我们就使用最新版本的 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 个接口函数,如

图 34.1.2 所示:

131b1a87bb83732c55800827e8bc2faf.png

图 34.1.2 diskio 需要实现的函数

通过以上三步,我们即可完成对 FATFS 的移植。

第一步,我们使用的是 MDK3.80a 编译器,器数据类型和 integer.h 里面定义的一致,所以

此步,我们不需要做任何改动。

第二步,关于 ffconf.h 里面的相关配置,我们在前面已经有介绍(之前介绍的 10 个配置),

我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。

第三步,因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁

盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。

这些函数一般有 6 个,在 diskio.c 里面。

首先是 disk_initialize 函数,该函数介绍如图 34.1.3 所示:

599f4a83bfe88b28f97bb475cce0cebf.png

图 34.1.3 disk_initialize 函数介绍

第二个函数是 disk_status 函数,该函数介绍如图 34.1.4 所示:

a179606a890197d384c5ac6a3059e3d1.png

图 34.1.4 disk_status 函数介绍

第三个函数是 disk_read 函数,该函数介绍如图 34.1.5 所示:

52a45c6094f6ccaaf4d91414fbb6f3ce.png

图 34.1.5 disk_read 函数介绍

第四个函数是 disk_write 函数,该函数介绍如图 34.1.6 所示:

8b5fe9cbd91c0cdbd15e82746066c1ae.png

图 34.1.6 disk_write 函数介绍

第五个函数是 disk_ioctl 函数,该函数介绍如图 34.1.7 所示:

99459d4471cec9b9e5191fb3d932066b.png

图 34.1.7 disk_ioctl 函数介绍

最后一个函数是 get_fattime 函数,该函数介绍如图 34.1.8 所示:

31f44aff9f80391c515ed6679766164e.png

图 34.1.8 get_fattime 函数介绍

以上六个函数,我们将在软件设计部分一一实现。通过以上 3 个步骤,我们就完成了对

FATFS 的移植,就可以在我们的代码里面使用 FATFS 了。

FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍(包括

参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount

函数注册一个工作区,才能开始后续 API 的使用,关于 FATFS 的介绍,我们就介绍到这里。大

家可以通过 FATFS 自带的介绍文件进一步了解和熟悉 FATFS 的使用。

34.2 硬件设计

本章实验功能简介:开机的时候先初始化 SD 卡,初始化成功之后,注册两个工作区(一

个给 SD 卡用,一个给 SPI FLASH 用),然后获取 SD 卡的容量和剩余空间,并显示在 LCD

模块上,最后等待 USMART 输入指令进行各项测试。本实验通过 DS0 指示程序运行状态。

本实验用到的硬件资源有:

1) 指示灯 DS0

2) 串口

3) TFTLCD 模块

4) SD 卡

5) SPI FLASH

这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。

34.3 软件设计

本章,我们将 FATFS 部分单独做一个分组,在工程目录下新建一个 FATFS 的文件夹,然

后将 FATFS R0.10a 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建一个 exfuns

的文件夹,用于存放我们针对 FATFS 做的一些扩展代码。设计完如图 34.3.1 所示:

101de597331cbe7b1d896a538a814fe3.png

图 34.3.1 FATFS 文件夹子目录

然后打开我们实验工程可以看到,我们新建了 FATFS 分组,将必要的源文件添加到了

FATFS 分组之下。打开 diskio.c,代码如下:

#include "mmc_sd.h"

#include "diskio.h"

#include "flash.h"

#include "malloc.h"

#define SD_CARD 0 //SD 卡,卷标为 0

#define EX_FLASH 1 //外部 flash,卷标为 1

#define FLASH_SECTOR_SIZE 512

//对于 W25Q64

//前 4.8M 字节给 fatfs 用,4.8M 字节后~4.8M+100K 给用户用,4.9M 以后,用于存放字库

u16 FLASH_SECTOR_COUNT= 9832;

//4.8M 字节,默认为 W25Q64

#define FLASH_BLOCK_SIZE

8 //每个 BLOCK 有 8 个扇区

//初始化磁盘

DSTATUS disk_initialize (BYTE pdrv)

{

u8 res=0;

switch(pdrv)

{

case SD_CARD://SD 卡

res = SD_Initialize();//SD_Initialize()

if(res)//sd 卡操作失败的时候如果不执行下面的语句,可能导致 SPI 读写异常

{

SD_SPI_SpeedLow();

SD_SPI_ReadWriteByte(0xff);//提供额外的 8 个时钟

SD_SPI_SpeedHigh();

}

break;

case EX_FLASH://外部 flash W25Q64

SPI_Flash_Init();

if(SPI_FLASH_TYPE==W25Q64)FLASH_SECTOR_COUNT=9832;

else FLASH_SECTOR_COUNT=0;

break;

default: res=1;

}

if(res)return STA_NOINIT;

else return 0; //初始化成功

}

//获得磁盘状态

DSTATUS disk_status (BYTE pdrv)

{

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);

if(res)//sd 卡操作失败的时候如果不执行下面的语句,可能导致 SPI 读写异常

{

SD_SPI_SpeedLow();

SD_SPI_ReadWriteByte(0xff);//提供额外的 8 个时钟

SD_SPI_SpeedHigh();

}

break;

case EX_FLASH://外部 flash

for(;count>0;count--)

{

SPI_Flash_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;

}

//写扇区

//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--)

{

SPI_Flash_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

//其他表参数的获得

//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:

SD_CS=0;

if(SD_WaitReady()==0)res = RES_OK;

else res = RES_ERROR;

SD_CS=1;

break;

case GET_SECTOR_SIZE:

*(WORD*)buff = 512;

res = RES_OK;

break;

case GET_BLOCK_SIZE:

*(WORD*)buff = 8;

res = RES_OK;

break;

case GET_SECTOR_COUNT:

*(DWORD*)buff = SD_GetSectorCount();

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(size);

}

//释放内存

void ff_memfree (void* mf)

{

myfree(mf);

}

该部分代码实现了我们 34.1 节提到的 6 个函数,同时因为在 ffconf.h 里面设置对长文件

名的支持为方法 3,所以必须实现 ff_memalloc 和 ff_memfree 这两个函数。本章,我们用 FATFS

管理了 2 个磁盘:SD 卡和 SPI FLASH。SD 卡比较好说,但是 SPI FLASH,因为其扇区是 4K 字节

大小,我们为了方便设计,强制将其扇区定义为 512 字节,这样带来的好处就是设计使用相对

简单,坏处就是擦除次数大增,所以不要随便往 SPI FLASH 里面写数据,非必要最好别写,如

果频繁写的话,很容易将 SPI FLASH 写坏。

保存 diskio.c,然后打开 ffconf.h,修改相关配置,并保存,此部分就不贴代码了,请大

家参考光盘源码。

前面提到,我们在 FATFS 文件夹下还新建了一个 exfuns 的文件夹,该文件夹用于保存一些

FATFS 一些针对 FATFS 的扩展代码,本章,我们编写了 4 个文件,分别是:exfuns.c、exfuns.h、

fattester.c 和 fattester.h。其中 exfuns.c 主要定义了一些全局变量,方便 FATFS 的使用,

同时实现了磁盘容量获取等函数。而 fattester.c 文件则主要是为了测试 FATFS 用,因为 FATFS

的很多函数无法直接通过 USMART 调用,所以我们在 fattester.c 里面对这些函数进行了一次再

封装,使其可以通过 USMART 调用。这些代码,我们就不贴出来了,请大家参考光盘源码,我们

将 exfuns.c 和 fattester.c 加入 FATFS 组下,同时将 exfuns 文件夹加入头文件包含路径。

然后,我们打开 main.c,修改 main 函数如下:

int main(void)

{

u32 total,free; u8 t=0;

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M

delay_init(72);

//初始化延时函数

uart_init(115200);

//初始化串口

usmart_dev.init(84);

//初始化 USMART

LED_Init();

//初始化 LED

KEY_Init();

//初始化按键

LCD_Init();

//初始化 LCD

mem_init();

//初始化内存池

POINT_COLOR=RED;

LCD_ShowString(30,50,200,16,16,"Mini 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,"2019/11/18");

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;

}

}

在 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

函数的测试。 至此,软件设计部分就结束了。

34.4 下载验证

在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看到

LCD 显示如图 34.4.1 所示的内容(默认 SD 卡已经接上了):

b98da3c62167da3bb2d0db9da3ce9bfe.png

图 34.4.1 程序运行效果图

打开串口调试助手,我们就可以串口调用前面添加的各种 FATFS 测试函数了,比如我们输

入 mf_scan_files("0:"),即可扫描 SD 卡根目录的所有文件,如图 34.4.2 所示:

73c15e5587c38e297e65e1caebcdb910.png

图 34.4.2 扫描 SD 卡根目录所有文件

其他函数的测试,用类似的办法即可实现。注意这里 0 代表 SD 卡,1 代表 SPI FLASH

(W25Q64)。另外,提醒大家,mf_unlink 函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值