Помимо файловой системы SPIFFS в контроллере ESP32 для энергонезависимого хранения данных используется также энергонезависимое хранилище данных NVS.
Энергонезависимое хранилища данных (Non-volatile storage, NVS) работает на уровне комплекта IDF и разработано оно для хранения пар ключ-значение в памяти FLASH.
Данный способ хранения порой упрощает работу с хранением структурированных данных, так как хранить их в файлах не совсем рентабельно. Так как файл надо полностью открыть, закрыть, перезаписать весь при незначительном изменении и т.д.
Комплект IDF предоставляет очень немалый функционал по работе с NVS, только полностью мы с ним со всем не будем в рамках данного занятия знакомиться, а познакомимся мы лишь с некоторой частью данного функционала. Это позволит нам не нагромождать теорию в мозгах и легче усвоить материал. То есть мы будем знакомиться лишь с той частью, которая, как я считаю нам пригодится в недалёком будущем, а если нам потребуется знать что-то ещё, мы всегда успеем это сделать.
Схема наша будет состоять из платы с контроллером ESP32, подключенной к порту USB компьютера
А проект мы возьмём из давнего урока 4, в котором мы работали с кнопкой, с именем BUTTON01 и на его основе создадим проект с именем NVS01.
Откроем наш проект в ESPRESSIF IDE и из файла Kconfig.projbuild удалим пункт меню по работе с ножкой, к которой была подключена кнопка (BUTTON_GPIO). Также в данном файле ножку порта по умолчанию для светодиода выберем вторую
default 48 if IDF_TARGET_ESP32S3
default 2
В функции app_main файла main.c оставим только вот эти строки
1 2 3 4 5 6 7 |
//------------------------------------------------------------- void app_main(void) { gpio_reset_pin(CONFIG_BLINK_GPIO); gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); } //------------------------------------------------------------- |
Не забываем также в конфигураторе выбрать 4 мегабайта объёма памяти FLASH
Попробуем собрать наш проект. Если всё нормально собирается, то ещё создадим заголовочный файл main.h, в который соберём все подключенные заголовочные файлы из main.c, а заодно и подключим заголовочные файлы для работы с хранилищем NVS
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //------------------------------------------------------------- #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "nvs_flash.h" #include "nvs.h" #include "sdkconfig.h" //------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Вернёмся в main.c и удалим подключение всех заголовочных файлов, а подключим только один
1 |
#include "main.h" |
В принципе, мы немного уже затрагивали тему хранилища NVS. По крайней мере, в проектах с использованием сети WiFi мы её инициализировали. Добавим такой же код инициализации в функции app_main и сейчас
1 2 3 4 5 6 7 |
gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); // Initialize NVS esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } |
Попробуем собрать проект и прошить его.
Запустим терминал и посмотрим, где у нас находится раздел хранилища nvs и каков его объём
С помощью esptool для чистоты эксперимента мы полностью очистим данный участок памяти
Вернёмся в наш проект в функцию app_main файла main.c и попробуем вывести терминал об уже существующих записях в хранилище
1 2 3 4 5 6 7 8 9 10 11 12 13 |
err = nvs_flash_init(); } //listing all the key-value pairs of any type under specified partition and namespace nvs_iterator_t it = NULL; it = nvs_entry_find("nvs", NULL, NVS_TYPE_ANY); while (it != NULL) { nvs_entry_info_t info; nvs_entry_info(it, &info); it = nvs_entry_next(it); printf("key '%s', type '%02x' \n", info.key, info.type); }; nvs_release_iterator(it); |
Краткое описание используемых функций в данном блоке кода:
nvs_entry_find возвращает итератор, который используется в последующих вызовах и который позволяет пролистать пары ключ-значение, сохраненные в NVS, основываясь на имени раздела, namespace, и типе данных;
nvs_entry_info возвращает информацию для каждой пары ключ-значение;
nvs_entry_next возвращает итератор для следующей пары ключ-значение.
Также следует отметить, что хранилище nvs позволяет работать с очень широким списком типов данных, а именно int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, строками, а также данными переменной длины (blob).
Соберём код, прошьём контроллер и посмотрим результат работы кода в терминале
Наш код нам ничего не вернул.
Это результат того, что мы очистили раздел. Если бы перед этим был бы прошит проект прошлого урока, то у нас бы были данные, так как мы работали с WiFi.
Поэтому давайте попробуем что-нибудь записать в хранилище.
Для этого мы сначала должны объявить хендл и открыть хранилище, передав ему пространство имён, способ открытия и адрес хендла. Затем мы пока закроем наше хранилище при удачном его открытии
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
nvs_release_iterator(it); nvs_handle_t my_handle; // Open printf("\n"); printf("Opening Non-Volatile Storage (NVS) handle... "); err = nvs_open("storage", NVS_READWRITE, &my_handle); if (err != ESP_OK) { printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err)); } else { printf("Done\n"); // Close nvs_close(my_handle); } printf("\n"); |
А дальнейший код мы уже будем, соответственно, писать перед закрытием хранилища nvs.
Попробуем собрать код и прошить контроллер.
В терминале мы видим, что хранилище удачно открылось
Попробуем что-нибудь записать в наше хранилище. Для этого сначала объявим инициализируем несколько переменных различных целочисленных типов
1 2 3 4 5 6 7 8 9 10 11 |
printf("Done\n"); // Write int8_t val01 = -108; uint8_t val02 = 198; int16_t val03 = -10135; uint16_t val04 = 61145; int32_t val05 = -101459; uint32_t val06 = 3352582294; int64_t val07 = -4047512760478312334; uint64_t val08 = 7214561357951254753; |
Затем попробуем записать данные значения в хранилище под различными именами ключей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
uint64_t val08 = 7214561357951254753; printf("Updating data in NVS ... "); err = nvs_set_i8(my_handle, "dt01", val01); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_u8(my_handle, "dt02", val02); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_i16(my_handle, "dt03", val03); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_u16(my_handle, "dt04", val04); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_i32(my_handle, "dt05", val05); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_u32(my_handle, "dt06", val06); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_i64(my_handle, "dt07", val07); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_u64(my_handle, "dt08", val08); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); |
Соберём код, прошьём контроллер и посмотрим результат
Если мы перезагрузим контроллер после этого, то предыдущий код по перебору записей нам также даст результаты
Мы можем не бояться, перезагружая контроллер, того, что у нас будет повторяться процесс записи и тем самым вызовет задвоение данных. Здесь ситуация такая. Если данные пишутся под уже существующим именем ключа, то они не создают новую пару ключ-значение в том случае, если не изменился тип данных, они просто модифицируются, если есть изменение значения.
Мы также видим в терминале только типы данных, сами данные мы не видим.
Давайте запишем ещё и строки
1 2 3 4 5 6 7 8 9 10 11 |
err = nvs_set_u64(my_handle, "dt08", val08); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_str(my_handle, "MySSID", "MyLogin"); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); err = nvs_set_str(my_handle, "MyPASS", "123abc7890"); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); printf("Committing updates in NVS ... "); err = nvs_commit(my_handle); printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); |
По окончанию записи мы вызываем функцию nvs_commit для применения изменений.
А чтобы видеть значения данных, для этого мы напишем код следующий.
Для начала в целях чистоты эксперимента мы объявим новые переменные, так как в существующих у нас уже есть значения
1 2 3 4 5 6 7 8 9 10 11 |
printf((err != ESP_OK) ? "Failed!\n" : "Done\n"); // Read int8_t res01 = 0; uint8_t res02 = 0; int16_t res03 = 0; uint16_t res04 = 0; int32_t res05 = 0; uint32_t res06 = 0; int64_t res07 = 0; uint64_t res08 = 0; |
Затем прочитаем пока целочисленные ключи и отобразим значения в терминале
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
uint64_t res08 = 0; nvs_get_i8(my_handle, "dt01", &res01); printf("dt01: %d\n", res01); nvs_get_u8(my_handle, "dt02", &res02); printf("dt02: %d\n", res02); nvs_get_i16(my_handle, "dt03", &res03); printf("dt03: %d\n", res03); nvs_get_u16(my_handle, "dt04", &res04); printf("dt04: %d\n", res04); nvs_get_i32(my_handle, "dt05", &res05); printf("dt05: %d\n", res05); nvs_get_u32(my_handle, "dt06", &res06); printf("dt06: %u\n", res06); nvs_get_i64(my_handle, "dt07", &res07); printf("dt07: %lld\n", res07); nvs_get_u64(my_handle, "dt08", &res08); printf("dt08: %llu\n", res08); |
Далее прочитаем ключи строкового (символьного) типа. Здесь всё несколько замороченно
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
printf("dt08: %llu\n", res08); printf("Reading MySSID from NVS ... "); size_t nvs_required_size; err = nvs_get_str(my_handle, "MySSID", NULL, &nvs_required_size); switch (err) { case ESP_OK: printf("Done\n"); char *nvs_ret_data; nvs_ret_data = malloc(nvs_required_size); err = nvs_get_str(my_handle, "MySSID", nvs_ret_data, &nvs_required_size ); printf("MySSID: %s\n", nvs_ret_data); free(nvs_ret_data); break; case ESP_ERR_NVS_NOT_FOUND: printf("The value is not initialized yet!\n"); break; default : printf("Error (%s) reading!\n", esp_err_to_name(err)); } printf("Reading MyPASS from NVS ... "); err = nvs_get_str(my_handle, "MyPASS", NULL, &nvs_required_size); switch (err) { case ESP_OK: printf("Done\n"); char *nvs_ret_data; nvs_ret_data = malloc(nvs_required_size); err = nvs_get_str(my_handle, "MyPASS", nvs_ret_data, &nvs_required_size ); printf("MyPASS: %s\n", nvs_ret_data); free(nvs_ret_data); break; case ESP_ERR_NVS_NOT_FOUND: printf("The value is not initialized yet!\n"); break; default : printf("Error (%s) reading!\n", esp_err_to_name(err)); } |
Соберём код, прошьём контроллер и посмотрим результат в терминале
Всё отлично прочиталось!
Итак, на данном уроке мы познакомились с энергонезависимым хранилищем NVS, а также поупражнялись с записью в данное хранилище и чтением из него различных типов данных.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий