czwartek, 21 września 2017

[2] ESP32 - NVS, przechowywanie danych

W tym poście chciałbym przedstawić NVS czyli NON Volatile Storage. Jest to biblioteka obsługująca pamięć flash. Pozwoli to na przechowywanie przesłanych haseł czy ważnych danych nawet po zaniku zasilania.

[Źródło: www.banggood.com]

Opis:


Dostęp do pamięci uzyskiwany jest przez funkcji spi_flash. Zakres pamięci ustanawiany jest poprzez funkcje nvs_flash_init. 

NVS do operowania na danych wykorzystuje klucze, które razem z wartością stanowią parę. Takimi kluczami są ciągi znaków ASCII. Mogą zawierać maksymalnie 15 znaków. Należy pamiętać o tym aby klucz był unikatowy dla każdej z wartości.

W przypadku wprowadzenia klucza o tej samej wartości więcej niż raz może doprowadzić do dwóch zdarzeń. Pierwsza z nich powoduje aktualizacje zmiennej, ale tylko wtedy gdy jest ona tego samego typu co wartość zapisywana po raz pierwszy. W przypadku gdy wartość jest innego typu wtedy zostaje zgłoszony błąd.

Wartości mogą przyjmować wartości takie jak: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t. Dodatkowo ciągi znaków mogą być przechowywane w postaci tabel czy różne sekwencje bajtów jak blob. Na moment tworzenia tego posta obsługa takich typów jak float czy double nie jest obsługiwana.

Do dyspozycji użytkownika, domyślnie bez modyfikacji partycji zapisanych w zewnętrznej pamięci flash, otrzymuje się dostęp do 24K bajtów.

Opis funkcji:


nvs_flash_init - Wywoływane w celu inicjalizacji biblioteki nvs. Partycje zostają ustawione zgodnie z informacjami wprowadzonymi w tabeli partycji.

esp_err_t nvs_flash_init();

Funkcja zwraca wartość ESP_OK w przypadku poprawnego ustawienia.

nvs_open - otwarcie schowka o podanej nazwie.

esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle);

Pierwszym argumentem jest nazwa schowka. Nazwa może wynosić do 16 znaków. Drugi parametr oznacza tryb w jakim uzyska się dostęp do pamięci. Do wyboru jest NVS_READWRITE, NVS_READONLY. Drugi tryb oferuje tylko odczyt. W przypadku jego wywołania niemożliwe jest zapisanie danych. Ostatni argument będzie zawierał zwracany uchwyt do miejsca w pamięci.

  • ESP_OK - operacja się powiodła;
  • ESP_ERR_NVS_NOT_INITIALIZED - nie uruchomiono biblioteki NVS;
  • ESP_ERR_NVS_NOT_FOUND - nie znaleziono zdefiniowanego miejsca w pamięci oraz ustawiono tryb tylko do odczytu
  • ESP_ERR_NVS_INVALID_NAME -  nie znaleziono zdefiniowanego miejsca w pamięci;


nvs_set_xxx - zapis zmiennej do pamięci. Do wyboru są takie typy jak: i8, u8, i16, u16, i32, u32, i64, u64.

esp_err_t nvs_set_u32(nvs_handle handle, const char* key, int8_t value);

Jako argumenty podaje się uchwyt do nvs. Następnie klucz który będzie odpowiadał zapisanej wartości. Ostatnia wartość odnosi się do zapisanej wartości.

Funkcja może zwrócić kilka parametrów:

  • ESP_OK - operacja przeprowadzona poprawnie;
  • ESP_ERR_NVS_INVALID_HANDLE - uchwyt został zamknięty, lub jego wartość wynosi NULL;
  • ESP_ERR_NVS_READ_ONLY - jeśli nvs został otwarty tylko z możliwością odczytu danych;
  • ESP_ERR_NVS_INVALID_NAME - wartość klucza nie odpowiada żadnej wartości w pamięci;
  • ESP_ERR_NVS_NOT_ENOUGH_SPACE - nie ma wystarczającej ilości miejsca aby zapisać dane;
  • ESP_ERR_NVS_REMOVE_FAILED - błąd podczas zapisu danych do pamięci pamięci. 

nvs_set_blob, nvs_set_str - zapis tzw. blob. Jako argumenty podaje się te same wartości co dla zapisu standardowego. Wyjątek stanowi jeden dodatkowy parametr dla funkcji nvs_set_blob, do którego podaje się długość tablicy danych.



esp_err_t nvs_set_str(nvs_handle handle, const char *key, const char *value);

esp_err_t nvs_set_blob(nvs_handle handle, const char *key, const void *value, size_t length);

Wartości zwracane są takie same jak dla funkcji opisanej powyżej.

nvs_get_xxx - pobranie informacji z pamięci. W miejsce xxx podstawia się odpowiedni typ wartości który ma zostać odzytany np. i8, u8, i16, u16, i32, u32, i64, u64. Każda z tych funkcji ma takie same parametry. Różnica jest tylko w ostatnim parametrze, gdzie podawany jest wskaźnik do wartości zwracanej przez układ. Musi on być tego samego typu co wartość jaką chce się odczytać. Przykładowy wygląd funkcji:

esp_err_t nvs_get_u32(nvs_handle handle, const char* key, uint32_t* out_value);

Funkcja może zwrócić następujące wartości:

  • ESP_OK - operacja przebiegła poprawnie
  • ESP_ERR_NVS_NOT_FOUND - nie udało się znaleźć podanego klucza,
  • ESP_ERR_NVS_INVALID_HANDLE - gdy uchwyt (handler) został zamknięty lub jego wartość wynosi NULL.
  • ESP_ERR_NVS_INVALID_NAME - gdy podany klucz jest złego typu lub posiada za dużą ilość znaków.
  • ESP_ERR_NVS_INVALID_LENGTH - jeśli typ danych nie odpowiada podanej wartości.

nvs_get_blob, nvs_get_str - funkcja właściwie identyczna do poprzedniej. Różnicą jest jeden dodatkowy parametr, który określa wielkość pobieranych danych.

esp_err_t nvs_get_str(nvs_handle handle, const char* key, char * out_value, size_t* length);
esp_err_t nvs_get_blob(nvs_handle handle, const char* key, char * out_value, size_t* length);

nvs_commit - funkcja służy do zatwierdzenia wprowadzonych zmian w określonym miejscu w pamięci.

esp_err_t nvs_commit(nvs_handle handle);

Jako argument podaje się uchwyt do miejsca w pamięci. Jest to ta sama wartość co podawana przy funkcji nvs_open dla zapisu danych.

Funkcja musi być wykonana po każdym zapisie do pamięci. Funkcja zwraca ESP_OK w przypadku powodzenia bądź ESP_ERR_NVS_INVALID_HANDLER, gdy dane do uchwytu są złe.

nvs_close - zamykanie uchwytu oraz usuwanie jego deklaracji z pamięci. Funkcja musi być wywoływana po każdym zakończeniu operacji na danym miejscu w pamięci.

void nvs_close(nvs_handle handle);

Jako argument podaje się uchwyt, który chce się zamknąć.

Dodatkowo można używać dwóch funkcji odpowiedzialnych za czyszczenie pamięci:

nvs_erase_key - funkcja pozwala na usunięcie pojedynczego klucza wraz z przypisaną wartością z pamięci.

esp_err_t nvs_erase_key(nvs_handle handle, const char* key);

Jako parametry podaje się uchwyt przypisany podczas otwierania pamięci. Drugim argumentem jest klucz, który ma zostać usunięty z pamięci.

nvs_erase_all - usunięcie wszystkich wartości z pamięci dostępnej dla użytkownika.

esp_err_t nvs_erase_all(nvs_handle handle);

Jako argument podawana jest wartość uchwytu, którą umieszczono w funkcji nvs_open.

Funkcje usuwające posiadają standardowe wartości zwracane. Po tych funkcjach należy wywołać nvs_commit.

Programowanie:


Na samym początku należy uruchomić bibliotekę NVS. Wykonuje się to poprzez wywołanie funkcji nvs_flash_init. W przypadku powodzenia zwraca wartość ESP_OK. Dalej należy pamięć odpowiednio przygotować. Należy to zrobić tylko jeden raz przed finalnym działaniem aplikacji.

Drugim krokiem jest identyfikacja partycji. Następnie nie zaszkodzi ją sformatować. Dalej operuje się na odczycie oraz na zapisie do pamieci. Przed każdym takim wywołaniem należy ja otworzyć. Do zapisu oraz odczytu wykorzystuje się funkcje opisane na górze strony.

Odczyt danych:

  1. uint8_t readDataFromNVS(dataStruct *infos)
  2. {
  3.     nvs_handle handle;
  4.     unsigned long size;
  5.     esp_err_t err_Status;
  6.     uint32_t readValue;
  7.     /* Open storage */
  8.     err_Status= nvs_open(FUNCTION_NAMESPACE, NVS_READWRITE, &handle);
  9.     /* Check if open procedure works corrently */
  10.     if(err_Status!= 0){
  11.         ESP_LOGE(TAG, "nvs_open error: %x", err_Status);
  12.         return 1;
  13.     }
  14.     /* Get 32 bit version */
  15.     err_Status = nvs_get_u32(handle, KEY_VALUE, &readValue);
  16.    
  17.     /* Check read operation status */
  18.     if(err_Status != ESP_OK){
  19.         ESP_LOGD(TAG, "Value can't be found %x", err_Status);
  20.         nvs_close(handle);
  21.         return 2;
  22.     }
  23.     size = sizeof(dataStruct );
  24.     /* Read blob value */
  25.     err_Status = nvs_get_blob(handle, KEY_DATA_INFO, infos, &size);
  26.     /* Check read blob */
  27.     if(err_Status != ESP_OK){
  28.         ESP_LOGE(TAG, "nvs_open error: %x", err);
  29.         nvs_close(handle);
  30.         return 4;
  31.     }
  32.     nvs_close(handle);
  33.     return 0;
  34. }

Zapis danych:

  1. static void saveConnectionInfo(dataStruct *infos)
  2. {
  3.     /* Before use this function need to call nvs_flash init, in main function */
  4.     nvs_handle handle;          /* Handle parameter */ 
  5.            
  6.     /* Open storage for with read write parameter */
  7.     ESP_ERROR_CHECK(nvs_open(FUNCTION_NAMESPACE, NVS_READWRITE, &handle));
  8.     /* Write blob data into storage */
  9.     ESP_ERROR_CHECK(nvs_set_blob(handle, KEY_DATA_INFO, infos, sizeof(dataStruct )));
  10.    
  11.     /* Write 32 bit data into storage */
  12.     ESP_ERROR_CHECK(nvs_set_u32(handle, KEY_VALUE, glob_Key_Value));
  13.    
  14.     /* Commit changes */
  15.     ESP_ERROR_CHECK(nvs_commit(handle));
  16.     /* Close storage, delete handle */
  17.     nvs_close(handle);
  18. }