3. SPIFFS 文件系统基础:挂载、格式化、文件读写操作

SPIFFS,全称是 SPI Flash File System。说白了,它就是 ESP32 内部 Flash 上的一块“小硬盘”。

我刚开始接触 ESP32 时,总觉得存个网页、配个文件很麻烦。后来发现 SPIFFS 这东西,其实就是帮我们把 Flash 的一部分划出来,当成一个微型文件系统用。你想想看,一个嵌入式设备能像电脑一样读写文件,是不是很爽?

核心概念:SPIFFS 是一个轻量级的文件系统,专门为 SPI Flash 芯片设计。它支持目录结构(虽然是扁平的),支持标准的文件操作(fopen、fclose、fread、fwrite)。

3.1 SPIFFS 的挂载与格式化

挂载,就是把文件系统“接”到系统里。就像你插 U 盘,系统得先识别它才能用。

在 ESP32 上,挂载 SPIFFS 需要调用 esp_vfs_spiffs_register() 函数。我个人习惯在 setup() 里就把它挂好,省得后面手忙脚乱。

#include <esp_spiffs.h>

void setup() {
    Serial.begin(115200);
    
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",      // 挂载路径
        .partition_label = NULL,     // 使用默认分区
        .max_files = 5,              // 最大同时打开文件数
        .format_if_mount_failed = true  // 挂载失败自动格式化
    };
    
    esp_err_t ret = esp_vfs_spiffs_register(&conf);
    
    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            Serial.println("挂载失败,已尝试格式化");
        } else {
            Serial.println("SPIFFS 初始化失败");
        }
        return;
    }
    
    Serial.println("SPIFFS 挂载成功!");
}

这里有个关键参数:format_if_mount_failed。我建议你把它设为 true。为什么?因为新芯片第一次用,Flash 里是空的,不格式化根本挂不上。我曾经踩过这个坑,折腾了半天才发现是没格式化。

小技巧:如果你不想每次挂载失败都格式化(比如怕丢数据),可以先用 esp_spiffs_info() 检查文件系统状态,再决定是否格式化。

3.2 文件读写操作:fopen / fclose

挂载好了,接下来就是读写文件。ESP32 的 SPIFFS 支持标准的 C 库文件操作函数。说白了,就是 fopen、fclose、fprintf、fread 这些老朋友。

我写代码时,习惯把文件操作封装成几个小函数。这样主程序看起来清爽,调试也方便。

3.2.1 写文件

void writeFile(const char *path, const char *content) {
    FILE *file = fopen(path, "w");
    if (file == NULL) {
        Serial.println("打开文件失败");
        return;
    }
    
    fprintf(file, "%s", content);
    fclose(file);
    
    Serial.println("文件写入成功");
}

注意:fopen 的第二个参数是模式字符串。常用的有:

模式 含义 注意事项
"w" 写入模式,覆盖原文件 文件不存在会创建
"a" 追加模式,在文件末尾添加 文件不存在会创建
"r" 只读模式 文件不存在返回 NULL
"r+" 读写模式 文件必须存在

3.2.2 读文件

void readFile(const char *path) {
    FILE *file = fopen(path, "r");
    if (file == NULL) {
        Serial.println("文件不存在");
        return;
    }
    
    char line[128];
    while (fgets(line, sizeof(line), file) != NULL) {
        Serial.print(line);
    }
    
    fclose(file);
}

这里我用了 fgets 逐行读取。如果你要读二进制文件,可以用 fread。我个人更推荐 fgets 处理文本,因为它自带换行处理,省事。

避坑指南:SPIFFS 不支持目录嵌套!所有文件都在根目录下。我曾经试图创建 /spiffs/data/config.txt,结果死活打不开。后来才发现,SPIFFS 是扁平结构,只能 /spiffs/config.txt。

3.3 文件系统信息查询

有时候你需要知道 SPIFFS 还剩多少空间。比如,你要存一张大图片,得先看看够不够。

void printSpiffsInfo() {
    size_t total = 0;
    size_t used = 0;
    
    esp_err_t ret = esp_spiffs_info(NULL, &total, &used);
    if (ret != ESP_OK) {
        Serial.println("获取 SPIFFS 信息失败");
        return;
    }
    
    Serial.printf("总空间: %d bytes\n", total);
    Serial.printf("已用空间: %d bytes\n", used);
    Serial.printf("剩余空间: %d bytes\n", total - used);
}

嗯,这里要注意:esp_spiffs_info 的第一个参数是分区标签。如果你用的是默认分区,传 NULL 就行。

3.4 文件删除与重命名

删除文件用 remove(),重命名用 rename()。这两个函数都是标准 C 库的,用起来很简单。

void deleteFile(const char *path) {
    if (remove(path) == 0) {
        Serial.println("文件删除成功");
    } else {
        Serial.println("文件删除失败");
    }
}

void renameFile(const char *oldPath, const char *newPath) {
    if (rename(oldPath, newPath) == 0) {
        Serial.println("文件重命名成功");
    } else {
        Serial.println("文件重命名失败");
    }
}

小技巧:如果你要批量删除文件,可以用 opendir() 和 readdir() 遍历目录。不过 SPIFFS 文件数不多,直接循环删除也行。

3.5 完整示例:一个简单的文件管理器

我把上面这些操作整合成一个简单的示例。你可以在串口输入命令,实现文件读写、删除、查看信息等功能。

void setup() {
    Serial.begin(115200);
    
    // 挂载 SPIFFS
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",
        .partition_label = NULL,
        .max_files = 5,
        .format_if_mount_failed = true
    };
    
    if (esp_vfs_spiffs_register(&conf) != ESP_OK) {
        Serial.println("SPIFFS 挂载失败");
        return;
    }
    
    Serial.println("SPIFFS 就绪,输入命令:");
    Serial.println("  write <文件名> <内容>");
    Serial.println("  read <文件名>");
    Serial.println("  delete <文件名>");
    Serial.println("  info");
}

void loop() {
    if (Serial.available()) {
        String cmd = Serial.readStringUntil('\n');
        cmd.trim();
        
        if (cmd.startsWith("write ")) {
            // 解析命令
            int space1 = cmd.indexOf(' ', 6);
            String path = cmd.substring(6, space1);
            String content = cmd.substring(space1 + 1);
            
            writeFile(("/spiffs/" + path).c_str(), content.c_str());
        }
        else if (cmd.startsWith("read ")) {
            String path = cmd.substring(5);
            readFile(("/spiffs/" + path).c_str());
        }
        else if (cmd.startsWith("delete ")) {
            String path = cmd.substring(7);
            deleteFile(("/spiffs/" + path).c_str());
        }
        else if (cmd == "info") {
            printSpiffsInfo();
        }
    }
}

这个示例虽然简单,但已经涵盖了 SPIFFS 的核心操作。你可以在实际项目中直接套用。

3.6 SPIFFS 工作流程

下面这张图展示了 SPIFFS 从挂载到文件读写的完整流程。我画这张图时,特意把关键步骤和错误处理都标出来了。

SPIFFS 文件操作流程图 SPIFFS 文件操作流程图 开始 配置 esp_vfs_spiffs_conf_t 挂载 SPIFFS 挂载成功? (esp_err_t) 失败 自动格式化 重新挂载 格式化成功? 失败 报错退出 成功 文件操作 fopen / fclose / fread / fwrite 结束 注意事项 • 挂载失败自动格式化 • 文件路径以 /spiffs 开头 • 不支持目录嵌套

从这张图可以看出,SPIFFS 的操作流程其实很清晰。挂载是第一步,文件读写是核心,错误处理是关键。我建议你在实际项目中,把挂载和格式化逻辑放在初始化阶段,文件操作封装成独立函数。

总结一下:SPIFFS 是 ESP32 上最常用的文件系统。挂载、格式化、文件读写,这三板斧学会了,后面做网页存储、配置管理就轻松多了。记住:挂载失败就格式化,文件操作完就关闭,路径别搞嵌套。