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ástroje
→ Partition Scheme
(viz obr. 1).
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 Soubor
→ Příklady
→ SPIFFS
→ SPIFFS_Test
. (viz obr. 2)
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_
.
/* 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 souborwriteFile
– 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 souborurenameFile
– změna názvu souborudeleteFile
– 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ž jetrue
, formátuje SPIFFS (časově náročné); když jefalse
, 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
nafoo.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 souborutest.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.
- Poznámka
- Pokud se při spuštění kódu zobrazí „SPIFFS Mount Failed“, nastavte hodnotu makra
FORMAT_
na hodnotuSPIFFS_ IF_FAILED 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/
). Parametrjméno souboru.txt 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 í funkcefopen
(standardní funkce jazyka C).
Funkce vrací objektFile
. Chcete-li zjistit, zda byl soubor úspěšně otevřen, použijte operátorboolean
. - 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
napathTo
. 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:
• proSeekSet
je pozice nastavena naoffset
bajtů od začátku
• proSeekCur
se aktuální pozice posune ooffset
bajtů
• proSeekEnd
je pozice nastavena naoffset
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.
getLast Write() - 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 není kompatibilní s prostředím Arduino IDE ver. 2, takže dále uváděný postup nelze v této verzi vývojového prostředí použít.
- AKTUALIZACE: (květen 2024)
- Byl vydán nový modul, jmenuje se LittleFS Uploader a je speciálně vytvořen pro prostředí Arduino IDE ver. 2.1.1+.
Zde uvedený postup je tedy platný pro Arduino IDE ver. 1, článek o SPIFFS v prostředí Arduino IDE ver. 2 připravujeme…
Pokud používáte systém Windows, nainstalujte nástroj pro nahrávání systému souborů podle následujících kroků:
- Přejděte na následující stránku https://github.com/
me-no-dev/ a kliknutím na souborarduino-esp32fs-plugin/ releases/ ESP32FS-1.0.zip
stáhněte nabízený soubor. - 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
Soubor
→Vlastnosti
a zkontrolujmeUmí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
- Přejděte tohoto umístění a vytvoříme tam složku
tools
. - Otevřeme stažený ZIP soubor a zkopírujeme z něj složku
ESP32FS
do právě vytvořené složkytools
. Měli bychom mít podobnou strukturu složek: (obr. č. 6)
- 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
:
Pokud používáte jiný operační systém, než zde uvedený Windows, doporučujeme navštívit stránku https://
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ů:
- 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.
- 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.
- 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“) - Abychom nahráli tento soubor do SPIFFS, stačí v prostředí Arduino IDE přejít na
Nástroje
→ESP32 Sketch Data Upload
(viz obr. 9).
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"
void 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).
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.
- 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/