OpenEdv-开源电子网

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

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

[复制链接]

1265

主题

1279

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5430
金钱
5430
注册时间
2019-5-8
在线时间
1413 小时
发表于 12 小时前 | 显示全部楼层 |阅读模式
第三十九章 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. #define DEFAULT_FD_NUM          5           /* 默认最大可打开文件数量 */
  2. #define DEFAULT_MOUNT_POINT     "/spiffs"   /* 文件系统名称 */
复制代码
下面我们再解析my_spiffs.c的程序,看一下初始化函数spiffs_init,代码如下:
  1. /**
  2. * @brief              spiffs初始化
  3. * [url=home.php?mod=space&uid=271674]@param[/url]             partition_label:分区表的分区名称
  4. * @param             mount_point:文件系统关联的文件路径前缀
  5. * @param             max_files:可以同时打开的最大文件数
  6. * @retval            ESP_OK:成功; ESP_FAIL:失败
  7. */
  8. esp_err_t spiffs_init(char *partition_label,
  9. char *mount_point,
  10. size_t max_files)
  11. {
  12.     size_t total = 0;   /* SPIFFS总容量 */
  13.     size_t used = 0;    /* SPIFFS已使用的容量 */
  14.    
  15.     esp_vfs_spiffs_conf_t spiffs_conf = {           /* 配置spiffs文件系统的参数 */
  16.         .base_path              = mount_point,      /* 磁盘路径,比如"0:","1:" */
  17.         .partition_label        = partition_label,  /* 分区表的分区名称 */
  18.         .max_files              = max_files,        /* 最大可同时打开的文件数 */
  19.         .format_if_mount_failed = true,             /* 挂载失败则格式化文件系统 */
  20.     };

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

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

  52.     return ret;
  53. }
复制代码
spiffs_init函数负责初始化和挂载SPIFFS分区,接收分区名称、文件系统路径前缀以及最大可同时打开的文件数作为参数。通过设置配置结构体并调用esp_vfs_spiffs_register来挂载分区,并在成功后获取和记录分区的总容量和已使用容量。如果挂载失败,会输出相应的错误信息。
注销分区挂载函数如下所示。

  1. /**
  2. * @brief              注销spiffs
  3. * @param              partition_label:分区表的分区名称
  4. * @retval             ESP_OK:注销成功; 其他:失败
  5. */
  6. esp_err_t spiffs_deinit(char *partition_label)
  7. {
  8.     return esp_vfs_spiffs_unregister(partition_label);
  9. }
复制代码
spiffs_deinit函数用于注销已挂载的SPIFFS分区,通过esp_vfs_spiffs_unregister来释放资源。
2,CMakeLists.txt文件
本例程的功能实现主要依靠SPIFFS驱动。要在main函数中,成功调用SPIFFS文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件(重点看红色内容),修改如下:

  1. set(src_dirs
  2.             LED
  3.             LCD
  4.             SPIFFS
  5.             )

  6. set(include_dirs
  7.             LED
  8.             LCD
  9.             SPIFFS)

  10. set(requires
  11.             driver
  12.             esp_lcd
  13.             esp_common
  14.             spiffs)

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

  17. component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码
3,main.c驱动代码
在main.c里面编写如下代码。

  1. #define WRITE_DATA              "ALIENTEK ESP32-P4"     /* 写入数据 */

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

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

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

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

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

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

  47. void app_main(void)
  48. {
  49.     esp_err_t ret;

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

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

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

  64.     spiffs_test();      /* SPIFFS测试 */

  65.     while (1)
  66.     {
  67.         LED0_TOGGLE();
  68.         vTaskDelay(pdMS_TO_TICKS(500));
  69.     }
  70. }
复制代码
上述代码实现了一个对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”字符串,写入成功后读取该文件的内容。
回复

使用道具 举报

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

本版积分规则


关闭

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

正点原子公众号

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

GMT+8, 2026-2-27 21:43

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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