ESP32 WROOM32-Modul

LittleFS – Dateisystem für den ESP32

LittleFS ist ein Dateisystem für den Flash-Speicher verschiedener Mikrocontroller. Auf den ESP-Controllern soll es die Nachfolge des weit verbreiteten SPIFFS antreten. Es bietet einige Vorteile wie echte Verzeichnisse, höhere Geschwindigkeit und bessere Stabilität, insbes. wenn das Dateisystem relativ voll ist. Ein Nachteil von LittleFS ist der höhere Platzbedarf kleiner Dateien – die minimale Sektorgröße ist 4 KB statt 256 Bytes bei SPIFFS. LittleFS speichert aber mehrere kleine Dateien in einem einzigen Sektor, um den Platzverbrauch zu reduzieren.

Wegen des Umfangs habe ich den Beitrag in zwei Teile aufgeteilt. Der erste Teil liefert Grundlagen zu LittleFS im Vergleich mit SPIFFS. Die LittleFS-Bibliotkek wird installiert und zwei Beispielprogramme zeigen die Nutzung des Flash-Dateisystems auf dem ESP32.

Der zweite Teil enthält eine ausführliche Anleitung zur Installation der Dateisystem-Tools, mit denen man in der Arduino-IDE ein Dateisystem so vorbereiten kann, dass es vorinstallierte Dateien enthält. Das Image (Abbild) des vorbelegten Dateisystems wird dann in den Flash-Speicher des ESP32 hochgeladen. Die Installation ist optional – man kommt auch ohne die Tools aus, wenn man kein Dateisystem mit vorinstallierten Dateien benötigt.

LittleFS als Nachfolger von SPIFFS

LittleFS Projekt-„Logo”
„Logo” des LittleFS-Projekts

Auf dem ESP8266 ist LittleFS bereits Bestandteil des Cores, also der Systembibliotheken. Beim ESP32 ist das in Arbeit: In der espressif-Entwicklungsumgebung ESP-IDF ist LittleFS seit einiger Zeit enthalten. Im aktuellen ESP32-Paket für die Arduino-IDE (Version 1.0.6) ist es zwar noch nicht integriert, aber in den Entwicklerzweig (Version 2.0.0) wurde es schon aufgenommen und wird dann in neuen Versionen standardmäßig mitinstalliert.[1]Das Arduino-Paket für den ESP32 befindet sich bei Github: github.com/espressif/arduino-esp32; wer die Entwicklerversion ausprobieren möchte, muss in der Boardverwaltung der Arduino-IDE folgende URL … Continue reading

In der ESP8266-Dokumentation ist dem Kapitel zu SPIFFS ein Hinweis vorangestellt, der empfiehlt, SPIFFS nicht weiter zu verwenden und auf LittleFS umzusteigen, weil SPIFFS schon seit einiger Zeit nicht mehr weiterentwickelt wird (die letzte stabile Version 0.3.7 ist von Juli 2017).

SPIFFS Deprecation Warning
SPIFFS is currently deprecated and may be removed in future releases of the core. Please consider moving your code to LittleFS. SPIFFS is not actively supported anymore by the upstream developer, while LittleFS is under active development, supports real directories, and is many times faster for most operations.[2]Eigene Übersetzung: »SPIFFS wird derzeit als veraltet betrachtet und wird möglicherweise in zukünftigen Versionen des Kerns entfernt. Bitte denken Sie darüber nach, Ihren Code auf LittleFS … Continue reading

Die APIs beider Dateisysteme unterscheiden sich nicht, d.h. die Funktionsaufrufe, die man als Programmierer nutzt, sollen die gleichen sein – man muss nur bei allen Funktionsaufrufen die Angabe „SPIFFS” in „LITTLEFS” ändern. Da es noch keine offizielle LittleFS-Dokumentation gibt, verweisen die Entwickler auf die SPIFFS-Dokumentation:

See the official ESP-IDF SPIFFS documentation, basically all the functionality is the same; just replace spiffs with littlefs in all function calls.
Also see the comments in include/esp_littlefs.h [3]Komplettes Zitat von github.com/joltwallet/esp_littlefs (die Entwickler der LittleFS-Bibliothek für die ESP-IDF, die Grundlage der aktuellen ESP32-Bibliothek für die Arduino-IDE ist): »See … Continue reading

Ohne größere Änderungen am eigenen Code soll die Umstellung von SPIFFS auf LittleFS auch mit folgenden Anweisungen an den (Pre-)Compiler gehen (Quelle):

#define USE_LittleFS
 
#include <FS.h>
#ifdef USE_LittleFS
  #define SPIFFS LITTLEFS
  #include <LITTLEFS.h> 
#else
  #include <SPIFFS.h>
#endif

Ob das in der Praxis tatsächlich so einfach ist, muss sich zeigen. Gerade das Vorhandensein von „echten” Verzeichnissen könnte in dem einen oder anderen Projekt Änderungen an der Programmlogik erforderlich machen.

Wenn man – wie ich – relativ neu mit eigenen Projekten startet, ist es vermutlich sinnvoll, gleich mit LittleFS statt SPIFFS einzusteigen. Da aber viel Beispielcode im Web auf SPIFFS beruht, muss man gucken, was sich auf LittleFS umstellen lässt und wo man vielleicht doch besser bei SPIFFS bleibt, wenn man ein fertiges Projekt nutzen will oder auf eine Bibliothek nicht verzichten kann, die SPIFFS als Dateisystem voraussetzt. Wegen der Ähnlichkeit der APIs dürfte es aber kein Problem sein, den Code des jeweils anderen Systems zumindest nachvollziehen zu können.

Performance und Overhead der verschiedenen Dateisysteme

Es gibt anders als bei SPIFFS bei LittleFS (theoretisch) keine Begrenzung der Anzahl gleichzeitig geöffneter Dateien.[4]Siehe github.com/joltwallet/esp_littlefs#documentation: »max_files field doesn’t exist since we removed the file limit«. In der Header-Datei LITTLEFS.h wird zwar beim Aufruf der … Continue reading
Außerdem verwendet LittleFS eine Dateistruktur mit echten Verzeichnissen. SPIFFS dagegen ist ein flaches Dateisystem – die Dateinamen sehen zwar so aus, als ob sie Directories enthielten, z.B. /dir1/dir2/datei.txt, tatsächlich sind die Schrägstriche aber Bestandteile des Dateinamens.

In einer Diskussion zur LittleFS-Bibliothek findet man interessante Vergleiche der verschiedenen Dateisysteme bzgl. der Anzahl Dateien im Dateisystem und Overhead bei verschiedenen Dateigrößen. U.a. erfährt man dort, dass LittleFS mehrere kleine Dateien (bis 512 Bytes) in einem einzigen Block speichern kann; erst ab 513 Bytes belegt jede Datei einen eigenen Block. So ist es möglich, in LittlerFS deutlich mehr (kleine) Dateien zu speichern, als das Dateisystem Blöcke hat.

Wie LittleFS genau arbeitet, wird in der Beschreibung des Designs erläutert, zur Speicherung kleiner Dateien speziell im Bereich Files (kleine Dateien werden direkt zusammen mit den Verwaltungsinformationen gespeichert und belegen keine eigenen Datenblöcke).

Eine Diskussion im Arduino-Forum beschreibt SPIFFS als „Speicher-Katastrophe”, gerade wenn das Dateisystem zu mehr als der Hälfte gefüllt ist. Dort gibt es auch Grafiken, wie bei SPIFFS die Zugriffszeiten bei steigendem Füllgrad des Dateisystems deutlich ansteigen und wie sich FatFS und LittleFS im Vergleich dazu verhalten.

Einige Performance-Vergleiche der Dateisysteme gibt es auf der Seite der LittleFS-Bibliothek für die ESP-IDF.

Installation der LittleFS-Bibliothek

Da LittleFS noch kein Standarddateisystem für den ESP32 ist, muss man für die aktuelle Version des Arduino-ESP32-Pakets (Version 1.0.6; ebenso die Vorversionen) die LittleFS-Bibliothek und bei Bedarf ein paar Tools selbst installieren.[5]Welche Version man installiert hat, kann man in der Boardverwaltung überprüfen: Menü Werkzeuge → Board "xyz" → Boardverwalter…, dort im Suchfeld „esp” eingeben.Für … Continue reading In zukünftigen Versionen (ab 2.0) soll die LittleFS-Unterstützung in den ESP32-Core für Arduino integriert sein und man erspart sich die manuelle Installation.

Die LittleFS-Bibliothek installiert man am einfachsten mit der Arduino-Bibliotheksverwaltung (Menü Werkzeuge → Bibliotheken verwalten…); als Suchbegriff kann man „littlefs” angeben und findet dann relativ weit unten in der Ergebnisliste den Eintrag „LittleFS_esp32”.
Will man sie manuell installieren, findet man sie auf Github unter der Adresse github.com/lorol/LITTLEFS.
(Ausführliche Hinweise zur Installation von Bibliotheken gibt der Beitrag Arduino-IDE: Bibliotheken verwalten.)

Partitionschemata für den ESP32 in der Arduino-IDE
Partitionschemata für den ESP32 in der Arduino-IDE

Standard-Dateisystem ist für den ESP32 SPIFFS; auch FatFS wird schon länger unterstützt, wie man an den verschiedenen Möglichkeiten zur Partitionierung des Flash im Menü Werkzeuge → Partition Scheme "xyz" sieht, wenn als Board ein ESP32-Modell ausgewählt ist.

LittleFS verwendet die gleichen Partitionierungsschemata wie SPIFFS. Man muss also keine neuen Schemata erstellen, sondern nutzt die bereits für SPIFFS vorhandenen mit.

Ein erster Test: Belegung des Dateisystems anzeigen

Das folgende kleine Programm versucht, ein (bestehendes) LittleFS-Dateisystem zu öffnen. Wird kein Dateisystem gefunden, erfolgt ein zweiter Versuch – diesmal mit der Option, das Dateisystem neu zu formatieren. Schlägt auch das fehl, wird das Programm beendet. Andernfalls wird die Belegung angezeigt (freie und belegte Bytes sowie ein Listing der evtl. vorhandenen Dateien).

Normalerweise kann man das Dateisystem gleich mit der Option öffnen, es bei Bedarf zu formatieren, wenn man weiß, dass das Programm es evtl. mit einer unformatierten Datenpartition zu tun bekommt (dabei wird ein mglw. vorhandenes anderes System wie SPIFFS überschrieben). Der hier gezeigte Ablauf ist komplizierter als nötig und dient nur zu Demonstrationszwecken.

Ein längeres Programm, das ein paar mehr Bibliotheksfunktionen nutzt und z.B. Dateien liest und schreibt, folgt weiter unten.

  1. /* ------------------------------------------------------------
  2.  * Testprogramm für Dateisystem LittleFS
  3.  * (verwendet Code aus der Beispieldatei
  4.  *  LittleFS_esp32\examples\LITTLEFS_test\LittleFS_test.ino)
  5.  * 
  6.  * Programm stellt nur fest, ob das Dateisystem genutzt werden 
  7.  * kann. Falls nein, wird es formatiert.
  8.  * 
  9.  * Falls ein Zugriff möglich ist, wird die Belegung angezeigt.
  10.  * 
  11.  * 2021-05-22 Heiko / unsinnsbasis.de
  12.  * ------------------------------------------------------------ */
  13.  
  14. /* ------------------------------------------------------------
  15.  *  Einbinden der benötigten Bibliotheken, 
  16.  *  Defintion von Konstanten,
  17.  *  Anlegen der Datenobjekte
  18.  * ------------------------------------------------------------ */
  19. // Übertragungsrate für Ausgabe zum seriellen Monitor
  20. #define SERIAL_BAUDRATE 115200
  21.  
  22. #include <Arduino.h>
  23. #include "FS.h"
  24. #include <LITTLEFS.h>
  25.  
  26. /* ------------------------------------------------------------
  27.  *  Liste aller Dateien und Unterverzeichnisse in einem
  28.  *  Verzeichnis anzeigen
  29.  *  
  30.  *  Parameter:
  31.  *  - Dateisystem (sollte LITTLEFS sein)
  32.  *  - Verzeichnisname ("/" für das Root-Dir)
  33.  *  - maximale Verzeichnistiefe
  34.  * ------------------------------------------------------------ */
  35. void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
  36.   Serial.printf("Directory-Listing: %s\r\n", dirname);
  37.  
  38.   File root = fs.open(dirname);
  39.   if (!root) {    // Verzeichnis nicht vorhanden
  40.     Serial.println("- Fehler beim Öffnen des Verzeichnisses");
  41.     return;
  42.   }
  43.   if (!root.isDirectory()) {  // kein Verzeichnis, sondern Datei
  44.     Serial.println(" - kein Verzeichnis");
  45.     return;
  46.   }
  47.  
  48.   File file = root.openNextFile();
  49.   // in einer Schleife durch die Liste aller vorhandenen
  50.   // Einträge gehen
  51.   while (file) {
  52.     if (file.isDirectory()) {
  53.       Serial.print("  DIR : ");
  54.       Serial.println(file.name());
  55.       // ist der Eintrag ein Verz., dann dessen Inhalt rekursiv
  56.       // anzeigen, wenn maximale Tiefe noch nicht erreicht ist
  57.       if (levels) {
  58.         listDir(fs, file.name(), levels-1);
  59.       }
  60.     } else {
  61.       Serial.print("  FILE: ");
  62.       Serial.print(file.name());
  63.       Serial.print("\tGröße: ");
  64.       Serial.println(file.size());
  65.     }
  66.     file = root.openNextFile();
  67.   }
  68. }
  69.  
  70. /* ------------------------------------------------------------ */
  71. void setup() {
  72.   Serial.begin(SERIAL_BAUDRATE);
  73.   delay(500);
  74.   Serial.println("\n-------- LITTLEFS Test --------");
  75.  
  76.   // versuchen, ein vorhandenes Dateisystem einzubinden
  77.   if (!LITTLEFS.begin(false)) {
  78.     Serial.println("LITTLEFS Mount fehlgeschlagen");
  79.     Serial.println("Kein Dateisystemsystem gefunden; wird formatiert");
  80.     // falls es nicht klappt, erneut mit Neu-Formatierung versuchen
  81.     if (!LITTLEFS.begin(true)) {
  82.       Serial.println("LITTLEFS Mount fehlgeschlagen");
  83.       Serial.println("Formatierung nicht möglich");
  84.       return;
  85.     } else {
  86.       Serial.println("Formatierung des Dateisystems erfolgt");
  87.     }
  88.   }
  89.  
  90.   Serial.println("Informationen zum Dateisystem:");
  91.   Serial.printf("- Bytes total:   %ld\n", LITTLEFS.totalBytes());
  92.   Serial.printf("- Bytes genutzt: %ld\n\n", LITTLEFS.usedBytes());
  93.  
  94.   // Liste aller vorhandenen Dateien anzeigen
  95.   // (rekursiv bis zu 3 Verzeichnisebenen tief)
  96.   listDir(LITTLEFS, "/", 3);
  97. }
  98.  
  99. void loop() {
  100.   // nichts tun
  101. }

Quellcode bei Github

Ausgabe im seriellen Monitor:

-------- LITTLEFS Test --------
K:\code\arduino\libraries\LittleFS_esp32\src\lfs.c:1076:error: Corrupted dir pair at {0x16d, 0x16e}
E (533) esp_littlefs: mount failed,  (-84)
E (534) esp_littlefs: Failed to initialize LittleFS
LITTLEFS Mount fehlgeschlagen
Kein Dateisystemsystem gefunden; wird formatiert
K:\code\arduino\libraries\LittleFS_esp32\src\lfs.c:1076:error: Corrupted dir pair at {0x16d, 0x16e}
E (554) esp_littlefs: mount failed,  (-84)
E (558) esp_littlefs: Failed to initialize LittleFS
Formatierung des Dateisystems erfolgt
Informationen zum Dateisystem:
- Bytes total:   2031616
- Bytes genutzt: 8192
 
Directory-Listing: /

Man sieht, dass die Formatierung erfolgreich war. Nach wenigen Sekunden steht ein leeres Dateisystem zur Verfügung, in dem zwei Blöcke (oder Sektoren) mit je 4096 Bytes für interne Zwecke des Dateisystems verwendet werden.
(Zum Testen kann man eine Neuformatierung auslösen, indem man im Menü Werkzeuge das Partitionierungsschema wechselt, z.B. von »Default 4MB with spiffs(1.2MB APP/1.5MB SPIFFS)« zu »No OTA (2MB APP/2 MB SPIFFS)«. Da sich dabei auch die Größe der Programm-Partition ändert, muss man das Programm neu auf den ESP32 hochladen; ein einfacher Reset reicht nicht.)

Ist bereits ein LittleFS-Dateisystem vorhanden, wird sofort das Listing angezeigt:

-------- LITTLEFS Test --------
Informationen zum Dateisystem:
- Bytes total:   2031616
- Bytes genutzt: 8192
 
Directory-Listing: /

Teil 2: Dateisystem-Tools (optional)

Der zweite Teil des Beitrags beschreibt die Installation der Dateisystem-Tools und flasht ein Dateisystem mit vorinstallierten Dateien auf den ESP32.

Programm zur Demonstration der Bibliotheksfunktionen

Das Programm demonstriert die Basisfunktionen, die man z.B. braucht, wenn man Sensordaten auslesen und speichern will. Viel mehr als das Anlegen und Ergänzen, bei Bedarf auch Löschen von Dateien, ist dazu erstmal nicht nötig. Wenn man tiefer einsteigen will, sind unten einige Dokumentations-Seiten verlinkt.

Die im Programm verwendeten Funktionen sind im Wesentlichen aus dem Beispielprogramm LittleFS_test.ino der LittleFS-Bibliothek übernommen (Menü Datei → Beispiele / Beispiele aus eigenen Bibliotheken → LittleFS_esp32 → LITTLEFS_test). Ich habe Kommentare ergänzt, sodass das Programm hoffentlich selbsterklärend ist.
Die Programmlogik baut zwar auf dem Beispiel-Dateisystem aus Teil 2 mit ein paar vorinstallierten Dateien auf, funktioniert aber auch mit einem leeren Dateisystem (manche Ausgaben sehen dann anders aus). Man muss also die Dateisystem-Tools nicht installieren.

  1. /* ------------------------------------------------------------
  2.  * Testprogramm für Dateisystem LittleFS
  3.  * (verwendet Code aus der Beispieldatei
  4.  *  LittleFS_esp32\examples\LITTLEFS_test\LittleFS_test.ino)
  5.  * 
  6.  * - Liste der vorhandenen Dateien anzeigen
  7.  * - Dateiinhalt anzeigen
  8.  * - Anlegen eines Verzeichnisses
  9.  * - Schreiben von Daten in eine neue Datei, weitere Daten anhängen
  10.  * - Datei und Verzeichnis löschen
  11.  * 
  12.  * 2021-05-21 Heiko / unsinnsbasis.de
  13.  * ------------------------------------------------------------ */
  14.  
  15. /* ------------------------------------------------------------
  16.  *  Einbinden der benötigten Bibliotheken, 
  17.  *  Defintion von Konstanten,
  18.  *  Anlegen der Datenobjekte
  19.  * ------------------------------------------------------------ */
  20. // Übertragungsrate für Ausgabe zum seriellen Monitor
  21. #define SERIAL_BAUDRATE 115200
  22.  
  23. #include <Arduino.h>
  24. #include "FS.h"
  25. #include <LITTLEFS.h>
  26.   // Dateisystem automatisch formatieren (true/false)
  27.   // (ist nicht nötig, wenn es mit ESP32FS (ESP32 Sketch
  28.   // Data Upload) erstellt wurde)
  29. #define FORMAT_LITTLEFS_IF_FAILED true
  30.  
  31. /* ------------------------------------------------------------
  32.  *  Liste aller Dateien und Unterverzeichnisse in einem
  33.  *  Verzeichnis anzeigen
  34.  *  
  35.  *  Parameter:
  36.  *  - Dateisystem (sollte LITTLEFS sein)
  37.  *  - Verzeichnisname ("/" für das Root-Dir)
  38.  *  - maximale Verzeichnistiefe
  39.  * ------------------------------------------------------------ */
  40. void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
  41.   Serial.printf("Directory-Listing: %s\r\n", dirname);
  42.  
  43.   File root = fs.open(dirname);
  44.   if (!root) {    // Verzeichnis nicht vorhanden
  45.     Serial.println("- Fehler beim Öffnen des Verzeichnisses");
  46.     return;
  47.   }
  48.   if (!root.isDirectory()) {  // kein Verzeichnis, sondern Datei
  49.     Serial.println(" - kein Verzeichnis");
  50.     return;
  51.   }
  52.  
  53.   File file = root.openNextFile();
  54.   // in einer Schleife durch die Liste aller vorhandenen
  55.   // Einträge gehen
  56.   while (file) {
  57.     if (file.isDirectory()) {
  58.       Serial.print("  DIR : ");
  59.       Serial.println(file.name());
  60.       // ist der Eintrag ein Verz., dann dessen Inhalt rekursiv
  61.       // anzeigen, wenn maximale Tiefe noch nicht erreicht ist
  62.       if (levels) {
  63.         listDir(fs, file.name(), levels-1);
  64.       }
  65.     } else {
  66.       Serial.print("  FILE: ");
  67.       Serial.print(file.name());
  68.       Serial.print("\tGröße: ");
  69.       Serial.println(file.size());
  70.     }
  71.     file = root.openNextFile();
  72.   }
  73. }
  74. /* ------------------------------------------------------------
  75.  *  Daten in eine Datei schreiben
  76.  *  - existiert die Datei, wird sie überschrieben (Modus FILE_WRITE)
  77.  *    oder die Daten werden angehängt (Modus FILE_APPEND)
  78.  *  
  79.  *  Parameter:
  80.  *  - Dateisystem (sollte LITTLEFS sein)
  81.  *  - Dateiname (vollständige Pfadangabe)
  82.  *  - zu schreibende Daten (als Zeichenkette)
  83.  *  - Modus zum Öffnen (FILE_WRITE oder FILE_APPEND)
  84.  * ------------------------------------------------------------ */
  85. void writeFile(fs::FS &fs, 
  86.                const char * path,
  87.                const char * message,
  88.                const char * mode) {
  89.   Serial.printf("Schreibe Datei: %s\r\n", path);
  90.  
  91.   if (mode != FILE_WRITE && mode != FILE_APPEND) {
  92.     Serial.println("- ungültiger Zugriffsmodus (WRITE/APPEND)");
  93.   }
  94.   File file = fs.open(path, mode);
  95.   if (!file) {
  96.     Serial.println("- kann Datei nicht zum Schreiben öffnen");
  97.     return;
  98.   }
  99.   if (file.print(message)) {
  100.     if (mode == FILE_WRITE) {
  101.       Serial.println("- Datei neu geschrieben");
  102.     } else {
  103.       Serial.println("- Daten an Datei angehängt");
  104.     }
  105.   } else {
  106.     Serial.println("- Schreiben fehlgeschlagen");
  107.   }
  108.   file.close();
  109. }
  110. /* ------------------------------------------------------------
  111.  *  Inhalt einer (Text-)Datei ausgeben
  112.  *  
  113.  *  Parameter:
  114.  *  - Dateisystem (sollte LITTLEFS sein)
  115.  *  - Dateiname (vollständige Pfadangabe)
  116.  * ------------------------------------------------------------ */
  117. void readFile(fs::FS &fs, const char * path) {
  118.   Serial.printf("Lese Datei: %s\r\n", path);
  119.  
  120.   File file = fs.open(path);
  121.   if (!file || file.isDirectory()) {
  122.     Serial.println("- kann Datei nicht zum Lesen öffnen");
  123.     return;
  124.   }
  125.  
  126.   Serial.println("- lese Datei:");
  127.   while(file.available()) {
  128.     // Dateiinhalt Zeichen für Zeichen lesen und ausgeben
  129.     Serial.write(file.read());
  130.   }
  131.   file.close();
  132. }
  133. /* ------------------------------------------------------------
  134.  *  Datei löschen
  135.  *  
  136.  *  Parameter:
  137.  *  - Dateisystem (sollte LITTLEFS sein)
  138.  *  - Dateiname (vollständige Pfadangabe)
  139.  * ------------------------------------------------------------ */
  140. void deleteFile(fs::FS &fs, const char * path) {
  141.   Serial.printf("Lösche Datei: %s\r\n", path);
  142.   if (fs.remove(path)) {
  143.     Serial.println("- Datei gelöscht");
  144.   } else {
  145.     Serial.println("- Datei konnte nicht gelöscht werden");
  146.   }
  147. }
  148. /* ------------------------------------------------------------
  149.  *  Verzeichnis anlegen
  150.  *  
  151.  *  Parameter:
  152.  *  - Dateisystem (sollte LITTLEFS sein)
  153.  *  - Verzeichnisname (vollständige Pfadangabe)
  154.  * ------------------------------------------------------------ */
  155. void createDir(fs::FS &fs, const char * path) {
  156.   Serial.printf("Erstelle Verzeichnis: %s\n", path);
  157.   if (fs.mkdir(path)) {
  158.     Serial.println("Verzeichnis erstellt");
  159.   } else {
  160.     Serial.println("Erstellen des Verzeichnisses fehlgeschlagen");
  161.   }
  162. }
  163. /* ------------------------------------------------------------
  164.  *  Verzeichnis löschen
  165.  *  
  166.  *  Parameter:
  167.  *  - Dateisystem (sollte LITTLEFS sein)
  168.  *  - Verzeichnisname (vollständige Pfadangabe)
  169.  * ------------------------------------------------------------ */
  170. void removeDir(fs::FS &fs, const char * path) {
  171.   Serial.printf("Lösche Verzeichnis: %s\n", path);
  172.   if (fs.rmdir(path)) {
  173.     Serial.println("Verzeichnis gelöscht");
  174.   } else {
  175.     Serial.println("Fehler beim Löschen");
  176.   }
  177. }
  178.  
  179. /* ------------------------------------------------------------ */
  180. void setup() {
  181.   Serial.begin(SERIAL_BAUDRATE);
  182.   delay(500);
  183.   Serial.println("\n-------- LITTLEFS Test --------");
  184.  
  185.   // Dateisystem einbinden
  186.   // (ggf. beim ersten Mal formatieren)
  187.   if(!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
  188.     Serial.println("LITTLEFS Mount fehlgeschlagen");
  189.     return;
  190.   }
  191.  
  192.   Serial.println("Informationen zum Dateisystem:");
  193.   Serial.printf("- Bytes total:   %ld\n", LITTLEFS.totalBytes());
  194.   Serial.printf("- Bytes genutzt: %ld\n\n", LITTLEFS.usedBytes());
  195.  
  196.   // Liste aller vorhandenen Dateien anzeigen
  197.   // (rekursiv bis zu 3 Verzeichnisebenen tief)
  198.   listDir(LITTLEFS, "/", 3);
  199.   Serial.println();
  200.  
  201.   // Liste eines nicht vorhandenen Verz. anzeigen
  202.   // -> Fehlermeldung
  203.   listDir(LITTLEFS, "/unsinnsbasis", 3);
  204.   Serial.println();
  205.  
  206.   // versuchen, einen Dateinamen als Verzeichnis anzuzeigen
  207.   // -> Fehlermeldung
  208.   listDir(LITTLEFS, "/testdatei.txt", 3);
  209.   Serial.println();
  210.  
  211.   // Inhalt der Testdatei anzeigen
  212.   readFile(LITTLEFS, "/testdatei.txt");
  213.   Serial.println();
  214.  
  215.   // bereits vorhandenes Verzeichnis nochmal erstellen
  216.   createDir(LITTLEFS, "/test-dir");
  217.   Serial.println();
  218.  
  219.   // neues Verzeichnis anlegen
  220.   createDir(LITTLEFS, "/mein-directory");
  221.   Serial.println();
  222.   // dort eine Datei anlegen bzw. bei Vorhandensein überschreiben
  223.   writeFile(LITTLEFS, "/mein-directory/demo.dat",
  224.             "Das ist eine Demodatei\n", FILE_WRITE);
  225.   Serial.println();
  226.   // Liste der Dateien zeigt, ob Datei angelegt wurde
  227.   listDir(LITTLEFS, "/mein-directory", 1);
  228.   Serial.println();
  229.   writeFile(LITTLEFS, "/mein-directory/demo.dat",
  230.             "Mehr Daten", FILE_APPEND);  // ohne Zeilenvorschub
  231.   Serial.println();
  232.   writeFile(LITTLEFS, "/mein-directory/demo.dat",
  233.             "Noch mehr Daten\n", FILE_APPEND);
  234.   Serial.println();
  235.   listDir(LITTLEFS, "/mein-directory", 1);
  236.   Serial.println();
  237.   readFile(LITTLEFS, "/mein-directory/demo.dat");
  238.   Serial.println();
  239.   // neu angelegtes Verzeichnis löschen
  240.   // -> Fehler, weil Verzeichnis nicht leer
  241.   removeDir(LITTLEFS, "/mein-directory");
  242.   // also Datei löschen und dann das Verzeichnis
  243.   deleteFile(LITTLEFS, "/mein-directory/demo.dat");
  244.   removeDir(LITTLEFS, "/mein-directory");
  245.   Serial.println();
  246.   // nach dem Aufräumen sollte es aussehen wie zu Beginn
  247.   listDir(LITTLEFS, "/", 1);
  248. }
  249.  
  250. void loop() {
  251.   // nichts tun
  252. }

Quellcode bei Github

Das Programm erzeugt folgende Ausgabe im seriellen Monitor:

  1. -------- LITTLEFS Test --------
  2. Informationen zum Dateisystem:
  3. - Bytes total:   2031616
  4. - Bytes genutzt: 16384
  5.  
  6. Directory-Listing: /
  7.   DIR : /test-dir
  8. Directory-Listing: /test-dir
  9.   FILE: /test-dir/test2.txt	Größe: 53
  10.   FILE: /testdatei.txt	Größe: 52
  11.  
  12. Directory-Listing: /unsinnsbasis
  13. - Fehler beim Öffnen des Verzeichnisses
  14.  
  15. Directory-Listing: /testdatei.txt
  16.  - kein Verzeichnis
  17.  
  18. Lese Datei: /testdatei.txt
  19. - lese Datei:
  20. Testdatei
  21. Vom PC ins Dateisystem-Image übernommen.
  22. Erstelle Verzeichnis: /test-dir
  23. Verzeichnis erstellt
  24.  
  25. Erstelle Verzeichnis: /mein-directory
  26. Verzeichnis erstellt
  27.  
  28. Schreibe Datei: /mein-directory/demo.dat
  29. - Datei neu geschrieben
  30.  
  31. Directory-Listing: /mein-directory
  32.   FILE: /mein-directory/demo.dat	Größe: 23
  33.  
  34. Schreibe Datei: /mein-directory/demo.dat
  35. - Daten an Datei angehängt
  36.  
  37. Schreibe Datei: /mein-directory/demo.dat
  38. - Daten an Datei angehängt
  39.  
  40. Directory-Listing: /mein-directory
  41.   FILE: /mein-directory/demo.dat	Größe: 49
  42.  
  43. Lese Datei: /mein-directory/demo.dat
  44. - lese Datei:
  45. Das ist eine Demodatei
  46. Mehr DatenNoch mehr Daten
  47.  
  48. Lösche Verzeichnis: /mein-directory
  49. Fehler beim Löschen
  50. Lösche Datei: /mein-directory/demo.dat
  51. - Datei gelöscht
  52. Lösche Verzeichnis: /mein-directory
  53. Verzeichnis gelöscht
  54.  
  55. Directory-Listing: /
  56.   DIR : /test-dir
  57. Directory-Listing: /test-dir
  58.   FILE: /test-dir/test2.txt	Größe: 53
  59.   FILE: /testdatei.txt	Größe: 52

Wenn man das Dateisystem mit den vorinstallierten Dateien nutzt, sieht man, dass das Erstellen des Verzeichnisses /test-dir keine Fehlermeldung hervorruft (Zeilen 22/23 der Ausgabe), auch wenn es schon existiert. Das Ergebnis ist ja auch das gleiche: Nach dem Aufrufen der Funktion createDir() existiert das Verzeichnis, unabhängig davon, ob es vorher schon da war oder nicht.
Außerdem muss man beim Schreiben der (Text-)Daten selbst an die Zeilenumbrüche denken – die Methode writeFile() ergänzt sie nicht automatisch. In Programmzeile 229/230 wird der Text „Mehr Daten” ausgegeben. In Zeile 46 der Ausgabe sieht man, dass die nächsten Daten direkt angehängt wurden.

Die Angabe zu Beginn, dass die wenigen vorinstallierten Daten (2 Dateien mit jeweils nur etwas über 50 Bytes) 16 KB Platz belegen, mag erst einmal verwundern.[6]Bei einem anderen Test habe ich festgestellt, dass selbst das Anlegen eines leeren Verzeichnisses in einem neu initialisierten LittleFS-Dateisystem genausoviel Platz verbraucht. Sie relativiert sich schon etwas, wenn man den folgenden Hinweis kennt: »A freshly formatted LittleFS will have 2 blocks in use, making it seem like 8KB are in use.« Das zeigt auch die Belegung eines neu initialisierten LittleFS-Sytem im vorigen Beispiel – 8192 Bytes werden sofort vom Dateisystem selbst genutzt.
Außerdem kann LittleFS mehrere Dateien mit einer Größe bis 512 Bytes in einem Block zusammenlegen (siehe oben). In den momentan belegten Blöcken lassen sich also noch ein paar kleine Dateien zusätzlich unterbringen. Das macht sich aber bei zwei Dateien und einem Directory logischerweise kaum bemerkbar, sondern erst dann, wenn viele Dateien gespeichert sind. Der Overhead zur Verwaltung des Dateisystems wird dann im Verhältnis zu den Nutzdaten deutlich kleiner.

Weitere Dateisystem-Funktionen zeigt das Beispielprogramm LittleFS_test.ino der LittleFS-Bibliothek. Das andere Beispiel LittleFS_time.ino ist ähnlich und zeigt auch, wie man das Zugriffsdatum einer Datei ausliest; außerdem sieht man dort, wie einfach (zumindest in diesem Fall) die Umstellung des Programmcodes von SPIFFS auf LittleFS möglich ist.

Es gibt noch keine offizielle Dokumentation von espressif oder den Enwicklern für die ESP32-Version von LittleFS.

  • LittleFS-Bibliothek für den ESP32 in der Arduino-IDE: Beispielprogramme; insbes. LITTLEFS_test.ino zeigt die Nutzung aller Basisfunktionen
  • Dokumentation von espressif zu SPIFFS (espressif-IDE ESP-IDF)
  • Dokumentation von espressif zu Dateisystemen für den ESP8266 – dort sieht man, wie SPIFFS und LittleFS auf dem ESP8266 programmiert werden.
  • SPIFFS-Bibliothek im Arduino-Paket für den ESP32

Weiterführende Links:

Änderungen:
23.08.2021 Tippfehler korrigiert – auch in Kommentaren der Testprogramme

Fußnoten

Fußnoten
1 Das Arduino-Paket für den ESP32 befindet sich bei Github: github.com/espressif/arduino-esp32; wer die Entwicklerversion ausprobieren möchte, muss in der Boardverwaltung der Arduino-IDE folgende URL eintragen: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
2 Eigene Übersetzung: »SPIFFS wird derzeit als veraltet betrachtet und wird möglicherweise in zukünftigen Versionen des Kerns entfernt. Bitte denken Sie darüber nach, Ihren Code auf LittleFS umzustellen. SPIFFS wird vom ursprünglichen Entwickler nicht mehr aktiv unterstützt, während LittleFS aktiv entwickelt wird, echte Verzeichnisse unterstützt und bei den meisten Operationen um ein Vielfaches schneller ist.«
3 Komplettes Zitat von github.com/joltwallet/esp_littlefs (die Entwickler der LittleFS-Bibliothek für die ESP-IDF, die Grundlage der aktuellen ESP32-Bibliothek für die Arduino-IDE ist):
»See the official ESP-IDF SPIFFS documentation, basically all the functionality is the same; just replace spiffs with littlefs in all function calls.
Also see the comments in include/esp_littlefs.h
Slight differences between this configuration and SPIFFS’s configuration is in the esp_vfs_littlefs_conf_t:
    max_files field doesn’t exist since we removed the file limit, thanks to @X-Ryl669
    partition_label is not allowed to be NULL. You must specify the partition name from your partition table. This is because there isn’t a define littlefs partition subtype in esp-idf. The subtype doesn’t matter.«
(Die erwähnte include-Datei esp_littlefs.h ist hier zu finden: github.com/joltwallet/esp_littlefs/blob/master/include/esp_littlefs.h.)
Auch „lorol”, der Entwickler der ESP32-Bibliothek, schreibt in einem Forums-Beitrag:
»Here’s my port of LittleFS that has the same API as the esp-idf SPIFFS port. Basically it should just work if you replace all your “spiffs” calls with “littlefs”. Only slight difference is in your configuration, you don’t specify “max_files”.«
4 Siehe github.com/joltwallet/esp_littlefs#documentation: »max_files field doesn’t exist since we removed the file limit«. In der Header-Datei LITTLEFS.h wird zwar beim Aufruf der begin-Methode die Anzahl 10 voreingestellt; das hat aber nur Kompatibiltätsgründe (siehe die Dokumentation der Bibliothek).
5 Welche Version man installiert hat, kann man in der Boardverwaltung überprüfen: Menü Werkzeuge → Board "xyz" → Boardverwalter…, dort im Suchfeld „esp” eingeben.
Für Core-Version 1.0.4 sollte man den Kompatibilitäts-Hinweis zu CONFIG_LITTLEFS_FOR_IDF_3_2 hier oder im Quelltext beachten.
6 Bei einem anderen Test habe ich festgestellt, dass selbst das Anlegen eines leeren Verzeichnisses in einem neu initialisierten LittleFS-Dateisystem genausoviel Platz verbraucht.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.