OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 31|回复: 0

《ESP32-P4开发指南— V1.0》第三十九章 SPIFFS文件系统

[复制链接]

1244

主题

1258

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5346
金钱
5346
注册时间
2019-5-8
在线时间
1375 小时
发表于 昨天 09:22 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2026-1-21 09:22 编辑

第三十九章 SPIFFS文件系统

1)实验平台:正点原子DNESP32P4开发板

2)章节摘自【正点原子】ESP32-P4开发指南— V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=873309579825

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729


2.jpg

3.png

在现代嵌入式系统中,存储管理是一个至关重要的方面,尤其是在资源有限的环境中。SPIFFS(SPI Flash File System)作为一种专为SPI NOR Flash设备设计的文件系统,提供了一种高效的方式来管理和存储数据。其特性如磨损均衡和文件系统一致性检查,使得SPIFFS成为许多嵌入式应用的理想选择。本章将深入探讨SPIFFS文件系统的结构、性能特性以及使用方法。我们将讨论其扁平文件结构的实现、垃圾收集机制的工作原理以及如何通过工具生成和管理SPIFFS镜像。这些内容将为开发者在使用ESP32-P4等嵌入式平台时提供必要的知识,帮助他们有效地利用SPIFFS文件系统来实现数据存储需求。希望读者能够通过本章的学习,掌握SPIFFS的基本概念及其在实际项目中的应用
39.1 SPIFFS简介
39.2 硬件设计
39.3 程序设计
39.4 下载验证


39.1 SPIFFS简介
SPIFFS(SPI Flash File System)是专为SPI NOR Flash设计的嵌入式文件系统,支持磨损均衡和文件系统一致性检查等功能。用户可通过Posix接口或VFS操作SPIFFS的大部分功能(见下图所示)。与FAT文件系统相比,SPIFFS在RAM资源占用上更为高效,适用于容量小于128 MB的Flash芯片。在第七章中,笔者介绍了分区表的管理与使用,其中storage分区表的存储类型为SPIFFS,允许通过相关文件系统操作函数对自定义的SPIFFS存储区域进行数据存储和数据操作。

第三十九章 SPIFFS文件系统674.png
图39.1.1 VFS虚拟文件系统和真实文件系统的关系

上图中,ESP32-P4支持VFS(虚拟文件系统),通过一个统一接口使应用层可以便捷地访问底层文件系统,如SPIFFS和FAT。VFS为开发者提供了标准的POSIX风格文件操作接口,无需针对不同文件系统编写特定代码。SPIFFS文件系统适合小容量的SPI NOR Flash存储,具备磨损均衡和一致性检查功能,而FAT文件系统则适用于容量较大的存储设备,如SD卡。在ESP32-P4中,VFS实现了文件系统的抽象化,提升了代码的通用性和灵活性。
下面笔者着重讲解SPIFFS文件系统结构和性能与限制等相关知识。
1,SPIFFS文件系统结构
SPIFFS目前不支持目录结构,采用扁平结构。这意味着在挂载于/spiffs时,创建文件的路径如/spiffs/tmp/myfile.txt实际上会生成名为/tmp/myfile.txt的文件,而不是在/spiffs/tmp下创建。
2,SPIFFS性能与限制
SPIFFS并不是实时操作系统,因此写入操作的时间并不固定,这取决于写入数据的大小和文件系统的状态。此外,SPIFFS在稳定使用时,仅建议使用约75%的分区容量。一旦空间不足,垃圾收集器会多次尝试回收可用空间。需要注意的是,如果在文件系统操作期间发生断电,可能会导致SPIFFS损坏,但可以使用esp_spiffs_check函数进行恢复。
3,SPIFFS垃圾收集
垃圾收集器会在每次扫描中尝试释放可用数据块。默认情况下,垃圾收集的最大运行次数为10次,可以通过SPIFFS_GC_MAX_RUNS选项进行配置。写入数据的大小不得超过最大运行次数乘以数据块大小,否则将返回错误。

39.2 硬件设计

39.2.1 程序功能
在storage分区区域新建holle.txt文件,然后对这文件进行读写操作。

39.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)LCD
        4.3/7 寸 ALIENTEK RGBLCD屏
        5/10.1寸ALIENTEK MIPI屏
3 SPIFFS

39.2.3 原理图
SPIFFS器件相关原理图,如下图所示。

第三十九章 SPIFFS文件系统1588.png
图39.2.3.1 Flash硬件原理图

在上图中,我们将16M Flash划分为五个子分区,其中 storage 分区用于作为SPIFFS文件系统的存储区域。本章的实验将在该分区内进行操作。

39.3 程序设计

39.3.1 SPIFFS的IDF驱动
SPIFFS外设驱动位于ESP-IDF的components\spiffs目录。该目录中的include文件夹存放SPIFFS相关的头文件,声明了SPIFFS函数和结构体等;而esp_spiffs.c文件是实现SPIFFS操作函数。要使用SPIFFS功能,必须先导入以下头文件。
  1. #include "esp_spiffs.h"
复制代码
接下来,作者将介绍一些常用的SPIFFS函数,这些函数的描述及其作用如下:
1,注册和挂载SPIFFS esp_vfs_spiffs_register
该函数用于将SPIFFS注册并挂载到VFS中,其函数原型如下:
  1. esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf);
复制代码
函数形参:

1.png
表39.3.1.1 esp_vfs_spiffs_register函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_INVALID_STATE表示已挂载或分区加密。
ESP_ERR_NOT_FOUND表示未找到SPIFFS分区。
ESP_FAIL表示挂载或格式化失败。
conf为指向SPIFFS配置结构体的指针。接下来,笔者将详细介绍esp_vfs_spiffs_conf_t结构体中的各个成员变量,如下代码所示:
  1. typedef struct {
  2.         const char* base_path;      /* 与文件系统关联的文件路径前缀。 */
  3. /* 可选,使用的SPIFFS分区标签。
  4. 如果设置为NULL,将使用第一个子类型为spiffs的分区。 */
  5.         const char* partition_label;   
  6.         size_t max_files;           /* 同时可以打开的最大文件数。 */
  7. /* 如果为true,当文件系统挂载失败时将格式化文件系统。 */
  8.         bool format_if_mount_failed;
  9. } esp_vfs_spiffs_conf_t;
复制代码
结构体用于配置SPIFFS文件系统参数,以下对各个成员做简单介绍。
1)base_path
用于设置文件系统的路径前缀,一般我们设置“0:/”。
2)partition_label
用于指向操作那个分区名称,这个字段是根据项目工程的分区表的子分区名称而定的。
3)max_files
用于配置最大打开文件数量,一般我们配置为5。
4)format_if_mount_failed
若设置为true,则文件系统挂载失败后会格式化子分区。
2,卸载和注销SPIFFS esp_vfs_spiffs_unregister
该函数用于从VFS中卸载并注销SPIFFS,其函数原型如下:
  1. esp_err_t esp_vfs_spiffs_unregister(const char* partition_label);
复制代码
函数形参:

2.png
表39.3.1.3 esp_vfs_spiffs_unregister函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_STATE表示已卸载。
3,检查SPIFFS挂载状态esp_spiffs_mounted
该函数用于检查指定分区是否挂载,其函数原型如下:
  1. bool esp_spiffs_mounted(const char* partition_label);
复制代码
函数形参:

3.png
表39.3.1.4 esp_spiffs_mounted函数形参描述

返回值:
true表示已挂载。
false表示未挂载。
4,格式化SPIFFS esp_spiffs_format
该函数用于格式化指定分区,其函数原型如下:
  1. esp_err_t esp_spiffs_format(const char* partition_label);
复制代码
函数形参:

4.png
表39.3.1.5 esp_spiffs_format函数形参描述

返回值:
ESP_OK表示成功。
ESP_FAIL表示失败。
5,获取SPIFFS信息esp_spiffs_info
该函数用于获取文件系统的大小和已用字节数,其函数原型如下:
  1. esp_err_t esp_spiffs_info(const char* partition_label,
  2. size_t *total_bytes, size_t *used_bytes);
复制代码
函数形参:

5.png
表39.3.1.6 esp_spiffs_info函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_STATE表示未挂载。
6,SPIFFS完整性检查esp_spiffs_check
该函数用于检查文件系统的完整性,其函数原型如下:
  1. esp_err_t esp_spiffs_check(const char* partition_label);
复制代码
函数形参:

6.png
表39.3.1.7 esp_spiffs_check函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_STATE表示未挂载。
ESP_FAIL表示出错。
7,垃圾回收esp_spiffs_gc
该函数用于在SPIFFS分区中执行垃圾回收,至少释放指定字节的空间,其函数原型如下:
  1. esp_err_t esp_spiffs_gc(const char* partition_label, size_t size_to_gc);
复制代码
函数形参:

7.png
表39.3.1.8 esp_spiffs_gc函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_NOT_FINISHED表示无法回收指定大小。
ESP_ERR_INVALID_STATE表示分区未挂载。
ESP_FAIL表示其他错误。

39.3.2 程序流程图

第三十九章 SPIFFS文件系统4475.png
图39.3.2.1 SPIFFS实验程序流程图

39.3.3 程序解析
在30_spiffs例程中,作者在30_spiffs\components\BSP路径下新建SPIFFS文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1,SPIFFS驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SPIFFS驱动源码包括两个文件:my_spiffs.c和my_spiffs.h。
下面先解析my_spiffs.h的程序。对SPIFFS文件数量与磁盘路径做了定义。
  1. <font size="3">#define DEFAULT_FD_NUM          5           /* 默认最大可打开文件数量 */</font>
  2. <font size="3">#define DEFAULT_MOUNT_POINT     "/spiffs"   /* 文件系统名称 */</font>
复制代码
下面我们再解析my_spiffs.c的程序,看一下初始化函数spiffs_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief              spiffs初始化</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]             partition_label:分区表的分区名称</font>
  4. <font size="3"> * @param             mount_point:文件系统关联的文件路径前缀</font>
  5. <font size="3"> * @param             max_files:可以同时打开的最大文件数</font>
  6. <font size="3"> * @retval            ESP_OK:成功; ESP_FAIL:失败</font>
  7. <font size="3"> */</font>
  8. <font size="3">esp_err_t spiffs_init(char *partition_label, </font>
  9. <font size="3">char *mount_point, </font>
  10. <font size="3">size_t max_files)</font>
  11. <font size="3">{</font>
  12. <font size="3">    size_t total = 0;   /* SPIFFS总容量 */</font>
  13. <font size="3">    size_t used = 0;    /* SPIFFS已使用的容量 */</font>
  14. <font size="3">    </font>
  15. <font size="3">    esp_vfs_spiffs_conf_t spiffs_conf = {           /* 配置spiffs文件系统的参数 */</font>
  16. <font size="3">        .base_path              = mount_point,      /* 磁盘路径,比如"0:","1:" */</font>
  17. <font size="3">        .partition_label        = partition_label,  /* 分区表的分区名称 */</font>
  18. <font size="3">        .max_files              = max_files,        /* 最大可同时打开的文件数 */</font>
  19. <font size="3">        .format_if_mount_failed = true,             /* 挂载失败则格式化文件系统 */</font>
  20. <font size="3">    };</font>

  21. <font size="3">/* 初始化和挂载SPIFFS分区 */</font>
  22. <font size="3">esp_err_t ret = esp_vfs_spiffs_register(&spiffs_conf);  </font>
  23. <font size="3">    if (ret != ESP_OK)</font>
  24. <font size="3">    {</font>
  25. <font size="3">        if (ret == ESP_FAIL)</font>
  26. <font size="3">        {</font>
  27. <font size="3">            ESP_LOGE(spiffs_tag, "Failed to mount or format filesystem");</font>
  28. <font size="3">        }</font>
  29. <font size="3">        else if (ret == ESP_ERR_NOT_FOUND)</font>
  30. <font size="3">        {</font>
  31. <font size="3">            ESP_LOGE(spiffs_tag, "Failed to find SPIFFS partition");</font>
  32. <font size="3">        }</font>
  33. <font size="3">        else</font>
  34. <font size="3">        {</font>
  35. <font size="3">            ESP_LOGE(spiffs_tag, "Failed to initialize SPIFFS (%s)", </font>
  36. <font size="3">esp_err_to_name(ret));</font>
  37. <font size="3">        }</font>

  38. <font size="3">        return ESP_FAIL;</font>
  39. <font size="3">    }</font>
  40. <font size="3">/* 获取SPIFFS的总容量和已使用的容量 */</font>
  41. <font size="3">ret = esp_spiffs_info(spiffs_conf.partition_label, &total, &used);  </font>
  42. <font size="3">    if (ret != ESP_OK)</font>
  43. <font size="3">    {</font>
  44. <font size="3">        ESP_LOGI(spiffs_tag, "Failed to get SPIFFS partition information (%s)", </font>
  45. <font size="3">esp_err_to_name(ret));</font>
  46. <font size="3">    }</font>
  47. <font size="3">    else</font>
  48. <font size="3">    {</font>
  49. <font size="3">        ESP_LOGI(spiffs_tag, "Partition size: total: %d Bytes, used: %d Bytes", </font>
  50. <font size="3">total, used);</font>
  51. <font size="3">    }</font>

  52. <font size="3">    return ret;</font>
  53. <font size="3">}</font>
复制代码
spiffs_init函数负责初始化和挂载SPIFFS分区,接收分区名称、文件系统路径前缀以及最大可同时打开的文件数作为参数。通过设置配置结构体并调用esp_vfs_spiffs_register来挂载分区,并在成功后获取和记录分区的总容量和已使用容量。如果挂载失败,会输出相应的错误信息。
注销分区挂载函数如下所示。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief              注销spiffs</font>
  3. <font size="3"> * @param              partition_label:分区表的分区名称</font>
  4. <font size="3"> * @retval             ESP_OK:注销成功; 其他:失败</font>
  5. <font size="3"> */</font>
  6. <font size="3">esp_err_t spiffs_deinit(char *partition_label)</font>
  7. <font size="3">{</font>
  8. <font size="3">    return esp_vfs_spiffs_unregister(partition_label);</font>
  9. <font size="3">}</font>
复制代码
spiffs_deinit函数用于注销已挂载的SPIFFS分区,通过esp_vfs_spiffs_unregister来释放资源。
2,CMakeLists.txt文件
本例程的功能实现主要依靠SPIFFS驱动。要在main函数中,成功调用SPIFFS文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件(重点看红色内容),修改如下:
  1. <font size="3">set(src_dirs</font>
  2. <font size="3">            LED</font>
  3. <font size="3">            LCD</font>
  4. <font size="3">            SPIFFS</font>
  5. <font size="3">            )</font>

  6. <font size="3">set(include_dirs</font>
  7. <font size="3">            LED</font>
  8. <font size="3">            LCD</font>
  9. <font size="3">            SPIFFS)</font>

  10. <font size="3">set(requires</font>
  11. <font size="3">            driver</font>
  12. <font size="3">            esp_lcd</font>
  13. <font size="3">            esp_common</font>
  14. <font size="3">            spiffs)</font>

  15. <font size="3">idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs}</font>
  16. <font size="3">                       REQUIRES ${requires})</font>

  17. <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码
3,main.c驱动代码
在main.c里面编写如下代码。
  1. <font size="3">#define WRITE_DATA              "ALIENTEK ESP32-P4"     /* 写入数据 */</font>

  2. <font size="3">void spiffs_test(void)</font>
  3. <font size="3">{</font>
  4. <font size="3">    ESP_LOGI("spiffs_test", "Opening file");</font>
  5. <font size="3">    /* 建立一个名为/spiffs/hello.txt的只写文件 */</font>
  6. <font size="3">    FILE *file_obj = fopen("/spiffs/hello.txt", "w");   </font>
  7. <font size="3">    if (file_obj == NULL)</font>
  8. <font size="3">    {</font>
  9. <font size="3">        ESP_LOGE("spiffs_test", "Failed to open file for writing");</font>
  10. <font size="3">    }</font>

  11. <font size="3">    fprintf(file_obj, WRITE_DATA);      /* 写入字符 */</font>
  12. <font size="3">    fclose(file_obj);                   /* 关闭文件 */</font>
  13. <font size="3">    ESP_LOGI("spiffs_test", "File written");</font>

  14. <font size="3">    /* 重命名之前检查目标文件是否存在 */</font>
  15. <font size="3">    struct stat st;</font>
  16. <font size="3">    if (stat("/spiffs/foo.txt", &st) == 0)  /* 获取文件信息,获取成功返回0 */</font>
  17. <font size="3">    {   /*  从文件系统中删除一个名称。</font>
  18. <font size="3">            如果名称是文件的最后一个连接,并且没有其它进程将文件打开,</font>
  19. <font size="3">            名称对应的文件会实际被删除。 */</font>
  20. <font size="3">        unlink("/spiffs/foo.txt");</font>
  21. <font size="3">    }</font>

  22. <font size="3">    /* 重命名创建的文件 */</font>
  23. <font size="3">    ESP_LOGI("spiffs_test", "Renaming file");</font>
  24. <font size="3">    if (rename("/spiffs/hello.txt", "/spiffs/foo.txt") != 0)</font>
  25. <font size="3">    {</font>
  26. <font size="3">        ESP_LOGE("spiffs_test", "Rename failed");</font>
  27. <font size="3">    }</font>

  28. <font size="3">    /* 打开重命名的文件并读取 */</font>
  29. <font size="3">    ESP_LOGI("spiffs_test", "Reading file");</font>
  30. <font size="3">    file_obj = fopen("/spiffs/foo.txt", "r");</font>
  31. <font size="3">    if (file_obj == NULL)</font>
  32. <font size="3">    {</font>
  33. <font size="3">        ESP_LOGE("spiffs_test", "Failed to open file for reading");</font>
  34. <font size="3">    }</font>

  35. <font size="3">    char line[64];</font>
  36. <font size="3">    fgets(line, sizeof(line), file_obj);    /* 从指定的流中读取数据 */</font>
  37. <font size="3">    fclose(file_obj);</font>
  38. <font size="3">    </font>
  39. <font size="3">    char *pos = strchr(line, '\n'); /* 指针pos指向第一个找到‘\n’ */</font>
  40. <font size="3">    if (pos)</font>
  41. <font size="3">    {</font>
  42. <font size="3">        *pos = '\0';                /* 将‘\n’替换为‘\0’ */</font>
  43. <font size="3">    }</font>
  44. <font size="3">    ESP_LOGI("spiffs_test", "Read from file: '%s'", line);</font>
  45. <font size="3">    lcd_show_string(110, 130, 200, 16, 16, line, BLUE);</font>
  46. <font size="3">}</font>

  47. <font size="3">void app_main(void)</font>
  48. <font size="3">{</font>
  49. <font size="3">    esp_err_t ret;</font>

  50. <font size="3">    ret = nvs_flash_init();           /* 初始化NVS */</font>
  51. <font size="3">    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
  52. <font size="3">    {</font>
  53. <font size="3">        ESP_ERROR_CHECK(nvs_flash_erase());</font>
  54. <font size="3">        ESP_ERROR_CHECK(nvs_flash_init());</font>
  55. <font size="3">    }</font>

  56. <font size="3">    led_init();                         /* LED初始化 */</font>
  57. <font size="3">    lcd_init();                         /* LCD屏初始化 */</font>
  58. <font size="3">ESP_ERROR_CHECK(spiffs_init("storage", DEFAULT_MOUNT_POINT, </font>
  59. <font size="3">DEFAULT_FD_NUM));    /* SPIFFS初始化 */</font>

  60. <font size="3">    lcd_show_string(30, 50,  200, 16, 16, "ESP32-P4", RED);</font>
  61. <font size="3">    lcd_show_string(30, 70,  200, 16, 16, "SPIFFS TEST", RED);</font>
  62. <font size="3">    lcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);</font>
  63. <font size="3">    lcd_show_string(30, 130, 200, 16, 16, "Read Text:", RED);</font>

  64. <font size="3">    spiffs_test();      /* SPIFFS测试 */</font>

  65. <font size="3">    while (1)</font>
  66. <font size="3">    {</font>
  67. <font size="3">        LED0_TOGGLE();</font>
  68. <font size="3">        vTaskDelay(pdMS_TO_TICKS(500));</font>
  69. <font size="3">    }</font>
  70. <font size="3">}</font>
复制代码
上述代码实现了一个对SPIFFS(SPI Flash File System)的基本测试程序,包含文件的创建、写入、重命名和读取功能。spiffs_test函数首先创建一个名为/spiffs/hello.txt的文件,并写入预定义的数据。如果目标文件/spiffs/foo.txt已存在,它会在重命名之前删除该文件。接着,它将hello.txt重命名为foo.txt,并读取该文件的内容,处理后显示在LCD屏幕上。
主函数app_main初始化非易失性存储(NVS)、LED和LCD屏幕,随后调用spiffs_init初始化SPIFFS文件系统,最后通过调用spiffs_test函数进行文件操作测试,并在一个无限循环中切换LED状态。这段代码展示了如何在ESP32-P4上使用SPIFFS进行基本的文件操作,并通过LCD实时显示结果。

39.4 下载验证
在程序下载完成后,如果SPIFFS文件系统挂载失败,则会自动执行擦除操作,擦除对象为storage分区。此时,屏幕会显示白屏。当擦除操作成功完成后,便可以对storage分区进行正常操作。下图中,笔者向storage分区的foo.txt文件写入“ALIENTEK ESP32-P4”字符串,写入成功后读取该文件的内容。

第三十九章 SPIFFS文件系统10422.png
图39.4.1 向storage分区读取foo.txt文件内容
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-1-22 16:46

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表