Nel tutorial di oggi vedremo come poter salvare informazioni in maniera permanente, in modo che si conservino anche se il chip esp32 viene resettato o gli viene tolta l’alimentazione.
NVS
NVS (Non Volatile Storage – memorizzazione non volatile) è una libreria del framework esp-idf che consente di memorizzare informazioni (rappresentate dalla coppia chiave/valore) all’interno della memoria flash, il cui contenuto non viene cancellato a fronte di un reset del chip o della mancanza di alimentazione.
Se ricordate, in un precedente articolo vi ho parlato di come viene organizzata la memoria flash esterna al chip esp32. Il compito principale di tale memoria è sicuramente quello di contenere il programma da eseguire. E’ però possibile dividere la flash in diverse partizioni: il framework propone alcune partition tables già pronte ma è anche possibile definirne di custom.
Se utilizziamo quella di default (“Single factory app, no OTA”), possiamo notare che essa contiene anche una partizione di tipo data e sottotipo nvs:
La dimensione di default di tale partizione è 24Kbyte.
Grazie alla libreria NVS, potremo memorizzare in tale partizione dati utili al nostro programma. I dati saranno organizzati in coppia chiave/valore, ovvero ad ogni dato (= valore) sarà associata una etichetta (= chiave), al massimo di 15 caratteri:
E’ possibile memorizzare diversi tipi di dati: da quelli numerici, a stringhe di testo fino a sequenze di bytes (blob, binary large object). Come vedremo la libreria mette a disposizione metodi specifici in base al tipo di dato.
Per poter utilizzare la libreria nei nostri programmi vanno inclusi i relativi headers:
#include "esp_partition.h" #include "esp_err.h" #include "nvs_flash.h" #include "nvs.h" |
Inizializzazione
La prima cosa da fare per poter utilizzare la partizione nvs è inizializzare la libreria, con il comando:
esp_err_t err = nvs_flash_init(); |
Il comando restituisce ESP_OK in caso di successo; al contrario in caso di errore viene restituito uno dei codici contenuti nel file nvs.h (vedi paragrafi successivi). In particolare, se la partizione è stata ridimensionata o modificata, è possibile che venga restituito il codice di errore ESP_ERR_NVS_NO_FREE_PAGES; tale errore può essere superato “formattando” la partizione.
Per prima cosa dobbiamo identificare la partizione nvs:
const esp_partition_t* nvs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); if(!nvs_partition) printf("FATAL ERROR: No NVS partition found\n"); |
Quindi possiamo formattarla con il comando esp_partition_erase_range():
err = (esp_partition_erase_range(nvs_partition, 0, nvs_partition->size)); if(err != ESP_OK) printf("FATAL ERROR: Unable to erase the partition\n"); |
Infine apriamo la partizione; possiamo farlo in modalità READONLY o READWRITE:
nvs_handle my_handle; err = nvs_open("storage", NVS_READWRITE, &my_handle); if (err != ESP_OK) printf("FATAL ERROR: Unable to open NVS\n"); |
Set – Get
Una volta aperta la partizione, è possibile memorizzare dati (set) o recuperare dati già presenti (get).
Esistono diversi metodi, in base al tipo di dato da gestire (i8 sta per integer a 8bit, u8 per integer senza segno a 8bit…):
[checklist]
- nvs_set_i8(), nvs_set_u8(), nvs_set_i16(), nvs_set_u16()…
- nvs_set_str()
- nvs_set_blob()
[/checklist]
Tutti i metodi set ricevono in ingresso l’handler della partizione aperta, la chiave e il dato da memorizzare (valore):
esp_err_t nvs_set_i8(nvs_handle handle, const char* key, int8_t value); |
Unica eccezione il metodo nvs_set_blob() che richiede anche un parametro aggiuntivo: la lunghezza dei dati da memorizzare:
esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length); |
Una volta eseguito un metodo set, è necessario eseguire la commit della modifica con il metodo:
esp_err_t nvs_commit(nvs_handle handle); |
Per recuperare i dati presenti nella flash esistono metodi analoghi:
[checklist]
- nvs_get_i8(), nvs_get_u8(), nvs_get_i16(), nvs_get_u16()…
- nvs_get_str()
- nvs_get_blob()
[/checklist]
I parametri in ingresso sono l’handler della partizione, la chiave e un puntatore alla variabile da aggiornare con il dato recuperato:
esp_err_t nvs_get_i8(nvs_handle handle, const char* key, int8_t* out_value); |
Visto che non è possibile conoscere a priori la dimensione dei dati di tipo stringa o blob, possiamo utilizzare un “trucco”: eseguire una prima chiamata al metodo nvs_get_string() passando NULL come puntatore per avere la lunghezza della stringa, allocare in memoria una variabile di lunghezza adeguata e quindi chiamare nuovamente il metodo per ottenere il dato:
size_t string_size; esp_err_t err = nvs_get_str(my_handle, parameter, NULL, &string_size); char* value = malloc(string_size); err = nvs_get_str(my_handle, parameter, value, &string_size); |
Erase
La libreria mette a disposizione anche due metodi per cancellare il contenuto della partizione nvs:
esp_err_t nvs_erase_key(nvs_handle handle, const char* key); esp_err_t nvs_erase_all(nvs_handle handle); |
Il primo metodo è più selettivo e consente di cancellare una singola chiave mentre il secondo cancella l’intero contenuto della memoria.
Entrambi i metodi devono essere seguiti dal comando nvs_commit() come spiegato in precedenza.
Gestione errori
Tutti i metodi contenuti nella libreria nvs restituiscono un codice di errore (variabile esp_err_t).
I possibili errori sono elencati nel file nvs.h:
Se ad esempio stiamo utilizzando un metodo get passando una chiave non presente, otterremo l’errore ESP_ERR_NOT_FOUND. Possiamo gestire i diversi errori all’interno del nostro programma:
esp_err_t err = nvs_get_i32(my_handle, parameter, &value); if(err == ESP_ERR_NVS_NOT_FOUND) printf("\nKey %s not found\n", parameter); |
Demo
Ho preparato un programma che consente, tramite una semplice command line, di memorizzare e recuperare informazioni dalla partizione nvs. Il codice sorgente è disponibile su Github, ecco un filmato che lo mostra all’opera (sottotitoli italiani disponibili):
Hi,
thanks for the tutorials, they are easy to follow.
I just tried to check a couple of things in the source code from your repository, but I coundn’t find this source file.
Hi Toni, thanks for pointing it out, I forgot to sync the repository 🙂 now you should find it.
I thought it was just that, thank you for sharing.
Hello,
Thanks for your tutorials
I have a simple question : How many int or string can be stored in the nvs memory
And how many times this memory can be write and erased?
A have the projet to use esp32 or esp8266 to build some station to measure ground and air data. These stations need to be on battery so to minimize energy consumption (mainly by wifi) so a solution maybe to store locally (in the nvs??) and send data by packet.
Thanks in advance.
Hi Robert! The number of data you can store in the NVS partition depends on the size of the partition itself… you can change it with a customized partition layout, of course the upper limit is the actual flash size (4Mb) that must also contain your program. The number of operations depends on the flash, please refer to the datasheet (GD25Q32C).
Hi luca !!
I want to ask that , why haven’t you used ESP_ERR_CHECK ( ) function in this program for error handling & displaying purposes ??