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 的文件。

posted @ 2025-03-27 21:00  星光映梦  阅读(425)  评论(0)    收藏  举报