Fyzikální kabinet FyzKAB

Články Moduly ESP32 a ESP32-CAM ESP32 – vlastnosti a metody Kam se soubory? Aneb SPIFFS v ESP32

Kam se soubory? Aneb SPIFFS v ESP32

Základní práce se soubory na ESP32 v oblasti SPIFFS[1]

V tomto textu se podíváme na SPIFFS (SPI Flash File Storage), který se používá pro ukládání větších dat ve formě souborů. Představme si SPIFFS jako velmi malou SD kartu na samotném čipu ESP32. Ve výchozím nastavení je pro SPIFFS přiděleno asi 1,5 MB integrované paměti flash. Můžeme se o tom přesvědčit sami, když v prostředí Arduino IDE otevřeme NástrojePartition Scheme (viz obr. 1).

Partition Scheme

Obrázek č. 1 – zobrazení možností nastavení SPIFFS pro modul ESP32

Můžeme vidět, že je k dispozici několik dalších možností rozdělení. Nicméně nepouštějme se hned do „větších akcí“ a nechme nastavení ve výchozí konfiguraci. Změna schématu oddílů stejně nebude pro většinu vašich aplikací potřeba. Všechny ukázky v tomto tutoriálu budou dobře fungovat s výchozím schématem oddílů.

Nyní se podíváme na proces vytváření, úpravu, čtení a mazání souborů ze SPIFFS – nejlépe na konkrétním příkladu.

Ukázkový kód

Na úvodní ukázku využijeme nabízený příklad kódu. Přejděte na SouborPříkladySPIFFSSPIFFS_Test. (viz obr. 2)

ukazkovy kod SPIFFS

Obrázek č. 2 – otevření ukázkového kódu pro SPIFFS

Tento kód je ideální pro pochopení všech operací se soubory, které jsou možné pomocí SPIFFS provádět.

Kód začíná připojením dvou knihoven: FS.h a SPIFFS.h. Knihovna SPIFFS slouží pro práci s pamětí SPIFFS, knihovna FS (File System) je knihovna pro práci se soubory.

#include FS.h
#include SPIFFS.h


Dále v kódu vidíme definici makra FORMAT_SPIFFS_IF_FAILED.

/* Systém SPIFFS je třeba naformátovat pouze při prvním spuštění.
nebo použít zásuvný modul SPIFFS k vytvoření oddílu.
https://github.com/me-no-dev/arduino-esp32fs-plugin */

#define FORMAT_SPIFFS_IF_FAILED true


Připojený komentáři naznačuje, že je třeba paměťový prostor SPIFF před prvním použitím naformátovat (při prvním spuštění programu testu). To znamená, že po prvním spuštění můžete nastavit hodnotu tohoto makra na false. Formátování SPIFFS vyžaduje čas a není nutné jej provádět při každém spuštění kódu.

Dobrou praxí, kterou lidé často používají, je mít samostatný kód pro formátování SPIFFS, který nahrají před flashováním hlavního kódu. Hlavní kód pak neobsahuje příkaz format.

V tomto příkladu však bylo pro úplnost toto makro zachováno a je nastaveno na výchozí hodnotu true.

Dále můžeme vidět, že pro různé operace souborového systému je definována řada funkcí:

  • listDir – vypíše seznam všech adresářů
  • readFile – načte konkrétního soubor
  • writeFile – zápis do zadaného souboru (tím se přepíše obsah, který se již v souboru nachází)
  • appendFile – připojení zadaných dat k obsahu souboru
  • renameFile – změna názvu souboru
  • deleteFile – smazání souboru

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");
    }
}


Všimněme si, že všechny výše uvedené funkce nevyžadují název souboru. Požadují úplnou cestu k souboru. To je tím, že v případě SPIFFS je skutečně jedná o souborový systém. V tomto prostoru můžeme mít adresáře, podadresáře a soubory. Proto modul ESP32 potřebuje znát úplnou cestu k souboru, se kterým chcete pracovat (stejně jako bychom pracovali na disku nebo třeba na SD kartě).

Následuje funkce, která není přesně funkcí pro práci se soubory – testFileIO. Jedná se spíše o funkci časového „benchmarkingu“ (hodnotící test).

Funkce testFileIO dělá následující:

  • Zapíše asi 1 MB (2048 * 512 bajtů) dat do vámi poskytnuté cesty souboru a změří dobu zápisu
  • Načte stejný soubor a změří dobu čtení

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");
    }
}


Všimněte si, že pole buf není nikdy inicializováno žádnou hodnotou. Je možné, že do souboru zapisujeme nesmyslné bajty. Na tom nezáleží, protože účelem funkce je měřit dobu zápisu a dobu čtení.

Jakmile máme všechny naše funkce definovány, přejdeme k nastavení, kde je prezentováno volání každé z těchto funkcí.

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" );
}


Tato část programu provádí v podstatě následující:

  • Nejprve inicializuje SPIFFS pomocí SPIFFS.begin(). Zde se používá makro definované na začátku. Když je true, formátuje SPIFFS (časově náročné); když je false, inicializuje SPIFFS bez formátování.
  • Poté vypíše seznam všech adresářů na kořenové úrovni. Všimněte si, že jsme zadali úrovně jako 0. Proto neuvádíme podadresáře v adresářích. Vnoření můžete zvýšit zvýšením argumentu úrovní (třetí parametr funkce).
  • Poté zapíše text "Hello " do souboru hello.txt v kořenovém adresáři. (pokud soubor neexistuje, vytvoří se)
  • Následuje připojení textu "World!\r\n" do souboru hello.txt.
  • Poté se soubor hello.txt načte a obsah vypíše na sériový port (Můžeme vidět v sériovém monitoru prostředí Arduino IDE)
  • Následně dojde k přejmenování souboru hello.txt na foo.txt.
  • Opět se načte soubor foo.txt, aby se zjistilo, zda přejmenování fungovalo. Měli bychom vidět vytištěné "Hello World!", protože to je to, co jsme před tím do souboru uložili.
  • Dalším krokem je odstranění souboru foo.txt.
  • Poté provede rutinu testFileIO na novém souboru test.txt
  • Jakmile je rutina dokončena, odstraní se soubor test.txt.

A je to! Tento příklad kódu velmi pěkně ukazuje a testuje všechny funkce, které můžete chtít použít s paměťovým prostorem SPIFFS provádět. Pochopitelně pro praktické využití, například jako jednoduchý souborový systém řídicího webového serveru, můžeme tento výchozí kód libovolně upravit a hrát si s různými dalšími funkcemi.

Ještě je třeba dodat, že jelikož tento kód proběhne pouze jednou a skončí, je je jasné, že hlavní smyčka loop() je prázdná.

void loop() {
}


Následující obrázek č. 3 ukazuje ukázku výstupu (zobrazen na sériovém monitoru), jak bude vypadat po spuštění ukázkového kódu.

esp32 spiffs - vystup

Obrázek č. 3 – Ukázka výstupu ukázkového programu na sériovém monitoru prostředí Arduino IDE

Poznámka
Pokud se při spuštění kódu zobrazí „SPIFFS Mount Failed“, nastavte hodnotu makra FORMAT_SPIFFS_IF_FAILED na hodnotu false a zkuste to znovu.

Knihovna SPIFFS

Odkaz na aktuální knihovnu SPIFFS: Knihovna ESP32 Arduino SPIFFS.

Knihovna SPIFFS.h umožňuje nejen přístup a správu paměťové oblasti určené pro SPIFFS, ale umí i pracovat přímo se soubory. V předešlém příkladu práci se soubory zajišťovala knihovna FS.h. Zkusme se podíváme na přehled příkazů knihovny SPIFFS, pak si můžeme (už ale jen za „domácí úkol“) zkusit předešlý kód přepsat jen s SPIFFS knihovnou, tedy zcela bez užití knihovny FS. 😉

Příkazy knihovny SPIFFS:[2]

Existuje několik standardních příkazů, které můžete s tímto souborovým systémem použít.

SPIFFS.begin()
Tato metoda připojí souborový systém SPIFFS a musí být zavolána před použitím jakéhokoli jiného rozhraní FS API.
Vrací true, pokud byl souborový systém úspěšně připojen, false v opačném případě.
SPIFFS.format()
Formátuje souborový systém.
Vrací true, pokud bylo formátování úspěšné.
SPIFFS.open(path, mode)
Otevře zadaný soubor (zadaný parametrem path), cesta by měla být absolutní cestou začínající lomítkem (např. /dir/jméno souboru.txt). Parametr mode je řetězec určující režim přístupu – jedna z možností "r" (read), "w" (write), "a" (append) (čtení/zápis/připojení). Význam těchto režimů je stejný jako u í funkce fopen (standardní funkce jazyka C).
Funkce vrací objekt File. Chcete-li zjistit, zda byl soubor úspěšně otevřen, použijte operátor boolean.
SPIFFS.exists(path)
Funkce ověří existenci souboru zadaného cestou path.
Vrací true, pokud soubor s danou cestou existuje, false v opačném případě.
SPIFFS.remove(path)
Odstraní soubor zadaný absolutní cestou path.
Vrací true, pokud byl soubor úspěšně odstraněn.
SPIFFS.rename(pathFrom, pathTo)
Přejmenuje soubor z pathFrom na pathTo. Cesty musí být absolutní.
Vrací true, pokud byl soubor úspěšně přejmenován.
SPIFFS.totalBytes()
Vrátí celkový počet bajtů povolených na SPIFFS.
Vrací počet bajtů.
SPIFFS.usedBytes()
Vrátí celkový počet použitých bajtů povolených na SPIFFS.
Vrací počet bajtů.
file.seek(offset, mode)
Tato funkce se chová jako funkce fseek v jazyce C. V závislosti na hodnotě mode posouvá aktuální pozici v souboru následujícím způsobem:
• pro SeekSet je pozice nastavena na offset bajtů od začátku
• pro SeekCur se aktuální pozice posune o offset bajtů
• pro SeekEnd je pozice nastavena na offset bajtů od konce souboru
Vrací true, pokud byla pozice úspěšně nastavena.
file.size()
Vrací velikost souboru v bajtech.
file.name()
Vrací název souboru jako const char*.
file.close()
Zavře aktuálně otevřený soubor.
file.getLastWrite()
Vrátí „epochální“ čas posledního zápisu (pro správu data použijte interní čas).

Vložení souborů do oblasti SPIFFS již při kompilaci kódu[3]

V předchozím byla zmínka o možném využití paměťového prostoru SPIFFS pro účely webového serveru. To ale vyžaduje již na samém začátku vložit již připravené soubory potřebné pro webový server (webové stránky, soubory JavaScpriptu, CSS styly, obrázky… apod.). Z předchozí ukázky již umíme, jak soubory vytvořit, načíst, smazat… apod., ale nikoliv jak konkrétní již připravené soubory do SPIFFS vložit. Na to se podíváme nyní.

Pro tuto operaci využijeme zásuvný modul Filesystem Uploader pro prostředí Arduino IDE.

UPOZORNĚNÍ:
Zásuvný modul Filesystem Uploader zatím není kompatibilní s prostředí, Arduino IDE ver. 2.0, takže dále uváděný postup nelze v této verzi vývojového prostředí použít.

Pokud používáte systém Windows, nainstalujte nástroj pro nahrávání systému souborů podle následujících kroků:

  1. Přejděte na následující stránku https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/ a kliknutím na soubor ESP32FS-1.0.zip stáhněte nabízený soubor.
  2. Najděte umístění svého Sketchbooku (místo pro ukládání projektů prostředí Arduino IDE). Konkrétně v prostředí Arduino IDE přejdeme na SouborVlastnosti a zkontrolujme Umístění projektů. V tomto konkrétním případě to je na následující cestě: (viz obr. 5) C:\Users\FyzKab\Documents\Arduino.

cesta ke Sketchbooku

Obrázek č. 5 – Nalezení tzv. umístění Sketchbooku v prostředí Arduino IDE

  1. Přejděte tohoto umístění a vytvoříme tam složku tools.
  2. Otevřeme stažený ZIP soubor a zkopírujeme z něj složku ESP32FS do právě vytvořené složky tools. Měli bychom mít podobnou strukturu složek: (obr. č. 6)

slozka projektu

Obrázek č. 6 – Rozbalení staženého ZIP souboru do umístění Sketchbooku (složka projektů)

  1. Nyní restartujte své prostředí Arduino IDE.

Chceme-li zkontrolovat, zda byl právě přidaný zásuvný modul úspěšně nainstalován, otevřeme své prostředí Arduino IDE, kde vybereme desku ESP32 a přejděte na Nástroje. Zkontrolujte, se nám zde přidala možnost ESP32 Sketch Data Upload:

ESP32 Sketch Data Upload

Obrázek č. 7 – Úspěšné přidání zásuvného modulu do prostředí Arduino IDE

Pokud používáte jiný operační systém, než zde uvedený Windows, doporučujeme navštívit stránku https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/, kde je například uveden návod pro operační systém MacOS X.

Nahrávání souborů pomocí Filesystem Uploader

Pokud chceme nahrát soubory do souborového systému ESP32, musíme postupovat podle následujících pokynů:

  1. Vytvoříme požadovaný program v prostředí Arduino IDE a uložíme jej. Pro nyní pro naše čistě demonstrační účely můžeme klidně uložit prázdný projekt.
  2. Otevřeme složku projektu. Můžete přejít na Projekt > Zobraz adresář s projekty. Měla by se otevřít složka, ve které je právě uložená skica uložena.

zobrazit slozku projektu

Obrázek č. 8 – Otevření složky projektu, kam uložíme doplňkové soubory

  1. V této složce vytvoříme novou složku s názvem data. Uvnitř složky data je místo, kam budeme umisťovat soubory, které chcete chtít uložit do souborového systému ESP32. Jako příklad vytvoříme např. textový soubor test_example.txt s nějakým textem uvnitř.
    (např. „A sample text file to be uploaded to SPIFFS of ESP32“)
  2. Abychom nahráli tento soubor do SPIFFS, stačí v prostředí Arduino IDE přejít na NástrojeESP32 Sketch Data Upload (viz obr. 9).

upload na SPIFFS

Obrázek č. 9 – Upload souboru do oblasti SPIFFS na modulu ESP32

Poznámky

  • Každé nahrání soborů přepíše vše, co jste již uložili do souborového systému.
  • Na některých vývojových deskách ESP32 musíte stisknout vestavěné tlačítko BOOT.
    Poznáme to podle toho, že uvidíte zprávu: Connecting ……____……
  • Po úspěšném nahrání souborů se zobrazí zpráva: SPIFFS Image Uploaded.

Nyní si to zkusíme na konkrétním příkladu. Nahrajeme následující kód na desku ESP32:

#include "SPIFFS.h"

setup() {
    Serial.begin(115200);
    if(!SPIFFS.begin(true)) {
       Serial.println("Pri pripojovani systemu SPIFFS doslo k chybe.");
       return;
    }
    File file = SPIFFS.open("/test_example.txt");
    if(!file) {
       Serial.println("Nepodarilo se otevrit soubor pro cteni");
       return;
    }
    Serial.println("Obsah souboru:");
    while(file.available()) {
       Serial.write(file.read());
    }

    file.close();
}

void loop() {
}


Pochopitelně před přeložením programu, nesmíme zapomenout zapsat soubor test_example.txt do SPIFFS!

Po nahrání kódy do modulu ESP32 otevřeme sériový monitor s přenosovou rychlostí 115200. Po stisknutí reset tlačítka ENABLE/RST na modulu ESP32 měli bychom v okně sériového monitoru prostředí ESP32 vidět vypsaný obsah našeho TXT souboru (viz obr. 4. 10).

vypis souboru

Obrázek č. 10 – Výpis obsahu souboru uloženého na SPIFSS v modulu ESP32

Použití pluginu pro nahrávání souborového systému je jedním z nejjednodušších způsobů, jak nahrát soubory do souborového systému SPIFFS modulu ESP32. Jak již bylo naznačeno, asi nejčastějším využitím může být vytvoření jednoduchého webového serveru pomocí souborů HTML a CSS uložených právě v souborovém systému.

pouziti ESP32 SPIFFS

Obrázek č. 11 – Schéma možného využití uložení souboru v oblasti SPIFSS v modulu ESP32



zdroje:
[1] SPIFFS in ESP32 – https://www.tutorialspoint.com/esp32_for_iot/esp32_for_iot_spiffs_storage.htm
[2] ESP32: integrated SPIFFS FileSystem (Part 2) – https://www.mischianti.org/2020/06/04/esp32-integrated-spiffs-filesystem-part-2/
[3] Install ESP32 Filesystem Uploader in Arduino IDE – https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/
Reklama:
UPOZORNĚNÍ:
Nesouhlasíme s vyřazením Newtonových zákonů, Ohmova zákona a zákona zachování energie z učiva fyziky základních škol v České republice!