4、SPIFFS文件系统:从原理到实战
文件系统,说白了就是让单片机也能像电脑一样管理文件。ESP32 跑 SPIFFS,我用了好几年,踩过坑也填过坑。今天咱们把这套东西彻底讲透。
4.1 SPIFFS 原理与特性
SPIFFS 全称是 SPI Flash File System,专门为嵌入式设备设计。它跑在 SPI Flash 上,不是 SD 卡,也不是 EEPROM。
它的核心特性,我列个表,你一看就明白:
| 特性 | 说明 | 我的评价 |
|---|---|---|
| 轻量级 | 代码体积小,RAM 占用低 | 适合资源受限场景 |
| 掉电安全 | 写入时掉电不会损坏已有文件 | 这个很关键,我吃过亏 |
| 磨损均衡 | 自动分散写入位置,延长 Flash 寿命 | 不是完美均衡,但够用 |
| 目录支持 | 支持多级目录,但扁平化效率更高 | 别建太深,三层以内 |
| 最大文件数 | 默认约 32 个文件句柄 | 够用,但别滥用 |
为什么会选 SPIFFS 而不是 FAT?因为 FAT 需要额外缓冲区,而且对 Flash 的磨损均衡做得不好。SPIFFS 是专门为 Flash 优化的,说白了就是更懂 Flash 的脾气。
核心原理一句话:SPIFFS 把 Flash 分成多个逻辑块,每个块又分成页。写入时先找空闲页,如果块满了就擦除整个块。这种设计让写入和擦除次数尽量平均。
4.2 SPIFFS 分区配置
分区配置是第一步,也是最容易出错的一步。我记得第一次用 SPIFFS 时,分区表写错了,折腾了一下午才找到原因。
ESP32 的分区表在 partitions.csv 里定义。一个典型配置长这样:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000,
spiffs, data, spiffs, 0x1F0000,0x100000,
注意几个关键点:
- Type 必须是
data,SubType 必须是spiffs - Offset 和 Size 要对齐 Flash 的扇区大小(通常是 4KB)
- Size 不要超过 Flash 剩余空间,否则编译报错
我个人习惯给 SPIFFS 分配 1MB 空间。太小了存不了几个网页文件,太大了又浪费。你想想看,一个 HTML 页面加几个 CSS 和 JS,500KB 左右就够用了。
避坑指南:我曾经把 SPIFFS 分区设得太大,结果 OTA 分区不够用,导致无法远程升级。建议先算好总 Flash 大小,再分配各分区。
4.3 文件读写操作
读写文件是基本功。ESP32 的 SPIFFS API 和标准 C 的 fopen/fread/fwrite 很像,但有几个坑要注意。
先看一个完整的读写示例:
#include <SPIFFS.h>
void setup() {
Serial.begin(115200);
// 挂载 SPIFFS
if(!SPIFFS.begin(true)){
Serial.println("SPIFFS 挂载失败");
return;
}
// 写文件
File file = SPIFFS.open("/config.txt", FILE_WRITE);
if(!file){
Serial.println("打开文件失败");
return;
}
file.println("ssid=MyWiFi");
file.println("password=12345678");
file.close();
// 读文件
file = SPIFFS.open("/config.txt", FILE_READ);
if(!file){
Serial.println("打开文件失败");
return;
}
while(file.available()){
Serial.write(file.read());
}
file.close();
}
这里有几个细节,我特别想强调:
SPIFFS.begin(true)里的true表示自动格式化。第一次用或者分区损坏时,这个参数能救命。- 文件路径必须以
/开头,比如/config.txt,不是config.txt。 - 写完一定要
close(),否则数据可能没刷进 Flash。
小技巧:如果你要频繁写小数据,比如传感器读数,建议用 NVS 而不是 SPIFFS。SPIFFS 适合存大文件,比如网页、图片、配置文件。
4.4 目录管理
SPIFFS 支持目录,但别指望它能像 Windows 那样高效。我建议目录层级不超过三层,否则遍历起来很慢。
常用的目录操作:
// 创建目录
SPIFFS.mkdir("/www");
// 创建子目录
SPIFFS.mkdir("/www/css");
SPIFFS.mkdir("/www/js");
// 列出目录内容
File root = SPIFFS.open("/www");
File file = root.openNextFile();
while(file){
Serial.print("文件名: ");
Serial.println(file.name());
Serial.print("大小: ");
Serial.println(file.size());
file = root.openNextFile();
}
// 删除目录
SPIFFS.rmdir("/www/css");
嗯,这里要注意:rmdir() 只能删除空目录。如果目录里有文件,得先删文件再删目录。我曾经写了个脚本,忘了先删文件,结果目录删不掉,排查了半天。
下面这张图展示了 SPIFFS 的目录结构和文件操作流程:
从图里能看出来,SPIFFS 的目录结构其实很直观。操作流程就是:先挂载,再打开,读写完记得关闭。
4.5 避坑与最佳实践
最后,我总结几个实战中容易踩的坑:
- 不要频繁写入:Flash 有擦写寿命,大概 10 万次。频繁写日志会快速消耗寿命。
- 文件句柄有限:默认 32 个,用完就报错。记得及时
close()。 - 路径大小写敏感:
/Config.txt和/config.txt是两个文件。 - 不要存二进制大文件:SPIFFS 适合小文件,超过 1MB 的文件建议用 SD 卡。
我的习惯:每次写完文件后,调用 SPIFFS.info() 检查剩余空间。这样能提前发现空间不足的问题,避免写入失败。
好了,SPIFFS 的核心内容就这些。记住:挂载、读写、关闭,三步走。别偷懒,别省那几行代码。