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.