嵌入式MCU文件系统技术分享:从选型到FatFS深度应用
嵌入式MCU文件系统技术分享:从选型到FatFS深度应用
文章目录
- 嵌入式MCU文件系统技术分享:从选型到FatFS深度应用
- 嵌入式文件系统概述
- 一、为什么需要文件系统?
- 二、嵌入式存储介质类型
- 三、各类文件系统深度比较
- 嵌入式文件系统分类
- 文件系统对比
- 选择考量因素
- FatFS文件系统解析
- FatFS特点与优势
- 配置模式对比
- 配置模式对比
- 常见概念说明
- 扇区(Sector)的定义
- FATFS簇大小的定义
- 簇大小的影响因素
- 簇大小的优缺点
- 块(Block)的定义
- 四、FatFS 0.16移植指南
- ffconf.h 详细配置说明
- FatFS主要接口使用详解
- 文件系统管理
- 挂载与卸载
- 获取文件系统信息
- 文件操作详解
- 基本文件操作
- 文件查找与遍历
- 目录操作详解
- 五、性能优化
- 应用层性能优化
- 簇大小优化
- 缓存策略优化
- 批量写入优化
- 顺序写入/读取
- 避免频繁open/close
- 同一文件读写分句柄
- 定期碎片化清理
- 驱动层性能优化
- 通信时钟优化
- 使能多扇区读写接口
- 使能DMA传输
- SD卡命令判断策略
- 总结
嵌入式文件系统概述
在嵌入式MCU开发中,文件系统提供了对存储介质的抽象管理,使得应用程序可以像在PC上一样通过文件、目录等概念来组织数据,而无需关心底层存储的具体物理特性。
一、为什么需要文件系统?
数据管理规范化:提供标准化的API接口(open、read、write、seek等)
增强可靠性:具备断电保护机制,防止数据损坏或丢失
延长存储寿命:通过磨损均衡技术平均分配Flash写入操作
支持复杂结构:能够保存日志、配置备份等结构化数据
跨平台兼容:便于与PC或其他系统进行数据交换
二、嵌入式存储介质类型
介质类型 | 特点 | 典型应用 |
---|---|---|
NOR Flash | 随机访问快、写入慢、擦除块大 | 代码存储、小容量数据 |
NAND Flash | 顺序访问快、容量大、有坏块 | 大容量数据存储 |
SD/TF卡 | 容量大、可移动、兼容性好 | 据记录、扩展存储 |
eMMC | 集成控制器、高可靠性 | 嵌入式大容量存储 |
SPI Flash | 接口简单、成本低、容量小 | 参数存储、日志记录 |
三、各类文件系统深度比较
嵌入式文件系统分类
通用型文件系统
FAT/FatFS:兼容性好,适合与PC交换数据
ext2/3/4:Linux标准文件系统,功能完善
专用型文件系统
LittleFS:为嵌入式Flash设计,断电安全
SPIFFS:针对SPI Flash优化的小型文件系统
YAFFS2:专为NAND Flash设计
轻量型文件系统
NVS (Non-Volatile Storage):键值对存储,简单高效
EEPROM模拟:将Flash模拟为EEPROM使用
文件系统对比
文件系统 | 技术特点 | 最佳适用场景 | 性能特点 | 资源占用 |
---|---|---|---|---|
LittleFS | 日志结构、写时复制、磨损均衡 | 嵌入式Flash、参数存储 | 小文件读写优,随机访问好 | RAM: 1-4KB, ROM: 10-20KB |
FatFS | FAT兼容、通用性强、可配置 | SD卡、通用存储、跨平台 | 平衡性好,顺序读写佳 | RAM: 0.7-2KB, ROM: 10-30KB |
SPIFFS | 针对SPI Flash、磨损均衡 | SPI NOR Flash、小容量存储 | 小文件性能好,垃圾回收有延迟 | RAM: 1-2KB, ROM: 8-15KB |
NVS | 键值对、简单安全、断电保护 | 结构化配置数据存储 | 读写速度快,适合小数据 | RAM: <1KB, ROM: 5-10KB |
JFFS2 | 日志结构、压缩、Linux原生 | NOR Flash、Linux系统 | 可靠性高,占用CPU资源多 | RAM: 10-100KB, ROM: 50-100KB |
ext2/3/4 | Linux标准、功能完善 | eMMC、大容量存储 | 性能好,功能丰富 | RAM: 10-50KB, ROM: 50-200KB |
选择考量因素
存储介质特性
NOR Flash:擦除块大,适合LittleFS等
NAND Flash:需要坏块管理,适合YAFFS2
SD卡:标准块设备,适合FatFS
数据特性
小文件多:LittleFS、SPIFFS
大文件多:FatFS、ext2
键值对:NVS
资源限制
RAM有限:NVS、SPIFFS
ROM有限:LittleFS、FatFS精简配置
可靠性要求
高可靠性:LittleFS、JFFS2
一般需求:FatFS、SPIFFS
功能需求
长文件名:FatFS(需配置)
权限控制:ext2/3/4
压缩:JFFS2
FatFS文件系统解析
FatFS采用分层架构设计,从上到下分为:
应用层:用户调用文件API接口
FatFS核心层:实现FAT文件系统逻辑
磁盘I/O层:提供底层存储介质访问
物理层:具体存储硬件驱动
+--------------------------------+
| 应用程序 |
+--------------------------------+
| FatFS核心 |
| (ff.c, ff.h, ffconf.h) |
+--------------------------------+
| 磁盘I/O层 |
| (diskio.c, diskio.h) |
+--------------------------------+
| 物理驱动层 |
| (sd_diskio.c, spi_flash.c) |
+--------------------------------+
FatFS特点与优势
- 平台无关性:易于移植到各种微控制器
- 代码占用小:最小配置下仅需几千字节ROM和几百字节RAM
- 功能丰富:支持长文件名、多卷、多种编码页
- 线程安全:支持RTOS环境下的多线程访问
- 兼容性好:与Windows、Linux等系统完全兼容
配置模式对比
配置模式对比
配置模式 | ROM占用 | RAM占用 | 功能特点 |
---|---|---|---|
最小配置 | 6-8KB | 0.7-1KB | 仅基本读写,无格式化和长文件名 |
标准配置 | 12-18KB | 1.2-2KB | 包含常用功能,支持有限长文件名 |
全功能配置 | 25-35KB | 3-5KB | 支持所有功能包括长文件名、格式化等 |
常见概念说明
扇区(Sector)的定义
扇区是存储设备(如 SD 卡、硬盘)的最小可寻址单元,通常大小为 512 字节或 4KB(取决于设备)。FatFS 通过 DISKIO 接口与物理设备交互时,所有读写操作均以扇区为单位。
#define FF_MIN_SS 512 // 最小扇区大小
#define FF_MAX_SS 4096 // 最大扇区大小
#define FF_USE_TRIM 0 // 是否启用块擦除功能
FATFS簇大小的定义
簇(Cluster)是FAT文件系统中分配存储空间的最小单位,由连续的扇区组成。一个文件占用的空间始终是簇大小的整数倍,即使文件实际大小不足一簇。
簇大小的影响因素
簇大小通常由卷(分区)大小和FAT类型(FAT12/FAT16/FAT32)决定:
FAT12:适用于小容量存储(如软盘),簇大小较小(如512B-4KB)。
FAT16:中容量存储,簇范围通常为2KB-32KB。
FAT32:大容量存储,簇范围多为4KB-32KB。
具体数值可通过格式化工具或磁盘属性查看,不同操作系统可能提供默认推荐值。
簇大小的优缺点
较小簇:减少空间浪费(尤其是小文件),但文件碎片化风险增加,管理开销更大。
较大簇:提升大文件读写效率,减少碎片,但小文件可能浪费更多空间(如1KB文件在32KB簇中占用32KB)。
计算公式
簇大小(Bytes)= 每簇扇区数 × 每扇区字节数(通常512B)。例如:
若每簇8扇区,则簇大小 = 8 × 512B = 4KB。
U盘/小容量设备:选择较小簇(如4KB)以优化空间利用率。
大容量硬盘:选择较大簇(如32KB)以提高性能。
通过diskgenius或Windows格式化工具可手动调整簇大小,需权衡存储效率与性能需求。
块(Block)的定义
块是 FatFS 文件系统的逻辑单元,由多个连续扇区组成。块大小在格式化时确定(如 1KB、4KB、32KB),必须是扇区大小的整数倍。块用于文件系统内部管理(如簇分配)。
块大小与簇的关系
1 簇(Cluster) = 1 块(Block)。
块越大,文件分配粒度越粗,可能浪费空间但性能更高。
格式化时指定块大小
通过 f_mkfs 函数的参数 opt 指定:
MKFS_PARM opt;
opt.fmt = FM_FAT32; // 文件系统类型
opt.n_fat = 1; // FAT 表数量
opt.au_size = 4096; // 块大小(此处为 4KB)
f_mkfs("0:", &opt, work, sizeof(work));
四、FatFS 0.16移植指南
- 获取源码和准备工程
从FatFS官网下载源码: elm-chan.org/fsw/ff/00index_e.html - 将以下文件添加到工程
source/ffsystem.c - FatFS系统锁 – 用户配置
source/ff.c - FatFS核心模块
source/ff.h - FatFS头文件
source/diskio.c - 磁盘I/O模板 – 用户实现
source/ffconf.h - 配置文件 – 用户裁剪
source/ffunicode.c - FatFS字符编码 - 根据硬件平台创建硬件驱动文件
#include "ff.h"
#include "diskio.h"
#include "sd_card.h" // 你的SD卡驱动头文件// 获取磁盘状态
DSTATUS disk_status(BYTE pdrv) {switch(pdrv) {case 0: // 驱动器0return SD_GetStatus() ? 0 : STA_NOINIT;default:return STA_NOINIT;}
}// 初始化磁盘
DSTATUS disk_initialize(BYTE pdrv) {switch(pdrv) {case 0:if(SD_Init() == SD_OK) {return 0;} else {return STA_NOINIT;}default:return STA_NOINIT;}
}// 读取扇区
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {switch(pdrv) {case 0:for(UINT i = 0; i < count; i++) {if(SD_ReadDisk(buff, sector + i, 1) != SD_OK) {return RES_ERROR;}buff += 512; // 假设扇区大小为512字节}return RES_OK;default:return RES_PARERR;}
}// 写入扇区
DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {switch(pdrv) {case 0:for(UINT i = 0; i < count; i++) {if(SD_WriteDisk(buff, sector + i, 1) != SD_OK) {return RES_ERROR;}buff += 512;}return RES_OK;default:return RES_PARERR;}
}// 磁盘控制
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {switch(pdrv) {case 0:switch(cmd) {case CTRL_SYNC: // 确保写入完成SD_WaitWriteComplete();return RES_OK;case GET_SECTOR_SIZE: // 获取扇区大小*(WORD*)buff = 512;return RES_OK;case GET_BLOCK_SIZE: // 获取擦除块大小*(DWORD*)buff = 1;return RES_OK;case GET_SECTOR_COUNT: // 获取总扇区数*(DWORD*)buff = SD_GetCapacity() / 512;return RES_OK;default:return RES_PARERR;}default:return RES_PARERR;}
}// 获取当前时间(用于文件时间戳)
DWORD get_fattime(void) {// 这里需要实现RTC获取时间,格式为:// bit31:25 - 年(0-127)从1980年算起// bit24:21 - 月(1-12)// bit20:16 - 日(1-31)// bit15:11 - 时(0-23)// bit10:5 - 分(0-59)// bit4:0 - 秒/2(0-29)// 示例:2023年10月15日 14:30:00return ((2023 - 1980) << 25) | (10 << 21) | (15 << 16) | (14 << 11) | (30 << 5) | (0 >> 1);
}
ffconf.h 详细配置说明
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/#define FFCONF_DEF 80386 /* Revision ID *//*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */#define FF_USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */#define FF_USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */#define FF_USE_CHMOD 1
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */#define FF_USE_LABEL 1
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) *//*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/#define FF_CODE_PAGE 437
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/#define FF_USE_LFN 2
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */#define FF_LFN_UNICODE 0
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */#define FF_STRF_ENCODE 3
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/#define FF_FS_RPATH 2
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*//*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/#define FF_VOLUMES 2
/* Number of volumes (logical drives) to be used. (1-10) */#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/#define FF_MULTI_PARTITION 1
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */#define FF_LBA64 0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */#define FF_MIN_GPT 0x100000000
/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. *//*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */#define FF_FS_NORTC 0
#define FF_NORTC_MON 9
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2020
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. 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). */#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. *//* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. *//*--- End of configuration options ---*/
FatFS主要接口使用详解
文件系统管理
挂载与卸载
FATFS fs; // 文件系统对象
FRESULT res; // 操作结果// 挂载文件系统
res = f_mount(&fs, "0:", 1); // "0:"表示第一个驱动器,1:立即挂载
if (res != FR_OK) {printf("挂载失败: %d\n", res);if (res == FR_NO_FILESYSTEM) {printf("未找到文件系统,需要格式化...\n");// 格式化文件系统MKFS_PARM opt;opt.fmt = FM_FAT32; // 文件系统类型opt.n_fat = 1; // FAT表数量opt.align = 0; // 卷对齐(0:自动)opt.n_root = 512; // 根目录条目数opt.au_size = 0; // 簇大小(0:自动)res = f_mkfs("0:", &opt, work, sizeof(work));if (res == FR_OK) {printf("格式化成功,重新挂载...\n");res = f_mount(&fs, "0:", 0); // 重新挂载}}
}// 卸载文件系统(在程序退出或需要重新初始化时调用)
f_mount(NULL, "0:", 0);
获取文件系统信息
void show_fs_info(void) {FATFS *fs;DWORD fre_clust, fre_sect, tot_sect;// 获取驱动器状态res = f_getfree("0:", &fre_clust, &fs);if (res == FR_OK) {// 计算总空间和空闲空间tot_sect = (fs->n_fatent - 2) * fs->csize;fre_sect = fre_clust * fs->csize;printf("文件系统类型: FAT%d\n", (fs->fs_type == FS_FAT32) ? 32 : (fs->fs_type == FS_FAT16) ? 16 : 12);printf("总空间: %lu KB\n", tot_sect / 2);printf("可用空间: %lu KB\n", fre_sect / 2);printf("簇大小: %lu 扇区(%lu bytes)\n", fs->csize, fs->csize * 512);}
}
文件操作详解
常用宏定义
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
基本文件操作
// 文件打开模式标志
#define FA_READ 0x01 // 读模式
#define FA_WRITE 0x02 // 写模式
#define FA_OPEN_EXISTING 0x00 // 打开已存在文件
#define FA_CREATE_NEW 0x04 // 创建新文件(文件存在则失败)
#define FA_CREATE_ALWAYS 0x08 // 总是创建新文件(覆盖已存在)
#define FA_OPEN_ALWAYS 0x10 // 打开或创建文件
#define FA_OPEN_APPEND 0x30 // 以追加模式打开// 文件属性标志
#define AM_RDO 0x01 // 只读
#define AM_HID 0x02 // 隐藏
#define AM_SYS 0x04 // 系统
#define AM_DIR 0x10 // 目录
#define AM_ARC 0x20 // 存档// 完整的文件操作示例
void file_operations(void) {FIL fil;UINT bw, br;FRESULT res;char buffer[128];// 1. 创建并写入文件res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);if (res == FR_OK) {// 写入数据f_write(&fil, "Hello FatFS!\n", 13, &bw);// 移动文件指针到指定位置f_lseek(&fil, 20); // 移动到第20字节// 在指定位置写入数据f_write(&fil, "Data at position 20", 19, &bw);// 截断文件到当前指针位置f_truncate(&fil);// 确保数据写入物理设备f_sync(&fil);f_close(&fil);}// 2. 读取文件内容res = f_open(&fil, "test.txt", FA_READ);if (res == FR_OK) {// 获取文件大小FSIZE_t size = f_size(&fil);printf("文件大小: %lu bytes\n", size);// 读取文件内容while (f_gets(buffer, sizeof(buffer), &fil)) {printf("%s", buffer);}f_close(&fil);}// 3. 文件属性操作FILINFO fno;res = f_stat("test.txt", &fno);if (res == FR_OK) {printf("文件名: %s\n", fno.fname);printf("文件大小: %lu\n", fno.fsize);printf("修改时间: %u/%u/%u %u:%u:%u\n", (fno.fdate >> 9) + 1980, (fno.fdate >> 5) & 15, fno.fdate & 31,fno.ftime >> 11, (fno.ftime >> 5) & 63, (fno.ftime & 31) * 2);}// 4. 重命名文件f_rename("test.txt", "backup.txt");// 5. 删除文件f_unlink("backup.txt");
}
文件查找与遍历
void find_files(const char* path) {DIR dir;FILINFO fno;FRESULT res;res = f_opendir(&dir, path);if (res == FR_OK) {printf("目录 %s 内容:\n", path);while (1) {res = f_readdir(&dir, &fno);if (res != FR_OK || fno.fname[0] == 0) break;if (fno.fattrib & AM_DIR) {printf(" [DIR] %s/\n", fno.fname);} else {printf(" [FILE] %s (%lu bytes)\n", fno.fname, fno.fsize);}}f_closedir(&dir);}
}// 递归遍历目录
void traverse_directory(const char* path, int depth) {DIR dir;FILINFO fno;char full_path[256];if (f_opendir(&dir, path) == FR_OK) {while (f_readdir(&dir, &fno) == FR_OK && fno.fname[0] != 0) {// 缩进显示层级for (int i = 0; i < depth; i++) printf(" ");if (fno.fattrib & AM_DIR) {printf("+ %s/\n", fno.fname);// 构建子目录路径并递归遍历snprintf(full_path, sizeof(full_path), "%s/%s", path, fno.fname);traverse_directory(full_path, depth + 1);} else {printf("- %s (%lu bytes)\n", fno.fname, fno.fsize);}}f_closedir(&dir);}
}
目录操作详解
void directory_operations(void) {FRESULT res;// 1. 创建目录res = f_mkdir("/data/logs");if (res == FR_OK) {printf("目录创建成功\n");} else if (res == FR_EXIST) {printf("目录已存在\n");}// 2. 切换当前目录f_chdir("/data/logs");// 3. 获取当前工作目录char cwd[256];res = f_getcwd(cwd, sizeof(cwd));if (res == FR_OK) {printf("当前目录: %s\n", cwd);}// 4. 删除空目录f_rmdir("/data/temp");
}
五、性能优化
应用层性能优化
簇大小优化
// 格式化时选择最优簇大小
void format_with_optimal_cluster(DWORD volume_size) {MKFS_PARM opt;opt.fmt = FM_FAT32;// 根据容量选择簇大小if (volume_size < 16 * 1024 * 1024) { // <16MBopt.au_size = 512; // 0.5KB簇} else if (volume_size < 128 * 1024 * 1024) { // <128MBopt.au_size = 4096; // 4KB簇} else if (volume_size < 1024 * 1024 * 1024) { // <1GBopt.au_size = 8192; // 8KB簇} else {opt.au_size = 32768; // 32KB簇}f_mkfs("0:", &opt, work, sizeof(work));
}
缓存策略优化
// 自定义缓存配置(需要在ffconf.h中启用相关选项)
#if FF_USE_EXPAND// 为频繁读写的文件增加缓存FIL fil;BYTE file_buffer[4096]; // 4KB文件缓存f_open(&fil, "frequent_file.dat", FA_READ | FA_WRITE);f_expand(&fil, sizeof(file_buffer), file_buffer); // 设置自定义缓存
#endif
批量写入优化
// 批量写入数据,减少同步次数
void batch_write_optimized(const char* filename, const void* data, size_t size) {FIL fil;UINT bw;const size_t CHUNK_SIZE = 4096; // 4KB chunksconst uint8_t* ptr = (const uint8_t*)data;f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE);// 分块写入while (size > 0) {size_t chunk = (size > CHUNK_SIZE) ? CHUNK_SIZE : size;f_write(&fil, ptr, chunk, &bw);ptr += chunk;size -= chunk;}// 最后一次性同步f_sync(&fil);f_close(&fil);
}
顺序写入/读取
FRESULT copy_file(const char* src, const char* dst)
{FIL src_file, dst_file;FRESULT fr;UINT br, bw;uint8_t buffer[2048]; // 2KB缓冲区// 打开源文件fr = f_open(&src_file, src, FA_READ);if (fr != FR_OK) return fr;// 创建目标文件fr = f_open(&dst_file, dst, FA_WRITE | FA_CREATE_ALWAYS);if (fr != FR_OK) {f_close(&src_file);return fr;}// 顺序复制数据while (1) {fr = f_read(&src_file, buffer, sizeof(buffer), &br);if (fr != FR_OK || br == 0) break;fr = f_write(&dst_file, buffer, br, &bw);if (fr != FR_OK || bw != br) break;}// 关闭文件f_close(&src_file);f_close(&dst_file);return fr;
}
避免频繁open/close
对频繁操作的文件,打开后保留句柄,不关闭文件
同一文件读写分句柄
// 写入句柄:使用追加模式,避免覆盖现有数据
f_open(&write_fil, "file.txt", FA_WRITE | FA_OPEN_APPEND);// 读取句柄:使用只读模式,避免意外修改
f_open(&read_fil, "file.txt", FA_READ);
//注意, 两个句柄状态不会自动同步,关闭后打开即可同步
定期碎片化清理
// 写入句柄:使用追加模式,避免覆盖现有数据
f_open(&write_fil, "file.txt", FA_WRITE | FA_OPEN_APPEND);// 读取句柄:使用只读模式,避免意外修改
f_open(&read_fil, "file.txt", FA_READ);
//注意, 两个句柄状态不会自动同步,关闭后打开即可同步
驱动层性能优化
通信时钟优化
根据卡的类型/MCU的外设 合理选择时钟频率
使能多扇区读写接口
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count)
{
#define DISK_READ_MAX_RETRY 3uint32_t rt = 0;uint32_t retry = 0;if (!count){return RES_PARERR;}switch (pdrv){case DEV_SD:if (count == 1){do{rt = sd_drv_single_block_read(sector, buff);} while (rt && retry++ < DISK_READ_MAX_RETRY);return ((rt == 0) ? RES_OK : RES_ERROR);}else{do{rt = sd_drv_mutil_block_read(sector, buff, count);} while (rt && retry++ < DISK_READ_MAX_RETRY);return ((rt == 0) ? RES_OK : RES_ERROR);}default:return RES_ERROR;}
}
使能DMA传输
使能DMA传输,合理配置中断优先级
SD卡命令判断策略
避免在SD卡命令返回判断中使用延时等待
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。