ESP32中的SPIFFS



在上一章中,我们了解了首选项作为一种在非易失性存储器中存储数据的方式,并了解了如何使用它们来存储键值对。在本节中,我们将介绍SPIFFS(SPI闪存文件存储),它用于以文件形式存储更大的数据。可以将SPIFFS视为ESP32芯片本身上的一个非常小的SD卡。默认情况下,大约1.5 MB的片上闪存分配给SPIFFS。您可以通过“工具”->“分区方案”来查看。

ESP32 Partition Scheme

您可以看到还有其他几个分区选项可用。但是,现在我们先不要深入研究。对于大多数应用程序,更改分区方案都是不需要的。本教程中的所有章节都可以在默认分区方案下正常工作。

现在,让我们通过一个示例了解创建、修改、读取和从SPIFFS删除文件的过程。

代码演练

我们将再次使用提供的示例代码。转到“文件”->“示例”->“SPIFFS”->“SPIFFS_Test”。此代码非常适合了解SPIFFS可能进行的所有文件操作。它也可以在GitHub上找到。

我们从包含两个库开始:FS.h和SPIFFS.h。FS代表文件系统。

#include "FS.h"
#include "SPIFFS.h"

接下来,您将看到一个宏定义FORMAT_SPIFFS_IF_FAILED。有一个关联的注释建议您仅在第一次运行测试时才需要格式化SPIFFS。这意味着您可以在第一次运行后将此宏的值设置为false。格式化SPIFFS需要时间,并且不需要每次运行代码时都执行。因此,人们采用的做法是为格式化SPIFFS编写单独的代码,并在刷新主代码之前刷新它。主代码不包含格式化命令。但是,在本示例中,为了完整起见,此宏已保留为true。

/* You only need to format SPIFFS the first time you run a
   test or else use the SPIFFS plugin to create a partition
   https://github.com/me−no−dev/arduino−esp32fs−plugin */
#define FORMAT_SPIFFS_IF_FAILED true

接下来,您可以看到已为不同的文件系统操作定义了许多函数。它们是:

  • listDir - 列出所有目录

  • readFile - 读取特定文件

  • writeFile - 写入文件(这会覆盖文件中原有的内容)

  • appendFile - 将内容追加到文件(当您想要添加到现有内容而不是覆盖它时使用)

  • renameFile - 更改文件名

  • deleteFile - 删除文件

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
   Serial.printf("Listing directory: %s\r\n", dirname);

   File root = fs.open(dirname);
   if(!root){
      Serial.println("− failed to open directory");
      return;
   }
   if(!root.isDirectory()){
      Serial.println(" − not a directory");
      return;
   }

   File file = root.openNextFile();
   while(file){
      if(file.isDirectory()){
         Serial.print("  DIR : ");
         Serial.println(file.name());
         if(levels){
            listDir(fs, file.name(), levels -1);
         }
      } else {
         Serial.print("  FILE: ");
         Serial.print(file.name());
         Serial.print("\tSIZE: ");
         Serial.println(file.size());
      }
      file = root.openNextFile();
   }
}

void readFile(fs::FS &fs, const char * path){
   Serial.printf("Reading file: %s\r\n", path);

   File file = fs.open(path);
   if(!file || file.isDirectory()){
       Serial.println("− failed to open file for reading");
       return;
   }

   Serial.println("− read from file:");
   while(file.available()){
      Serial.write(file.read());
   }
}

void writeFile(fs::FS &fs, const char * path, const char * message){
   Serial.printf("Writing file: %s\r\n", path);

   File file = fs.open(path, FILE_WRITE);
   if(!file){
      Serial.println("− failed to open file for writing");
      return;
   }
   if(file.print(message)){
      Serial.println("− file written");
   }else {
      Serial.println("− frite failed");
   }
}

void appendFile(fs::FS &fs, const char * path, const char * message){
   Serial.printf("Appending to file: %s\r\n", path);

   File file = fs.open(path, FILE_APPEND);
   if(!file){
      Serial.println("− failed to open file for appending");
      return;
   }
   if(file.print(message)){
      Serial.println("− message appended");
   } else {
      Serial.println("− append failed");
   }
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
   Serial.printf("Renaming file %s to %s\r\n", path1, path2);
   if (fs.rename(path1, path2)) {
      Serial.println("− file renamed");
   } else {
      Serial.println("− rename failed");
   }
}

void deleteFile(fs::FS &fs, const char * path){
   Serial.printf("Deleting file: %s\r\n", path);
   if(fs.remove(path)){
      Serial.println("− file deleted");
   } else {
      Serial.println("− delete failed");
   }
}

请注意,以上所有函数都没有要求提供文件名。它们要求提供完整的文件路径。因为这是一个文件系统。您可以在这些子目录中拥有目录、子目录和文件。因此,ESP32需要知道您要操作的文件的完整路径。

接下来是一个不完全是文件操作函数的函数 - testFileIO。这更像是一个时间基准测试函数。它执行以下操作:

  • 将大约1 MB(2048 * 512字节)的数据写入您提供的文件路径,并测量写入时间

  • 读取相同的文件并测量读取时间

void testFileIO(fs::FS &fs, const char * path){
   Serial.printf("Testing file I/O with %s\r\n", path);

   static uint8_t buf[512];
   size_t len = 0;
   File file = fs.open(path, FILE_WRITE);
   if(!file){
      Serial.println("− failed to open file for writing");
      return;
   }

   size_t i;
   Serial.print("− writing" );
   uint32_t start = millis();
   for(i=0; i<2048; i++){
      if ((i & 0x001F) == 0x001F){
         Serial.print(".");
      }
      file.write(buf, 512);
   }
   Serial.println("");
   uint32_t end = millis() − start;
   Serial.printf(" − %u bytes written in %u ms\r\n", 2048 * 512, end);
   file.close();

   file = fs.open(path);
   start = millis();
   end = start;
   i = 0;
   if(file && !file.isDirectory()){
      len = file.size();
         size_t flen = len;
         start = millis();
         Serial.print("− reading" );
         while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            if ((i++ & 0x001F) == 0x001F){
              Serial.print(".");
            }
            len −= toRead;
         }
      Serial.println("");
      end = millis() - start;
      Serial.printf("- %u bytes read in %u ms\r\n", flen, end);
      file.close();
   } else {
      Serial.println("- failed to open file for reading");
   }
}

请注意,buf数组从未初始化任何值。我们很可能会将垃圾字节写入文件。这没关系,因为该函数的目的是测量写入时间和读取时间。

在定义完函数后,我们将继续进行设置,其中显示了每个函数的调用。

void setup(){
   Serial.begin(115200);
   if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
      Serial.println("SPIFFS Mount Failed");
      return;
   }
   listDir(SPIFFS, "/", 0);
   writeFile(SPIFFS, "/hello.txt", "Hello ");
   appendFile(SPIFFS, "/hello.txt", "World!\r\n");
   readFile(SPIFFS, "/hello.txt");
   renameFile(SPIFFS, "/hello.txt", "/foo.txt");
   readFile(SPIFFS, "/foo.txt");
   deleteFile(SPIFFS, "/foo.txt");
   testFileIO(SPIFFS, "/test.txt");
   deleteFile(SPIFFS, "/test.txt");
   Serial.println( "Test complete" );
}

设置基本上执行以下操作:

  • 它首先使用SPIFFS.begin()初始化SPIFFS。此处使用了开头定义的宏。为true时,它会格式化SPIFFS(耗时);为false时,它会在不格式化的前提下初始化SPIFFS。

  • 然后,它列出根级别上的所有目录。请注意,我们已将级别指定为0。因此,我们没有列出目录内的子目录。您可以通过增加levels参数来增加嵌套级别。

  • 然后,它将“Hello”写入根目录中的hello.txt文件。(如果文件不存在,它将被创建)

  • 然后,它读取回hello.txt

  • 然后,它将hello.txt重命名为foo.txt

  • 然后,它读取foo.txt以查看重命名是否成功。您应该看到打印出“Hello”,因为这就是存储在文件中的内容。

  • 然后,它删除foo.txt

  • 然后,它在新的文件test.txt上执行testFileIO例程

  • 执行完例程后,它删除test.txt

就是这样。此示例代码很好地列出了并测试了您可能想要与SPIFFS一起使用的所有函数。您可以继续修改此代码,并尝试不同的函数。

由于我们不想在这里执行任何重复活动,因此循环为空。

void loop(){
}

串行监视器中显示的输出可能类似于下图:

ESP32 SPIFFS Sketch Output

注意 - 如果在运行草图时出现“SPIFFS Mount Failed”,请将FORMAT_SPIFFS_IF_FAILED的值设置为false,然后重试。

参考

广告

© . All rights reserved.