17. SPIFFS文件系统
一、SPIFS简介
SPIFFS 是一个用于 SPI NOR Flash 设备的嵌入式文件系统,支持磨损均衡(嵌入式设备使用的大多数存储芯片都支持每个扇区有限的擦除集,如果没有均衡,则嵌入式设备的寿命可能会受到影响)、文件系统一致性检查等功能。该文件系统只需要少量的RAM就可以运行。
二、SPIFFS常用函数
ESP-IDF 提供了一套 API 来配置高精度定时器。要使用此功能,需要导入必要的头文件:
#include "esp_spiffs.h"
2.1、注册装载SPIFFS
我们可以使用 esp_vfs_spiffs_register() 函数 给定的路径前缀将 SPIFFS 注册并装载到 VFS,其函数原型如下所示:
/**
* @brief 注册装载SPIFFS
*
* @param conf 指向esp_vfs_spiffs_conf_t配置结构的指针
* @return esp_err_t ESP_OK注册成功,其它注册失败
*/
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf);
形参 conf 是 指向 esp_vfs_spiffs_conf_t 配置结构的指针,它的定义如下:
typedef struct
{
const char* base_path; // 与文件系统关联的文件路径前缀
const char* partition_label; // 可选,要使用的SPIFFS分区的标签。如果设置为NULL,则将使用subtype=spiffs的第一个分区
size_t max_files; // 可以同时打开的最大文件数
bool format_if_mount_failed; // 如果为true,则在装载失败时将格式化文件系统
} esp_vfs_spiffs_conf_t;
2.2、注销和卸载SPIFFS
我们可以使用 esp_vfs_spiffs_unregister() 函数 从 VFS 注销和卸载 SPIFFS,其函数原型如下所示:
/**
* @brief 注销和卸载SPIFFS
*
* @param partition_label 指向分区表的指针,分区表名称
* @return esp_err_t ESP_OK注销成功,其它注销失败
*/
esp_err_t esp_vfs_spiffs_unregister(const char* partition_label);
2.3、获取SPIFFS的信息
我们可以使用 esp_spiffs_info() 函数 获取 SPIFFS 的信息,其函数原型如下所示:
/**
* @brief 获取SPIFFS的信息
*
* @param partition_label 指向分区标签的指针,分区表名称
* @param total_bytes 文件系统的大小
* @param used_bytes 文件系统中当前使用的字节数
* @return esp_err_t ESP_OK获取成功,其它获取失败
*/
esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes);
2.4、读写数据
挂载好 SD 卡后,我们就可以使用 C 标准操作文件的函数向 SD 卡中读写数据。
【1】、打开文件
/**
* @brief 打开文件函数
*
* @param _name 打开的文件的文件名
* @param _type 打开模式
* @return FILE* 指向文件类型的指针
*/
FILE * fopen(const char *__restrict _name, const char *__restrict _type);
【2】、关闭文件
/**
* @brief 关闭文件
*
* @param stream 文件指针
* @return int 正常操作时返回值为 0,否则返回 EOF
*/
int fclose(FILE *stream );
【3】、读写一个字符
/**
* @brief 写入一个字符到文件中
*
* @param ch 要写入的字符
* @param stream 文件指针
* @return int 写入成功返回值写入的字符;如果写入失败,就返回 EOF
*/
int fputc( int ch, FILE *stream );
/**
* @brief 从文件中读取一个字符
*
* @param stream 文件指针
* @return int 读取的字符
*/
int fgetc( FILE *stream );
【4】、读写字符串
/**
* @brief 写入一个字符串到文件中
*
* @param str 字符串
* @param stream 文件指针
* @return int 写入成功返回写入的字符串的长度,失败返回EOF
*/
int fputs( const char *restrict str, FILE *restrict stream );
/**
* @brief 从文件中读取字符串
*
* @param str 保存字符串的缓冲区
* @param count 要读取的字符串长度
* @param stream 文件指针
* @return char* 如果成功则返回字符串的地址,如果没读入任何字符则返回NULL
*/
char *fgets( char *restrict str, int count, FILE *restrict stream );
【5】、格式化读写数据
/**
* @brief 格式化写入数据到文件中
*
* @param stream 文件指针
* @param format 格式化字符串
* @param ... 格式控制符
* @return int 成功匹配的参数个数
*/
int fprintf( FILE *restrict stream, const char *restrict format, ... );
/**
* @brief 格式化从文件中读取数据
*
* @param stream 文件指针
* @param format 格式化字符串
* @param ... 格式控制符
* @return int 成功匹配的参数个数
*/
int fscanf( FILE *restrict stream, const char *restrict format, ... );
三、实验例程
我们在使用 SPIFFS 创建分区时,需要先自定义一个分区表。这里,我们将自定义的分区表取名为 partitions.csv,它的内容如下:
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 0xF0000,
然后我们需要在 IDF 中将分区表指定为自定义的分区表。

修改【main】文件夹下的 main.c 文件。
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "esp_spiffs.h"
#define SPIFFS_MOUNT_POINT "/spiffs"
// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{
FILE *fp = NULL;
char write_chr = 'S', read_chr = 0;
char write_str[32] = "hello world!", read_str[32];
size_t total = 0, used = 0;
// 首次使用SPIFFS系统,需要格式化分区
esp_vfs_spiffs_conf_t esp_vfs_spiffs_config =
{
.base_path = SPIFFS_MOUNT_POINT,
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = true,
};
esp_vfs_spiffs_register(&esp_vfs_spiffs_config); // 注册SPIFFS文件系统
esp_spiffs_info(esp_vfs_spiffs_config.partition_label, &total, &used); // 获取SPIFFS文件系统信息
fp = fopen(SPIFFS_MOUNT_POINT"/test.txt", "w");
fputc(write_chr, fp);
fclose(fp);
fp = fopen(SPIFFS_MOUNT_POINT"/test.txt", "r");
read_chr = fgetc(fp);
fclose(fp);
printf("chr: %c\n", read_chr);
fp = fopen(SPIFFS_MOUNT_POINT"/test2.txt", "w");
fputs(write_str, fp);
fclose(fp);
fp = fopen(SPIFFS_MOUNT_POINT"/test2.txt", "r");
fgets(read_str, strlen(write_str), fp);
fclose(fp);
printf("str: %s\n", read_str);
fp = fopen(SPIFFS_MOUNT_POINT"/test3.txt", "w");
fprintf(fp, "%s", write_str);
fclose(fp);
memset(read_str, 0, sizeof(read_str));
fp = fopen(SPIFFS_MOUNT_POINT"/test3.txt", "r");
fscanf(fp, "%s", read_str);
fclose(fp);
printf("str: %s\n", read_str);
esp_vfs_spiffs_unregister(esp_vfs_spiffs_config.partition_label); // 注销SPIFFS文件系统
while (1)
{
// 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数
vTaskDelay(pdMS_TO_TICKS(100));
}
}
SPIFFS 尚不支持目录,但可以生成扁平结构。如果 SPIFFS 挂载在
/spiffs下,在/spiffs/tmp/myfile.txt路径下创建一个文件则会在 SPIFFS 中生成一个名为/tmp/myfile.txt的文件,而不是在/spiffs/tmp下生成名为myfile.txt的文件。

浙公网安备 33010602011771号