La memoria flash dell’ESP32 può essere immaginata come una sorta di hard disk che contiene programmi e files. Così come un hard disk, la memoria flash può anche essere suddivisa in più partizioni.

Esistono diverse versioni di ESP32 con tagli di memoria flash da 4MB, 8MB e 16MB.

Possiamo avere diversi tipi di partizioni:

  • app partitions: le partizioni che contengono il firmware/software vero e proprio (anche più di una!),
  • data partitions: le partizioni che contengono dati.

Questi due tipi di partizioni si suddividono a loro volta in diversi sottotipi (subType), tra cui quello oggetto di questo articolo: SPIFFS.

SPIFFS sta per SPI Flash File System e, come sicuramente avrete intuito, è un tipo di partizione che possiamo utilizzare per memorizzare files e directory, proprio come un hard disk. Ad esempio, potremmo voler salvare delle immagini (come abbiamo già visto qui), oppure dei file di configurazione.

Nella stragrande maggioranza dei casi, non è necessario modificare la tabella delle partizioni, ma basta utilizzare quella di default che contiene già una piccola partizione SPIFFS. Vedremo in un altro articolo come modificare la tabella delle partizioni in caso di necessità particolari.

Esempi

Abbiamo come consuetudine preparato un repository GitHub con diversi esempi. Le informazioni su come clonare un repository remoto le trovi in questo articolo.

Simple test

Iniziamo con il vedere 3 operazioni fondamentali che possiamo fare con SPIFFS: Inizializzazione, scrittura e lettura di un file.

Riporto per intero il file scr/main.cpp perchè è davvero semplice ed autoesplicativo :wink:

In realtà l’operazione di scrittura è sufficiente solo una volta. Puoi provare a commentare/rimuovere quella parte dopo un primo avvio: noterai che l’ESP32 continuerà a leggere il contenuto del file che era stato scritto in precedenza.

#include <Arduino.h>

#include "SPIFFS.h"

const char *filename = "/test.txt";

void setup() {
  Serial.begin(115200);

  /*

  SPIFFS BEGIN

  */
  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS Mount Failed");
    return;
  }
  Serial.println("========================================");

  /*

  FILE WRITE

  */
  Serial.printf("Writing file: %s\r\n", filename);  
  File file_write = SPIFFS.open(filename, FILE_WRITE);
  if (!file_write) {
    Serial.println("- failed to open file for writing");
    return;
  }
  if (file_write.print("Hello world! This text is from spiffs file!")) {
    Serial.println("- file written");
  } else {
    Serial.println("- write failed");
  }
  file_write.close();
  Serial.println("========================================");

  /*

  FILE READ

  */
  Serial.printf("Reading file: %s\r\n", filename);
  File file = SPIFFS.open(filename);
  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());
  }
  file.close();
  Serial.println();
  Serial.println("========================================");
}

void loop() {}

Full test

Sulla falsa riga del precedente, vediamo un esempio più completo che è stato suddiviso su diverse funzioni che richiamiamo dal setup():

listDir(SPIFFS, "/", 0);
writeFile(SPIFFS, "/testfile.txt", "Text to write on the file ");
appendFile(SPIFFS, "/testfile.txt", "World!\r\n");
readFile(SPIFFS, "/testfile.txt");
renameFile(SPIFFS, "/testfile.txt", "/testfile_renamed.txt");
deleteFile(SPIFFS, "/testfile_renamed.txt");
testFileIO(SPIFFS, "/test.txt");

Spulciando le funzioni di questo esempio possiamo praticamente effettuare qualsiasi operazione sui file.

Images e data folder

Nei due precedenti esempi abbiamo visto come manipolare file di testo su SPIFFS da codice durante l’esecuzione del programma (runtime), dalla creazione, alla modifica, all’eliminazione. Ma abbiamo anche un’altra possibilità, ovvero quello di caricare dei files su SPIFFS in fase di compilazione (compile-time).

Con Platform.IO questa operazione è davvero semplice ed immediata: come prima cosa è necessario creare una directory “data” nella root del progetto, nella quale andremo a copiare i files che vogliamo caricare nella partizione SPIFFS:

Una volta che abbiamo i nostri files nel directory data, possiamo trasferirli sulla partizione SPIFFS dal menu di Platform.IO (click sul logo a sinistra) -> Upload FileSystem Image.

Un esempio che legge le immagini jpeg da spiffs e le visualizza su display lo abbiamo già visto qui.

ArduinoJson config file

Per quanto mi riguarda, questo è un esempio fondamentale, è mi è capitato spesso di utilizzarlo come base di partenza per dei progetti.

ArduinoJson è la libreria più diffusa e completa per la manipolazione di files Json. Json è un formato di interscambio dati, inizialmente nato per JavaScript ed usato principalmente in applicazioni ajax, ma che molto presto si è diffuso in motissimi altri ambiti per la sua semplicità di implementazione.

Per mezzo di questa libreria, è possibile convertire delle strutture dati (struct) in files json (e viceversa) salvati su SPIFFS, per rendere questi dati persistenti al riavvio del controller.

Nell’esempio proposto nel nostro repository, vogliamo registrare su SPIFFS il SID e la password per la connessione WIFI.

Come prima cosa, facciamo l’upload del file data/conf.json come appena destritto nell’esempio precedente.

Dopo aver incluso la libreria ArduinoJson sia su platformio.ini (lib_deps) che nella testa del file sorgente src/main.cpp, definiamo la struct che conterrà i dati ed il nome del file:

struct Config {
  String sid;
  String password;
} wifiConfig;

const char *configFile = "/conf.json";

Nell’esempio, ho definito 2 funzioni, una per salvare i dati, una per caricarli:

// assegno i valori a wifiConfig e li salvo sul file conf.json
wifiConfig.sid = "this_is_my_sid";
wifiConfig.password = "this_is_my_pwd";
saveConfiguration(configFile, wifiConfig);

// carico su wifiConfig i valori presenti in conf.json
loadConfiguration(configFile, wifiConfig);

Con loadConfiguration() e saveConfiguration() andiamo ad utilizzare due funzioni fondamentali di ArduinoJson, la serializzazione (da struct a json) e la deserializzazione (da json a struct).

Il sito della libreria è ricco di documentazione ed esempi per approfondire. Da segnalare uno strumento meraviglioso, sia per conoscere meglio la libreria che per velocizzare il processo di programmazione con ArduinoJson: ArduinoJson Assistant. L’assistente ci permette di generare codice sorgente da utilizzare nei nostri progetti semplicemente rispondendo ad alcune domande!