FATFS移植 - 基于N32G4FR在SD卡(sd nand)上移植FATFS文件系统

news2024/11/14 15:16:46

FATFS移植 - 基于N32G4FR在SD卡(sd nand)上移植FATFS文件系统

文章目录

  • FATFS移植 - 基于N32G4FR在SD卡(sd nand)上移植FATFS文件系统
    • 1. 前言
    • 2. FATFS文件系统介绍
      • 2.1 文件系统的概念和作用
      • 2.2 FATFS的特点和优势
    • 3. 相关源码获取
      • 3.1 FatFs文件源码下载
      • 3.2 国民技术N32软件开发套件获取
    • 4. 文件架构说明
      • 4.1 FatFs文件架构说明
        • 4.1.1 本地离线文档访问方式
        • 4.1.2 源码文件介绍
      • 4.2 国民技术N32文件架构说明
    • 5. FatFs移植
      • 5.1 SD卡/SD nand读写实现
      • 5.2 FatFs移植
        • 5.2.1 添加源文件
        • 5.2.2 添加设备号
        • 5.2.3 修改 `disk_status()` 函数
        • 5.2.4 修改 `disk_initialize()` 函数
        • 5.2.5 修改 `disk_read()` 函数
        • 5.2.6 修改 `disk_write()` 函数
        • 5.2.7 修改 `disk_ioctl()` 函数
        • 5.2.8 修改 `ffconf.h` 取消 `get_fattime` 实现
        • 5.2.9 接口测试
        • 5.2.10 调用通用文件操作接口进行读写测试
          • 5.2.10.1 `f_mount()` 函数
          • 5.2.10.2 `f_mkfs()` 函数
          • 5.2.10.3 读写测试
    • 6. 结束语

1. 前言

FATFS(File Allocation Table File System)是一个轻量级的文件系统,被广泛应用于嵌入式系统和小型存储设备中。它由Chan提供,并在嵌入式系统中得到了广泛的应用和支持。

FatFS官网地址为:FatFs - Generic FAT Filesystem Module。

本文主要分享关于 FatFS 文件系统在SD卡/SD nand上的移植使用,本文所采用的硬件环境如下:

  • 控制器:国民技术 N32G4FR,Cortex-M4内核控制器
  • SD nand:创世 CSNP4GCR01-AMW,4Gb SD nand

在这里插入图片描述

2. FATFS文件系统介绍

2.1 文件系统的概念和作用

在计算机系统中,文件系统是用于组织和管理存储介质上的文件和目录的一种结构。文件系统提供了对文件的读取、写入、删除、重命名等操作,以及对目录的创建、删除、遍历等操作。它使得用户和应用程序能够方便地访问和管理存储设备上的数据。

2.2 FATFS的特点和优势

FATFS是一个开源的文件系统,具有以下特点和优势:

  1. 轻量级和高效性:FATFS是一个轻量级的文件系统,适用于资源受限的嵌入式系统和小型存储设备。它的代码量相对较小,占用的存储空间较少,并且具有较高的运行效率。

  2. 跨平台兼容性:FATFS可以在多个操作系统和平台上运行,包括嵌入式系统、Windows、Linux等。这使得开发人员可以方便地将FATFS应用于不同的硬件平台和操作系统环境中。

  3. 易于集成和使用:FATFS的源代码结构清晰,具有简单的API接口,易于集成到嵌入式系统的应用程序中。开发人员可以通过简单的函数调用来实现文件的读写、目录的创建和遍历等操作。

  4. 支持多种存储介质:FATFS支持多种存储介质,包括SD卡、SPI Flash、硬盘等。它可以根据不同的存储介质进行配置和适配,并提供统一的文件系统接口。

  5. 支持多种文件操作:FATFS支持常见的文件操作,如文件的打开、关闭、读取、写入、定位等。它还支持目录的创建、删除、重命名和遍历,以及文件和目录的属性管理。

  6. 支持长文件名和短文件名:FATFS同时支持长文件名(Long File Name,LFN)和短文件名(Short File Name,SFN)。这使得文件系统更加灵活和兼容,能够处理各种不同的文件命名规则。

  7. 支持文件系统的格式化和检查:FATFS提供了格式化和检查文件系统的功能。开发人员可以通过相应的API函数来格式化存储介质并创建文件系统,也可以进行文件系统的检查和修复。这使得文件系统能够保持良好的状态,提高数据的可靠性和完整性。

  8. 支持文件的读写缓存:FATFS允许开发人员配置读写缓存,以提高文件的读写性能。通过使用读写缓存,可以减少对存储介质的频繁访问,提高文件的读写速度。

  9. 具有错误处理和容错机制:FATFS提供了错误处理和容错机制,能够检测和处理各种错误情况。例如,当存储介质出现错误或不可用时,FATFS能够进行相应的错误处理和恢复,保证文件系统的稳定性和可靠性。

3. 相关源码获取

3.1 FatFs文件源码下载

移植FatFs,第一步当然就是获取对应的移植文件了, FatFs的源文件在其官网上即可直接下载,地址:FatFs - Generic FAT Filesystem Module

在这里插入图片描述

3.2 国民技术N32软件开发套件获取

国民技术作为一款国产的IC,对标stm32,性能上也还是很不错了,不过其开发资料下载会有点麻烦,不过有幸你看到这里,就不会感觉很麻烦了,其开发资料下载方法如下:

  1. 打开电脑的文件管理器,输入以下内容:ftp://download.nationstech.com ,之后回车访问
    在这里插入图片描述
    在这里插入图片描述
  2. 此远程文件夹就是国民技术所有IC的全部资料了,之后便可根据自己手上所使用的IC,下载对应的资料,此处我们使用的是N32G4FR系列芯片,进入1-Microcontrollers 目录,选择对应的压缩包单击鼠标右键,选择 复制到文件夹 进行下载
    在这里插入图片描述

4. 文件架构说明

4.1 FatFs文件架构说明

从官网上下载FatFs文件解压后,主要有两个目录:

在这里插入图片描述

  • documents:主要存放关于FatFs有关离线本地文档,文档内容和官网一致,等同于一个本地的副本,可以很好解决官网访问速度过慢的问题

  • source:里面存放了FatFs有关的源码

4.1.1 本地离线文档访问方式

使用本地浏览器打开 documents 目录下的 00index_e.html 文件,即可访问一个和官网完全一样的网页文档!且文档内的超链接也可直接点击跳转!

此文档非常重要,在FatFs文件使用中,很多API的接口,我们都需要参考此文档才行!

在这里插入图片描述
在这里插入图片描述

4.1.2 源码文件介绍

进入source 目录我们即可看到 fatfs 所有源码文件,7个文件,非常的精简,其各自功能如下:

文件名功能
ff.cFatFs 核心文件,不用修改。
ffconf.hFatFs 配置文件,需要修改此文件完成FatFs功能的配置,FatFs的裁剪也可通过修改此文件完成
ff.hFatFs 模块和应用程序公共头文件,不用修改,应用FatFs的时候需要包含此头文件
diskio.hFatFs 和 磁盘读写公共头文件, 不用修改。
diskio.c存放了一个示例,关于FatFs操作磁盘接口的,移植主要是实现此文件下的API函数
ffunicode.c可选的Unicode实用函数,不用修改。
ffsystem.c可选 O/S 相关功能的示例,不用修改。

综上,其实完成FatFs文件的移植,主要是修改 diskio.cffconf.h 这两个文件就可以了,如此简单!

diskio.c 文件内实现物理存储器的读写实体访问,在ffconf.h 内实现FatFs文件系统子功能的开关,之后就可以使用FatFs文件系统了!

4.2 国民技术N32文件架构说明

N32 资料压缩包下载后,内容如下,里面包含了对应系列芯片的所有资料,手册、应用笔记、软硬件资料等等。
在这里插入图片描述

其中我们关注最核心的几个文件,路径如下:

  • 芯片包路径如下:N32G4FR_V3.0.0\6-软件开发套件(Software Development Kit)

  • 例程Demo路径如下:N32G4FR_V3.0.0\6-软件开发套件(Software Development Kit)\Nationstech.N32G4FR_Library.2.1.0\projects\n32g4fr_EVAL\examples

5. FatFs移植

5.1 SD卡/SD nand读写实现

SD卡和SD nand只是封装上存在差异,软件上没有区别,因此后续不作区分

我们需要在SD nand上移植FatFs文件系统,肯定首先需要实现对SD nand的基本读写访问,之后再在读写访问的基础上给它穿上文件系统这层外套了,这就像一件一件穿衣服一样。

关于SD nand的读写操作,我们可以直接打开N32对应配套的SDIO例程,例程已完成了对SD卡的读写测试,我们直接拿过来测一下,可以跑起来说明硬件上就没有什么问题,关于例程的优化可以后续再慢慢进行。

例程路径:N32G4FR_V3.0.0\6-软件开发套件(Software Development Kit)\Nationstech.N32G4FR_Library.2.1.0\projects\n32g4fr_EVAL\examples\SDIO

在这里插入图片描述

对应例程硬件配置如下:

    1、SystemClock:144MHz
    2、DMA通道:DMA2_CH4
    3、SDIO 配置:
            D0   -->   PC8          50MHz,AF_PP
            D1   -->   PC9          50MHz,AF_PP
            D2   -->   PC10         50MHz,AF_PP
            D3   -->   PC11         50MHz,AF_PP
            CLK   -->  PC12         50MHz,AF_PP
            CMD   -->  PD2          50MHz,AF_PP

            分频系数:178    (SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + 分频系数))
            上升沿有效
            禁用旁路
            禁用时钟保持
            总线位宽4bit
    
    4、USART1配置:
            TX  -->  PA9            50MHz,AF_PP
            波特率:115200
            数据位:8bit
            停止位:1bit
            无校验

    5、测试步骤与现象
        a,测试前请先安装好TF卡
        b,编译下载代码复位运行
        c,从串口看打印信息,验证结果

例程正确运行后,串口打印结果如下:

在这里插入图片描述
在这里插入图片描述

备注:
如果例程运行不起来,硬件也确认没有问题,尝试修改下SD卡识别过程中的速度,具体内容如下:

sdio_sdcard.c文件 710 行,SDIO_InitStructure.ClkDiv = 178; 修改为 SDIO_InitStructure.ClkDiv = 179;
在这里插入图片描述

为什么修改这里呢?

因为我觉得这里Demo里面可能有错误,此IC主频为144MHz,看时钟树,对应此处HCLK应该也是144MHz,那么ClkDiv的值应该配置为179时,SDIO_CK才为400KHz,Demo里面的配置应该是800KHz!
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实测SDIO CLK亦是如此,确实是800k,而对于部分SD卡,识别过程最大仅支持400K,因此建议可以尝试修改此分频系数值,排除此原因!

通过main.c文件的demo可知,关于SDIO访问SD nand的核心是如下几个函数:

  • 初始化SD nand
	Status = SD_Init(0, 3, 4);
    if (Status != SD_OK)
    {
        printf("SD Card initialization failed!\r\n");
        return testResult;
    }
  • 写SD nand,同时注意写区分多块写和单块写操作
#ifdef MUL_BLOCK_RW
    Status = SD_WriteMultiBlocks(Buf_TX, 0x01, BLOCK_SIZE, 4);
#else
    Status = SD_WriteBlock(Buf_TX, 0x00, BLOCK_SIZE);
#endif
    
    Status = SD_WaitWriteOperation();
    while (SD_GetStatus() != SD_TRANSFER_OK);
    if (Status != SD_OK)
    {
        printf("SD Card write block failed!\r\n");
        return testResult;
    }
  • 读SD nand,注意读也区分多块读和单块读
#ifdef MUL_BLOCK_RW
    Status = SD_ReadMultiBlocks(Buf_RX, 0x00, BLOCK_SIZE, 4);
#else
    Status = SD_ReadBlock(Buf_RX, 0x00, BLOCK_SIZE);
#endif
    
    Status = SD_WaitReadOperation();
    while (SD_GetStatus() != SD_TRANSFER_OK);
    if (Status != SD_OK)
    {
        printf("SD Card read block failed!\r\n");
        return testResult;
    }

5.2 FatFs移植

5.2.1 添加源文件

  1. 拷贝FatFs至工程路径
    在这里插入图片描述

  2. 将相关源文件添加进工程
    在这里插入图片描述

  3. 添加相关头文件路径
    在这里插入图片描述

  4. 打开diskio.c文件,采用SD nand读写及初始化接口完成对应 diskio.c 文件内函数的实现,对应FatFs文档内的 Media Access Interface,实现下述接口即可使用FatFs。
    在这里插入图片描述

5.2.2 添加设备号

首先,包含对应sd nand相关程序的头文件,并定义对应的物理设备号

#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "./sdio_sdcard/sdio_sdcard.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 */
#define DEV_SD    0

FatFs通过物理设备号区别实际的设备,如果我们的系统上有两个硬件存储设备,且均需移植FatFs,则应再次定义,且设备号应不一样。

同时 ffconf.h 文件内的 FF_VOLUMES 配置项,用于设置FatFs实际支持的物理设备数量上限,如果有超过一个以上的物理设备使用FatFs,需要修改此参数

#define FF_VOLUMES		1
/* Number of volumes (logical drives) to be used. (1-10) */

5.2.3 修改 disk_status() 函数

在线文档链接 disk_status

disk_status() 用于获取存储设备的状态,函数原型如下:

DSTATUS disk_status (
  BYTE pdrv     /* [IN] Physical drive number */
);

传入参数:

  • pdrv:用于标识目标设备的物理驱动器号,在单驱动系统中始终为零。

返回值:

  • DSTATUS:返回驱动器的状态,FatFs仅关心 STA_NOINITSTA_PROTECT
    • STA_NOINIT: 表示驱动器尚未初始化
    • STA_NODISK:表示驱动器中没有介质
    • STA_PROTECT:表示介质被写保护,如果 STA_NODISK 状态位为1,则此位无效

注意,每个状态占据一个bit位,返回值为三个数据位的或。

/* Disk Status Bits (DSTATUS) */

#define STA_NOINIT		0x01	/* Drive not initialized */
#define STA_NODISK		0x02	/* No medium in the drive */
#define STA_PROTECT		0x04	/* Write protected */

根据SD nand操作接口,修改实现如下:

DSTATUS disk_status (
    BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
    int ret  = 0, result = 0;
  
    if (pdrv == DEV_SD) {
        ret = SD_GetStatus();
        if(ret == SD_TRANSFER_ERROR)
            result |= STA_NODISK;
        else
            result = 0;
    }

    return result;
}

5.2.4 修改 disk_initialize() 函数

在线文档链接 disk_initialize

disk_initialize() 此函数初始化存储设备并使其准备好进行通用读/写,当函数成功时,返回值中的STA_NOINIT标志被清除,函数原型如下:

DSTATUS disk_initialize (
  BYTE pdrv            /* [IN] Physical drive number */
);

传入参数:

  • pdrv:用于标识目标设备的物理驱动器号,在单驱动系统中始终为零。

返回值:

  • DSTATUS:该函数返回当前驱动器状态标志作为结果。参考disk_status() 函数

根据SD nand操作接口,修改实现如下:

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

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

    ret = SD_Init(0, 3, 4);
    if (ret != SD_OK)
        return STA_NOINIT;
    else
        return 0;
}

5.2.5 修改 disk_read() 函数

在线文档链接 disk_read

disk_read() 用于从存储设备中读取数据,函数原型如下:

DRESULT disk_read (
  BYTE pdrv,     /* [IN] Physical drive number */
  BYTE* buff,    /* [OUT] Pointer to the read data buffer */
  LBA_t sector,  /* [IN] Start sector number */
  UINT count     /* [IN] Number of sectros to read */
);

传入参数:

  • pdrv:用于标识目标设备的物理驱动器号,在单驱动系统中始终为零。
  • buff:指向存储读取数据的字节数组的指针。
  • sector:起始扇区号,注意此处数据类型LBA_tDWORDQWORD的别名,具体取决于配置选项。
  • count:读取的扇区数。

返回值:

  • DRESULT
    • RES_OK : 成功
    • RES_ERROR:错误
    • RES_PARERR:无效参数
    • RES_NOTRDY:设备尚未初始化
/* Results of Disk Functions */
typedef enum {
	RES_OK = 0,		/* 0: Successful */
	RES_ERROR,		/* 1: R/W Error */
	RES_WRPRT,		/* 2: Write Protected */
	RES_NOTRDY,		/* 3: Not Ready */
	RES_PARERR		/* 4: Invalid Parameter */
} DRESULT;

根据SD nand操作接口,修改实现如下:

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

    if (count == 1) {
        ret = SD_ReadBlock(buff, sector * 512, 512);
    } else {
        ret = SD_ReadMultiBlocks(buff, sector * 512, 512, count);
    }
    if (ret != SD_OK)   goto error;
    ret = SD_WaitReadOperation();
    if (ret != SD_OK)   goto error;
    while (SD_GetStatus() != SD_TRANSFER_OK);
    if (ret != SD_OK)   goto error;

    return RES_OK;
error:
    return RES_ERROR;
}

关于 disk_read 有以下注意事项:

  1. 对通用存储设备(如存储卡、硬盘和光盘)的读/写操作是以称为扇区的数据字节块单位进行的,FatFs 支持 512 到 4096 字节范围内的扇区大小。当 FatFs 配置为固定扇区大小(FF_MIN_SS == FF_MAX_SS,这是大多数情况)时,通用读/写功能必须仅在此扇区大小下工作。当 FatFs 配置为可变扇区大小(FF_MIN_SS < FF_MAX_SS )时,在disk_initialize函数成功后使用disk_ioctl函数查询介质的扇区大小。

  2. 此外,关于 buff 传递的内存地址,它不一定是内存对齐的,这个需要大家在使用的时候注意。

5.2.6 修改 disk_write() 函数

在线文档链接 disk_write

disk_write() 用于往存储设备中写入数据,函数原型如下:

DRESULT disk_write (
  BYTE pdrv,        /* [IN] Physical drive number */
  const BYTE* buff, /* [IN] Pointer to the data to be written */
  LBA_t sector,     /* [IN] Sector number to write from */
  UINT count        /* [IN] Number of sectors to write */
);

传入参数:

  • pdrv:用于标识目标设备的物理驱动器号,在单驱动系统中始终为零。
  • buff:指向存储被写入数据的字节数组的指针。
  • sector:起始扇区号,注意此处数据类型LBA_tDWORDQWORD的别名,具体取决于配置选项。
  • count:读取的扇区数。

返回值:

  • DRESULT
    • RES_OK : 成功
    • RES_ERROR:错误
    • RES_WRPRT:设备处于写保护状态
    • RES_PARERR:无效参数
    • RES_NOTRDY:设备尚未初始化
/* Results of Disk Functions */
typedef enum {
	RES_OK = 0,		/* 0: Successful */
	RES_ERROR,		/* 1: R/W Error */
	RES_WRPRT,		/* 2: Write Protected */
	RES_NOTRDY,		/* 3: Not Ready */
	RES_PARERR		/* 4: Invalid Parameter */
} DRESULT;

根据SD nand操作接口,修改实现如下:

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

    if (count == 1) {
        ret = SD_WriteBlock((uint8_t *)buff, sector * 512, 512);
    } else {
        ret = SD_WriteMultiBlocks((uint8_t *)buff, sector * 512, 512, count);
    }
    if (ret != SD_OK)   goto error;
    ret = SD_WaitWriteOperation();
    if (ret != SD_OK)   goto error;
    while (SD_GetStatus() != SD_TRANSFER_OK);
    if (ret != SD_OK)   goto error;

    return RES_OK;
error:
    return RES_ERROR;
}

#endif

关于 disk_write() 有以下注意事项:

  1. buff 参数和disk_read一样,不一定是字节对齐的,需要注意
  2. 多个扇区的写请求通常不建议拆解成单个的扇区写操作,这回降低传输速率,但是我们在使用的时候也需要注意对应的SD卡/SD nand所支持的最大连续写入扇区数限制!
  3. 调用disk_write()函数不要求写操作完成后才返回,可以在写过程或数据丢入缓存后返回,但是需要注意返回后,buff指针所指向的内容将变成非法的,不能再使用。写完成操作完成,可在调用 disk_ioctl() 函数CTRL_SYNC 命令时完成。通过延迟写入的实现,可增大文件系统的吞吐量(加快读写速度的点子!!!

5.2.7 修改 disk_ioctl() 函数

在线文档链接 disk_ioctl

disk_ioctl() 用于控制设备的除了读写之外的其他特定功能,函数原型如下:

DRESULT disk_ioctl (
  BYTE pdrv,     /* [IN] Drive number */
  BYTE cmd,      /* [IN] Control command code */
  void* buff     /* [I/O] Parameter and data buffer */
);

传入参数:

  • pdrv:用于标识目标设备的物理驱动器号,在单驱动系统中始终为零。
  • cmd:命令代码。
  • buff:指针参数,具体取决于 cmd 参数。

返回值:

  • DRESULT
    • RES_OK : 成功
    • RES_ERROR:错误
    • RES_PARERR:无效参数
    • RES_NOTRDY:设备尚未初始化
/* Results of Disk Functions */
typedef enum {
	RES_OK = 0,		/* 0: Successful */
	RES_ERROR,		/* 1: R/W Error */
	RES_WRPRT,		/* 2: Write Protected */
	RES_NOTRDY,		/* 3: Not Ready */
	RES_PARERR		/* 4: Invalid Parameter */
} DRESULT;

关于命令参数,分为标准命令参数以及可选命令参数,当 FF_FS_READONLY == 1FF_MAX_SS == FF_MIN_SS时, disk_ioctl 函数不需要实现也可运行,此处我们先简单实现标准命令参数。

标准命令参数有与以下几种:

命令描述
CTRL_SYNC同步命令,确保设备写操作完成。
GET_SECTOR_COUNT获取有效扇区大小,此数据将被存放在一个LBA_t类型的buff指针指向的数据中。此命令被f_mkfsf_fdisk函数使用。f_mkfs函数用于创建文件系统,f_fdisk函数用于对磁盘进行分区。在执行这两个函数时,需要确定要创建的文件系统或分区的大小,所以需要获取磁盘上可用扇区的数量。
GET_SECTOR_SIZE获取扇区大小,也就是读写操作的最小数据单位,使用WORD类型buff指针指向的数据存储。有效扇区大小有512、1024、2048、4096。此命令只有在FF_MAX_SS>FF_MIN_SS时被调用,如果FF_MAX_SS=FF_MIN_SS,此命令不会被调用,此时读写必须按照每扇区FF_MAX_SS字节访问。
GET_BLOCK_SIZE获取擦除块大小,单位为扇区!使用DWORD类型的buff指针指向的数据存储。允许的值范围在 1 ~ 32768,但值必须时2的幂,即2的指数倍。 如果无法确定擦除块大小或介质不是闪存存储器则返回1。此命令在f_mkfs函数未指定块大小时使用。当创建文件系统时,如果没有指定擦除块大小,f_mkfs函数会尝试根据获取到的擦除块大小来对齐数据区域,以提高闪存的性能。
CTRL_TRIM通知磁盘 I/O 层或存储设备不再需要扇区块上的数据,可以将其擦除。扇区块在由 buff 指向的 LBA _ t 数组{ < Start LBA > ,< End LBA > }中指定。这是一个与 ATA 设备相同的命令。如果不支持此函数或不支持闪存设备,则不对此命令执行任何操作。FatFs 不会检查结果代码,即使扇区块未擦除,文件函数也不会受到影响。在删除集群链并在 f _ mkfs 函数中调用此命令。当 FF _ USE _ TRIM == 1时,它必须实现。

以下是可选命令,大家亦可自行查看官方文档,此处不再实现。
在这里插入图片描述

根据SD nand操作接口,修改实现如下:


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

DRESULT disk_ioctl (
    BYTE pdrv,		/* Physical drive nmuber (0..) */
    BYTE cmd,		/* Control code */
    void *buff		/* Buffer to send/receive control data */
)
{
    switch (cmd) {
        case CTRL_SYNC:
        break;
        case GET_SECTOR_COUNT:
            *(LBA_t *)buff = (SDCardInfo.CardCapacity / SDCardInfo.CardBlockSize);
        break;
        case GET_SECTOR_SIZE:
            *(WORD *)buff = SDCardInfo.CardBlockSize;
        break;
        case GET_BLOCK_SIZE:
            *(DWORD *)buff = 1;
        break;
        case CTRL_TRIM:
        break;
    }

    return RES_OK;
}

5.2.8 修改 ffconf.h 取消 get_fattime 实现

get_fattime()函数用于获取当前时间,此函数不影响fatfs访问sd nand操作,因此我们暂时先屏蔽,后续再实现

进入ffconf.h 文件,修改 FF_FS_NORTC 宏为 1

#define FF_FS_NORTC		1
#define FF_NORTC_MON	5
#define FF_NORTC_MDAY	14
#define FF_NORTC_YEAR	2023
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/  an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/  timestamp feature. Every object modified by FatFs will have a fixed timestamp
/  defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/  To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/  added to the project to read current time form real-time clock. FF_NORTC_MON,
/  FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */

5.2.9 接口测试

在使用使用通用文件操作接口(指 f_open、f_read、f_write、f_close)访问SD nand之前,我们可以先测试下我们刚刚实现的 diskio.c 内的接口是否可用。因为这是通用文件操作底层所依赖的接口,如果这些接口出问题,通用文件操作接口肯定是跑不起来的!

修改 main 函数如下:

#include "diskio.h"
int main(void)
{
    int res;
    
    bsp_uart_init();
    printf("fatfs test!\r\n");
    
    Memset(Buf_RX, 0x00, Buf_Len);
    Fill_Buffer(Buf_TX, Buf_Len, 0x00);
    
    res = disk_initialize(0);
    printf("disk inital res:%d\r\n", res);
    SD_Info(&SDCardInfo);
    
    res = disk_read(0, Buf_RX, 1, 4);
    printf("disk read res:%d\r\n", res);
    dataShow(Buf_RX, Buf_Len);
    
    res = disk_write(0, Buf_TX, 1, 4);
    printf("disk write res:%d\r\n", res);
    dataShow(Buf_TX, Buf_Len);
    
    res = disk_read(0, Buf_RX, 1, 4);
    printf("disk read2 res:%d\r\n", res);
    dataShow(Buf_RX, Buf_Len);
	
	while(1);
}

5.2.10 调用通用文件操作接口进行读写测试

测试通过,接下来,我们便可调用通用的文件操作接口,通过FatFs完成对SD nand的读写访问了。

在进行读写访问之前,我们首先需要格式化SD nand,这是因为新的SD nand上是没有数据的,也就没有文件系统,因此我们需要先对齐进行格式化。此操作与U盘插入电脑上点击格式化操作类似。
在这里插入图片描述

    // 初始化文件系统
    res = f_mount(&fs, "0:", 1);
    if (res != FR_OK) {
        printf("fatfs mount error! ret = %d\r\n", res);
    }
    
    if(res == FR_NO_FILESYSTEM) {  //FR_NO_FILESYSTEM值为13,表示没有有效的设备
        // 格式化SD卡
        res = f_mkfs("0:", 0, buffer, sizeof(buffer));
        if (res != FR_OK) {
            printf("fatfs mkfs error! ret:%d\r\n", res);
        }

        res = f_mount(NULL, "0:", 1);
        printf("fatfs unmount ret:%d!\r\n", res);
        res = f_mount(&fs, "0:", 1);
        if (res != FR_OK) {
            printf("2: fatfs mount error! ret = %d\r\n", res);
        }
    }
5.2.10.1 f_mount() 函数

f_mount() 函数用于挂载文件系统以及取消卸载文件系统,函数原型如下:

FRESULT f_mount (
  FATFS*       fs,    /* [IN] Filesystem object */
  const TCHAR* path,  /* [IN] Logical drive number */
  BYTE         opt    /* [IN] Initialization option */
);

参数:

  • fs:指向一个文件系统对象指针,当传入为 NULL 时,表示卸载文件系统
  • path:一个非空字符串,内容为特定的逻辑物理设备号。
  • opt:可选项,0:不立即挂载(第一次访问时挂载);1:立即挂载

返回值:

  • FR_OK
  • FR_INVALID_DRIVE
  • FR_DISK_ERR
  • FR_NOT_READY
  • FR_NOT_ENABLED
  • FR_NO_FILESYSTEM

使用 f_mount 即可实现挂载和卸载,当然卸载也可以使用f_unmount函数,原型如下:

FRESULT f_unmount (
  const TCHAR* path   /* [IN] Logical drive number */
);

上述代码,首先调用f_mount挂载设备,当检测到设备内没有文件系统时调用f_mkfs格式化设备。

5.2.10.2 f_mkfs() 函数

f_mkfs函数原型如下:

FRESULT f_mkfs (
  const TCHAR* path,   /* [IN] Logical drive number */
  const MKFS_PARM* opt,/* [IN] Format options */
  void* work,          /* [-]  Working buffer */
  UINT len             /* [IN] Size of working buffer */
);

参数:

  • path:一个非空字符串,内容为特定的逻辑物理设备号。
  • opt:指定格式选项结构MKFS_PARM持有格式选项。如果给出空指针,它会以默认值为函数提供每个选项。该结构有五个成员,顺序如下:
    • BYTE fmt:指定 FAT 类型标志FM_FAT、FM_FAT32、FM_EXFAT和这三者的按位或FM_ANY的组合。未启用 exFAT 时忽略FM_EXFAT 。这些标志指定要创建的 FAT 卷类型。如果指定了两种或多种类型,将根据卷大小和au_size选择其中一种。标志FM_SFD指定以 SFD 格式在驱动器上创建卷。默认值为FM_ANY。
    • BYTE n_fat:指定 FAT/FAT32 卷上的 FAT 副本数。此成员的有效值为 1 或 2。默认值 (0) 和任何无效值都为 1。如果 FAT 类型为 exFAT,则此成员无效。
    • UINT n_align:以扇区为单位指定卷数据区(文件分配池,通常是闪存介质的擦除块边界)的对齐方式。该成员的有效值介于 1 和 32768 之间(包括 2 的幂)。如果给出零(默认值)或任何无效值,该函数将使用 disk_ioctl 函数从较低层获取块大小。
    • DWORD au_size:以字节为单位指定分配单元(簇)的大小。对于 FAT/FAT32 卷,有效值为扇区大小和 128 * 扇区大小(包括在内)之间的 2 的幂,或者对于 exFAT 卷,最大为 16 MB。如果给出零(默认值)或任何无效值,则该函数使用默认分配单元大小,具体取决于卷大小。
    • UINT n_root:指定 FAT 卷上的根目录条目数。此成员的有效值最大为 32768 并与扇区大小/32 对齐。默认值 (0) 和任何无效值都为 512。如果 FAT 类型为 FAT32 或 exFAT,则此成员无效。
  • work:格式化过程中的工作缓冲区指针
  • len:缓冲区大小,单位字节。大小最小为FF_MAX_SS,大量的工作缓冲区减少了向驱动器写入事务的数量,因此格式化过程将很快完成。

返回值:

  • FR_OK
  • FR_DISK_ERR
  • FR_NOT_READY
  • FR_WRITE_PROTECTED
  • FR_INVALID_DRIVE
  • FR_MKFS_ABORTED
  • FR_INVALID_PARAMETER
  • FR_NOT_ENOUGH_CORE
5.2.10.3 读写测试

SD nand格式好了之后,就可以调用通用文件操作函数接口(f_open、f_read、f_write、f_close)对齐进行读写了,修改 main 函数如下:


#include "ff.h"
#include <string.h>

BYTE buffer[FF_MAX_SS];
/**
 * @brief   Main program
 */
int main(void)
{
	FATFS fs;
    FIL file;
    FRESULT res;
    
    bsp_uart_init();

    printf("fatfs test!\r\n");
    
    // 初始化文件系统
    res = f_mount(&fs, "0:", 1);
    if (res != FR_OK) {
        printf("fatfs mount error! ret = %d\r\n", res);
    }
    
    if(res == FR_NO_FILESYSTEM) {  //FR_NO_FILESYSTEM值为13,表示没有有效的设备
        // 格式化SD卡
        res = f_mkfs("0:", 0, buffer, sizeof(buffer));
        if (res != FR_OK) {
            printf("fatfs mkfs error! ret:%d\r\n", res);
        }

        res = f_mount(NULL, "0:", 1);
        printf("fatfs unmount ret:%d!\r\n", res);
        res = f_mount(&fs, "0:", 1);
        if (res != FR_OK) {
            printf("2: fatfs mount error! ret = %d\r\n", res);
        }
    }

    // 创建一个新文件
    res = f_open(&file, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
    if (res != FR_OK) {
        printf("fatfs open error!\r\n");
    }

    // 写入数据到文件
    const char* data = "Hello, FatFs!";
    UINT bytes_written;
    res = f_write(&file, data, strlen(data), &bytes_written);
    if (res != FR_OK) {
        printf("fatfs write error!\r\n");
    }

    // 关闭文件
    res = f_close(&file);
    if (res != FR_OK) {
        printf("fatfs close error!\r\n");
    }

    // 打开文件并读取数据
    res = f_open(&file, "test.txt", FA_READ);
    if (res != FR_OK) {
        printf("2: fatfs open error!\r\n");
    }

    // 读取文件数据
    char read_data[50];
    UINT bytes_read;
    res = f_read(&file, read_data, sizeof(read_data), &bytes_read);
    if (res != FR_OK) {
        printf("2: fatfs read error!\r\n");
    }

    // 关闭文件
    res = f_close(&file);
    if (res != FR_OK) {
        printf("2: fatfs close error!\r\n");
    }

    // 打印读取的数据
    read_data[bytes_read] = '\0';  // 添加字符串结束符
    printf("Read data: %s\n", read_data);
    
    while (1) {
    }
}

测试结果如下:
在这里插入图片描述

6. 结束语

至此,FatFs已经移植完成,需要注意的是,在此次移植过程中,以分享移植流程、思路为主,对于部分函数接口的实现比较粗糙,在实际项目中需要结合本文中对相应接口的描述,对其进行优化以确保产品的稳定性,不过相信那些都不是什么难题。

以上就是关于FatFs移植的全部流程了,欢迎大家讨论!共同进步!


创作不易,转载请注明出处!

关注、点赞+收藏,可快速查收博主有关分享!


博客主页:爱出名的狗腿子(点击跳转)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我们详细讲讲UI自动化测试最佳设计模式POM

概念 什么是POM&#xff1f; POM是PageObjectModule&#xff08;页面对象模式&#xff09;的缩写&#xff0c;其目的是为了Web UI测试创建对象库。 在这种模式下&#xff0c;应用涉及的每一个页面应该定义为一个单独的类&#xff0c;类中应该包含此页面上的页面元素对象和处…

【Java校招面试】实战面经(一)

目录 前言一、单链表找三等分点&#xff08;链表可能有环&#xff09;二、讲几个熟悉的设计模式三、Spring IoC、AOP是什么&#xff0c;是如何实现的&#xff0c;Spring MVC是什么&#xff1f;四、怎么实现线程安全&#xff0c;各个实现方法有什么区别&#xff0c;synchronized…

Linux防火墙之iptables(下)

目录 一、通用匹配 1&#xff09;协议匹配 2&#xff09;地址匹配 3&#xff09;接口匹配 二、隐含匹配 1&#xff09;端口匹配 2&#xff09;TCP标志位的匹配 3&#xff09;ICMP的类型匹配 ①请求规则设置 ②回显匹配 ②显示目的不可达匹配 三、显示匹配 1 &…

教你精通Java语法之第十三章、反射

目录 一、定义 二、用途 三、反射基本信息 四、反射相关的类 4.1Class类 4.1.1Class类中的相关方法(方法的使用方法在后边的示例当中) ​编辑 4.2反射示例 4.2.1获得Class对象的三种方式 4.2.2反射的使用 五、反射优点和缺点 六、重点总结 一、定义 Java的反射&am…

计算机图形学 | 实验十:几何纹理(法线贴图)

计算机图形学 | 实验十&#xff1a;几何纹理&#xff08;法线贴图&#xff09; 计算机图形学 | 实验十&#xff1a;几何纹理&#xff08;法线贴图&#xff09;什么是法线贴图为什么需要切线空间加载法线贴图引入切线空间结果 华中科技大学《计算机图形学》课程 MOOC地址&#…

PCB基础~电源和地平面,去耦电容

电源和地平面 • 应该尽可能的使用电源和地平面&#xff0c; Why? – 在设备和电源之间提供一个低阻抗的路径 – 提供屏蔽 – 提供散热 – 降低分布电感 • 一个完整的无破损的平面是最优选择 – 破碎的地平面会在走线的上下层之间 引入寄生电感 • Remember! • 低频时&…

【服务器】利用树莓派搭建 web 服务器-无需公网IP

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 概述 使用 Raspberry Pi Imager 安装 Raspberry Pi OS 设置 Apache Web 服务器 测试 web 站点 安装静态样例站点 将web站点发布到公网 安装 Cpolar内网穿透 cpolar进行tok…

SQL-DDL语句DQL语句

SQL学习笔记 DDL语句--操作数据表 /* 快捷键: insert键 在插入 和 替换模式之间切换 ctrl 字母z 撤销上一步操作 tab 往后缩进(默认4个空格) shift tab 往前缩进(默认4个空格) …

SpringBoot配置文件和日志

目录 SpringBoot配置文件 SpringBoot配置文件的作用 项目中的重要数据写在配置文件当中 降低代码耦合 SpringBoot配置文件的格式 properties配置文件 读取配置文件中的内容&#xff08;Value注解使用${}格式读取&#xff09; properties优缺点 yml配置文件 yml特…

ChatGPT官方APP正式发布!附安装使用教程

目录 前言 APP功能演示 1.与机器人聊天&#xff0c;询问问题 2.语音输入&#xff0c;人机交互 3.聊天历史&#xff0c;新建聊天分组 安装教程 1.下载应用 2.登录账号 3.愉快的玩耍吧 总结 写到最后 大家好&#xff0c;我是大侠&#xff0c;AI领域的专业博主 前言 …

Docker安装常用软件-Kafka集群

零、为了方便开发调试&#xff0c;使用kafka部署一套kafka环境&#xff0c;进行功能调试&#xff0c;方便快捷 一、部署zookeeper 1、下载镜像 docker pull wurstmeister/zookeeper 2、运行zookeeper镜像 docker run -d --restartalways --log-driver json-file --log-op…

(转载)从0开始学matlab(第10天)—自顶向下的编程思想

在前面的内容中&#xff0c;我们开发了几个完全运转的 MATLAB 程序。但是这些程序都十分简单&#xff0c;包括一系列的 MATLAB 语句&#xff0c;这些语句按照固定的顺序一个接一个的执行。像这样的程序我们称之顺序结构程序。它首先读取输入&#xff0c;然后运算得到所需结果&a…

QT学习记录(三)绘图

按照下面两个教程学习 QT学习教程&#xff08;全面&#xff09;_Strive--顾的博客-CSDN博客_qt学习 天山老妖S的博客_QT开发(3)_51CTO博客 1、绘图 VC项目右键增加QT GUI Class&#xff0c;在QT Designer中编辑DlgDraw.ui 在DlgDraw中重载函数 void DlgDraw::paintEvent(Q…

Flutter控件之图片Image封装

Flutter控件之基类Widget封装 Flutter控件之文本Text封装 为什么要进行繁琐的封装&#xff1f;直接用也挺好啊&#xff0c;这个回答一点毛病没有&#xff0c;大部分视图都可以原生绘制&#xff0c;可在Flutter中偏偏原生的控件&#xff0c;少了很多需要又常用的属性&#xff…

最小生成树—Kruskal算法和Prim算法

1.最小生成树 连通图&#xff1a;在无向图中&#xff0c;若从顶点v1到顶点v2有路径&#xff0c;则称顶点v1与顶点v2是连通的。如果图中任 意一对顶点都是连通的&#xff0c;则称此图为连通图。 生成树&#xff1a;一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通…

Java基础-面向对象总结(3)

本篇文章主要讲解Java面向对象的知识点 面向对象的三大特性类的扩展(抽象类,接口,内部类,枚举) 目录 面向对象和面向过程的区别? 面向对象的五大基本原则 面向对象三大特性 继承 怎么理解继承 ? 继承和聚合的区别&#xff1f; 封装 多态 什么是多态 什么是运行时多…

面试阿里、字节全都一面挂,被面试官说我的水平还不如应届生

测试员可以先在大厂镀金&#xff0c;以后去中小厂毫无压力&#xff0c;基本不会被卡&#xff0c;事实果真如此吗&#xff1f;但是在我身上却是给了我很大一巴掌... 所谓大厂镀金只是不卡简历而已&#xff0c;如果面试答得稀烂&#xff0c;人家根本不会要你。况且要不是大厂出来…

MySQL数据库基础4-内置函数

文章目录 日期函数字符串函数数学函数其他函数 日期函数 函数名称描述current date()当前日期current time()当前时间current timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date add(date, interval d_value type)在date中添加日期或时间&#xff0c;interv…

GitHub Actions Error “Waiting for a runner to pick up this job”

GitHub Actions Error “Waiting for a runner to pick up this job” 什么是GitHub Actions GitHub Actions 是一个 CI/CD&#xff08;持续集成和持续部署&#xff09;平台&#xff0c;可以让您自动化工作流程并与 GitHub 存储库中的代码集成。使用 GitHub Actions&#xff…

智能排班系统 【数据库设计】

文章目录 数据库设计规范ER图物理模型数据表登录日志表操作日志表菜单表角色表企业表门店表省市区表门店节日表消息表职位表排班规则表排班任务表排班结果存储scheduling_date排班日表scheduling_shift排班班次表shift_user班次员工中间表 定时通知表用户表中间表role_menu角色…