FATFS移植 - 基于N32G4FR在SD卡(sd nand)上移植FATFS文件系统
文章目录
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是一个开源的文件系统,具有以下特点和优势:
-
轻量级和高效性:FATFS是一个轻量级的文件系统,适用于资源受限的嵌入式系统和小型存储设备。它的代码量相对较小,占用的存储空间较少,并且具有较高的运行效率。
-
跨平台兼容性:FATFS可以在多个操作系统和平台上运行,包括嵌入式系统、Windows、Linux等。这使得开发人员可以方便地将FATFS应用于不同的硬件平台和操作系统环境中。
-
易于集成和使用:FATFS的源代码结构清晰,具有简单的API接口,易于集成到嵌入式系统的应用程序中。开发人员可以通过简单的函数调用来实现文件的读写、目录的创建和遍历等操作。
-
支持多种存储介质:FATFS支持多种存储介质,包括SD卡、SPI Flash、硬盘等。它可以根据不同的存储介质进行配置和适配,并提供统一的文件系统接口。
-
支持多种文件操作:FATFS支持常见的文件操作,如文件的打开、关闭、读取、写入、定位等。它还支持目录的创建、删除、重命名和遍历,以及文件和目录的属性管理。
-
支持长文件名和短文件名:FATFS同时支持长文件名(Long File Name,LFN)和短文件名(Short File Name,SFN)。这使得文件系统更加灵活和兼容,能够处理各种不同的文件命名规则。
-
支持文件系统的格式化和检查:FATFS提供了格式化和检查文件系统的功能。开发人员可以通过相应的API函数来格式化存储介质并创建文件系统,也可以进行文件系统的检查和修复。这使得文件系统能够保持良好的状态,提高数据的可靠性和完整性。
-
支持文件的读写缓存:FATFS允许开发人员配置读写缓存,以提高文件的读写性能。通过使用读写缓存,可以减少对存储介质的频繁访问,提高文件的读写速度。
-
具有错误处理和容错机制:FATFS提供了错误处理和容错机制,能够检测和处理各种错误情况。例如,当存储介质出现错误或不可用时,FATFS能够进行相应的错误处理和恢复,保证文件系统的稳定性和可靠性。
3. 相关源码获取
3.1 FatFs文件源码下载
移植FatFs,第一步当然就是获取对应的移植文件了, FatFs的源文件在其官网上即可直接下载,地址:FatFs - Generic FAT Filesystem Module
3.2 国民技术N32软件开发套件获取
国民技术作为一款国产的IC,对标stm32,性能上也还是很不错了,不过其开发资料下载会有点麻烦,不过有幸你看到这里,就不会感觉很麻烦了,其开发资料下载方法如下:
- 打开电脑的文件管理器,输入以下内容:
ftp://download.nationstech.com
,之后回车访问
- 此远程文件夹就是国民技术所有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.c | FatFs 核心文件,不用修改。 |
ffconf.h | FatFs 配置文件,需要修改此文件完成FatFs功能的配置 ,FatFs的裁剪也可通过修改此文件完成 |
ff.h | FatFs 模块和应用程序公共头文件,不用修改,应用FatFs的时候需要包含此头文件 。 |
diskio.h | FatFs 和 磁盘读写公共头文件, 不用修改。 |
diskio.c | 存放了一个示例,关于FatFs操作磁盘接口的,移植主要是实现此文件下的API函数 。 |
ffunicode.c | 可选的Unicode实用函数,不用修改。 |
ffsystem.c | 可选 O/S 相关功能的示例,不用修改。 |
综上,其实完成FatFs文件的移植,主要是修改 diskio.c
和 ffconf.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 添加源文件
-
拷贝FatFs至工程路径
-
将相关源文件添加进工程
-
添加相关头文件路径
-
打开
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_NOINIT
和STA_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_t
是DWORD
或QWORD
的别名,具体取决于配置选项。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
有以下注意事项:
-
对通用存储设备(如存储卡、硬盘和光盘)的读/写操作是以称为扇区的数据字节块单位进行的,FatFs 支持 512 到 4096 字节范围内的扇区大小。当 FatFs 配置为固定扇区大小(
FF_MIN_SS == FF_MAX_SS
,这是大多数情况)时,通用读/写功能必须仅在此扇区大小下工作。当 FatFs 配置为可变扇区大小(FF_MIN_SS
<FF_MAX_SS
)时,在disk_initialize函数成功后使用disk_ioctl函数查询介质的扇区大小。 -
此外,关于
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_t
是DWORD
或QWORD
的别名,具体取决于配置选项。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()
有以下注意事项:
buff
参数和disk_read
一样,不一定是字节对齐的,需要注意- 多个扇区的写请求通常不建议拆解成单个的扇区写操作,这回降低传输速率,但是我们在使用的时候也需要注意对应的SD卡/SD nand所支持的最大连续写入扇区数限制!
- 调用
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 == 1
和 FF_MAX_SS == FF_MIN_SS
时, disk_ioctl
函数不需要实现也可运行,此处我们先简单实现标准命令参数。
标准命令参数有与以下几种:
命令 | 描述 |
---|---|
CTRL_SYNC | 同步命令,确保设备写操作完成。 |
GET_SECTOR_COUNT | 获取有效扇区大小,此数据将被存放在一个LBA_t 类型的buff 指针指向的数据中。此命令被f_mkfs 和f_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移植的全部流程了,欢迎大家讨论!共同进步!
创作不易,转载请注明出处!
关注、点赞+收藏,可快速查收博主有关分享!
博客主页:爱出名的狗腿子(点击跳转)