资讯动态

【ESP32笔记】VFS与SPIFFS文件系统

发布时间:2026/10/15 19:05:16 来源:尧图企业网站定制
本节目标理解 VFS 抽象层与 SPIFFS 的关系会挂载 SPIFFS 并用 C 标准库读写文件会把文件夹打包进 flash 分区一、本节要解决的问题上一节的 NVS 适合存键值式的小配置但遇到下面这些需求就力不从心了要把一个网页html、一段长文本、一张小图片存到 flash 里想按文件名组织管理这些内容像在电脑上一样用fopen、fread读写文件数量、大小不固定不想每加一个文件就改一次地址。这就需要一个文件系统。ESP32 上常用的嵌入式文件系统是SPIFFS面向 SPI NOR flash支持磨损均衡。而 ESP-IDF 里所有文件系统都通过VFS虚拟文件系统这层抽象接入 C 标准库——注册好之后fopen(/spiffs/xxx)就能直接打开 SPIFFS 里的文件。本节先讲清 VFS 这层翻译官的作用再把 spiffs 例程跑起来最后介绍如何把电脑上的文件夹打包进 flash 分区一起烧录。二、相关知识2.1 文件系统是什么文件系统是操作系统中用于组织、管理和存储持久化数据的组件由三部分组成文件系统的接口应用程序与文件系统交互的方式如 fopen、fwrite对象操纵和管理的软件集合实现创建、删除、读写、重命名等操作对象及属性实际存储的数据文件以及相关的元数据信息。有了它调用fwrite(buffer, size, count, file)时完全不用关心写到哪个地址、偏移多少只要说明往哪个文件写什么内容即可。2.2 VFS让 C 库函数认识各种文件系统ESP32 上可能有多种文件系统SPIFFS、FATFS……甚至 SD 卡VFS 为驱动程序提供统一接口把 C 库函数和具体的文件系统驱动连接起来每个 FS 驱动注册时绑定一个路径前缀挂载点例如/fat调用fopen(/fat/file.txt)时VFS 按最长前缀匹配找到对应驱动把调用转给它驱动收到的是去掉前缀的相对路径VFS 调用 FAT 驱动的 open参数是/file.txt后续的 read、write、close 也全部转给这个驱动。挂载点必须以/开头。存在嵌套挂载时按最长前缀匹配打开/data/static/index.html只会找挂在/data/static的驱动即使它里面没有这个文件也不会退回/data去找。ESP-IDF 已经把SPIFFS VFS 注册封装好了我们不需要自己定义esp_vfs_t结构体、调用esp_vfs_register()直接用esp_vfs_spiffs_register()就能一步完成挂载。2.3 SPIFFS 的特点与限制专为 SPI NOR flash 设计支持磨损均衡和文件系统一致性检查使用前要在分区表里划一块Typedata、SubTypespiffs的分区限制先了解踩坑时回来查不支持目录只有扁平结构。挂载在/spiffs下时/spiffs/tmp/myfile.txt在 SPIFFS 内部实际叫/tmp/myfile.txt不是实时文件系统每次写操作耗时不等空间不足触发垃圾回收时一次写可能要好几秒只能稳定使用约75%的指定分区容量文件系统操作期间断电可能导致损坏可用esp_spiffs_check()修复。三、知识点3.1 挂载配置 esp_vfs_spiffs_conf_t字段含义base_path挂载点路径前缀如 “/spiffs”之后用fopen(/spiffs/...)访问文件partition_label指定要挂载的 spiffs 分区名填 NULL 表示自动使用分区表中第一个 spiffs 类型的分区max_files最大可同时打开的文件数format_if_mount_failed挂载失败时是否自动格式化开发阶段填 true 方便正式产品要谨慎格式化会清空数据3.2 一个函数完成三件事esp_vfs_spiffs_register(conf)内部依次做初始化 SPIFFS 文件系统必要时格式化把 SPIFFS 的读写等操作注册到 VFS把文件系统挂载到base_path指定的挂载点。挂载成功后就可以用fopen/fprintf/fgets/rename/unlink/stat这些标准 C 库函数操作文件了——VFS 负责把它们转发给 SPIFFS 驱动。3.3 文件系统维护三件套API用途esp_spiffs_check(label)检查并修复文件系统清理未引用页面等官方推荐在esp_spiffs_info返回 used 大于 total 或出现一致性错误码时调用esp_spiffs_info(label, total, used)获取分区总容量与已用字节数esp_spiffs_format(label)格式化分区数据全部丢失3.4 把文件夹打包进分区spiffs_create_partition_image还有一个问题怎么把电脑上的文件放进 flash 里的 spiffs 分区总不能每次手工写入。在组件的 CMakeLists.txt 里加一行即可仓库中的web_monitor工程正是用这种方式把网页打包进 flash 的idf_component_register(SRCS ws.c softap.c dht11.c main.c led_ws2812.c INCLUDE_DIRS .) spiffs_create_partition_image(html ../html FLASH_IN_PROJECT)三个参数的含义html分区名必须与分区表 CSV 里的 Name 一致。web_monitor/partitions_webserver.csv中对应的行是html, data, spiffs, , 0x4000,即 16KB 的 spiffs 分区../html要打包的目录相对当前 CMakeLists.txt里面只有一个esp.html约 4KB能放进 16KB 分区FLASH_IN_PROJECT让生成的镜像跟随idf.py flash一起烧录。不加这个选项时镜像照样会生成构建目录下的html.bin但需要用idf.py html-flash单独烧或手工写到分区的偏移地址。镜像按 SPIFFS 格式生成并烧进分区后程序就能通过/spiffs/esp.html这样的路径打开它——web_monitor/main/main.c里正是这么做的#defineINDEX_HTML_PATH/spiffs/esp.html...esp_vfs_spiffs_conf_tconf{.base_path/spiffs,.partition_labelNULL,.max_files5,.format_if_mount_failedtrue};//挂载spiffsESP_ERROR_CHECK(esp_vfs_spiffs_register(conf));注意镜像里的文件是烧录时写入的运行时用fopen读取如果挂载失败且format_if_mount_failed true触发了格式化预置文件就会被清空。产品固件通常把它设为 false确认需要时才允许格式化。四、代码分析4.1 工程结构spiffs/ ├── CMakeLists.txt ├── partitions_example.csv # 自定义分区表storage, data, spiffs, 0xF0000 ├── sdkconfig # CONFIG_PARTITION_TABLE_CUSTOMy文件名指向上面的 CSV └── main/ ├── CMakeLists.txt └── main.c # 挂载 → 检查 → 读写 → 卸载分区表内容注意 storage 分区的 Offset 留空由构建工具自动计算# Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, storage, data, spiffs, , 0xF0000,前三行与默认表一致只是把 Offset 显式写了出来factory到 0x110000 结束storage自动排到0x110000大小 0xF0000960KB全部用作 SPIFFS 存储代码里partition_label NULL所以挂载的就是这张表中第一个 spiffs 类型的分区——storage。4.2 挂载与错误处理staticconstchar*TAGexample;voidapp_main(void){ESP_LOGI(TAG,Initializing SPIFFS);esp_vfs_spiffs_conf_tconf{.base_path/spiffs,//可以认为挂着点后续使用C库函数fopen(/spiffs/...).partition_labelNULL,//指定spiffs分区如果为NULL则默认为分区表中第一个spiffs类型的分区.max_files5,//最大可同时打开的文件数.format_if_mount_failedtrue};//初始化和挂载spiffs分区esp_err_tretesp_vfs_spiffs_register(conf);//失败处理if(ret!ESP_OK){if(retESP_FAIL){ESP_LOGE(TAG,Failed to mount or format filesystem);}elseif(retESP_ERR_NOT_FOUND){ESP_LOGE(TAG,Failed to find SPIFFS partition);}else{ESP_LOGE(TAG,Failed to initialize SPIFFS (%s),esp_err_to_name(ret));}return;}错误处理区分了三种情况挂载或格式化失败ESP_FAIL、找不到 spiffs 分区ESP_ERR_NOT_FOUND通常是分区表没写对、其他初始化错误。4.3 检查与读容量//执行SPIFFS文件系统检查ESP_LOGI(TAG,Performing SPIFFS_check().);retesp_spiffs_check(conf.partition_label);//操作spiffs文件系统器件断电可能会导致 SPIFFS 损坏可通过esp_spiffs_check恢复...//获取SPIFFS可用区域大小size_ttotal0,used0;retesp_spiffs_info(conf.partition_label,total,used);例程为了演示挂载后立刻检查一次如果esp_spiffs_info获取失败则调用esp_spiffs_format格式化后返回。若读出的used total说明文件系统状态异常代码会再执行一次esp_spiffs_check。4.4 用 C 库函数读写文件核心演示只有几行注意路径都带/spiffs前缀//结合VFS可以使用标准C库函数进行文件读写ESP_LOGI(TAG,Opening file);FILE*ffopen(/spiffs/hello.txt,w);if(fNULL){ESP_LOGE(TAG,Failed to open file for writing);return;}fprintf(f,Hello World!\n);fclose(f);接着演示先删、再改名、再读回//检查/spiffs/foo.txt这个文件是否存在如果存在删除它structstatst;if(stat(/spiffs/foo.txt,st)0){// 删除/spiffs/foo.txt文件unlink(/spiffs/foo.txt);}//重命名文件ESP_LOGI(TAG,Renaming file);if(rename(/spiffs/hello.txt,/spiffs/foo.txt)!0){ESP_LOGE(TAG,Rename failed);return;}...charline[64];fgets(line,sizeof(line),f);fclose(f);// strip newlinechar*posstrchr(line,\n);if(pos){*pos\0;}ESP_LOGI(TAG,Read from file: %s,line);stat判断文件是否存在存在就unlink删除SPIFFS 不支持目录这些函数都在扁平结构上操作rename改名fgets读回一行后把末尾的换行符去掉再打印。最后卸载文件系统//测试完成卸载esp_vfs_spiffs_unregister(conf.partition_label);ESP_LOGI(TAG,SPIFFS unmounted);与教程不一致PDF 里static const char *TAG spiffs;仓库里改成了example所以实际日志的前缀是 example 而不是 spiffs其余代码与教程基本一致。另外教程只演示了挂载与读写流程并没有讲文件夹打包烧录的部分本笔记用同仓库web_monitor的真实用法做了补充。五、关键 API 速查API作用esp_vfs_spiffs_register(conf)初始化 SPIFFS、注册 VFS、挂载到 base_path声明在esp_spiffs.hesp_vfs_spiffs_unregister(label)卸载文件系统label 为 NULL 时操作分区表中第一个 spiffs 分区esp_spiffs_check(label)检查 / 修复文件系统esp_spiffs_info(label, total, used)查询总容量与已用字节数esp_spiffs_format(label)格式化分区fopen / fprintf / fgets / fclose标准 C 库文件接口经 VFS 落到 SPIFFS 驱动stat / unlink / rename判断文件是否存在 / 删除 / 重命名spiffs_create_partition_image(分区名 目录 [FLASH_IN_PROJECT])CMake 函数把目录打包成 SPIFFS 镜像可选随固件一起烧录六、编译与现象验证cdesp32-board/spiffs idf.py build idf.py-pCOM端口flash monitor正常输出如下数值以实际运行为准I (xxx) example: Initializing SPIFFS I (xxx) example: Performing SPIFFS_check(). I (xxx) example: SPIFFS_check() successful I (xxx) example: Partition size: total: ..., used: ... I (xxx) example: Opening file I (xxx) example: File written I (xxx) example: Renaming file I (xxx) example: Reading file I (xxx) example: Read from file: Hello World! I (xxx) example: SPIFFS unmounted最后打印出Hello World!说明文件走完写入 → 改名 → 读回整个流程都是通的。另一个值得注意的现象程序每次上电都会重新执行一遍hello.txt被改名为foo.txt后下次开机stat(/spiffs/foo.txt)命中就先删掉再重建——文件确实保存在 flash 里掉电并没有消失。如果把写入部分注释掉、只保留读取重启后依然能读到上次留下的文件。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑