FatFs操作SD卡上的文件(SWM341芯片作为案例)

软件开发大郭
0 评论
/
32 阅读
/
28798 字
22 2022-05

简单介绍FATFS

FatFS一般作为一个文件系统,存储介质支持USB,SD卡,NANDFlash等等,
这里主要介绍下如何对接SD卡使用。

实现的功能和效果

由于UI是采用LVGL来实现的,而且大面积用到了贴图和动画效果,占用的存储空间远远超过芯片的存储空间,
所以准备把资源部分单独以BIN格式的文件存放到sd卡上,sd卡的文件系统格式采用FAT32的,这样子程序和资源
就分离开了,程序直接存放到芯片的flash中,图片资源独立存放到sd卡上。

先看SD卡的硬件原理图

SD卡原理图
SD卡原理图
主控MCU
主控原理图

对接底层IO

操作SD卡,所有的操作模块都对应的在DEV_MMC分支里操作。

包含进IO操作相关的头文件

#include "SWM341.h"

我这里是SWM341的芯片,如果是其他芯片例如STM32的,则要包含对应的IO操作的头文件进来。

IO初始化

对应位置:fatfs/diskio.c

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

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

    switch (pdrv) {
    case DEV_RAM:
        //result = RAM_disk_initialize();

        // translate the reslut code here

        return stat;

    case DEV_MMC:
        //result = MMC_disk_initialize();

        // translate the reslut code here
        PORT_Init(PORTM, PIN2, PORTM_PIN2_SD_CLK, 0);
        PORT_Init(PORTM, PIN4, PORTM_PIN4_SD_CMD, 1);
        PORT_Init(PORTM, PIN5, PORTM_PIN5_SD_D0, 1);
        PORT_Init(PORTM, PIN6, PORTM_PIN6_SD_D1, 1);
        PORT_Init(PORTN, PIN0, PORTN_PIN0_SD_D2, 1);
        PORT_Init(PORTN, PIN1, PORTN_PIN1_SD_D3, 1);

        result = SDIO_Init(10000000);
        if (result == SD_RES_OK) {
            stat = RES_OK;

            sd_initialized = 1;
        } else {
            stat = STA_NOINIT;

            sd_initialized = 0;
        }

        return stat;
        return stat;

    case DEV_USB:
        //result = USB_disk_initialize();

        // translate the reslut code here

        return stat;
    }
    return STA_NOINIT;
}

读取IO配置

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

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;
    int result;

    switch (pdrv) {
    case DEV_RAM:
        // translate the arguments here

        //result = RAM_disk_read(buff, sector, count);

        // translate the reslut code here

        res = RES_PARERR;

        return res;

    case DEV_MMC:
        // translate the arguments here

        //result = MMC_disk_read(buff, sector, count);

        // translate the reslut code here
        if (count == 1) {
            result = SDIO_BlockRead(sector, (uint32_t*) buff);
        } else {
            result = SDIO_MultiBlockRead(sector, count, (uint32_t*) buff);
        }

        if (result == SD_RES_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

        return res;

    case DEV_USB:
        // translate the arguments here

        //result = USB_disk_read(buff, sector, count);

        // translate the reslut code here
        res = RES_PARERR;

        return res;
    }

    return RES_PARERR;
}

写IO配置

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

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;
    int result;

    switch (pdrv) {
    case DEV_RAM:
        // translate the arguments here

        //result = RAM_disk_write(buff, sector, count);

        // translate the reslut code here

        return res;

    case DEV_MMC:
        // translate the arguments here

        //result = MMC_disk_write(buff, sector, count);

        // translate the reslut code here
        if (count == 1) {
            result = SDIO_BlockWrite(sector, (uint32_t*) buff);
        } else {
            result = SDIO_MultiBlockWrite(sector, count, (uint32_t*) buff);
        }

        if (result == SD_RES_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

        return res;

    case DEV_USB:
        // translate the arguments here

        //result = USB_disk_write(buff, sector, count);

        // translate the reslut code here

        return res;
    }

    return RES_PARERR;
}

#endif

IO控制

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

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

    switch (pdrv) {
    case DEV_RAM:

        // Process of the command for the RAM drive
        res = RES_PARERR;

    case DEV_MMC:

        // Process of the command for the MMC/SD card
        res = RES_OK;

    case DEV_USB:

        // Process of the command the USB drive

        res = RES_PARERR;
    }

    return RES_PARERR;
}

结合LVGL使用

完整的fatfs/diskio.c 文件

/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include "ff.h"            /* Obtains integer types */
#include "diskio.h"        /* Declarations of disk functions */
#include "SWM341.h"
/* Definitions of physical drive number for each drive */
#define DEV_RAM        0    /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC        1    /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB        2    /* Example: Map USB MSD to physical drive 2 */

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/
static int sd_initialized = 0;

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

    switch (pdrv) {
    case DEV_RAM:
        //result = RAM_disk_status();

        // translate the reslut code here
        stat = STA_NOINIT;
        return stat;

    case DEV_MMC:
        //result = MMC_disk_status();

        // translate the reslut code here
        if (sd_initialized)
            stat = RES_OK;
        else
            stat = STA_NOINIT;

        return stat;
        return stat;

    case DEV_USB:
        //result = USB_disk_status();
        stat = STA_NOINIT;
        // translate the reslut code here

        return stat;
    }
    return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

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

    switch (pdrv) {
    case DEV_RAM:
        //result = RAM_disk_initialize();

        // translate the reslut code here

        return stat;

    case DEV_MMC:
        //result = MMC_disk_initialize();

        // translate the reslut code here
        PORT_Init(PORTM, PIN2, PORTM_PIN2_SD_CLK, 0);
        PORT_Init(PORTM, PIN4, PORTM_PIN4_SD_CMD, 1);
        PORT_Init(PORTM, PIN5, PORTM_PIN5_SD_D0, 1);
        PORT_Init(PORTM, PIN6, PORTM_PIN6_SD_D1, 1);
        PORT_Init(PORTN, PIN0, PORTN_PIN0_SD_D2, 1);
        PORT_Init(PORTN, PIN1, PORTN_PIN1_SD_D3, 1);

        result = SDIO_Init(10000000);
        if (result == SD_RES_OK) {
            stat = RES_OK;

            sd_initialized = 1;
        } else {
            stat = STA_NOINIT;

            sd_initialized = 0;
        }

        return stat;

    case DEV_USB:
        //result = USB_disk_initialize();

        // translate the reslut code here

        return stat;
    }
    return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

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;
    int result;

    switch (pdrv) {
    case DEV_RAM:
        // translate the arguments here

        //result = RAM_disk_read(buff, sector, count);

        // translate the reslut code here

        res = RES_PARERR;

        return res;

    case DEV_MMC:
        // translate the arguments here

        //result = MMC_disk_read(buff, sector, count);

        // translate the reslut code here
        if (count == 1) {
            result = SDIO_BlockRead(sector, (uint32_t*) buff);
        } else {
            result = SDIO_MultiBlockRead(sector, count, (uint32_t*) buff);
        }

        if (result == SD_RES_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

        return res;

    case DEV_USB:
        // translate the arguments here

        //result = USB_disk_read(buff, sector, count);

        // translate the reslut code here
        res = RES_PARERR;

        return res;
    }

    return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

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;
    int result;

    switch (pdrv) {
    case DEV_RAM:
        // translate the arguments here

        //result = RAM_disk_write(buff, sector, count);

        // translate the reslut code here

        return res;

    case DEV_MMC:
        // translate the arguments here

        //result = MMC_disk_write(buff, sector, count);

        // translate the reslut code here
        if (count == 1) {
            result = SDIO_BlockWrite(sector, (uint32_t*) buff);
        } else {
            result = SDIO_MultiBlockWrite(sector, count, (uint32_t*) buff);
        }

        if (result == SD_RES_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

        return res;

    case DEV_USB:
        // translate the arguments here

        //result = USB_disk_write(buff, sector, count);

        // translate the reslut code here

        return res;
    }

    return RES_PARERR;
}

#endif

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

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

    switch (pdrv) {
    case DEV_RAM:

        // Process of the command for the RAM drive
        res = RES_PARERR;

    case DEV_MMC:

        // Process of the command for the MMC/SD card
        res = RES_OK;

    case DEV_USB:

        // Process of the command the USB drive

        res = RES_PARERR;
    }

    return RES_PARERR;
}

修改port/lv_port_fs.c文件

/**
 * @file lv_port_fs.c
 *
 */

/*Copy this file as "lv_port_fs.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_fs.h"
#include "fatfs/ff.h"
#include "../lvgl/lvgl.h"
#include "stdio.h"
#include <stdio.h>
#define log(fmt, arg...)    printf("[lv_port_fs] "fmt, ##arg)
/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void fs_init(void);
static bool fs_ready(struct _lv_fs_drv_t *drv);
static void* fs_open(struct _lv_fs_drv_t *drv, const char *path,
        lv_fs_mode_t mode);
static lv_fs_res_t fs_close(struct _lv_fs_drv_t *drv, void *file_p);
static lv_fs_res_t fs_read(struct _lv_fs_drv_t *drv, void *file_p, void *buf,
        uint32_t btr, uint32_t *br);
static lv_fs_res_t fs_write(struct _lv_fs_drv_t *drv, void *file_p,
        const void *buf, uint32_t btw, uint32_t *bw);
static lv_fs_res_t fs_seek(struct _lv_fs_drv_t *drv, void *file_p, uint32_t pos,
        lv_fs_whence_t whence);
static lv_fs_res_t fs_tell(struct _lv_fs_drv_t *drv, void *file_p,
        uint32_t *pos_p);
static void* fs_dir_open(struct _lv_fs_drv_t *drv, const char *path);
static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t *drv, void *rddir_p,
        char *fn);
static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t *drv, void *rddir_p);

/**********************
 *  STATIC VARIABLES
 **********************/
static FATFS fatfs;
static bool fs_is_ready;
/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_fs_init(void) {
    /*----------------------------------------------------
     * Initialize your storage device and File System
     * -------------------------------------------------*/

    fs_init();

    /*---------------------------------------------------
     * Register the file system interface in LVGL
     *--------------------------------------------------*/

    /*Add a simple drive to open images*/
    static lv_fs_drv_t fs_drv;
    lv_fs_drv_init(&fs_drv);

    /*Set up fields...*/
    fs_drv.letter = 'S';
    fs_drv.ready_cb = fs_ready;
    fs_drv.open_cb = fs_open;
    fs_drv.close_cb = fs_close;
    fs_drv.read_cb = fs_read;
    fs_drv.write_cb = fs_write;
    fs_drv.seek_cb = fs_seek;
    fs_drv.tell_cb = fs_tell;

    fs_drv.dir_open_cb = fs_dir_open;
    fs_drv.dir_read_cb = fs_dir_read;
    fs_drv.dir_close_cb = fs_dir_close;

    lv_fs_drv_register(&fs_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

//LV_FS_RES_OK=0,
//LV_FS_RES_HW_ERR,            /*低级硬件错误*/
//LV_FS_RES_FS_ERR,            /*文件系统结构中的错误*/
//LV_FS_RES_NOT_EX,            /*驱动程序、文件或目录不存在*/
//LV_FS_RES_FULL,            /*磁盘已满*/
//LV_FS_RES_LOCKED,            /*文件已打开*/
//LV_FS_RES_DENIED,            /*访问被拒绝。检查“fs_打开”模式和写保护*/
//LV_FS_RES_BUSY,            /*文件系统现在无法处理它,请稍后再试*/
//LV_FS_RES_TOUT,            /*过程时间路由*/
//LV_FS_RES_NOT_IMP,        /*请求的函数未实现*/
//LV_FS_RES_OUT_OF_MEM,        /*内存不足,无法执行内部操作*/
//LV_FS_RES_INV_PARAM,        /*参数中的参数无效*/
//LV_FS_RES_UNKNOWN,        /*其他未知错误*/
static lv_fs_res_t to_lvfs_res(FRESULT res) {
    switch (res) {
    case FR_OK:
        return LV_FS_RES_OK; /*(0)成功*/
    case FR_DISK_ERR:
        return LV_FS_RES_HW_ERR; /*(1)在低级别磁盘I/O层中发生了一个硬错误*/
    case FR_INT_ERR:
        return LV_FS_RES_FS_ERR; /*(2)断言失败*/
    case FR_NOT_READY:
        return LV_FS_RES_HW_ERR; /*(3)物理驱动器无法工作*/
    case FR_NO_FILE:
        return LV_FS_RES_NOT_EX; /*(4)找不到该文件*/
    case FR_NO_PATH:
        return LV_FS_RES_NOT_EX; /*(5)找不到路径*/
    case FR_INVALID_NAME:
        return LV_FS_RES_INV_PARAM; /*(6)路径名格式无效*/
    case FR_DENIED:
        return LV_FS_RES_DENIED; /*(7)拒绝访问:磁盘以满\使用写模式打开只读文件\删除只读文件...等等*/
    case FR_EXIST:
        return LV_FS_RES_DENIED; /*(8)已经存在同名的文件或目录*/
    case FR_INVALID_OBJECT:
        return LV_FS_RES_INV_PARAM; /*(9)文件/目录对象无效*/
    case FR_WRITE_PROTECTED:
        return LV_FS_RES_DENIED; /*(10)物理驱动器是写保护的*/
    case FR_INVALID_DRIVE:
        return LV_FS_RES_INV_PARAM; /*(11)逻辑驱动器号无效*/
    case FR_NOT_ENABLED:
        return LV_FS_RES_HW_ERR; /*(12)当前卷没有工作区*/
    case FR_NO_FILESYSTEM:
        return LV_FS_RES_HW_ERR; /*(13)没有有效的FAT卷*/
    case FR_MKFS_ABORTED:
        return LV_FS_RES_INV_PARAM; /*(14)f_MKFS()在格式化开始前终止*/
    case FR_TIMEOUT:
        return LV_FS_RES_BUSY; /*(15)无法在定义的时间段内获得访问卷的授权*/
    case FR_LOCKED:
        return LV_FS_RES_LOCKED; /*(16)根据文件共享策略拒绝该操作*/
    case FR_NOT_ENOUGH_CORE:
        return LV_FS_RES_OUT_OF_MEM; /*(17)无法分配LFN工作缓冲区*/
    case FR_TOO_MANY_OPEN_FILES:
        return LV_FS_RES_BUSY; /*(18)打开的文件数目大于 FF_FS_LOCK*/
    case FR_INVALID_PARAMETER:
        return LV_FS_RES_INV_PARAM; /*(19)给定参数无效*/
    }
    return LV_FS_RES_UNKNOWN;
}

/*Initialize your Storage device and File system.*/
static void fs_init(void) {
    /*E.g. for FatFS initialize the SD card and FatFS itself*/
    FRESULT res;
    res = f_mount(&fatfs, "sd:", 1);
    if (res != FR_OK) {
        fs_is_ready = false;
        log("f_mount failed...\n");
        return;
    }

    log("f_mount success...\n");
    fs_is_ready = true;
    return;
}

/***********************************************************************************
 *    函数:             fs_ready
 *    功能:            LVGL 会通过此函数判断文件系统是否就绪
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *    返回值:
 *        true:        已就绪
 *        false:        未就绪
 ***********************************************************************************/
static bool fs_ready(struct _lv_fs_drv_t *drv) {
    FRESULT res;
    if (false == fs_is_ready) {
        res = f_mount(&fatfs, "sd:", 1);
        if (res != FR_OK) {
            fs_is_ready = false;
            log("f_mount failed...\n");
            return fs_is_ready;
        }

        if (false == fs_is_ready) {
            log("f_mount failed...\n");
        }
    }
    return fs_is_ready;
}

/***********************************************************************************
 *    函数:             fs_open
 *    功能:            LVGL 会通过此函数打开指定的文件
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        path:        要打开的文件路径
 *        mode:        打开模式 LV_FS_MODE_WR\LV_FS_MODE_RD
 *    返回值:
 *        >0:            返回打开文件的句柄,后续操作皆以此句柄为核心(void * file_p)
 *        NULL:        打开文件失败
 ***********************************************************************************/
static void* fs_open(struct _lv_fs_drv_t *drv, const char *path,
        lv_fs_mode_t mode) {
    FRESULT fr_res;
    unsigned char f_mode = 0x00;
    char *path_buf = NULL;
    uint16_t path_len = strlen(path);

    /************************************************
     * 在这里,我们需要先进行设备的判断,因为我们实际上
     * 可能不只有一个设备,而且LVGL里使用letter(即单个字符)
     * 作为驱动号
     * 而在FatFS里使用VOLUME_STR(字符串)或者一个字节的pdrv
     * 驱动号,使用前要进行转换。
     *
     * 有一种方便的方法,就是上面提到的user_data,我们可以把
     * VOLUME_STR提前装入user_data,那我们的程序进行判断时
     * 耦合度会更低,在更多设备时,这种方法会更方便
     *************************************************/

    // 根据传入的letter判断是什么存储设备
    switch (drv->letter) {
    case 'S':       // SD card
        path_buf = (char*) lv_mem_alloc(sizeof(char) * (path_len + 4));
        log("origin path:%s",path);
        sprintf(path_buf, "sd:/%s", path);
        break;
    case 'F':       // SPI FALSH
        path_buf = (char*) lv_mem_alloc(sizeof(char) * (path_len + 6));
        sprintf(path_buf, "SPIF:/%s", path);
        break;
    default:
        printf("No drive %c\n", drv->letter);
        return LV_FS_RES_NOT_EX;
    }

    /* 调用FatFs的函数 */

    FIL *fil = (FIL*) lv_mem_alloc(sizeof(FIL));
    if (NULL == fil) {
        log("fs_open: lv_mem_alloc failed %d Byte...\n", sizeof(FIL));
        return NULL;
    }

    /* 文件操作方法,将FatFS的转换成LVGL的操作方法 */
    if (mode == LV_FS_MODE_WR) {
        f_mode = FA_OPEN_ALWAYS | FA_WRITE;
    } else if (mode == LV_FS_MODE_RD) {
        f_mode = FA_OPEN_EXISTING | FA_READ;
    } else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) {
        f_mode = FA_WRITE | FA_READ;
    }

    if ((fr_res = f_open(fil, path_buf, f_mode)) != FR_OK) {
        //释放内存
        lv_mem_free(fil);
        log("f_open failed:[0x%.2X][%d][%s]...\n", f_mode, fr_res, path_buf);
        return NULL;
    }
    //释放内存
    lv_mem_free(path_buf);
    return (void*) fil;
}

/***********************************************************************************
 *    函数:             fs_close
 *    功能:            LVGL 会通过此函数关闭指定的文件
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        file_p:     文件对应的句柄, 由 fs_open() 成功时取得
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_close(struct _lv_fs_drv_t *drv, void *file_p) {
    if (file_p) {
        f_close((FIL*) file_p);
        lv_mem_free(file_p);
        return LV_FS_RES_OK;
    }

    return LV_FS_RES_INV_PARAM;
}

/***********************************************************************************
 *    函数:             fs_close
 *    功能:            LVGL 会通过此函数读取指定的文件
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        file_p:     文件对应的句柄, 由 fs_open() 成功时取得
 *        buf:        存储文件数据的缓冲区
 *        btr:        存储文件数据的缓冲区大小
 *        br:            实际读取文件的数据长度
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_read(struct _lv_fs_drv_t *drv, void *file_p, void *buf,
        uint32_t btr, uint32_t *br) {
    FRESULT fr_res = FR_OK;

    if ((fr_res = f_read((FIL*) file_p, buf, btr, br)) != FR_OK) {
        log("f_read failed:[%d]...\n", fr_res);
    } else {
        log("f_read success:btr:[%d] br:[%d]...\n", btr, *br);
    }
    return to_lvfs_res(fr_res);
}

/***********************************************************************************
 *    函数:             fs_write
 *    功能:            LVGL 会通过此函数写入指定的文件
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        file_p:     文件对应的句柄, 由 fs_open() 成功时取得
 *        buf:        存储要写入文件的数据
 *        btw:        写于文件数据的长度
 *        bw:            实际写入文件的数据长度
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_write(struct _lv_fs_drv_t *drv, void *file_p,
        const void *buf, uint32_t btw, uint32_t *bw) {
    FRESULT fr_res = f_write((FIL*) file_p, buf, btw, bw);

    if (FR_OK == fr_res && *bw < btw) {
        log("fs_write failed disk full: %d < %d...\n", *bw, btw);
        return LV_FS_RES_FULL;
    }

    if (FR_OK != fr_res) {
        log("f_write failed:[%d]...\n", fr_res);
    }

    return to_lvfs_res(fr_res);
}

/***********************************************************************************
 *    函数:             fs_seek
 *    功能:            LVGL 会通过此函数调整文件指针的偏移位置
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        file_p:     文件对应的句柄, 由 fs_open() 成功时取得
 *        pos:        文件位置
 *        whence:        指定以什么方式调整: LV_FS_SEEK_SET(起始)、 LV_FS_SEEK_CUR(当前)、 LV_FS_SEEK_END(结束)
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_seek(struct _lv_fs_drv_t *drv, void *file_p, uint32_t pos,
        lv_fs_whence_t whence) {
    switch (whence) {
    case LV_FS_SEEK_SET:
        return to_lvfs_res(f_lseek((FIL*) file_p, pos));
    case LV_FS_SEEK_CUR:
        return to_lvfs_res(f_lseek((FIL*) file_p, f_tell((FIL *)file_p) + pos));
    case LV_FS_SEEK_END:
        return to_lvfs_res(f_lseek((FIL*) file_p, f_size((FIL *)file_p) + pos));
    }
    return LV_FS_RES_INV_PARAM;
}

/***********************************************************************************
 *    函数:             fs_tell
 *    功能:            LVGL 会通过此函数获取当前文件指针的位置
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        file_p:     文件对应的句柄, 由 fs_open() 成功时取得
 *        pos:        通过此参数返回文件指针的位置
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_tell(struct _lv_fs_drv_t *drv, void *file_p,
        uint32_t *pos_p) {
    return to_lvfs_res(f_tell((FIL* )file_p));
}

/***********************************************************************************
 *    函数:             fs_dir_open
 *    功能:            LVGL 会通过此函数打开当前目录
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        path:          要打开的目录路径
 *    返回值:
 *        >0:            返回打开目录的句柄,后续操作皆以此句柄为核心(void * rddir_p)
 *        NULL:        打开目录失败
 ***********************************************************************************/
static void* fs_dir_open(struct _lv_fs_drv_t *drv, const char *path) {
    FRESULT fr_res = FR_OK;

    DIR *dir = (DIR*) lv_mem_alloc(sizeof(DIR));
    if (NULL == dir) {
        log("f_opendir: lv_mem_alloc %d byte failed...\n", sizeof(DIR));
        return NULL;
    }

    if ((fr_res = f_opendir(dir, path)) != FR_OK) {
        lv_mem_free(dir);
        log("f_opendir failed:[%d][%s]...\n", fr_res, path);
        return NULL;
    }

    return (void*) dir;
}

/***********************************************************************************
 *    函数:             fs_dir_read
 *    功能:            LVGL 会通过此函数获取当前目录的条目(即该目录下有哪些文件夹和文件)
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        rddir_p:     目录对应的句柄, 由 fs_dir_open() 成功时取得
 *        fn:            通过此参数返回目录内容, 一次只返回一个条目(如有十个文件, 只返回一个)
 *    返回值:            参考 lv_fs_res_t
 *    备注说明:
 *                    1. 没有条目或读取完条目则返回空文本内容到 fn (即 \0)
 *                    2. LGVL 对上述似乎并没有明确要求, 并且参数也显得不合理(缺少缓冲区长度信息)
 *                    3. 使用时务必注意 fn 越界问题, fn 缓冲区大小必须大于 FILINFO->fname+1 大小
 ***********************************************************************************/
static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t *drv, void *rddir_p,
        char *fn) {
    FRESULT fr_res = FR_OK;
    FILINFO fno;

    fr_res = f_readdir((DIR*) rddir_p, &fno);
    if (fr_res != FR_OK) {
        log("f_readdir failed:[%d]...\n", fr_res);
        return to_lvfs_res(fr_res);
    }

    if ('\0' == fno.fname[0]) {
        fn[0] = '\0';
        return LV_FS_RES_OK;
    }

    if (fno.fattrib & AM_DIR) {
        fn[0] = '/';
        fn++;
    }

    // 注意, 这里会存在内存溢出的风险, LVGL 的接口并未提供缓冲区长度信息
    strcpy(fn, fno.fname); // strncpy(fn, fno.fname, fn_size);

    return LV_FS_RES_OK;
}

/***********************************************************************************
 *    函数:             fs_dir_close
 *    功能:            LVGL 会通过此函数关闭指定目录
 *    参数:
 *        drv:        上下文, 我的理解, 便于取 letter、user_data 成员变量
 *        rddir_p:     目录对应的句柄, 由 fs_dir_open() 成功时取得
 *    返回值:            参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t *drv, void *rddir_p) {
    if (rddir_p) {
        f_closedir((DIR*) rddir_p);
        lv_mem_free(rddir_p);
        return LV_FS_RES_OK;
    }

    return LV_FS_RES_INV_PARAM;
}

#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
    暂无数据