FATFS R0.14b移植到STM32
FATFS简介
FatFs 是一种完全免费开源的 FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准C 语言编写,所以具有良好的硬件平台独立性,可以移植到 8051、 PIC、 AVR、 SH、 Z80、 H8、 ARM 等系列单片机上而只需做简单的修改。它支持 FATl2、 FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。
特点
- Windows兼容的FAT文件系统
- 不依赖于平台,易于移植
- 代码和工作区占用空间非常小
- 多种配置选项
- 多卷(物理驱动器和分区)
- 多ANSI/OEM代码页,包括DBCS
- 在ANSI/OEM或Unicode中长文件名的支持
- RTOS的支持
- 多扇区大小的支持
- 只读,最少API,I/O缓冲区等等
移植性
fatfs模块是ANSI C(C89)编写的。 没有平台的依赖, 编译器只要符合ANSI C标准就可以编译。
数据类型
fatf模块假设大小的字符/短/长8/16/32位和int是16或32位。 这些数据类型在integer.h文件中定义。这些数据类型在大多数的编译器中定义都符合要求。 如果现有的定义与编译器有任何冲突发生时,需要自己解决。
FATFS移植准备
本文档移植的开发环境与芯片型号:
目标芯片:STM32F405RGT6
编译软件:KEIL5.27
移植文件系统之前,需要有一个完整的工程,并且有完整的SD卡驱动。
还需要准备SD一张。
FATFS源码下载
下载地址:FatFs - Generic FAT Filesystem Module
写这篇文档时,官网最新的版本是:R0.14b
官网截图:
FATFS有两个版本,一个大版本,一个小版本。小版本主要用于8位机(内存小)使用。
下载图:
FATFS源码文件介绍
将下载的源码解压后可以得到两个文件夹: documents和 source。 documents里面主要是对 FATFS 的介绍(离线文档—英文和日文),而 source里面才是我们需要的源码,其中,与平台无关的是:
- ffconf.h FATFS配置文件
- ff.h 应用层头文件
- ff.c 应用层源文件
- diskio.h 硬件层头文件
- ffunicode.c 外部功能(比如支持中文等)
- ffsystem.c 可选O/S相关函数的例子
与平台相关的代码:
diskio.c 底层接口文件(需要用户修改)
FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。
FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。
FATFS框架图介绍
最顶层是应用层,使用者无需理会 FATFS 的内部结构和复杂的 FAT 协议,只需要调用
FATFS 模块提供给用户的一系列应用接口函数,如 f_open, f_read, f_write 和 f_close 等,就可以像在 PC 上读/写文件那样简单。
中间层 FATFS 模块, 实现了 FAT 文件读/写协议。 FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
需要我们编写移植代码的是 FATFS 模块提供的底层接口,它包括存储媒介读/写接口 ( disk、I/O) 和供给文件创建修改时间的实时时钟。
移植文件系统步骤
1. 把下载的FATFS源码拷贝到具备SD(MMC)卡驱动的工程并解压。
2. 打开KEIL工程,添加FATFS文件源码:
3. 添加FATFS相关头文件路径
4. 编译工程,出现以下错误,然后进行修改。
5. 修改diskio.c文件
1) 添加头文件及宏
注释掉现在不需要的用到的文件,因为我们现在用的是SD卡,与RAM,MMC,USB卡没关系。
添加SD卡驱动对应的头文件(可能存在不同的写法)。我的为:
#include "sd_driver.h"
并加入一个新的宏 :
#define DEV_SD 0
定义SD卡的物理驱动器号为0。
2) 修改 disk_status函数,该函数主要是用来获取磁盘状态。现在未用到,可以直接函数体内代码删除。
修改截图:
代码示例:
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "sd_driver.h"
#define DEV_SD 0 /* Example: Map SDCard to physical drive 0 */
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return RES_OK;
}
3) 修改disk_initialize函数,添加SD卡的初始化,其他不用到的代码直接删掉,该函数成功返回0,失败返回1。
修改截图:
代码示例:
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv) {
case DEV_SD :
stat = SD_Initialize(); //SD卡初始化,用户自己提供
return stat;
}
return STA_NOINIT;
}
4) 修改disk_read函数,加入SD卡读任意扇区的函数(需要用户自己提供),其他不用到的选项可以删掉。
修改代码如下:
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;
switch (pdrv) {
case DEV_SD:
res = SD_ReadDisk(buff, sector, count);//SD卡读扇区函数
return res;
}
return RES_PARERR;
}
5) 修改disk_write 函数,添加写扇区函数:
代码:
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;
switch (pdrv) {
case DEV_SD :
res = SD_WriteDisk(( BYTE *)buff, sector, count);//SD卡写扇区
return res;
}
return RES_PARERR;
}
6) 修改disk_ioctl 函数,填充ioctl命令功能。这些功能是标准的命令,在diskio.h有定义。
代码如下:
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
switch (pdrv) {
case DEV_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 = 4096;
res = RES_OK;
break;
case GET_SECTOR_COUNT: //SD卡扇区数量
*(unsigned int *)buff = SD_GetSectorCount();break;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
return RES_PARERR;
}
6. 修改ffconf.h文件
1) 修改编码格式
#define _CODE_PAGE 936 //采用中文GBK编码
2) 修改LFN支持
#define _USE_LFN 3 //动态的堆上工作
#define _MAX_LFN 255 /*_USE_LFN选项开关LFN(长文件名)特性。
7. 实现动态内存分配函数
在ffsystem.c文件有动态内存的释放,动态内存申请,在此文件中添加动态内存的释放,动态内存申请函数所需要的头文件。
8. 实现时间函数
此时编译工程,出现以下错误:
此错误原因是没有在ff.h文件中定义时间获取函数。实现此函数可使用STM32的RTC功能,如果没有实现RTC,可以直接返回0。
没有RTC。
有RTC。需要根据自行的RTC进行修改。
示例代码:
/*********************************************************************************************************
* 函 数 名 : get_fattime
* 功能说明 : 获取时间,创建文件或更新文件时需要用到
* 形 参 : 无
* 返 回 值 : 时间数据
* 备 注 : 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)
{
DWORD date = 0;
RTC_GetTimeDate();
date =
( (
(RTC_DateStruct.RTC_Year+20) << 25) |
(RTC_DateStruct.RTC_Month << 21 ) |
(RTC_DateStruct.RTC_Date << 16 ) |
(RTC_TimeStruct.RTC_Hours << 11 ) |
(RTC_TimeStruct.RTC_Minutes << 5 ) |
(RTC_TimeStruct.RTC_Seconds )
);
return date;
}
9. 修改堆栈空间
完成了上述的修改,还需要修改堆栈空间,因为长文件支持需要占用堆空间。
修改STM32启动文件如下:
至此,已经完成了FATFS的移植。
FATFS示例
修改完毕之后,给开发板插上SD卡,调用API函数在SD卡创建一个文件,并写入数据,测试是否成功:
示例:
#include "led.h"
#include "key.h"
#include "systick.h"
#include "usart1.h"
#include "sd_driver.h"
#include "ff.h"
int main()
{
FATFS fs;
FRESULT res;
FIL fp;
UINT bw;
UINT br;
u8 readBuf[13]={0};
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断分组,只配置一次
Systick_Inti(168); //系统滴答初始化
Usart1_Init(115200); //初始化USART1,波特率为115200
Key_Init(); //按键端口初始化
/*FATFS测试*/
//1. 注册
res = f_mount(&fs,"0",1);
if(res ==FR_OK)
{
printf("SD卡注册成功\r\n");
}else{
printf("SD卡注册失败\r\n");
}
//2.打开或者创建
res = f_open(&fp,"chenxingxing.txt",FA_CREATE_ALWAYS|FA_WRITE);
if(res ==FR_OK)
{
printf("成功打开或创建文件\r\n");
}else{
printf("打开失败\r\n");
}
//3.写数据
res = f_write(&fp,"hello world",12,&bw);
if(res ==FR_OK)
{
printf("成功写入%d个字节\r\n",bw);
}else{
printf("写入文件失败\r\n");
}
//4.关闭文件
f_close(&fp); //关闭文件
//5.以只读方式打开文件
f_open(&fp,"chenxingxing.txt",FA_READ);
//6.读取文件
res = f_read(&fp,readBuf,12,&br);
if(res ==FR_OK)
{
printf("成功读取%d个字节,读到的内容为:%s\r\n",br,readBuf);
}else{
printf("读取文件失败\r\n");
}
while(1)
{
}
}
测试结果: