飛書文檔https://x509p6c8to.feishu.cn/wiki/QB0Zw7GLeio4l4kyaWQcuQT3nZS
非易失性存儲 (NVS) 庫主要用于在 flash 中存儲鍵值格式的數據。
它允許我們在芯片的閃存中存儲和讀取數據,即使在斷電后,這些數據也不會丟失。
NVS 是 ESP32 flash(flash就是板子上的一個存儲芯片)中的一個存儲分區,我們可以在其中存儲鍵值對(key-value pairs)。每個鍵值對都有一個唯一的鍵名(key name)和一個對應的值(value)。這種組合類似于哈希表的(key-value)對應結構。
初始化NVS
在開始使用NVS之前,需要先初始化整個NVS分區。通常在應用程序啟動階段完成這一操作
esp_err_t nvs_flash_init(void);
返回值
esp_err_t
表示函數執行的結果,通常為以下幾種情況:
ESP_OK: 成功初始化 NVS(Non-Volatile Storage)閃存。
ESP_ERR_NVS_NO_FREE_PAGES: NVS 分區沒有可用的頁。
ESP_ERR_NVS_NEW_VERSION_FOUND: NVS 分區版本更新,需要格式化。
ESP_ERR_NVS_NOT_INITIALIZED: NVS 未初始化。
ESP_ERR_NVS_INVALID_STATE: NVS 已經初始化。
其他可能的錯誤代碼,具體取決于底層實現。示例參考:
#include <nvs_flash.h>
void app_main()
{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();}ESP_ERROR_CHECK(err);
}
使用 NVS 存儲數據
我們可以使用 nvs_open 、nvs_set_* 、nvs_commit三個函數來存儲數據。
nvs_open 函數用于打開一個 NVS 命名空間,并返回一個句柄,通過該句柄可以進行后續的讀寫操作。
esp_err_t nvs_open(const char *name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle);
參數
const char *name: NVS 命名空間的名稱。命名空間用于組織存儲的數據,類似于文件夾的概念。
nvs_open_mode_t open_mode: 打開命名空間的模式,可以是以下幾種:
NVS_READONLY: 只讀模式。
NVS_READWRITE: 讀寫模式。
nvs_handle_t *out_handle: 輸出參數,用于返回打開的命名空間的句柄。通過這個句柄可以進行后續的讀寫操作。nvs_set_i32 函數用于將一個 32 位整數值存儲到 NVS 中,使用指定的鍵名標識該數據。存儲的數據可以通過該鍵名在后續的讀取操作中檢索。
esp_err_t nvs_set_i32(nvs_handle_t handle, const char *key, int32_t value);
參數
nvs_handle_t handle: 通過 nvs_open 函數獲得的命名空間句柄。
const char *key: 要存儲的數據的鍵名。鍵名用于標識存儲的數據。
int32_t value: 要存儲的 32 位整數值。nvs_commit 函數用于將對 NVS 命名空間所做的更改(如設置、刪除鍵值對等)提交到閃存中。
在調用 nvs_set_i32 或其他設置函數后,必須調用 nvs_commit 以確保更改被持久化到 NVS 分區中。
如果不調用 nvs_commit,更改將不會保存到閃存中,下次讀取時將不會看到這些更改
esp_err_t nvs_commit(nvs_handle_t handle);
參數
nvs_handle_t handle: 通過 nvs_open 函數獲得的命名空間句柄。// 存儲數據
nvs_handle_t my_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle);??????? //打開命名空間
if (err == ESP_OK) {err = nvs_set_i32(my_handle, "restart_counter", 1);if (err == ESP_OK) {err = nvs_commit(my_handle);}nvs_close(my_handle);
}
在這個例子中,我們首先打開了一個名為 “storage” 的 NVS 名稱空間,然后在其中存儲了一個鍵名為 “restart_counter”、值為1的鍵值對。
而nvs_commit()函數的主要作用是將所有掛起的更改寫入NVS。當你在NVS中設置一個鍵值對后,這個更改首先被存儲在內存中。只有當你調用nvs_commit()函數時,這些更改才會被寫入閃存。
所以,如果你在調用nvs_commit()函數之前重啟了設備,那么你在NVS中設置的所有鍵值對都將丟失。因此,每次在NVS中設置鍵值對后,都應該調用nvs_commit()函數,以確保這些更改在設備重啟后仍然存在。
當然,還有其它不同類型的數據存儲接口
nvs_set_i8
功能: 存儲 8 位整數。
esp_err_t nvs_set_i8(nvs_handle_t handle, const char *key, int8_t value);nvs_set_u8
功能: 存儲 8 位無符號整數。
esp_err_t nvs_set_u8(nvs_handle_t handle, const char *key, uint8_t value);nvs_set_i16
功能: 存儲 16 位整數。
esp_err_t nvs_set_i16(nvs_handle_t handle, const char *key, int16_t value);nvs_set_u16
功能: 存儲 16 位無符號整數。
esp_err_t nvs_set_u16(nvs_handle_t handle, const char *key, uint16_t value);nvs_set_u32
功能: 存儲 32 位無符號整數。
esp_err_t nvs_set_u32(nvs_handle_t handle, const char *key, uint32_t value);nvs_set_i64
功能: 存儲 64 位整數。
esp_err_t nvs_set_i64(nvs_handle_t handle, const char *key, int64_t value);nvs_set_u64
功能: 存儲 64 位無符號整數。
esp_err_t nvs_set_u64(nvs_handle_t handle, const char *key, uint64_t value);nvs_set_str
功能: 存儲字符串。
esp_err_t nvs_set_str(nvs_handle_t handle, const char *key, const char *value);
參數:
handle: NVS 命名空間句柄。
key: 鍵名。
value: 要存儲的字符串。nvs_set_blob
功能: 存儲二進制數據塊。
esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *value, size_t length);
參數:
handle: NVS 命名空間句柄。
key: 鍵名。
value: 要存儲的二進制數據塊。
length: 數據塊的長度(以字節為單位)。使用示例:// 存儲不同類型的數據err = nvs_set_i8(my_handle, "int8_key", 10);ESP_ERROR_CHECK(err);err = nvs_set_u8(my_handle, "uint8_key", 20);ESP_ERROR_CHECK(err);err = nvs_set_i16(my_handle, "int16_key", 300);ESP_ERROR_CHECK(err);err = nvs_set_u16(my_handle, "uint16_key", 400);ESP_ERROR_CHECK(err);err = nvs_set_u32(my_handle, "uint32_key", 5000);ESP_ERROR_CHECK(err);err = nvs_set_i64(my_handle, "int64_key", 60000);ESP_ERROR_CHECK(err);err = nvs_set_u64(my_handle, "uint64_key", 70000);ESP_ERROR_CHECK(err);const char *str_value = "Hello, NVS!";err = nvs_set_str(my_handle, "str_key", str_value);ESP_ERROR_CHECK(err);uint8_t blob_value[] = {0x01, 0x02, 0x03, 0x04};size_t blob_length = sizeof(blob_value);err = nvs_set_blob(my_handle, "blob_key", blob_value, blob_length);ESP_ERROR_CHECK(err);
使用 NVS 讀取數據
我們可以使用 nvs_get_* 函數來讀取數據。例如,我們可以使用 nvs_get_i32 函數來讀取一個整數:
nvs_get_i32 函數用于從 NVS 中讀取一個 32 位整數值,使用指定的鍵名標識該數據。讀取的數據存儲在輸出參數中,可以通過該參數訪問。
esp_err_t nvs_get_i32(nvs_handle_t handle, const char *key, int32_t *out_value);
參數
nvs_handle_t handle: 通過 nvs_open 函數獲得的命名空間句柄。const char *key: 要讀取的數據的鍵名。鍵名用于標識存儲的數據。int32_t *out_value: 輸出參數,用于存儲從 NVS 中讀取的 32 位整數值。使用參考
// 檢索數據
nvs_handle_t my_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err == ESP_OK) {int32_t value;err = nvs_get_i32(my_handle, "restart_counter", &value);if (err == ESP_OK) {printf("Value = %d\n", value);}nvs_close(my_handle);
}
刪掉NVS上的數據
esp_err_t nvs_flash_erase(void);
返回值
esp_err_t
表示函數執行的結果,通常為以下幾種情況:
ESP_OK: 成功擦除 NVS 分區。
ESP_ERR_NVS_NOT_INITIALIZED: NVS 未初始化。
ESP_ERR_NVS_INVALID_STATE: NVS 處于無效狀態。
其他可能的錯誤代碼,具體取決于底層實現
最終參考程序:
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"static const char *TAG = "NVS"; // 定義日志標簽void app_main(void)
{// 初始化 NVSesp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// NVS 分區被截斷,需要擦除// 重新初始化 nvs_flashESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK(err);// 打開命名空間ESP_LOGI(TAG, "打開非易失性存儲 (NVS) 句柄...");nvs_handle_t my_handle;err = nvs_open("storage", NVS_READWRITE, &my_handle);if (err != ESP_OK) {ESP_LOGI(TAG, "打開 NVS 句柄時出錯 (%s)!\n", esp_err_to_name(err));} else {// 讀取重啟計數器ESP_LOGI(TAG, "從 NVS 讀取重啟計數器 ... ");int32_t restart_counter = 0; // 如果 NVS 中未設置值,則默認為 0err = nvs_get_i32(my_handle, "restart_counter", &restart_counter);ESP_LOGI(TAG, "重啟計數器 = %" PRIu32 "\n", restart_counter);// 更新重啟計數器ESP_LOGI(TAG, "更新 NVS 中的重啟計數器 ... ");restart_counter++;err = nvs_set_i32(my_handle, "restart_counter", restart_counter);// 提交寫入的值。設置任何值后,必須調用 nvs_commit() 以確保更改寫入閃存存儲。ESP_LOGI(TAG, "提交 NVS 中的更改 ... ");err = nvs_commit(my_handle);// 關閉命名空間nvs_close(my_handle);}ESP_LOGI(TAG, "\n");// 重啟模塊for (int i = 10; i >= 0; i--) {ESP_LOGI(TAG, "將在 %d 秒后重啟...\n", i);vTaskDelay(1000 / portTICK_PERIOD_MS);}ESP_LOGI(TAG, "現在重啟。\n");fflush(stdout);esp_restart();
}