第三十八章 ESP32S3 SPIFFS 实验

上一章实验中已经成功驱动 SD 卡,并可对 SD 卡进行读写操作,但读写 SD 卡时都是直接读出或写入二进制数据,这样使用起来显得十分不方便,因此本章将介绍 SPIFFS, SPIFFS是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨损均衡以及文件系统一致性检查等功能。通过本章的学习,将学习到 SPIFFS 的基本使用。

本章分为如下几个小节:

38.1 SPIFFS 简介

38.2 硬件设计

38.3 程序设计

38.4 下载验证

38.1 SPIFFS 介绍

SPIFFS​ ​ 是一个专为​​嵌入式系统​ ​和​​物联网(IoT)设备​ ​设计的​​开源文件系统​ ​。它的名字是 ​​SPI Flash File System​ ​ 的缩写,顾名思义,它主要用于在通过 ​​SPI​ ​ 接口连接的 ​​NOR Flash​​ 存储器上存储文件。SPIFFS 的设计遵循了嵌入式环境的特定约束,其核心特点包括:

特性 描述
​​轻量级​​ 代码量极小(通常只有几千字节),对 RAM 和 CPU 的资源占用极低,非常适合资源受限的 MCU。
​​掉电安全​​ 在设计上考虑了意外断电的情况,通过机制确保文件系统结构不会轻易损坏。
​​动态磨损均衡​​ 自动将写操作均匀分布到整个 Flash 存储区,避免某些扇区过早磨损而失效,​​显著延长 Flash 寿命​​。
​​基于页管理​​ 其内部存储管理基于 Flash 的物理页(Page)和扇区(Sector)结构,效率更高。
​​支持随机访问​​ 可以随机读取和写入文件中的任何位置。

表38.1.1 SPIFFS核心特点

工作原理与结构:

SPIFFS 将 Flash 存储器视为一个线性的、可寻址的空间,并将其逻辑上组织为以下结构:

  • 页 (Page):

这是​​最基本的读写单元​​,大小通常与 Flash 芯片的物理页大小对齐(例如 256 字节)。每个页包含​​数据区​​和​​元数据头​​。元数据头记录了该页的状态(有效、无效、已删除)以及它属于哪个文件。

  • 扇区 (Sector):

由多个​​页​​组成,是​​擦除(Erase)操作的基本单元​​。Flash 存储器只能将位从 1 改为 0(写入),而将 0 改回 1 必须通过​​扇区擦除​​。擦除操作非常耗时。SPIFFS 的核心工作之一就是通过​​垃圾回收(Garbage Collection)​​ 机制,将包含无效数据的扇区进行擦除,使其变为可用状态。

  • 文件索引:

SPIFFS 没有传统的目录结构(如 FATFS),所有文件都位于根目录下。它使用一种​​自定义的线性索引​​来跟踪文件及其数据页的位置,而不是像 FAT 表那样的结构。

SPIFFS 内部结构示意图:文件数据被分散存储在多个物理页中,通过元数据链接起来。

与常见文件系统的对比:

特性 ​SPIFFS​ ​FATFS​​ (用于SD卡) ​LittleFS​​ (SPIFFS的替代者)
​设计目标​ 极简,小文件,NOR Flash 通用,兼容性,块设备 更可靠,更高性能,NOR Flash
​目录支持​ ​不支持​​(只有根目录) ​支持​​(多级目录) ​支持​​(有限的多级目录)
​功耗优化​ 一般 一般 ​更优​​(更少的写放大)
​动态磨损均衡​ ​支持​ 不支持(需手动管理) ​支持​​(更优的算法)
​掉电安全性​ 较好 一般(易产生碎片错误) ​更好​​(一致性更强)
​适用介质​ ​NOR Flash​​ (SPI接口) ​SD卡、MMC​​ (块设备) ​NOR Flash​​ (SPI接口)

表38.1.2 SPIFFS与常见文件系统的对比

SPIFFS​​ 是嵌入式领域一个经典的、轻量级的解决方案,特别适合在​​片外 NOR Flash​​ 上存储​​配置文件、网页资源、小数据日志​​等。

然而,由于其已知的缺点和已停止维护的状态,对于​​新项目​​,**强烈建议考虑其现代替代品---​​LittleFS​​。**LittleFS 提供了更好的性能、更高的可靠性以及更友好的目录支持,正在成为嵌入式Flash 文件系统的新标准。在 ESP-IDF 中,LittleFS已经得到了很好的支持,其使用方式与SPIFFS 非常相似。

38.2 硬件设计

38.2.1 例程功能

1.在 nor flash 指定区域新建 holle.txt 文件,然后对这文件进行读写操作;

  1. LED 闪烁,指示程序正在运行。

38.2.2 硬件资源

  1. LED 灯

LED -IO0

  1. XL9555

IIC_SDA-IO41

IIC_SCL-IO42

  1. SPILCD

CS-IO21

SCK-IO12

SDA-IO11

DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)

PWR- IO1_3(XL9555)

RST- IO1_2(XL9555)

  1. SPIFFS

38.2.3 原理图

本章实验使用的 SPIFFS 为软件库,因此没有对应的连接原理图。

38.3 程序设计

38.3.1 程序流程图

本实验的程序流程图:

图 38.3.1.1 IIC_EXIO 实验程序流程图

38.3.2 SPIFFS 函数解析

在 ESP-IDF 环境中,使用 SPIFFS 通常通过 ​​虚拟文件系统 (VFS)​ ​ 层进行挂载,然后使用标准的 POSIX 函数(如 open, read, write, close)或 C 库函数(fopen, fprintf, fscanf)进行操作。

(1)注册装载 SPIFFS

该函数使用给定的路径前缀将 SPIFFS 注册并装载到 VFS,其函数原型如下所示:

复制代码
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf);

该函数的形参描述,如下表所示:

形参 描述
conf 指向 esp_vfs_spiffs_conf_t 配置结构的指针

表 38.3.2.1 函数 esp_vfs_spiffs_register ()形参描述

该函数的返回值描述,如下表所示:

返回值 描述
ESP_OK 返回: 0,配置成功
ESP_ERR_NO_MEM 如果无法分配对象
ESP_ERR_INVALID_STATE 如果已安装或分区已加密
ESP_ERR_NOT_FOUND 如果找不到 SPIFFS 的分区
ESP_FAIL 如果装载或格式化失败

表 38.3.2.2 函数 esp_vfs_spiffs_register ()返回值描述

该函数使用 esp_vfs_spiffs_conf_t 类型的结构体变量传入,该结构体的定义如下所示:

结构体 成员变量 可选参数
esp_vfs_spiffs_conf_t base_path 与文件系统关联的文件路径前缀。
esp_vfs_spiffs_conf_t partition_label 可选,要使用的 SPIFFS 分区的标签。如果设置为 NULL,则 f
esp_vfs_spiffs_conf_t max_files 可以同时打开的最大文件数。
esp_vfs_spiffs_conf_t format_if_mount_failed 如果为 true,则在装载失败时将格式化文件系统。

表 38.3.2.3 esp_vfs_spiffs_conf_t 结构体参数值描述

完成上述结构体参数配置之后,可以将结构传递给esp_vfs_spiffs_register 函数,用以实例化

SPIFFS。

(2)获取 SPIFFS 的信息

该函数用于获取 SPIFFS 的信息,其函数原型如下所示:

复制代码
esp_err_t esp_spiffs_info(const char* partition_label,
                          size_t *total_bytes,
                          size_t *used_bytes);

该函数的形参描述,如下表所示:

形参 描述
param partition_label 指向分区标签的指针,分区表名称
total_bytes 文件系统的大小
used_bytes 文件系统中当前使用的字节数

表 38.3.2.4 函数 esp_spiffs_info ()形参描述

该函数的返回值描述,如下表所示:

返回值 描述
ESP_OK 返回: 0,配置成功
ESP_FAIL 如果装载失败

表 38.3.2.5 函数 esp_spiffs_info ()返回值描述

(3)注销和卸载 SPIFFS

该函数从 VFS 注销和卸载 SPIFFS,其函数原型如下所示:

复制代码
esp_err_t esp_vfs_spiffs_unregister(const char* partition_label);

该函数的形参描述,如下表所示:

形参 描述
param partition_label 指向分区表的指针,分区表名称

表 38.3.2.6 函数 esp_vfs_spiffs_unregister ()形参描述

该函数的返回值描述,如下表所示:

返回值 描述
ESP_OK 返回: 0,配置成功
ESP_ERR_INVALID_STATE 已注销

表 38.3.2.7 函数 esp_vfs_spiffs_unregister ()返回值描述

38.3.3 SPIFFS 驱动解析

在IDF版的StandardExampleIDF(v5.3.x)\27_spiffs例程中,在分区表中添加 了 SPIFFS 的内容 , 27_spiffs\components\BSP 路径下并无新的驱动文件增加。分区表内容如下:

Name Type SubType Offset Size Flags
nvs data nvs 0x9000 0x6000
phy_init data phy 0xf000 0x1000
factory app factory 0x10000 0x1F0000
vfs data fat 0x200000 0xA00000
storage data spiffs 0xC00000 0x400000

(1)my_spiffs.h

复制代码
#define DEFAULT_FD_NUM          5           /* 默认最大可打开文件数量 */
#define DEFAULT_MOUNT_POINT     "/spiffs"   /* 文件系统名称 */

/* 函数声明 */
esp_err_t spiffs_init(char *partition_label, char *mount_point, size_t max_files);      /* spiffs初始化 */

(2)my_spiffs.c

复制代码
static const char *spiffs_tag = "spiffs";

/**
 * @brief       spiffs初始化
 * @param       partition_label:分区表的分区名称
 * @param       mount_point:文件系统关联的文件路径前缀
 * @param       max_files:可以同时打开的最大文件数
 * @retval      ESP_OK:成功; ESP_FAIL:失败
 */
esp_err_t spiffs_init(char *partition_label, char *mount_point, size_t max_files)
{
    size_t total = 0;   /* SPIFFS总容量 */
    size_t used = 0;    /* SPIFFS已使用的容量 */
    
    esp_vfs_spiffs_conf_t spiffs_conf = {           /* 配置spiffs文件系统的参数 */
        .base_path              = mount_point,      /* 磁盘路径,比如"0:","1:" */
        .partition_label        = partition_label,  /* 分区表的分区名称 */
        .max_files              = max_files,        /* 最大可同时打开的文件数 */
        .format_if_mount_failed = true,             /* 挂载失败则格式化文件系统 */
    };

    esp_err_t ret = esp_vfs_spiffs_register(&spiffs_conf);  /* 初始化和挂载SPIFFS分区 */
    if (ret != ESP_OK)
    {
        if (ret == ESP_FAIL)
        {
            ESP_LOGE(spiffs_tag, "Failed to mount or format filesystem");
        }
        else if (ret == ESP_ERR_NOT_FOUND)
        {
            ESP_LOGE(spiffs_tag, "Failed to find SPIFFS partition");
        }
        else
        {
            ESP_LOGE(spiffs_tag, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        }

        return ESP_FAIL;
    }

    ret = esp_spiffs_info(spiffs_conf.partition_label, &total, &used);  /* 获取SPIFFS的总容量和已使用的容量 */
    if (ret != ESP_OK)
    {
        ESP_LOGI(spiffs_tag, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
    }
    else
    {
        ESP_LOGI(spiffs_tag, "Partition size: total: %d Bytes, used: %d Bytes", total, used);
    }

    return ret;
}

/**
 * @brief       注销spiffs
 * @param       partition_label:分区表的分区名称
 * @retval      ESP_OK:注销成功; 其他:失败
 */
esp_err_t spiffs_deinit(char *partition_label)
{
    return esp_vfs_spiffs_unregister(partition_label);
}

38.3.4 CMakeLists.txt 文件

打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:

复制代码
set(src_dirs
            LED
            MYIIC
            XL9555
            MYSPI
            SPILCD
            SPIFFS)

set(include_dirs
            LED
            MYIIC
            XL9555
            MYSPI
            SPILCD
            SPIFFS)

set(requires
            driver
            esp_lcd
            spiffs)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

38.3.5 实验应用代码

打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

复制代码
#define WRITE_DATA              "ALIENTEK ESP32-S3"     /* 写入数据 */

/**
 * @brief       测试spiffs
 * @param       无
 * @retval      无
 */
void spiffs_test(void)
{
    ESP_LOGI("spiffs_test", "Opening file");
    
    FILE *file_obj = fopen("/spiffs/hello.txt", "w");   /* 建立一个名为/spiffs/hello.txt的只写文件 */
    if (file_obj == NULL)
    {
        ESP_LOGE("spiffs_test", "Failed to open file for writing");
    }

    fprintf(file_obj, WRITE_DATA);      /* 写入字符 */
    fclose(file_obj);                   /* 关闭文件 */
    ESP_LOGI("spiffs_test", "File written");

    /* 重命名之前检查目标文件是否存在 */
    struct stat st;
    if (stat("/spiffs/foo.txt", &st) == 0)  /* 获取文件信息,获取成功返回0 */
    {
        /*  从文件系统中删除一个名称。
            如果名称是文件的最后一个连接,并且没有其它进程将文件打开,
            名称对应的文件会实际被删除。 */
        unlink("/spiffs/foo.txt");
    }
 
    /* 重命名创建的文件 */
    ESP_LOGI("spiffs_test", "Renaming file");
    if (rename("/spiffs/hello.txt", "/spiffs/foo.txt") != 0)
    {
        ESP_LOGE("spiffs_test", "Rename failed");
    }
 
    /* 打开重命名的文件并读取 */
    ESP_LOGI("spiffs_test", "Reading file");
    file_obj = fopen("/spiffs/foo.txt", "r");
    if (file_obj == NULL)
    {
        ESP_LOGE("spiffs_test", "Failed to open file for reading");
    }

    char line[64];
    fgets(line, sizeof(line), file_obj);    /* 从指定的流中读取数据 */
    fclose(file_obj);
    
    char *pos = strchr(line, '\n'); /* 指针pos指向第一个找到'\n' */
    if (pos)
    {
        *pos = '\0';                /* 将'\n'替换为'\0' */
    }

    ESP_LOGI("spiffs_test", "Read from file: '%s'", line);

    spilcd_show_string(110, 130, 200, 16, 16, line, BLUE);
}

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    
    ret = nvs_flash_init();     /* 初始化NVS */
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    }

    led_init();                 /* LED初始化 */
    my_spi_init();              /* SPI初始化 */
    myiic_init();               /* MYIIC初始化 */
    xl9555_init();              /* XL9555初始化 */
    spilcd_init();              /* SPILCD初始化 */
    ESP_ERROR_CHECK(spiffs_init("storage", DEFAULT_MOUNT_POINT, DEFAULT_FD_NUM));    /* SPIFFS初始化 */

    spilcd_show_string(30, 50,  200, 16, 16, "ESP32-S3", RED);
    spilcd_show_string(30, 70,  200, 16, 16, "SPIFFS TEST", RED);
    spilcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
    spilcd_show_string(30, 130, 200, 16, 16, "Read Text:", RED);

    spiffs_test();              /* SPIFFS测试 */

    while (1)
    {
        LED0_TOGGLE();
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

在 SPIFFS 驱动中,首先初始化并挂载了一个 SPIFFS 分区,然后使用 POSIX 和 C 库 API 写入和读取数据。

可以看到,本实验的应用代码中,在一系列初始化之后,配置 spiffs文件系统各个参数,再

建立一个名为/spiffs/hello.txt 的只写文件, LED 闪烁表明程序正在运行。

38.4 下载验证

在完成编译和烧录操作后,在指定区域新建 hello.txt 文件,然后对这文件进行读写操作。

图 38.4.1 程序运行效果图

相关推荐
苏苏susuus2 小时前
NLP:迁移学习基础讲解
人工智能·自然语言处理·迁移学习
SEO-狼术2 小时前
Oxygen AI Positron Assistant Enterprise
人工智能·.net
国科安芯2 小时前
关于软错误的常见问题解答
单片机·嵌入式硬件·安全·硬件架构·软件工程
dog2502 小时前
时延抖动的物理本质
人工智能·算法·机器学习
jianqiang.xue2 小时前
ESP32-S3入门第九天:摄像头入门与应用
单片机·嵌入式硬件·物联网
文火冰糖的硅基工坊3 小时前
[人工智能-综述-18]:AI重构千行百业的技术架构
大数据·人工智能·重构·架构·系统架构·制造·产业链
用户5191495848453 小时前
Linux发行版切换技术全解析
人工智能·aigc
苏苏susuus3 小时前
NLP:迁移学习关于领域自适应的基础讲解
人工智能·自然语言处理·迁移学习
IT古董3 小时前
【第五章:计算机视觉-项目实战之图像分割实战】2.图像分割实战:人像抠图-(3)数据读取模块搭建
人工智能·计算机视觉