14 fatfs
一、概述
作者:王棚嶙
fatfs库提供了对FAT32文件系统的支持,主要用于读写TF卡、SD卡等外部存储设备。
1.1 存储系统概述
LuatOS的存储系统采用清晰的分层架构设计,其核心目标是为应用层提供统一、简洁的文件操作接口,从而屏蔽底层不同存储介质和文件系统的差异。开发者无需关心数据具体存储在何种硬件上,也无需关心底层文件系统的具体实现,只需调用统一的API即可完成文件操作。
该系统的层次结构与各组件间的协作关系如下图所示,其数据流与依赖关系自上而下贯穿各层:
1.1.1 物理硬件层
这是数据存储的物理载体,LuatOS支持多种常见的嵌入式存储硬件:
1、SPI/SDIO TF卡:通过高速SPI或SDIO接口连接的块存储设备,特点是容量大、可插拔,适用于存储大量数据(如音频、图片文件);
2、SPI NOR Flash:通过SPI接口访问的NOR型闪存。其特点是读写速度快,通常用于存放固件代码或需要快速读写的关键数据;
3、SPI NAND Flash:通过SPI接口访问的NAND型闪存。其特点是容量大、成本低,适用于存储日志等容量需求较大的数据;
1.1.2 文件系统实现层(fatfs&lf)
文件系统负责管理存储设备的空间,组织文件和目录结构。LuatOS根据硬件特性,适配了两种主流的嵌入式文件系统:
1、FATFS:一个兼容FAT12/FAT16/FAT32标准的通用文件系统。它通常与SD/TF卡配对使用,;
2、LFS(LittleFS):它通常与SPI NOR Flash和SPI NAND Flash配对使用,非常适合在裸Flash设备上运行;
1.1.3 统一API层 (io)
fs库不推荐新客户使用,该库仅对已经使用过的老客户进行保留,其功能已经完全移植到io库中,且更加完善,新客户建议都参考io核心库:
1、fs
库:提供文件系统的额外操作。不建议使用,请使用io库进行替代;
2、io
库:提供标准的数据流操作接口,用于文件的打开、读写、关闭等,是处理文件内容的主要手段,包含fs库的所有功能,推荐使用;
1.1.4 应用层(luatOS应用脚本)
客户通过调用io库,fatfs库,lf库以及fs库中的api接口,来实现自己的业务逻辑。
1.1.5 推荐使用
你的需求 | 推荐使用的库和函数 | 理由 |
---|---|---|
快速检查一个文件的大小 | io.exists(path) | 最简洁、最直接,一行代码搞定。 |
快速查看文件系统的整体使用情况(如剩余空间) | io.fsstat(path) | 提供通用信息,代码兼容性好。 |
需要对FAT格式的U盘、SD卡进行深度管理 | fatfs 库 (如fatfs.getfree(), fatfs.mount()) | 只有它才能提供FAT文件系统的专业控制和详细信息。 |
要读取、写入文件内容,或进行复杂的文件操作 | io 库 (如 io.open, file:read, file:write) | 这是进行文件I/O操作的标准和核心方式,不可替代。 |
对于FAT文件系统需求,使用 fatfs 库。
对于文件内容操作需求,使用 io 库。
1.2 本文中扇区 (Sector) 的概念
扇区:存储设备,如TF卡、硬盘等外部存储设备中最小的信息管理单位;
1.2.1 物理层面 (最根本的定义)
1、扇区是存储设备(如TF卡、SD卡、硬盘、U盘)能够进行读写操作的最小物理单元;
2、这是由硬件制造商决定的,对于绝大多数现代存储设备,这个大小被固定为 512字节;
3、这意味着,即使您只想在文件中保存1个字节的数据(比如一个字母'A'),存储设备实际上也必须读写整整一个扇区(512字节);
1.2.2 逻辑/软件层面
1、操作系统和文件系统(如FAT32,也就是FatFS操作的对象)在此基础上建立了更高级的管理结构;
2、文件系统会将多个扇区组合成一个“簇” (Cluster) 或“块” (Block) 来进行文件管理,以提高效率;
1.2.3 如何计算总容量
既然1个扇区 = 512字节,那么:
总容量计算:
总容量 (字节) = total_sectors * 512
总容量 (KB) = total_sectors * 512 / 1024 = total_sectors / 2
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/tf_card;
--挂载失败默认格式化,
-- 如无需格式化应改为fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000, nil, 1, false),
-- 一般是在测试硬件是否有问题的时候把格式化取消掉
mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
if mount_ok then
log.info("fatfs.mount", "挂载成功", mount_err)
else
log.error("fatfs.mount", "挂载失败", mount_err)
goto resource_cleanup
end
-- ########## 获取SD卡的可用空间信息并打印。 ##########
data, err = fatfs.getfree("/sd")
if data then
--打印SD卡的可用空间信息
log.info("fatfs", "getfree", json.encode(data))
else
--打印错误信息
log.info("fatfs", "getfree", "err", err)
goto resource_cleanup
end
-- 列出所有挂载点,如不需要,可注释掉。
data = io.lsmount()
log.info("fs", "lsmount", json.encode(data))
::resource_cleanup::
log.info("结束", "开始执行关闭操作...")
-- 如已挂载需先卸载文件系统,未挂载直接关闭SPI
if mount_ok then
if fatfs.unmount("/sd") then
log.info("文件系统", "卸载成功")
else
log.error("文件系统", "卸载失败")
end
end
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
3.1 fatfs.SPI(SPI模式)
常量含义: SPI总线模式,通过SPI接口挂载存储设备(如TF卡);
数据类型: number;
取值范围: 暂无;
注意事项:1. 需预先初始化SPI接口(spi.setup);
2. 需提供有效的SPI设备对象或ID;
示例代码:-- 创建SPI设备对象;
local spi_device = spi.deviceSetup(1, 20, 0, 0, 8, 24000000)
-- 挂载TF卡;
fatfs.mount(fatfs.SPI, "/sd", spi_device)
3.2 fatfs.SDIO(SDIO模式)
常量含义: SDIO模式,用于通过SDIO接口挂载存储设备;
数据类型: number;
取值范围: 暂无;
注意事项:1. 无需预先初始化接口
2. 仅需提供挂载点路径
示例代码:-- 使用SDIO模式挂载TF卡(不需要提供SPI相关参数)
local mount_ok, err = fatfs.mount(fatfs.SDIO, "/sd", 24 * 1000 * 1000)
四、函数详解
4.1 fatfs.mount(mode, mount_point, spiid_or_spidevice, spi_cs, spi_speed, power_pin, power_on_delay, auto_format)
功能
挂载FAT文件系统;
注意事项
1、挂载前需确保SPI接口已正确初始化,SDIO模式不需要初始化; 2、若挂载失败,可以尝试打开调试信息查找原因;
参数
mode
参数含义:fatfs模式;
数据类型:number;
取值范围:fatfs.SPI(SPI模式)、fatfs.SDIO(SDIO模式);
是否必选:是;
注意事项:需根据硬件连接方式选择对应模式;
参数示例:fatfs.SPI
mount_point
参数含义:文件系统的挂载点;
数据类型:string;
取值范围:有效路径字符串;
是否必选:是;
注意事项:暂无;
参数示例:"/sd"
spiid_or_spidevice
参数含义:SPI的ID或者是SPI设备对象;
数据类型:number或userdata;
取值范围:有效的SPI ID或设备对象;
是否必选:是;
注意事项:若当前这个参数传的是spi device,后续spi_cs和spi_speed参数可不传,如传后续参数会自动忽略;
参数示例:-- 1. 初始化SPI总线并获取一个配置好的SPI设备对象
-- spi.setup 返回的 spi_device 已经包含了CS引脚(20)和速度(24MHz)的信息!
local spi_device = spi.deviceSetup(1, 20, 0, 0, 8, 24000000)
-- 2. 挂载文件系统:直接把这个配置好的“参数”交给fatfs
-- 注意:这里不需要再传 pin_cs 和 spi_speed 参数了!
fatfs.mount(fatfs.SPI, "/sd", spi_device)
spi_cs
参数含义:片选脚的GPIO号;
数据类型:number;
取值范围:有效的GPIO编号;
是否必选:SPI模式并且spiid_or_spidevice参数为SPI ID时,必选;
其余情况下,此参数无意义;
注意事项:SPI模式并且spiid_or_spidevice参数为SPI ID时,必选;
其余情况下,此参数无意义;
参数示例:20
spi_speed
参数含义:设置的SPI最高速度,tf卡初始化时会到达400kHz,之后会恢复到设置速度;
1、tf卡初始化时达到400kHz速率,是为了尽量兼容不同品牌的卡,采用一个最
保守、最通用的低速来进行第一次通信;
2、初始化后恢复设置速度,如24MHz,是为了极大程度的提高后续的读写能力;
数据类型:number;
取值范围:速率与主控芯片的spi_clk引脚或sdio_clk引脚的最大速率有关;
Air8000,Air780EH系列模块通过spi挂载tf卡,最高可支持25MHz,
有效取值范围400 kHz - 25 MHz,低于400kHz可能无法初始化;
Air8101系列或Air6101系列通过sdio挂载tf卡,最高可支持80MHz,
有效取值范围400 kHz - 80 MHz;
可根据要传输的数据大小,使用的主控硬件设备,选择的tf卡种类综合判断该参数传入的大小;
是否必选:否;
注意事项:默认10MHz,单位Hz;
参数示例:24000000 (24MHz)
power_pin
参数含义:TF卡电源控制脚;
数据类型:number;
取值范围:有效的GPIO编号;
是否必选:否;
注意事项:如果没有,或者是内置电源控制方式,可不传;
参数示例:140
power_on_delay
参数含义:TF卡电源复位过程时间;
当在 fatfs.mount 函数中指定了 power_pin 参数时,完整的电源复位流程才会被触发
其目的是通过完全断电再上电的方式,让TF卡硬件恢复到绝对的初始状态;
复位过程:
1、断电(强制TF卡完全停止工作);
2、放电等待(电压完全跌落到零,确保一次彻底的硬复位);
3、上电(驱动将 power_pin 恢复为高电平,即有效供电电平,重新为TF卡供电);
4、上电延时等待power_on_delay(目的是为了留出时间让电压稳定,并完成自检,时间同步,寄存器初始化等内部操作);
5、开始通信 (在power_on_delay时间结束后,驱动才认为TF卡已经准备就绪,随后开始发送最初的SPI命令进行初始化);
数据类型:number;
取值范围:≥0的整数;
是否必选:否;
注意事项:单位ms,默认值是1;
参数示例:10
auto_format
参数含义:挂载失败是否尝试格式化;
数据类型:boolean;
取值范围:true或false;
是否必选:否;
注意事项:默认是true,即自动格式化;
参数示例:true
返回值
local success, err = fatfs.mount(mode, mount_point, spiid_or_spidevice, spi_cs, spi_speed, power_pin, power_on_delay, auto_format)
有两个返回值success和err
success
含义说明:挂载是否成功;
数据类型:boolean;
取值范围:true或false;
注意事项:成功返回true,失败返回false;
返回示例:true
err
含义说明:失败的原因;
数据类型:string或者number;
取值范围:错误描述字符串;
注意事项:仅当success为false时有效;
返回示例:3
错误返回码对照表
返回码 | 符号名称 | 含义说明 | 常见原因及排查建议 |
---|---|---|---|
1 | FR_DISK_ERR | 底层磁盘I/O层发生硬件错误 | - SPI通信错误(检查接线) - 卡响应超时(降低SPI速度尝试) - 硬件故障(更换TF卡或模块) |
2 | FR_INT_ERR | 断言失败(内部错误) | - 系统资源不足 - 无法创建互斥锁(ff_mutex_create返回0) - FatFS库内部错误 |
3 | FR_NOT_READY | 物理驱动器不工作 | - 卡未正确插入(重新插拔TF卡) - 硬件连接问题(检查电路) - 电源问题(检查供电引脚和电压) |
13 | FR_NO_FILESYSTEM | 找不到有效的FAT卷 | - SD卡未格式化(使用工具格式化) - 文件系统损坏(尝试修复或格式化) - 卡类型不受支持(更换TF卡) |
14 | FR_MKFS_ABORTED | f_mkfs函数因某些问题中止 | - 格式化过程被中断 - 卡写保护(检查写保护开关) - 存储介质问题 |
15 | FR_TIMEOUT | 无法在定义的时间内获得卷控制 | - 卡响应慢(增加超时时间) - 硬件性能问题 - 通信干扰 |
17 | FR_NOT_ENOUGH_CORE | LFN工作缓冲区无法分配或给定缓冲区大小不足 | - 系统内存不足 - 需要增加LFN工作缓冲区大小 - 优化内存使用 |
19 | FR_INVALID_PARAMETER | 给定参数无效 | - 挂载点参数错误 - SPI参数配置错误 - 函数调用参数不正确 |
错误返回值对照表
返回值 | 含义 | 常见原因 | 排查建议 |
---|---|---|---|
"out of memory when malloc FATFS" | 系统无法为FATFS文件系统对象分配内存 | 1. 系统可用内存不足 2. 内存碎片严重 3. FATFS对象大小超过可用块 | 1. 检查内存使用情况 2. 优化程序释放未用内存 3. 减少同时挂载的文件系统数量 |
"out of memory when malloc luat_fatfs_spi_t" | 无法为SPI模式分配专用结构体内存 | 1. SPI模式专用内存池耗尽 2. 多次重复挂载未卸载 3. SPI参数配置过大 | 1. 卸载无用挂载点(fatfs.unmount()) 2. 检查SPI缓冲区大小设置 3. 重启设备释放残留内存 |
"out of memory when malloc luat_fatfs_sdio_t" | 无法为SDIO模式分配专用结构体内存 | 1. SDIO控制器实例超限 2. DMA缓冲区配置过大 3. 硬件不支持多SDIO设备 | 1. 确认硬件SDIO支持数量 2. 减小DMA缓冲区大小 3. 检查是否存在冲突外设 |
"fatfs_mode error" | 文件系统模式参数无效 | 1. 错误模式值传入 2. 常量名拼写错误 3. SDK版本不兼容 | 1. 检查mode参数值(fatfs.SPI等) 2. 验证SDK版本支持的模式 3. 使用常量而非硬编码数值 |
示例
-- 使用SPI模式挂载TF卡
local spiId = 2
-- 设置并启用SPI
local result = spi.setup(spiId, 255, 0, 0, 8, 400000)
local TF_CS = 8
gpio.setup(TF_CS, 1)
-- 挂载sd卡
local success, err = fatfs.mount(fatfs.SPI, "/sd", spiId, TF_CS, 24000000)
if success then
log.info("fatfs.mount", "挂载成功")
else
log.error("fatfs.mount", "挂载失败", err)
end
4.2 fatfs.unmount(mount_point)
功能
取消挂载fatfs文件系统;
注意事项
1、必须与fatfs.mount的挂载点参数一致;
2、卸载前应确保所有文件操作已完成;
参数
mount_point
参数含义:文件系统的挂载点;
数据类型:string;
取值范围:有效的挂载点路径;
是否必选:是;
注意事项:必须与fatfs.mount传入的值一致;
参数示例:"/sd"
返回值
local result = fatfs.unmount(mount_point)
有一个返回值 result
result
含义说明:操作结果;
数据类型:number;
取值范围:成功返回0,且取消挂载必然会成功;
注意事项:取消挂载必然会成功;
返回示例:0
示例
local result = fatfs.unmount("/sd")
-- 通过返回值是否等于0,判断是否卸载成功;
if result == 0 then
log.info("fatfs.unmount", "卸载成功")
else
log.error("fatfs.unmount", "卸载失败", result)
end
4.3 fatfs.getfree(mount_point)
功能
获取文件系统可用空间信息;
注意事项
1、当前扇区大小固定在512字节;
2、需要跟fatfs.mount传入的挂载点一致;
参数
mount_point
参数含义:文件系统挂载点;
数据类型:string;
取值范围:有效的挂载点路径;
是否必选:是;
注意事项:必须与fatfs.mount传入的值一致;
参数示例:"/sd"
返回值
local data, err = fatfs.getfree(mount_point)
有两个返回值data和err
data
含义说明:空间信息表,表的格式为{total_sectors, total_kb, free_sectors, free_kb},说明如下;
{
-- 含义说明:总扇区数量;
-- 数据类型:number;
-- 取值范围:必须>0;
-- 注意事项:当前扇区大小固定在512字节;
-- 返回示例:244264960
total_sectors = 244264960,
-- 含义说明:总存储容量;
-- 数据类型:number;
-- 取值范围:必须>0;
-- 注意事项:单位是千字节(KB),计算方式为total_sectors * 512 / 1024;
-- 返回示例:122132480
total_kb = 122132480,
-- 含义说明:空闲扇区数量;
-- 数据类型:number;
-- 取值范围:0 ≤ free_sectors ≤ total_sectors;
-- 注意事项:表示当前可用的扇区数量;
-- 返回示例:244262144
free_sectors = 244262144,
-- 含义说明:可用存储容量;
-- 数据类型:number;
-- 取值范围:0 ≤ free_kb ≤ total_kb;
-- 注意事项:单位是千字节(KB),计算方式为free_sectors * 512 / 1024;
-- 返回示例:122131072
free_kb = 122131072,
}
数据类型:table;
取值范围:成功时返回包含空间信息的table,失败时返回nil;
注意事项:包含total_sectors, total_kb, free_sectors, free_kb 字段;
返回示例:local data, err = fatfs.getfree("/sd")
if data then
--打印SD卡的可用空间信息
--fatfs getfree {"free_sectors":244262144,"total_kb":122132480,"free_kb":122131072,"total_sectors":244264960}
log.info("fatfs", "getfree", json.encode(data))
err
含义说明:底层错误码;
数据类型:number;
取值范围:系统错误码;
注意事项:仅当data为nil时有效
返回示例:3
错误返回码对照表
返回码 | 符号名称 | 含义说明 | 常见原因 | 排查建议 |
---|---|---|---|---|
0 | FR_OK | 函数执行成功 | 操作正常完成 | - |
1 | FR_DISK_ERR | 底层磁盘I/O错误 | SPI通信错误 卡响应超时 硬件故障 | 检查接线连接 降低SPI通信速度 更换TF卡测试 |
2 | FR_INT_ERR | 断言失败(内部错误) | 文件系统结构损坏 FAT表损坏 内部逻辑错误 | 格式化TF卡 使用磁盘修复工具 检查文件系统完整性 |
3 | FR_NOT_READY | 物理驱动器未准备好 | 卡未正确插入 硬件连接问题 电源问题 | 重新插拔TF卡 检查电路连接 验证电源电压稳定性 |
4 | FR_NO_FILE | 找不到文件 | 文件不存在 路径错误 文件名大小写问题 | 确认文件路径正确 检查文件名大小写 验证文件是否被删除 |
5 | FR_NO_PATH | 找不到路径 | 目录路径不存在 路径格式错误 | 检查路径格式 确认目录存在 使用绝对路径 |
6 | FR_INVALID_NAME | 路径名格式无效 | 文件名含非法字符 路径长度超限 | 避免使用特殊字符 缩短文件路径长度 使用合规命名 |
7 | FR_DENIED | 访问被拒绝 | 文件只读属性 磁盘空间不足 权限限制 | 检查文件属性 清理磁盘空间 确认访问权限 |
8 | FR_EXIST | 文件已存在 | 尝试创建已存在的文件 重命名目标已存在 | 先检查文件是否存在 选择不同的文件名 删除已存在的文件 |
9 | FR_INVALID_OBJECT | 文件对象无效 | 文件句柄已关闭 对象被删除或移动 | 重新打开文件 检查文件状态 验证对象有效性 |
10 | FR_WRITE_PROTECTED | 物理写保护 | TF卡写保护开关开启 文件系统只读挂载 | 关闭写保护开关 重新以读写模式挂载 检查硬件写保护状态 |
11 | FR_INVALID_DRIVE | 逻辑驱动器号无效 | 驱动器号超出范围 驱动器未初始化 | 检查驱动器编号 确认驱动器已初始化 验证配置参数 |
12 | FR_NOT_ENABLED | 卷无工作区 | 驱动器未挂载 工作区未分配 | 先挂载文件系统 分配工作区内存 检查初始化流程 |
13 | FR_NO_FILESYSTEM | 无有效FAT卷 | SD卡未格式化 文件系统损坏 卡类型不支持 | 格式化TF卡为FAT32 使用磁盘修复工具 更换兼容的存储卡 |
14 | FR_MKFS_ABORTED | 格式化中止 | 格式化过程被中断 卡写保护 存储介质问题 | 解除写保护 检查存储介质健康状态 重新执行格式化 |
15 | FR_TIMEOUT | 操作超时 | 卡响应缓慢 硬件性能问题 通信干扰 | 增加超时时间设置 检查硬件连接质量 降低通信频率 |
16 | FR_LOCKED | 文件被锁定 | 文件被其他进程锁定 共享冲突 | 关闭其他占用进程 等待资源释放 检查文件锁状态 |
17 | FR_NOT_ENOUGH_CORE | 内存不足 | 系统内存不足 LFN缓冲区太小 | 增加系统内存 扩大LFN工作缓冲区 优化内存使用 |
18 | FR_TOO_MANY_OPEN_FILES | 打开文件数超限 | 同时打开文件过多 FF_FS_LOCK设置太小 | 关闭不再使用的文件 增加FF_FS_LOCK配置值 优化文件打开策略 |
19 | FR_INVALID_PARAMETER | 参数无效 | 函数参数错误 参数超出范围 | 检查API调用参数 验证参数取值范围 查阅函数文档 |
示例
local data, err = fatfs.getfree("/sd")
if data then
--打印SD卡的可用空间信息
-- fatfs getfree {"free_sectors":244262144,"total_kb":122132480,"free_kb":122131072,"total_sectors":244264960}
log.info("fatfs", "getfree", json.encode(data))
else
--打印错误信息
log.info("fatfs", "getfree", "err", err)
goto resource_cleanup
end
4.4 fatfs.debug(value)
功能
设置fatfs调试模式;
注意事项
建议仅在排查问题时开启,增加调试日志;
参数
value
参数含义:设置fatfs调试模式的开关;
数据类型:number;
取值范围:1或0;
是否必选:是;
注意事项:1时表示开,0时表示关;
参数示例:1
返回值
无
示例
-- 若挂载失败,可以尝试打开调试信息,查找原因
fatfs.debug(1)
4.5 fatfs.config(crc_check, write_to)
功能
配置fatfs特殊参数;
注意事项
1、大部分卡无需配置;
2、部分不能正常读写的卡,经过配置后可能能读写成功(暂未有实际案例);
参数
crc_check
参数含义:读取时是否跳过CRC检查;
数据类型:number;
取值范围:0或1;
是否必选:否;
注意事项:1跳过不检查CRC,0不跳过检查CRC,默认0。跳过CRC检查的主要风险是无法保障数据的正确性,不应跳出过crc校验,除非你测试确认是由于校验失败导致的问题;
参数示例:1
write_to
参数含义:单次写入超时时间;
数据类型:number;
取值范围:≥0的整数;
是否必选:否;
注意事项:单位ms,默认100ms;
参数示例:200
返回值
无
示例
-- 配置跳过CRC检查,写入超时200ms
fatfs.config(1, 200)
五、产品支持说明
支持 LuatOS 开发的所有产品都支持fatfs核心库。