From 1f29a4c284b746ec64214d1a8b626dc1e291ea81 Mon Sep 17 00:00:00 2001 From: "radek.tandler" Date: Thu, 29 Jan 2026 15:16:06 +0100 Subject: [PATCH 1/2] docs(storage/nvs_flash): Improved NVS documentation - Added documentation of BDL support in NVS - Documentation structure was adjusted and refined for better clarity --- docs/en/api-reference/storage/nvs_flash.rst | 191 ++++++++++++-------- 1 file changed, 120 insertions(+), 71 deletions(-) diff --git a/docs/en/api-reference/storage/nvs_flash.rst b/docs/en/api-reference/storage/nvs_flash.rst index 00145c7587..f0ea30965b 100644 --- a/docs/en/api-reference/storage/nvs_flash.rst +++ b/docs/en/api-reference/storage/nvs_flash.rst @@ -8,41 +8,20 @@ Introduction Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This section introduces some concepts used by NVS. -Underlying Storage -^^^^^^^^^^^^^^^^^^ +Initialisation +^^^^^^^^^^^^^^ -Currently, NVS uses a portion of main flash memory through the :ref:`esp_partition ` API. The library uses all the partitions with ``data`` type and ``nvs`` subtype. The application can choose to use the partition with the label ``nvs`` through the :cpp:func:`nvs_open` API function or any other partition by specifying its name using the :cpp:func:`nvs_open_from_partition` API function. +NVS uses partitions from the partition table with ``data`` type and ``nvs`` subtype. The library can be initialised using: -Future versions of this library may have other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc. +- :cpp:func:`nvs_flash_init` - initialises the default NVS partition with label ``nvs`` +- :cpp:func:`nvs_flash_init_partition` - initialises a specific NVS partition by its label +- :cpp:func:`nvs_flash_init_partition_ptr` - initialises NVS from an ``esp_partition_t`` pointer -.. note:: if an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase-flash`` target to erase all contents of the flash chip. +Once initialised, applications access NVS namespaces using :cpp:func:`nvs_open` (for the default partition) or :cpp:func:`nvs_open_from_partition` (for a specific partition by label). -.. note:: NVS works best for storing many small values, rather than a few large values of the type 'string' and 'blob'. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library. +.. note:: NVS can also operate through the Block Device Layer (BDL) when :ref:`CONFIG_NVS_BDL_STACK` is enabled, allowing use of alternative storage backends beyond standard flash partitions. In BDL mode, :cpp:func:`nvs_flash_init_partition_ptr` is not available, but :cpp:func:`nvs_flash_init_partition_bdl` becomes available for custom block device initialisation. See :ref:`nvs_underlying_storage` in Internals for details. -.. note:: NVS component includes flash wear levelling by design. Set operations are appending new data to the free space after existing entries. Invalidation of old values doesn't require immediate flash erase operations. The organization of NVS space to pages and entries effectively reduces the frequency of flash erase to flash write operations by a factor of 126. - -Large Amount of Data in NVS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Although not recommended, NVS can store tens of thousands of keys and NVS partition can reach up to megabytes in size. - -.. note:: NVS component leaves RAM footprint on the heap. The footprint depends on the size of the NVS partition on flash and the number of keys in use. For RAM usage estimation, please use the following approximate figures: each 1 MB of NVS flash partition consumes 22 KB of RAM and each 1000 keys consumes 5.5 KB of RAM. - -.. note:: Duration of NVS initialization using :cpp:func:`nvs_flash_init` is proportional to the number of existing keys. Initialization of NVS requires approximately 0.5 seconds per 1000 keys. - -.. only:: SOC_SPIRAM_SUPPORTED - - By default, internal NVS allocates a heap in internal RAM. With a large NVS partition or big number of keys, the application can exhaust the internal RAM heap just on NVS overhead. - Applications using modules with SPI-connected PSRAM can overcome this limitation by enabling the Kconfig option :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` which redirects RAM allocation to the SPI-connected PSRAM. - This option is available in the nvs_flash component of the menuconfig menu when SPIRAM is enabled and and :ref:`CONFIG_SPIRAM_USE` is set to ``CONFIG_SPIRAM_USE_CAPS_ALLOC``. - .. note:: Using SPI-connected PSRAM slows down NVS API for integer operations by an approximate factor of 2.5. - -.. _nvs_bootloader: - -Use of NVS in Bootloader code ------------------------------ - -The standard NVS API described in this guide is available to the running application. It is also possible to read data from NVS in the custom bootloader code. More information can be found in the :doc:`nvs_bootloader` guide. +.. note:: If an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase-flash`` target to erase all contents of the flash chip. Keys and Values ^^^^^^^^^^^^^^^ @@ -53,15 +32,13 @@ NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length - zero-terminated string - variable length binary data (blob) -.. note:: +.. note:: NVS works best for storing many small values, rather than a few large values of the type 'string' and 'blob'. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library. - String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508,000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower. +.. note:: String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508,000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower. -.. note:: +.. note:: Before setting new or updating existing key-value pair, free entries in nvs pages have to be available. For integer types, at least one free entry has to be available. For the String value, at least one page capable of keeping the whole string in a contiguous row of free entries has to be available. For the Blob value, the size of new data has to be available in free entries. - Before setting new or updating existing key-value pair, free entries in nvs pages have to be available. For integer types, at least one free entry has to be available. For the String value, at least one page capable of keeping the whole string in a contiguous row of free entries has to be available. For the Blob value, the size of new data has to be available in free entries. - -Additional types, such as ``float`` and ``double`` might be added later. +Additional data types, such as ``float`` and ``double`` might be added later. Keys are required to be unique. Assigning a new value to an existing key replaces the old value and data type with the value and data type specified by a write operation. @@ -71,7 +48,7 @@ A data type check is performed when reading a value. An error is returned if the Namespaces ^^^^^^^^^^ -To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Furthermore, there can be no more than 254 different namespaces in one NVS partition. Namespace name is specified in the :cpp:func:`nvs_open` or :cpp:type:`nvs_open_from_partition` call. +To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of the namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Furthermore, there can be no more than 254 different namespaces in one NVS partition. Namespace name is specified in the :cpp:func:`nvs_open` or :cpp:type:`nvs_open_from_partition` call. This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace and partition, and key names will not collide with same names in other namespaces. The open mode parameter controls the access level and security behavior: @@ -81,24 +58,7 @@ The open mode parameter controls the access level and security behavior: This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces. -.. _data_purging_security: - -Data Purging and Security -^^^^^^^^^^^^^^^^^^^^^^^^^ - -By default, when NVS updates or erases key-value pairs, the old data is only marked as erased but remains physically present in flash memory. This approach optimizes write performance and flash wear leveling, but the erased data could potentially be recovered with physical access to the flash chip. - -For applications requiring enhanced security where sensitive data must be physically removed from flash memory, NVS provides two mechanisms: - -**Purging Mode** - When opening a namespace with ``NVS_READWRITE_PURGE`` mode, all write and erase operations automatically purge (physically wipe) the previously stored data in addition to writing new values. This ensures that old data cannot be recovered from flash memory. - -**Manual Purging** - The :cpp:func:`nvs_purge_all` function allows applications to explicitly purge all erased items within a namespace at any time. This can be used with handles opened in either ``NVS_READWRITE`` or ``NVS_READWRITE_PURGE`` mode. - -.. note:: - - Purging operations require additional flash write cycles compared to standard erase operations. Applications should balance security requirements with flash wear considerations when deciding whether to use purging features. +.. note:: Namespaces with the same name in different NVS partitions are considered as separate namespaces. NVS Iterators ^^^^^^^^^^^^^ @@ -113,7 +73,7 @@ There are the following functions available: In general, all iterators obtained via :cpp:func:`nvs_entry_find` have to be released using :cpp:func:`nvs_release_iterator`, which also tolerates ``NULL`` iterators. -:cpp:func:`nvs_entry_find` and :cpp:func:`nvs_entry_next` set the given iterator to ``NULL`` or a valid iterator in all cases except a parameter error occurred (i.e., return ``ESP_ERR_NVS_NOT_FOUND``). In case of a parameter error, the given iterator will not be modified. Hence, it is best practice to initialize the iterator to ``NULL`` before calling :cpp:func:`nvs_entry_find` to avoid complicated error checking before releasing the iterator. +:cpp:func:`nvs_entry_find` and :cpp:func:`nvs_entry_next` set the given iterator to ``NULL`` or a valid iterator in all cases except a parameter error occurred (i.e., return ``ESP_ERR_NVS_NOT_FOUND``). In case of a parameter error, the given iterator will not be modified. Hence, it is best practice to initialise the iterator to ``NULL`` before calling :cpp:func:`nvs_entry_find` to avoid complicated error checking before releasing the iterator. Security, Tampering, and Robustness @@ -127,19 +87,72 @@ Security, Tampering, and Robustness NVS is not directly compatible with the {IDF_TARGET_NAME} flash encryption system. However, data can still be stored in encrypted form if NVS encryption is used together with {IDF_TARGET_NAME} flash encryption or with the help of the HMAC peripheral. Please refer to :doc:`nvs_encryption` for more details. -If NVS encryption is not used, it is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. With NVS encryption enabled, it is not possible to alter or add a key-value pair and get recognized as a valid pair without knowing corresponding NVS encryption keys. However, there is no tamper-resistance against the erase operation. - -**Data Recovery Prevention**: By default, when key-value pairs are updated or erased, the old data remains physically present in flash memory and is only marked as invalid. For sensitive applications where old data must not be recoverable, use the ``NVS_READWRITE_PURGE`` mode when opening namespaces, or call :cpp:func:`nvs_purge_all` to explicitly purge erased data. See the :ref:`Data Purging and Security ` section for details. +If NVS encryption is not used, it is possible for anyone with physical access to the flash chip to read, alter, erase, or add key-value pairs. With NVS encryption enabled, it is not possible to read, alter or add a key-value pair and get recognized as a valid pair without knowing corresponding NVS encryption keys. However, there is no tamper-resistance against the erase operation. The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, except for the new key-value pair if it was being written at the moment of powering off. The library should also be able to initialize properly with any random data present in flash memory. -Unstable Power Conditions -------------------------- +.. _data_purging_security: -When NVS is used in systems powered by weak or unstable energy sources (such as solar or battery), flash erase operations may occasionally fail to complete without being detected by the application. This can create a mismatch between the actual flash contents and the expected layout of reserved pages. In rare cases, especially during unexpected power loss, this may exhaust the available NVS pages and cause partition initialization to fail with the error ``ESP_ERR_NVS_NO_FREE_PAGES``. +Data Purging and Security +^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, when NVS updates or erases key-value pairs, the data in flash is only marked as erased in metadata section. The values still remain in flash memory. This approach optimizes write performance. + +For applications requiring enhanced security where sensitive data must be physically removed (purged in terms of setting all bits to zeroes) from flash memory after calling erase or update functions, NVS provides two mechanisms: + +**One Time Purge** + The :cpp:func:`nvs_purge_all` function purges all existing items marked as erased within a namespace. It is useful in scenarios where the continuous purging mode was not used yet and the application needs to purge existing erased flash content. This function may be used with handles opened either using ``NVS_READWRITE`` or ``NVS_READWRITE_PURGE`` mode. + +**Continuous Purging Mode** + Namespace handle opened with ``NVS_READWRITE_PURGE`` mode automatically purges the flash space occupied by the erased or overwritten values on top of just marking them as erased. + +.. note:: + + Opening NVS namespace in ``NVS_READWRITE_PURGE`` mode does not purge data marked as erased already present in the flash. Perform One Time Purge before using Continuous Purging Mode if the namespace already contains some updated or erased data. + +.. note:: + + Purging operations represent additional flash write cycles compared to standard marking as erased mode. Applications should balance security requirements with flash write performance when deciding whether to use data purging features. + + +Special Use Cases +----------------- + +Large Amount of Data in NVS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Although not recommended, NVS can store tens of thousands of keys and NVS partition can reach up to megabytes in size. + +.. note:: NVS component leaves RAM footprint on the heap. The footprint depends on the size of the NVS partition on flash and the number of keys in use. For RAM usage estimation, please use the following approximate figures: each 1 MB of NVS flash partition consumes 22 KB of RAM and each 1000 keys consumes 5.5 KB of RAM. + +.. note:: Duration of NVS initialisation using :cpp:func:`nvs_flash_init` is proportional to the number of existing keys. Initialisation of NVS requires typically 0.5 seconds per 1000 keys. + +.. note:: The duration of initialisation gradually grows with the population of NVS partition and number of value updates. To avoid unexpected watchdog timeout during initialisation when your application is already in customer use, test your NVS initialisation in advance on a NVS partition containing all keys including expected history of updates. + +.. only:: SOC_SPIRAM_SUPPORTED + + By default, internal NVS allocates a heap in internal RAM. With a large NVS partition or big number of keys, the application can exhaust the internal RAM heap just on NVS overhead. + Applications using modules with SPI-connected PSRAM can overcome this limitation by enabling the Kconfig option :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` which redirects RAM allocation to the SPI-connected PSRAM. + This option is available in the nvs_flash component of the menuconfig menu when SPIRAM is enabled and :ref:`CONFIG_SPIRAM_USE` is set to ``CONFIG_SPIRAM_USE_CAPS_ALLOC``. + .. note:: Using SPI-connected PSRAM slows down NVS API for integer operations by an approximate factor of 2.5. + +Unstable Power Conditions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +When NVS is used in systems powered by weak or unstable energy sources (such as solar or battery), flash erase operations may occasionally fail to complete without being detected by the application. This can create a mismatch between the actual flash contents and the expected layout of reserved pages. In rare cases, especially during unexpected power loss, this may exhaust the available NVS pages and cause partition initialisation to fail with the error ``ESP_ERR_NVS_NO_FREE_PAGES``. To address this issue, the Kconfig option :ref:`CONFIG_NVS_FLASH_VERIFY_ERASE` enables verification of flash erase operations by reading back the affected page. If the page is not fully erased to ``0xFF`` after a ``flash_erase`` operation, the erase is retried until the page is correctly cleared. The total number of erase attempts, including the initial attempt, is controlled by the Kconfig option :ref:`CONFIG_NVS_FLASH_ERASE_ATTEMPTS`. +.. note:: When NVS is initialised on the writeable partition, the library will attempt to perform recovery operations if the partition is found to be in an inconsistent state. This may involve erasing and rewriting some pages and in continuously unstable power environment subsequently lead to the unintended loss of factory default data. For this reason, it is recommended to keep vital factory default data in separate, read-only partition where the recovery is not performed. + + +.. _nvs_bootloader: + +Use of NVS in Bootloader Code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The standard NVS API described in this guide is available to the running application. It is also possible to read data from NVS in the custom bootloader code. More information can be found in the :doc:`nvs_bootloader` guide. + .. _nvs_encryption: NVS Encryption @@ -197,7 +210,7 @@ You can find code examples in the :example:`storage/nvs` directory of ESP-IDF ex The value checked in this example holds the number of the {IDF_TARGET_NAME} module restarts. The value's function as a counter is only possible due to its storing in NVS. - The example also shows how to check if a read/write operation was successful, or if a certain value has not been initialized in NVS. The diagnostic procedure is provided in plain text to help you track the program flow and capture any issues on the way. + The example also shows how to check if a read/write operation was successful, or if a certain value has not been initialised in NVS. The diagnostic procedure is provided in plain text to help you track the program flow and capture any issues on the way. :example:`storage/nvs/nvs_rw_blob` @@ -236,20 +249,18 @@ Log of Key-Value Pairs NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, a new key-value pair is added at the end of the log and the old key-value pair is marked as erased. -**Standard Erase Behavior**: By default, erased entries remain physically present in flash memory with only their state bits changed to indicate they are no longer valid. This optimizes performance and reduces flash wear. - -**Purge Behavior**: When using ``NVS_READWRITE_PURGE`` mode or calling :cpp:func:`nvs_purge_all`, the library physically overwrites the content of erased entries to prevent data recovery. This operation incurs additional flash write cycles but provides enhanced security for sensitive data. +.. note:: NVS component includes flash wear levelling by design. Set operations are appending new data to the free space after existing entries. Invalidation of old values doesn't require immediate flash erase operations. The organization of NVS space to pages and entries effectively reduces the frequency of flash erase to flash write operations for data types fitting one entry by a factor of 126. Pages and Entries ^^^^^^^^^^^^^^^^^ NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states: -Empty/uninitialized +Empty/uninitialised Flash storage for the page is empty (all bytes are ``0xff``). Page is not used to store any data at this point and does not have a sequence number. Active - Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment. + Flash storage is initialised, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment. Full Flash storage is in a consistent state and is filled with key-value pairs. @@ -259,7 +270,7 @@ Erasing Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e., page should never stay in this state at the time when any API call returns. In case of a sudden power off, the move-and-erase process will be completed upon the next power-on. Corrupted - Page header contains invalid data, and further parsing of page data was canceled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in **uninitialized** state for later use. This may be useful for debugging. + Page header contains invalid data, and further parsing of page data was cancelled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in **uninitialised** state for later use. This may be useful for debugging. Mapping from flash sectors to logical pages does not have any particular order. The library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers. @@ -319,7 +330,7 @@ Entry and Entry State Bitmap Each entry can be in one of the following three states represented with two bits in the entry state bitmap. The final four bits in the bitmap (256 - 2 * 126) are not used. Empty (2'b11) - Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes are ``0xff``). + Nothing is written into the specific entry yet. It is in an uninitialised state (all bytes are ``0xff``). Written (2'b10) A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry. @@ -429,7 +440,45 @@ Each node in the hash list contains a 24-bit hash and 8-bit item index. Hash is Read-only NVS ^^^^^^^^^^^^^^ -The default minimal size for NVS to function properly is 12kiB (``0x3000``), meaning there have to be at least 3 pages with one of them being in Empty state. However if the NVS partition is flagged as ``readonly`` in the partition table CSV and is being opened in read-only mode, the partition can be as small as 4kiB (``0x1000``) with only one page in Active state and no Empty page. This is because the library does not need to write any data to the partition in this case. The partition can be used to store data that is not expected to change, such as calibration data or factory settings. Partitions of sizes 0x1000 and 0x2000 are always read-only and partitions of size 0x3000 and above are always read-write capable (still can be opened in read-only mode in the code). +There are two levels of read-only NVS support: +- At the partition table level, a partition can be flagged as ``readonly`` in the partition table CSV file. +- At the application level, NVS partitions can be opened in read-only mode using the :cpp:func:`nvs_open_from_partition` function with the ``NVS_READONLY`` flag. + +The default minimal size for NVS to function properly is 12kiB (``0x3000``), meaning there have to be at least 3 pages with at least one of them being in Empty state. However if the NVS partition is flagged as ``readonly`` in the partition table CSV and is being opened in read-only mode, the partition can be as small as 4kiB (``0x1000``). + +.. note:: At the moment read-only flag is not reflected if NVS is using Block Device Layer as a storage. + +.. _nvs_underlying_storage: + +Underlying Storage +^^^^^^^^^^^^^^^^^^ + +At build time the mode NVS will use for accessing its underlying storage can be configured. Two options are available in menuconfig option :ref:`CONFIG_NVS_BDL_STACK`. + +**ESP Partition API (default)**: NVS accesses storage using the :ref:`esp_partition `. +This is the default mode of operation, where NVS uses SPI flash partitions defined in the partition table. In this mode: + +- Initialisation functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) look up the partition and use :ref:`esp_partition ` APIs to access it. +- The application can provide custom ``esp_partition_t`` pointer and call :cpp:func:`nvs_flash_init_partition_ptr`. This enables applications to overcome limitations of partition table based partitions, such as using partitions not defined in the partition table. +- This mode provides the best performance, as there is no additional abstraction layer between NVS and the underlying storage. + +**Block Device Layer (BDL)**: NVS accesses storage using esp_blockdev. +This option enables NVS to operate on a block device that implements the esp_blockdev interface. In this mode: + +- Initialisation functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) transparently create block devices using :ref:`esp_partition `, manage their lifecycles and internally use the esp_blockdev. For the application, this mode is similar to the default mode of operation. +- The application can provide custom block device handle and call :cpp:func:`nvs_flash_init_partition_bdl` to register it for NVS. The application is responsible for managing the block device handle lifecycle. +- As the BDL abstracts the underlying storage, there is an overhead compared to using :ref:`esp_partition ` APIs directly. Choose this mode when esp_partition backed storage is not sufficient for the needs of the application. + +.. note:: + + If custom Block Device is used in NVS, it has to satisfy the following requirements: + + - ``read_size`` and ``write_size`` must be 1 byte (NVS requires byte-level granular access) + - ``erase_size`` must be a divisor of 4096 (NVS page size is fixed at 4096 bytes) + - ``disk_size`` must be a multiple of 4096 bytes + - ``default_val_after_erase`` flag must be set to 1 (erased memory reads as ``0xFF``) + - Operations (``read``, ``write``, ``erase``) must be implemented + API Reference From bb6757e6599a62fefcfc678715d4680399b97bd5 Mon Sep 17 00:00:00 2001 From: Zhang Shuxian Date: Fri, 20 Mar 2026 14:15:35 +0800 Subject: [PATCH 2/2] docs: Update CN translation for nvs_flash --- docs/en/api-reference/storage/nvs_flash.rst | 108 ++++++---- .../zh_CN/api-reference/storage/nvs_flash.rst | 195 ++++++++++++------ 2 files changed, 206 insertions(+), 97 deletions(-) diff --git a/docs/en/api-reference/storage/nvs_flash.rst b/docs/en/api-reference/storage/nvs_flash.rst index f0ea30965b..bfcc29acec 100644 --- a/docs/en/api-reference/storage/nvs_flash.rst +++ b/docs/en/api-reference/storage/nvs_flash.rst @@ -8,20 +8,24 @@ Introduction Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This section introduces some concepts used by NVS. -Initialisation +Initialization ^^^^^^^^^^^^^^ -NVS uses partitions from the partition table with ``data`` type and ``nvs`` subtype. The library can be initialised using: +NVS uses partitions from the partition table with ``data`` type and ``nvs`` subtype. The library can be initialized using: -- :cpp:func:`nvs_flash_init` - initialises the default NVS partition with label ``nvs`` -- :cpp:func:`nvs_flash_init_partition` - initialises a specific NVS partition by its label -- :cpp:func:`nvs_flash_init_partition_ptr` - initialises NVS from an ``esp_partition_t`` pointer +- :cpp:func:`nvs_flash_init` initializes the default NVS partition with label ``nvs``. +- :cpp:func:`nvs_flash_init_partition` initializes a specific NVS partition by its label. +- :cpp:func:`nvs_flash_init_partition_ptr` initializes NVS from an ``esp_partition_t`` pointer. -Once initialised, applications access NVS namespaces using :cpp:func:`nvs_open` (for the default partition) or :cpp:func:`nvs_open_from_partition` (for a specific partition by label). +Once initialized, applications access NVS namespaces using :cpp:func:`nvs_open` (for the default partition) or :cpp:func:`nvs_open_from_partition` (for a specific partition by label). -.. note:: NVS can also operate through the Block Device Layer (BDL) when :ref:`CONFIG_NVS_BDL_STACK` is enabled, allowing use of alternative storage backends beyond standard flash partitions. In BDL mode, :cpp:func:`nvs_flash_init_partition_ptr` is not available, but :cpp:func:`nvs_flash_init_partition_bdl` becomes available for custom block device initialisation. See :ref:`nvs_underlying_storage` in Internals for details. +.. note:: -.. note:: If an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase-flash`` target to erase all contents of the flash chip. + NVS can also operate through the Block Device Layer (BDL) when :ref:`CONFIG_NVS_BDL_STACK` is enabled, allowing use of alternative storage backends beyond standard flash partitions. In BDL mode, :cpp:func:`nvs_flash_init_partition_ptr` is not available, but :cpp:func:`nvs_flash_init_partition_bdl` becomes available for custom block device initialization. See :ref:`nvs_internals` > :ref:`nvs_underlying_storage` for details. + +.. note:: + + If an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase-flash`` target to erase all contents of the flash chip. Keys and Values ^^^^^^^^^^^^^^^ @@ -32,11 +36,17 @@ NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length - zero-terminated string - variable length binary data (blob) -.. note:: NVS works best for storing many small values, rather than a few large values of the type 'string' and 'blob'. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library. +.. note:: -.. note:: String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508,000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower. + NVS works best for storing many small values, rather than a few large values of the type ``string`` and ``blob``. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library. -.. note:: Before setting new or updating existing key-value pair, free entries in nvs pages have to be available. For integer types, at least one free entry has to be available. For the String value, at least one page capable of keeping the whole string in a contiguous row of free entries has to be available. For the Blob value, the size of new data has to be available in free entries. +.. note:: + + String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508,000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower. + +.. note:: + + Before setting new or updating existing key-value pair, free entries in nvs pages have to be available. For integer types, at least one free entry has to be available. For the string value, at least one page capable of keeping the whole string in a contiguous row of free entries has to be available. For the blob value, the size of new data has to be available in free entries. Additional data types, such as ``float`` and ``double`` might be added later. @@ -56,9 +66,9 @@ The open mode parameter controls the access level and security behavior: - ``NVS_READWRITE``: Standard read-write access. Erased data is marked as deleted but remains in flash. - ``NVS_READWRITE_PURGE``: Secure read-write access. Erased data is physically purged from flash memory. -This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces. +.. note:: -.. note:: Namespaces with the same name in different NVS partitions are considered as separate namespaces. + Namespaces with the same name in different NVS partitions are considered as separate namespaces. NVS Iterators ^^^^^^^^^^^^^ @@ -73,7 +83,7 @@ There are the following functions available: In general, all iterators obtained via :cpp:func:`nvs_entry_find` have to be released using :cpp:func:`nvs_release_iterator`, which also tolerates ``NULL`` iterators. -:cpp:func:`nvs_entry_find` and :cpp:func:`nvs_entry_next` set the given iterator to ``NULL`` or a valid iterator in all cases except a parameter error occurred (i.e., return ``ESP_ERR_NVS_NOT_FOUND``). In case of a parameter error, the given iterator will not be modified. Hence, it is best practice to initialise the iterator to ``NULL`` before calling :cpp:func:`nvs_entry_find` to avoid complicated error checking before releasing the iterator. +:cpp:func:`nvs_entry_find` and :cpp:func:`nvs_entry_next` set the given iterator to ``NULL`` or a valid iterator in all cases except a parameter error occurred (i.e., return ``ESP_ERR_NVS_NOT_FOUND``). In case of a parameter error, the given iterator will not be modified. Hence, it is best practice to initialize the iterator to ``NULL`` before calling :cpp:func:`nvs_entry_find` to avoid complicated error checking before releasing the iterator. Security, Tampering, and Robustness @@ -101,9 +111,11 @@ By default, when NVS updates or erases key-value pairs, the data in flash is onl For applications requiring enhanced security where sensitive data must be physically removed (purged in terms of setting all bits to zeroes) from flash memory after calling erase or update functions, NVS provides two mechanisms: **One Time Purge** + The :cpp:func:`nvs_purge_all` function purges all existing items marked as erased within a namespace. It is useful in scenarios where the continuous purging mode was not used yet and the application needs to purge existing erased flash content. This function may be used with handles opened either using ``NVS_READWRITE`` or ``NVS_READWRITE_PURGE`` mode. **Continuous Purging Mode** + Namespace handle opened with ``NVS_READWRITE_PURGE`` mode automatically purges the flash space occupied by the erased or overwritten values on top of just marking them as erased. .. note:: @@ -123,27 +135,40 @@ Large Amount of Data in NVS Although not recommended, NVS can store tens of thousands of keys and NVS partition can reach up to megabytes in size. -.. note:: NVS component leaves RAM footprint on the heap. The footprint depends on the size of the NVS partition on flash and the number of keys in use. For RAM usage estimation, please use the following approximate figures: each 1 MB of NVS flash partition consumes 22 KB of RAM and each 1000 keys consumes 5.5 KB of RAM. +.. note:: -.. note:: Duration of NVS initialisation using :cpp:func:`nvs_flash_init` is proportional to the number of existing keys. Initialisation of NVS requires typically 0.5 seconds per 1000 keys. + NVS component leaves RAM footprint on the heap. The footprint depends on the size of the NVS partition on flash and the number of keys in use. For RAM usage estimation, please use the following approximate figures: each 1 MB of NVS flash partition consumes 22 KB of RAM and each 1000 keys consumes 5.5 KB of RAM. -.. note:: The duration of initialisation gradually grows with the population of NVS partition and number of value updates. To avoid unexpected watchdog timeout during initialisation when your application is already in customer use, test your NVS initialisation in advance on a NVS partition containing all keys including expected history of updates. +.. note:: + + Duration of NVS initialization using :cpp:func:`nvs_flash_init` is proportional to the number of existing keys. Initialization of NVS requires typically 0.5 seconds per 1000 keys. + +.. note:: + + The duration of initialization gradually grows with the population of NVS partition and number of value updates. To avoid unexpected watchdog timeout during initialization when your application is already in customer use, test your NVS initialization in advance on a NVS partition containing all keys including expected history of updates. .. only:: SOC_SPIRAM_SUPPORTED By default, internal NVS allocates a heap in internal RAM. With a large NVS partition or big number of keys, the application can exhaust the internal RAM heap just on NVS overhead. + Applications using modules with SPI-connected PSRAM can overcome this limitation by enabling the Kconfig option :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` which redirects RAM allocation to the SPI-connected PSRAM. + This option is available in the nvs_flash component of the menuconfig menu when SPIRAM is enabled and :ref:`CONFIG_SPIRAM_USE` is set to ``CONFIG_SPIRAM_USE_CAPS_ALLOC``. - .. note:: Using SPI-connected PSRAM slows down NVS API for integer operations by an approximate factor of 2.5. + + .. note:: + + Using SPI-connected PSRAM slows down NVS API for integer operations by an approximate factor of 2.5. Unstable Power Conditions ^^^^^^^^^^^^^^^^^^^^^^^^^ -When NVS is used in systems powered by weak or unstable energy sources (such as solar or battery), flash erase operations may occasionally fail to complete without being detected by the application. This can create a mismatch between the actual flash contents and the expected layout of reserved pages. In rare cases, especially during unexpected power loss, this may exhaust the available NVS pages and cause partition initialisation to fail with the error ``ESP_ERR_NVS_NO_FREE_PAGES``. +When NVS is used in systems powered by weak or unstable energy sources (such as solar or battery), flash erase operations may occasionally fail to complete without being detected by the application. This can create a mismatch between the actual flash contents and the expected layout of reserved pages. In rare cases, especially during unexpected power loss, this may exhaust the available NVS pages and cause partition initialization to fail with the error ``ESP_ERR_NVS_NO_FREE_PAGES``. To address this issue, the Kconfig option :ref:`CONFIG_NVS_FLASH_VERIFY_ERASE` enables verification of flash erase operations by reading back the affected page. If the page is not fully erased to ``0xFF`` after a ``flash_erase`` operation, the erase is retried until the page is correctly cleared. The total number of erase attempts, including the initial attempt, is controlled by the Kconfig option :ref:`CONFIG_NVS_FLASH_ERASE_ATTEMPTS`. -.. note:: When NVS is initialised on the writeable partition, the library will attempt to perform recovery operations if the partition is found to be in an inconsistent state. This may involve erasing and rewriting some pages and in continuously unstable power environment subsequently lead to the unintended loss of factory default data. For this reason, it is recommended to keep vital factory default data in separate, read-only partition where the recovery is not performed. +.. note:: + + When NVS is initialized on the writeable partition, the library will attempt to perform recovery operations if the partition is found to be in an inconsistent state. This may involve erasing and rewriting some pages and in continuously unstable power environment subsequently lead to the unintended loss of factory default data. For this reason, it is recommended to keep vital factory default data in separate, read-only partition where the recovery is not performed. .. _nvs_bootloader: @@ -210,7 +235,7 @@ You can find code examples in the :example:`storage/nvs` directory of ESP-IDF ex The value checked in this example holds the number of the {IDF_TARGET_NAME} module restarts. The value's function as a counter is only possible due to its storing in NVS. - The example also shows how to check if a read/write operation was successful, or if a certain value has not been initialised in NVS. The diagnostic procedure is provided in plain text to help you track the program flow and capture any issues on the way. + The example also shows how to check if a read/write operation was successful, or if a certain value has not been initialized in NVS. The diagnostic procedure is provided in plain text to help you track the program flow and capture any issues on the way. :example:`storage/nvs/nvs_rw_blob` @@ -241,6 +266,8 @@ You can find code examples in the :example:`storage/nvs` directory of ESP-IDF ex After that, the example iterates over each individual data type as well as the generic ``NVS_TYPE_ANY`` type, and logs the information obtained from each iteration. +.. _nvs_internals: + Internals --------- @@ -249,18 +276,20 @@ Log of Key-Value Pairs NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, a new key-value pair is added at the end of the log and the old key-value pair is marked as erased. -.. note:: NVS component includes flash wear levelling by design. Set operations are appending new data to the free space after existing entries. Invalidation of old values doesn't require immediate flash erase operations. The organization of NVS space to pages and entries effectively reduces the frequency of flash erase to flash write operations for data types fitting one entry by a factor of 126. +.. note:: + + NVS component includes flash wear levelling by design. Set operations are appending new data to the free space after existing entries. Invalidation of old values doesn't require immediate flash erase operations. The organization of NVS space to pages and entries effectively reduces the frequency of flash erase to flash write operations for data types fitting one entry by a factor of 126. Pages and Entries ^^^^^^^^^^^^^^^^^ NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states: -Empty/uninitialised +Empty/uninitialized Flash storage for the page is empty (all bytes are ``0xff``). Page is not used to store any data at this point and does not have a sequence number. Active - Flash storage is initialised, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment. + Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment. Full Flash storage is in a consistent state and is filled with key-value pairs. @@ -270,7 +299,7 @@ Erasing Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e., page should never stay in this state at the time when any API call returns. In case of a sudden power off, the move-and-erase process will be completed upon the next power-on. Corrupted - Page header contains invalid data, and further parsing of page data was cancelled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in **uninitialised** state for later use. This may be useful for debugging. + Page header contains invalid data, and further parsing of page data was cancelled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in **uninitialized** state for later use. This may be useful for debugging. Mapping from flash sectors to logical pages does not have any particular order. The library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers. @@ -330,7 +359,7 @@ Entry and Entry State Bitmap Each entry can be in one of the following three states represented with two bits in the entry state bitmap. The final four bits in the bitmap (256 - 2 * 126) are not used. Empty (2'b11) - Nothing is written into the specific entry yet. It is in an uninitialised state (all bytes are ``0xff``). + Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes are ``0xff``). Written (2'b10) A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry. @@ -441,12 +470,15 @@ Read-only NVS ^^^^^^^^^^^^^^ There are two levels of read-only NVS support: + - At the partition table level, a partition can be flagged as ``readonly`` in the partition table CSV file. - At the application level, NVS partitions can be opened in read-only mode using the :cpp:func:`nvs_open_from_partition` function with the ``NVS_READONLY`` flag. -The default minimal size for NVS to function properly is 12kiB (``0x3000``), meaning there have to be at least 3 pages with at least one of them being in Empty state. However if the NVS partition is flagged as ``readonly`` in the partition table CSV and is being opened in read-only mode, the partition can be as small as 4kiB (``0x1000``). +The default minimal size for NVS to function properly is 12 KiB (``0x3000``), meaning there have to be at least 3 pages with at least one of them being in Empty state. However if the NVS partition is flagged as ``readonly`` in the partition table CSV and is being opened in read-only mode, the partition can be as small as 4 KiB (``0x1000``). -.. note:: At the moment read-only flag is not reflected if NVS is using Block Device Layer as a storage. +.. note:: + + At the moment read-only flag is not reflected if NVS is using Block Device Layer as a storage. .. _nvs_underlying_storage: @@ -455,17 +487,15 @@ Underlying Storage At build time the mode NVS will use for accessing its underlying storage can be configured. Two options are available in menuconfig option :ref:`CONFIG_NVS_BDL_STACK`. -**ESP Partition API (default)**: NVS accesses storage using the :ref:`esp_partition `. -This is the default mode of operation, where NVS uses SPI flash partitions defined in the partition table. In this mode: +**ESP Partition API (default)**: NVS accesses storage using the :ref:`esp_partition `. This is the default mode of operation, where NVS uses SPI flash partitions defined in the partition table. In this mode: -- Initialisation functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) look up the partition and use :ref:`esp_partition ` APIs to access it. +- Initialization functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) look up the partition and use :ref:`esp_partition ` APIs to access it. - The application can provide custom ``esp_partition_t`` pointer and call :cpp:func:`nvs_flash_init_partition_ptr`. This enables applications to overcome limitations of partition table based partitions, such as using partitions not defined in the partition table. - This mode provides the best performance, as there is no additional abstraction layer between NVS and the underlying storage. -**Block Device Layer (BDL)**: NVS accesses storage using esp_blockdev. -This option enables NVS to operate on a block device that implements the esp_blockdev interface. In this mode: +**Block Device Layer (BDL)**: NVS accesses storage using esp_blockdev. This option enables NVS to operate on a block device that implements the esp_blockdev interface. In this mode: -- Initialisation functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) transparently create block devices using :ref:`esp_partition `, manage their lifecycles and internally use the esp_blockdev. For the application, this mode is similar to the default mode of operation. +- Initialization functions (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) transparently create block devices using :ref:`esp_partition `, manage their lifecycles and internally use the esp_blockdev. For the application, this mode is similar to the default mode of operation. - The application can provide custom block device handle and call :cpp:func:`nvs_flash_init_partition_bdl` to register it for NVS. The application is responsible for managing the block device handle lifecycle. - As the BDL abstracts the underlying storage, there is an overhead compared to using :ref:`esp_partition ` APIs directly. Choose this mode when esp_partition backed storage is not sufficient for the needs of the application. @@ -473,11 +503,11 @@ This option enables NVS to operate on a block device that implements the esp_blo If custom Block Device is used in NVS, it has to satisfy the following requirements: - - ``read_size`` and ``write_size`` must be 1 byte (NVS requires byte-level granular access) - - ``erase_size`` must be a divisor of 4096 (NVS page size is fixed at 4096 bytes) - - ``disk_size`` must be a multiple of 4096 bytes - - ``default_val_after_erase`` flag must be set to 1 (erased memory reads as ``0xFF``) - - Operations (``read``, ``write``, ``erase``) must be implemented + - ``read_size`` and ``write_size`` must be 1 byte (NVS requires byte-level granular access). + - ``erase_size`` must be a divisor of 4096 (NVS page size is fixed at 4096 bytes). + - ``disk_size`` must be a multiple of 4096 bytes. + - ``default_val_after_erase`` flag must be set to 1 (erased memory reads as ``0xFF``). + - Operations (``read``, ``write``, ``erase``) must be implemented. diff --git a/docs/zh_CN/api-reference/storage/nvs_flash.rst b/docs/zh_CN/api-reference/storage/nvs_flash.rst index d93e367c6c..4e49f24507 100644 --- a/docs/zh_CN/api-reference/storage/nvs_flash.rst +++ b/docs/zh_CN/api-reference/storage/nvs_flash.rst @@ -8,41 +8,24 @@ 非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。本文档将详细介绍 NVS 常用的一些概念。 -底层存储 -^^^^^^^^^^^^^^^^^^ +初始化 +^^^^^^^^^^^^^^ -NVS 库通过调用 :ref:`esp_partition ` API 使用主 flash 的部分空间,即类型为 ``data`` 且子类型为 ``nvs`` 的所有分区。应用程序可调用 :cpp:func:`nvs_open` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 :cpp:func:`nvs_open_from_partition` API 选择使用指定名称的任意分区。 +NVS 使用分区表中类型为 ``data``、子类型为 ``nvs`` 的分区。该库可以通过以下方式初始化: -NVS 库后续版本可能会增加其他存储器后端,来将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。 +- :cpp:func:`nvs_flash_init` 初始化标签为 ``nvs`` 的默认 NVS 分区。 +- :cpp:func:`nvs_flash_init_partition` 通过其标签初始化特定的 NVS 分区。 +- :cpp:func:`nvs_flash_init_partition_ptr` 从 ``esp_partition_t`` 指针初始化 NVS 分区。 -.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase-flash`` 命令擦除 flash 上的所有内容。 +初始化完成后,应用程序使用 :cpp:func:`nvs_open` (用于默认分区)或 :cpp:func:`nvs_open_from_partition` (用于按标签指定的特定分区)访问 NVS 命名空间。 -.. note:: NVS 最适合存储一些较小的数据,而非字符串或二进制大对象 (BLOB) 等较大的数据。如需存储较大的 BLOB 或者字符串,请考虑使用基于磨损均衡库的 FAT 文件系统。 +.. note:: -.. note:: NVS 组件在设计上支持磨损均衡。进行设置操作时,新数据会添加至现存条目之后。即便要使旧值失效,也无需立即执行 flash 擦除操作。通过将数据存储在页面和条目中,该 NVS 空间组织方式大幅降低了 flash 擦除和写入的频率,实现了 126 倍的效率提升。 + 启用 :ref:`CONFIG_NVS_BDL_STACK` 后,NVS 也可以通过块设备层 (BDL) 运行,从而支持标准 flash 分区以外的其他存储后端。在 BDL 模式下,:cpp:func:`nvs_flash_init_partition_ptr` 不可用,但 :cpp:func:`nvs_flash_init_partition_bdl` 可用于自定义块设备初始化。详情见 :ref:`nvs_internals` > :ref:`nvs_underlying_storage`。 -在 NVS 中存储大量数据 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. note:: -NVS 支持至多存储数万个键,且 NVS 分区的大小也可以达到几兆字节(但并不推荐按此上限进行存储)。 - -.. note:: NVS 组件会在堆上占用一定的 RAM 空间,具体占用量取决于 flash 上 NVS 分区的大小以及使用的键的数量。可以参考以下近似数值,估算 RAM 的使用情况:每 1 MB 的 NVS flash 分区会占用 22 KB 的 RAM,每 1000 个键会占用 5.5 KB 的 RAM。 - -.. note:: 使用 :cpp:func:`nvs_flash_init` 初始化 NVS 的时间与已有键的数量成正比。每有 1000 个键,NVS 的初始化时间则增加约 0.5 秒。 - -.. only:: SOC_SPIRAM_SUPPORTED - - 默认情况下,内部 NVS 会在设备的内部 RAM 中分配堆。当 NVS 分区较大或键的数量较多时,可能会因为使用内部 NVS 所需的内存开销过大,设备的内部 RAM 堆耗尽,导致应用程序遇到内存不足的问题。 - 如果应用程序使用了带有 SPI 连接的 PSRAM 模块,则可以通过启用 Kconfig 选项 :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` 来克服此限制。启用该选项后,RAM 分配会重定向至带有 SPI 连接的 PSRAM 上。 - 启用 SPIRAM 且将 :ref:`CONFIG_SPIRAM_USE` 设为 ``CONFIG_SPIRAM_USE_CAPS_ALLOC`` 后,即可在 menuconfig 菜单的 nvs_flash 组件中启用 :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` 选项。 - .. note:: 使用带有 SPI 连接的 PSRAM 会导致 NVS API 的整数操作速度减慢约 2.5 倍。 - -.. _nvs_bootloader: - -在引导加载程序代码中使用 NVS ---------------------------------- - -运行中的应用程序可使用本指南中描述的标准 NVS API。也可以在自定义引导加载程序代码中从 NVS 读取数据。更多信息请参阅 :doc:`nvs_bootloader` 指南。 + 如果 NVS 分区被截断(例如,当分区表布局发生更改时),应擦除其内容。ESP-IDF 构建系统提供了 ``idf.py erase-flash`` 目标,用于擦除 flash 芯片的所有内容。 键值对 ^^^^^^^^^^^^^^^ @@ -55,13 +38,17 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的 .. note:: - 字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小的 97.6% 减去 4000 字节,以较低值为准。 + NVS 最适合存储大量的小型数据值,而非存储少量的大型字符串或二进制大对象 (blob) 类型数据。如果需要存储大型 blob 或字符串,考虑使用磨损均衡库上层的 FAT 文件系统功能。 .. note:: - 在设置新的或更新现有的键值对之前,需要确保 NVS 页面具备可用的空闲条目。对于整数类型,确保至少有一个可用的空闲条目。对于字符串值,确保至少有一个 NVS 页面,页面中有足够的连续空闲条目,以便能够完整地存储整个字符串。对于 Blob 值,确保在 NVS 中有足够的空闲条目,以容纳新数据的大小。 + 字符串值目前限制为 4000 字节(含终止符)。blob 值的限制为 508000 字节,或分区大小的 97.6% 减去 4000 字节,以两者较小值为准。 -后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。 +.. note:: + + 在设置新的键值对或更新现有键值对之前,NVS 页中必须有可用的空闲条目。对于整数类型,至少需要有一个空闲条目。对于字符串值,至少需要一个能够将整个字符串连续存储在空闲条目中的页面。对于 blob 值,空闲条目中需要有足够空间容纳新数据的大小。 + +其他数据类型,例如 ``float`` 和 ``double``,可能会在以后添加。 键必须唯一。为现有的键写入新值时,会将旧的值及数据类型更新为写入操作指定的值和数据类型。 @@ -71,7 +58,7 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的 命名空间 ^^^^^^^^ -为缓解不同组件间可能出现的键名冲突问题,NVS 将每个键值对分配到某个命名空间。命名空间的命名规则与键名相同,即最大长度为 15 个字符。此外,单个 NVS 分区中,最多支持 254 个不同的命名空间。调用 :cpp:func:`nvs_open` 或 :cpp:type:`nvs_open_from_partition` 可以指定命名空间名称。 +为减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配到一个命名空间。命名空间的命名规则遵循键名的命名规则,例如,最多可占 15 个字符。此外,单个 NVS 分区最多只能容纳 254 个不同的命名空间。命名空间的名称在调用 :cpp:func:`nvs_open` 或 :cpp:type:`nvs_open_from_partition` 中指定。调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 :cpp:func:`nvs_commit` 函数。这样,句柄就与命名空间和分区关联,键名不会与其他命名空间中的同名键发生冲突。 open mode 参数控制访问级别和安全行为: @@ -79,26 +66,9 @@ open mode 参数控制访问级别和安全行为: - ``NVS_READWRITE``:标准读写访问权限。被擦除的数据会被标记为已删除,但仍保留在 flash 中。 - ``NVS_READWRITE_PURGE``:安全的读写访问权限。已擦除的数据将从 flash 中物理移除。 -此调用返回一个不透明句柄,用于后续对 ``nvs_get_*``、``nvs_set_*`` 和 :cpp:func:`nvs_commit` 函数的调用。这样,句柄与某个命名空间关联,键名不会与其他命名空间中的同名键发生冲突。请注意,不同 NVS 分区中同名的命名空间被视为相互独立的命名空间。 - -.. _data_purging_security: - -数据清除与安全 -^^^^^^^^^^^^^^ - -默认情况下,当 NVS 更新或擦除键值对时,旧数据被标记为已擦除,但实际仍存在于 flash 中。这种做法可以提升写入性能,并且有助于减轻 flash 芯片的磨损。但如果能够物理访问 flash 芯片,被擦除的数据仍可能被恢复。 - -对于需要更高安全性、必须将敏感数据从 flash 中物理删除的应用,NVS 提供了两种机制: - -**清除模式** - 以 ``NVS_READWRITE_PURGE`` 模式打开命名空间时,所有写入和擦除操作在写入新值的同时会自动清除(物理擦除)先前存储的数据,确保旧数据无法从 flash 中恢复。 - -**手动清除** - 调用 :cpp:func:`nvs_purge_all` 函数,应用程序可以随时显式清理某个命名空间内所有已擦除的条目。可用于以 ``NVS_READWRITE`` 或 ``NVS_READWRITE_PURGE`` 模式打开的句柄。 - .. note:: - 相较于标准擦除操作,清除操作会增加 flash 擦写次数。应用程序在决定是否使用清除功能时,应在安全需求与 flash 磨损之间进行权衡。 + 在不同的 NVS 分区中,同名的的命名空间被视为相互独立的命名空间。 NVS 迭代器 ^^^^^^^^^^^^^ @@ -113,7 +83,7 @@ NVS 迭代器 总的来说,所有通过 :cpp:func:`nvs_entry_find` 获得的迭代器(包括 ``NULL`` 迭代器)都必须使用 :cpp:func:`nvs_release_iterator` 释放。 -一般情况下,:cpp:func:`nvs_entry_find` 和 :cpp:func:`nvs_entry_next` 会将给定的迭代器设置为 ``NULL`` 或为一个有效的迭代器。但如果出现参数错误(如返回 ``ESP_ERR_NVS_NOT_FOUND``),给定的迭代器不会被修改。因此,在调用 :cpp:func:`nvs_entry_find` 之前最好将迭代器初始化为 ``NULL``,这样可以避免在释放迭代器之前进行复杂的错误检查。 +:cpp:func:`nvs_entry_find` 和 :cpp:func:`nvs_entry_next` 在除发生参数错误之外的所有情况下,都会将给定的迭代器设为 ``NULL`` 或有效的迭代器(即返回 ``ESP_ERR_NVS_NOT_FOUND`` 时除外)。发生参数错误时,给定的迭代器不会被修改。因此,最佳实践是在调用 :cpp:func:`nvs_entry_find` 之前先将迭代器初始化为 ``NULL``,以避免在释放迭代器之前进行繁琐的错误检查。 安全性、篡改性及鲁棒性 @@ -127,19 +97,87 @@ NVS 迭代器 NVS 与 {IDF_TARGET_NAME} flash 加密系统不直接兼容。然而,如果 NVS 加密与 {IDF_TARGET_NAME} flash 加密或 HMAC 外设一起使用,数据仍可以加密形式存储。详情请参考 :doc:`nvs_encryption`。 -如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的用户都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值对。但是,针对擦除操作没有相应的防篡改功能。 - -**防止数据恢复**:默认情况下,当键值对被更新或擦除时,旧数据实际仍然存在于 flash 中,仅被标记为无效。对于禁止恢复旧数据的敏感应用,打开命名空间时应使用 ``NVS_READWRITE_PURGE`` 模式,或调用 :cpp:func:`nvs_purge_all` 以显式清除已擦除的数据。详情参见 :ref:`数据清除与安全 ` 部分。 +如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的用户都可以读取、修改、擦除或添加键值对。启用 NVS 加密后,在不知道相应的 NVS 加密密钥的情况下,无法读取、修改或添加键值对并将其识别为有效键值对。但是,针对擦除操作没有相应的防篡改功能。 当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应该能够在 flash 中存在任何随机数据的情况下正常初始化。 +.. _data_purging_security: + +数据清除与安全 +^^^^^^^^^^^^^^^^^^^^^^^^^ + +默认情况下,当 NVS 更新或擦除键值对时,flash 中的数据仅在元数据部分被标记为已擦除。这些值实际仍存在于 flash 中。这种做法可以提升写入性能。 + +对于需要更高安全性、必须将敏感数据从 flash 中物理删除的应用(即通过将所有位清零),NVS 提供了两种机制: + +**一次性清除** + + :cpp:func:`nvs_purge_all` 函数会清除命名空间内所有标记为已擦除的条目。该功能适用于尚未使用连续清除模式,且应用需要清理现有已擦除 flash 内容的场景。此函数可与以 ``NVS_READWRITE`` 或 ``NVS_READWRITE_PURGE`` 模式打开的句柄配合使用。 + +**连续清除模式** + + 以 ``NVS_READWRITE_PURGE`` 模式打开的命名空间句柄,除了将已擦除或覆盖的值标记为已擦除外,还会自动清除这些值所占用的 flash 空间。 + +.. note:: + + 以 ``NVS_READWRITE_PURGE`` 模式打开 NVS 命名空间时,不会清理 flash 中被标记为已擦除的数据。若命名空间中存在已更新或已擦除的数据,在使用连续清除模式前,请先执行一次性清除。 + +.. note:: + + 相较于标准的标记为已擦除模式,清除操作会增加额外的 flash 写入次数。在决定是否使用数据清除功能时,应用程序需要在安全需求和 flash 写入性能之间进行权衡。 + + +特殊使用场景 +----------------- + +NVS 中的大量数据 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +虽然不推荐这样做,但 NVS 可以存储数以万计的键,且 NVS 分区的大小可达数兆字节级别。 + +.. note:: + + NVS 组件会在堆上占用 RAM。占用量取决于 flash 上的 NVS 分区大小以及正在使用的键数量。为估算 RAM 用量,请参考以下近似数值:每 1 MB NVS flash 分区消耗 22 KB RAM;每 1000 个键消耗 5.5 KB RAM。 + +.. note:: + + 使用 :cpp:func:`nvs_flash_init` 进行 NVS 初始化所需的时间与现有键的数量成正比。初始化 NVS 时,通常每 1000 个键需要 0.5 秒。 + +.. note:: + + NVS 初始化耗时会随着分区内数据量的增加以及值更新次数的增多而逐渐增长。为避免应用程序在客户实际使用过程中因初始化过程意外触发看门狗超时,请提前在包含所有键(包括预期更新历史)的 NVS 分区上测试初始化过程。 + +.. only:: SOC_SPIRAM_SUPPORTED + + 默认情况下,内部 NVS 会在内部 RAM 中分配堆内存。对于较大的 NVS 分区或大量键,应用程序可能仅因 NVS 的开销就耗尽内部 RAM 的堆内存。 + + 如果应用程序所使用的模组配备了通过 SPI 连接的 PSRAM,则可通过启用 Kconfig 选项 :ref:`CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM` 来克服这一限制。该选项会将 RAM 分配重定向到通过 SPI 连接的 PSRAM。 + + 当启用 SPIRAM 且 :ref:`CONFIG_SPIRAM_USE` 设为 ``CONFIG_SPIRAM_USE_CAPS_ALLOC`` 时,此选项可在 menuconfig 菜单的 nvs_flash 组件中使用。 + + .. note:: + + 使用 SPI 接口的 PSRAM 后,NVS 整数操作的 API 耗时约为原来的 2.5 倍。 + 电源不稳定状态 -------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^ 当 NVS 用于弱电源或不稳定电源系统(如太阳能或电池供电系统)时,flash 擦除操作可能偶尔无法彻底完成,而应用程序无法检测到这一问题。这会导致实际 flash 内容与预留页面的预期布局不一致。在极少数情况下(特别是在意外断电时),可能造成可用 NVS 页面耗尽,导致分区初始化失败并返回 ``ESP_ERR_NVS_NO_FREE_PAGES`` 错误。 为解决此问题,可通过 Kconfig 选项 :ref:`CONFIG_NVS_FLASH_VERIFY_ERASE` 启用 flash 擦除操作的验证机制,通过回读受影响页面进行检测。若在 ``flash_erase`` 操作后页面未完全擦除为 ``0xFF``,系统将重试擦除操作直至页面被正确清空。包括首次尝试在内的擦除尝试总次数可通过 Kconfig 选项 :ref:`CONFIG_NVS_FLASH_ERASE_ATTEMPTS` 进行配置。 +.. note:: + + 在可写分区上初始化 NVS 时,如果发现分区处于不一致的状态,NVS 库会尝试执行恢复操作。该操作可能涉及擦除和重写部分页面,而在电源持续不稳定的环境下,进而可能导致出厂默认数据意外丢失。因此,建议将关键的出厂默认数据保存在单独的只读分区中,这样就不会对其执行恢复操作。 + + +.. _nvs_bootloader: + +在引导加载程序代码中使用 NVS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +本指南所述的标准 NVS API 可供正在运行的应用程序使用。此外,还可以在自定义引导加载程序代码中从 NVS 读取数据。更多信息见 :doc:`nvs_bootloader` 指南。 + .. _nvs_encryption: NVS 加密 @@ -228,6 +266,8 @@ ESP-IDF :example:`storage/nvs` 目录下提供了数个代码示例: 之后,本示例会遍历各个数据类型以及通用的 ``NVS_TYPE_ANY`` 类型,并记录在每次遍历过程中获取到的信息。 +.. _nvs_internals: + 内部实现 --------- @@ -236,9 +276,9 @@ ESP-IDF :example:`storage/nvs` 目录下提供了数个代码示例: NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需更新某一键值对,实际是在日志最后增加一对新的键值对,同时将旧的键值对标记为已擦除。 -**标准擦除行为**:默认情况下,被擦除的条目实际仍存在于 flash 中,仅修改其状态位以表明不再有效。这样可以优化性能并减少 flash 磨损。 +.. note:: -**清除行为**:使用 ``NVS_READWRITE_PURGE`` 模式或调用 :cpp:func:`nvs_purge_all` 时,NVS 库会对已擦除的条目内容进行物理覆盖,防止数据被恢复。此操作会增加 flash 擦写次数,但能为敏感数据提供更高的安全性。 + NVS 组件在设计上内置了 flash 磨损均衡功能。写入操作会将新数据追加到现有条目之后的空闲空间中,而将旧值标记为无效时,并不需要立即执行 flash 擦除操作。通过将 NVS 空间划分为页面和条目的结构,对于占用单个条目的数据类型,flash 擦除与写入操作的频率比可以有效降低为原来的 1/126。 页面和条目 ^^^^^^^^^^^^^^^^^ @@ -429,7 +469,46 @@ CRC32 只读 NVS ^^^^^^^^ -NVS 正常运行所需的最小大小默认为 12kiB (``0x3000``),这意味着至少需要 3 个页面,其中一个页面必须处于 Empty 状态。但是,如果 NVS 分区在分区表 CSV 中标记为 ``readonly`` 并以只读 (read-only) 模式打开,则该分区大小最少只需 4kiB(``0x1000``),此时仅需一个 Active 状态的页面,无需 Empty 页面。因为在这种情况下,库无需向分区写入任何数据。此类型分区适用于存储不会更改的数据,如校准数据或出厂设置。大小为 0x1000 和 0x2000 的分区始终为只读分区。大小为 0x3000 及以上的分区始终支持读写 (read-write),但仍可以在代码中以只读模式打开。 +NVS 支持两种级别的只读模式: + +- 在分区表层面,可在分区表 CSV 文件中将分区标记为 ``readonly``。 +- 在应用层面,可通过 :cpp:func:`nvs_open_from_partition` 函数并传入 ``NVS_READONLY`` 标志,以只读模式打开 NVS 分区。 + +NVS 正常运行所需的默认最小空间为 12 KiB (``0x3000``),即至少需要 3 页,且其中至少有一页处于空状态。但如果 NVS 分区在分区表 CSV 文件中被标记为 ``readonly``,并以只读模式打开,那么分区大小可以只有 4 KiB(``0x1000``)。 + +.. note:: + + 目前,如果 NVS 使用块设备层作为存储后端时,则不会反映只读标志。 + +.. _nvs_underlying_storage: + +底层存储 +^^^^^^^^^^^^^^^^^^ + +在构建时,可以配置 NVS 访问其底层存储的模式。menuconfig 选项 :ref:`CONFIG_NVS_BDL_STACK` 提供了两种模式。 + +**ESP 分区 API(默认)**: NVS 使用 :ref:`esp_partition ` 访问存储。这是默认运行模式,其中 NVS 使用由分区表定义的 SPI flash 分区。在此模式下: + +- 初始化函数 (:cpp:func:`nvs_flash_init`, :cpp:func:`nvs_flash_init_partition`) 会查找该分区,并使用 :ref:`esp_partition ` API 访问它。 +- 应用程序可以提供自定义的 ``esp_partition_t`` 指针并调用 :cpp:func:`nvs_flash_init_partition_ptr`。这使应用程序能够克服基于分区表的分区所带来的限制,例如使用分区表中未定义的分区。 +- 该模式提供最佳性能,因为 NVS 与底层存储之间没有额外的抽象层。 + +**块设备层 (BDL)**:NVS 通过 esp_blockdev 访问存储。此选项使 NVS 能在实现 esp_blockdev 接口的块设备上运行。在此模式下: + +- 初始化函数(:cpp:func:`nvs_flash_init`,:cpp:func:`nvs_flash_init_partition`)会通过 :ref:`esp_partition ` 透明地创建块设备,管理其生命周期,并在内部使用 esp_blockdev。对于应用而言,此模式与默认的运行模式类似。 +- 应用程序可以提供自定义块设备句柄,并调用 :cpp:func:`nvs_flash_init_partition_bdl` 将其注册到 NVS。应用程序负责管理该块设备句柄的生命周期。 +- 由于 BDL 抽象了底层存储,与直接使用 :ref:`esp_partition ` API 相比,会有额外开销。仅当基于 esp_partition 的存储无法满足应用需求时,才选择此模式。 + +.. note:: + + 如果在 NVS 中使用自定义块设备,则必须满足以下要求: + + - ``read_size`` 和 ``write_size`` 必须为 1 字节(NVS 要求按字节粒度进行访问)。 + - ``erase_size`` 必须是 4096 的约数(NVS 页大小固定为 4096 字节)。 + - ``disk_size`` 必须是 4096 字节的整数倍。 + - 必须将 ``default_val_after_erase`` 标志位设为 1(即擦除后的存储器读出的值为 ``0xFF``)。 + - 操作(``read``、``write``、``erase``)必须实现。 + API 参考