48. FATFS文件管理系统

一、FATFS简介

  FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。

FAT32文件系统布局

  • 系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)。
  • 文件分配表:记录文件存储中簇与簇之间连接的信息。
  • 根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)。
  • 数据区:文件等数据存放地方,占用大部分的磁盘空间。

  FAT 文件系统用 “簇” 作为数据单元,一个 “簇” 由一组连续的扇区组成,而一个扇区的大小为 512 字节。所有的簇从 2 开始进行编号,每个簇都有自己的地址编号,用户文件和数据都存储在簇中。

二、FATFS模块的层次结构

FATFS模块的层次结构图

  • 底层接口(Low level device controls)
    • 存储媒介读/写接口(disk I/O)和供给文件创建修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。
  • FATFS 模块(FatFs Module)
    • 实现了 FAT 文件读/写协议 .FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去。
  • 应用层(Application)
    • 使用者无需理会 FATFS 的内容结构和复杂的 FAT 协议,只需要调用 FATFS 模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,就可以像在 PC 机上读/写文件。

三、FATFS源文件的下载

  我们可以从官网(http://elm-chan.org/fsw/ff/00index_e.html)下载最新版本的 FATFS 软件包,解压后可以得到两个文件夹:【documents】 和 【source】。【documents】里面主要是对 FATFS 的介绍,而 【source】里面才是我们需要的源码。

FATFS的源码文件介绍

四、FATFS的移植步骤

  1. 数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。
  2. 配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。
  3. 函数编写:打开 diskio.c,进行底层驱动编写,需要编写 5 个接口函数。

  FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。FATFS 模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。

FATFS配置项

  在 ffconf.h 文件中 FF_FS_NORTC 宏为 0 时,我们需要实现 get_fattime() 函数。

get_fattime函数

  因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。这些函数一般有 5 个,在 diskio.c 里面。

【1】、初始化磁盘驱动器函数

DSTATUS disk_initialize (BYTE pdrv);                                            // 初始化磁盘驱动器

disk_initialize函数

【2】、获取磁盘状态函数

DSTATUS disk_status (BYTE pdrv);                                                // 获取磁盘状态

disk_status函数

【3】、从磁盘驱动器读扇区函数

DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);            // 从磁盘驱动器读扇区

disk_read函数

【4】、向磁盘驱动器写扇区函数

DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);     // 向磁盘驱动器写扇区

disk_write函数

【5】、控制设备实现指定功能函数

DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);                           // 控制设备实现指定功能,用于辅助FATFS中其它API

disk_ioctl函数

  FATFS 提供了很多 API 函数。

// 文件操作
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);                                 // 打开/创建一个文件
FRESULT f_close (FIL* fp);                                                              // 关闭一个打开的文件
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);                               // 从文件中读取数据
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);                        // 往文件中写入数据
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp);                                          // 读一个字符串
int f_putc (TCHAR c, FIL* fp)                                                           // 写一个字符
int f_puts (const TCHAR* str, FIL* cp);                                                 // 写一个字符串
int f_printf (FIL* fp, const TCHAR* str, ...);                                          // 写一个格式化字符串
FRESULT f_lseek (FIL* fp, FSIZE_t ofs);                                                 // 文件指针定位
FRESULT f_truncate (FIL* fp);                                                           // 截断文件
FRESULT f_sync (FIL* fp);                                                               // 刷新文件
FRESULT f_stat (const TCHAR* path, FILINFO* fno);                                       // 获取文件信息
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);   // 获取第一个文件
FRESULT f_findnext (DIR* dp, FILINFO* fno);                                             // 获取下一个文件
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt);                                      // 扩展文件
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf);         // 将数据转发到流

#define f_tell(fp) ((fp)->fptr)                                                         // 获取文件指针位置
#define f_size(fp) ((fp)->obj.objsize)                                                  // 获取文件大小
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))                              // 判断文件是否到达文件末尾
#define f_rewind(fp) f_lseek((fp), 0)                                                   // 移动文件指针到文件开头


// 目录操作
FRESULT f_opendir (DIR* dp, const TCHAR* path);                                         // 打开一个目录
FRESULT f_closedir (DIR* dp)                                                            // 关闭一个打开的目录
FRESULT f_readdir (DIR* dp, FILINFO* fno);                                              // 读取目录条目
FRESULT f_mkdir (const TCHAR* path);                                                    // 创建一个目录
FRESULT f_unlink (const TCHAR* path);                                                   // 删除一个文件或目录
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new);                        // 重命名/移动一个文件或目录
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask);                              // 修改文件/目录属性
FRESULT f_utime (const TCHAR* path, const FILINFO* fno);                                // 更新文件/目录修改时间
FRESULT f_chdir (const TCHAR* path);                                                    // 改变当前目录
FRESULT f_getcwd (TCHAR* buff, UINT len);                                               // 获取当前目录

#define f_rmdir(path) f_unlink(path)                                                    // 删除一个目录
#define f_rewinddir(dp) f_readdir((dp), 0)                                              // 移动目录指针到目录开头

// 卷管理
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);                               // 注册/注销一个工作区
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len);         // 格式化,创建一个文件系统
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);                     // 获取磁盘信息以及空闲簇数量
FRESULT f_chdrive (const TCHAR* path)                                                   // 改变当前驱动器
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn);                       // 获取卷标
FRESULT f_setlabel (const TCHAR* label);                                                // 设置卷标
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work);                            // 磁盘分区
FRESULT f_setcp (WORD cp);                                                              // 设置代码页

#define f_error(fp) ((fp)->err)                                                         // 获取错误代码
#define f_unmount(path) f_mount(0, path, 0)                                             // 注销一个工作区

五、源码实现

5.1、修改ffconf.h的配置项

【1】、FF_FS_NORTC 的配置

FF_FS_NORTCd的配置

  FF_FS_NORTC 宏为 0 时,我们需要实现 get_fattime() 函数。

#define FF_FS_NORTC     1
#define FF_NORTC_MON	1
#define FF_NORTC_MDAY	1
#define FF_NORTC_YEAR	2022

【2】、FF_CODE_PAGE 的配置

FF_CODE_PAGE的配置

  设置语言类型,简体中文设置为 963。

#define FF_CODE_PAGE	936

【3】、FF_USE_STRFUNC 的配置

FF_USE_STRFUNC的配置

  设置是否支持字符串类操作。

#define FF_USE_STRFUNC	1

【4】、FF_VOLUMES 的配置

FF_VOLUMES的配置

  设置 FATFS 支持的逻辑设备数目。

5.2、修改sidkio.c文件中的5个函数

#define DEV_SD      0

  获取磁盘状态函数:

DSTATUS disk_status (
    BYTE pdrv       /* Physical drive nmuber to identify the drive */
)
{
    return RES_OK;
}

  初始化磁盘驱动器函数:

DSTATUS disk_initialize (
    BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
    int result;

    switch (pdrv) 
    {
    case DEV_SD :
        result = SD_Init();
        if (result)
        {
            return STA_NOINIT;
        }
    }
    return RES_OK;
}

  从磁盘驱动器读扇区函数:

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 */
)
{
    int result;

    switch (pdrv) 
    {
    case DEV_SD :
        result = SD_ReadData(&g_sd_handler, sector, count, buff);
        if (result)
        {
            return RES_PARERR;
        }
    }

	return RES_OK;
}

  向磁盘驱动器写扇区函数:

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 */
)
{
    int result;

    switch (pdrv) 
    {
    case DEV_SD :
        result = SD_WriteData(&g_sd_handler, sector, count, (uint8_t *)buff);
        if (result)
        {
            return RES_PARERR;
        }
    }

    return RES_OK;
}

  控制设备实现指定功能函数:

DRESULT disk_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
{
    DRESULT result;

    switch (pdrv) 
    {
    case DEV_SD :
        switch (cmd)
        {
        case CTRL_SYNC:
            result = RES_OK;
            break;
        case GET_SECTOR_SIZE:
            *(DWORD*)buff = 512;
            result = RES_OK;
            break;;
        case GET_BLOCK_SIZE:
            *(DWORD*)buff = g_sd_handler.SdCard.BlockSize;
            result = RES_OK;
            break;
        case GET_SECTOR_COUNT:
            *(DWORD*)buff = g_sd_handler.SdCard.BlockNbr * g_sd_handler.SdCard.BlockSize / 512;
            result = RES_OK;
            break;
  
        default:
            result = RES_PARERR;
        }
    }

    return result;
}

5.3、main()函数

#include "ff.h"

int main(void)
{
    FATFS fs;
    FIL fil;
    FRESULT result;
  
    char writeData[] = "Hello, Sakura!";
    char readData[100] = {0};
    uint32_t write_data = 0, read_count = 0;
    uint32_t fSize = 0;
  
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    UART_Init(&g_usart1_handle, USART1, 115200);

    // 挂载SD卡设备
    // 第一个参数是挂载点,第二个参数是挂载设备编号,第三个参数是是立即挂载
    result = f_mount(&fs, "0:", 1);
    if (result)
    {
        printf("mount fail %d\r\n", result);
    }
    else
    {
        printf("mount success\r\n");
    }
  
    // 打开文件
    // 第一个参数是文件指针,第二个参数是文件路径,第三个参数是文件打开模式
    result = f_open(&fil, "0:test.txt", FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if (result)
    {
        printf("open fail %d\r\n", result);
    }
    else
    {
        printf("open success\r\n");
    }

    // 写入数据
    f_write(&fil, writeData, sizeof(writeData), (UINT *)&write_data);

    // 写完文件的读写指针已经到末尾,我们需要重新定位到文件开头
    f_lseek(&fil, 0);

    // 获取文件大小
    fSize = f_size(&fil);
  
    // 读取数据
    // 第一个参数是文件指针,第二个参数是读取的数据,第三个参数是要读取的数据长度,第四个参数保存实际读取的数据长度
    f_read(&fil, readData, fSize, (UINT *)&read_count);
    if (read_count)
    {
        printf("read count: %ld\r\n", read_count);
        printf("data:\r\n");
        printf("%s\r\n", readData);
    }
  
    // 关闭文件
    f_close(&fil);

    while (1)
    {
  
    }
  
    return 0;
}
posted @ 2024-01-23 19:32  星光樱梦  阅读(132)  评论(0编辑  收藏  举报