NVS (Non-Volatile Storage) в ESP32 - это энергонезависимое хранилище данных, которое хранит пары ключ-значение во FLASH-памяти. Его удобно задействовать для сохранения конфигурации.


Заумь об организации памяти в ESP32

Флешка в ESP32 разделена на несколько разделов:

Минимальный размер каждого раздела - 4 Кб. Начальная область выделена под загрузчик, далее располагается NVS, данные для инициализации PHY, а так же сама прошивка. В более сложных варинтах под прошивку может быть выделено несколько разделов (например, если используется OTA).

NVS поддреживает различные типы данных:

  • целочисленные значения (uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t);
  • ASCIIZ-строки (признак окончания строки - NUL-символ);
  • blob (массивы двоичных данных).

Однако, для хранения больших массивов данных она не очень подходит. В таком случае лучше использовать отдельную файловую систему с выравниваем износа секторов.

Для избежания конфликтов в именах ключей, NVS использует пространсва имен (namespace). С помощью пространст имен удобно логически группировать данные. Максимальная длина имен пространств составляет 15 символов (кстати, как и для ключей). Для использования конкретного пространсва имен используются функции nvs_open() и nvs_open_from_partition().

Рассмотрим работу с NVS на примере. В первую очередь необходимо инициализировать хранилище:

esp_err_t nvs_utils_init() {
    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();
    }
    return err;
}

После инициализации, мы можем выполнять чтение, запись и очистку хранилища:
esp_err_t nvs_utils_get_str(const char* namespace_name, const char* name, char* val) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READONLY, &nvs);
    if (err == ESP_OK) {
        size_t len;
        if ((err = nvs_get_str(nvs, name, val, &len)) != ESP_OK) {
            return err;
        }
        if (val == NULL) strcpy(val, "");
        nvs_close(nvs);
    } else {
        return err;
    }
    return ESP_OK;
}


esp_err_t nvs_utils_set_str(const char* namespace_name, const char* name, const char* val) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READWRITE, &nvs);
    if (err != ESP_OK) {
        return err;
    }

    err = nvs_set_str(nvs, name, val);
    if (err != ESP_OK) {
        nvs_close(nvs);
        return err;
    }

    err = nvs_commit(nvs);

    nvs_close(nvs);
    return err;
}


esp_err_t nvs_utils_get_int(const char* namespace_name, const char* name, int32_t* val) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READONLY, &nvs);
    if (err == ESP_OK) {
        if ((err = nvs_get_i32(nvs, name, val)) != ESP_OK) {
            return err;
        }
        nvs_close(nvs);
    } else {
        return err;
    }
    return ESP_OK;
}


esp_err_t nvs_utils_set_int(const char* namespace_name, const char* name, const int32_t val) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READWRITE, &nvs);
    if (err != ESP_OK) {
        return err;
    }

    err = nvs_set_i32(nvs, name, val);
    if (err != ESP_OK) {
        nvs_close(nvs);
        return err;
    }

    err = nvs_commit(nvs);

    nvs_close(nvs);
    return err;
}


esp_err_t nvs_utils_erase_by_key(const char* namespace_name, const char *key) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READWRITE, &nvs);
    if (err == ESP_OK) {
        err = nvs_erase_key(nvs, key);
        if (err == ESP_OK) {
            err = nvs_commit(nvs);
        }
        nvs_close(nvs);
    }

    return err;
}


esp_err_t nvs_utils_erase_all(const char* namespace_name) {
    nvs_handle_t nvs;

    esp_err_t err = nvs_open(namespace_name, NVS_READWRITE, &nvs);
    if (err == ESP_OK) {
        err = nvs_erase_all(nvs);
        if (err == ESP_OK) {
            err = nvs_commit(nvs);
        }
    }

    nvs_close(nvs);
    return ESP_OK;
}

Проверяем в работе:

extern "C" void app_main(void) {
    char buffer[64];
    int32_t counter;

    auto err = nvs_utils_init();
    ESP_ERROR_CHECK(err);

    err = nvs_utils_get_str("storage", "label", buffer);
    if (err != ESP_OK) printf("Error read value: %s. Error code: %d\n" , "label", err);
    
    auto label = std::string(buffer);
    if (label != "Restart counter: ") label = "Restart counter: ";

    err = nvs_utils_get_int("storage", "counter", &counter);
    if (err != ESP_OK) printf("Error read value: %s. Error code: %d\n" , "counter", err);

    printf("%s %d\n", buffer, (int)counter);
    if (counter > 1000) counter = 0;
    else counter++;

    err = nvs_utils_set_str("storage", "label", label.c_str());
    if (err != ESP_OK) printf("Error write value: %s. Error code: %d\n" , "label", err);

    err = nvs_utils_set_int("storage", "counter", counter);
    if (err != ESP_OK) printf("Error write value: %s. Error code: %d\n" , "counter", err);

    for (int i = 15; i >= 0; i--) {
        printf("Перезагрузка через %d секунд...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
}