官方文档翻译-ESP32-NVS

9 篇文章 1 订阅
8 篇文章 1 订阅

非易失性存储库

介绍

Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This sections introduces some concepts used by NVS.

非易失性存储(NVS)库旨在将键值对存储在flash中。本节介绍NVS用到的一些概念。

底层存储

Currently NVS uses a portion of main flash memory through spi_flash_{read|write|erase} APIs. The library uses the all the partitions with data type and nvs subtype. The application can choose to use the partition with label nvs through nvs_open API or any of the other partition by specifying its name through nvs_open_from_part API.

目前,NVS通过spi_flash_{read|write|erase}API使用主flash的一部分。该库使用data类型和nvs子类型的所有分区。应用程序可以使用nvs_openAPI选用nvs表中的分区或通过nvs_open_from_partAPI指定其名称后使用其他分区。

Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc.

该库的未来版本可能会添加其他存储后端,以将数据保存在另一个flash芯片(SPI或I2C),RTC,FRAM等中

注意

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 make erase_flash target to erase all contents of the flash chip.

如果NVS分区被截断(例如,当分区表格布局改变时),其内容应该被擦除。ESP-IDF构建系统提供了make erase_flash来擦除flash芯片所有内容。

注意

NVS works best for storing many small values, rather than a few large values of type ‘string’ and ‘blob’. If storing large blobs or strings is required, consider using the facilities provided by the FAT filesystem on top of the wear levelling library.

NVS最适合存储许多较小的值,而不是“string”和“blob”类型的一些大值。如果需要存储大块或字符串,请考虑使用损耗均衡库里的FAT文件系统提供的工具(参考examples\storage)。

键和值

NVS operates on key-value pairs. Keys are ASCII strings, maximum key length is currently 15 characters. Values can have one of the following types:

NVS在键值对上运行。密钥是ASCII字符串,最大密钥长度目前是15个字符。值可以具有以下类型之一:

  • integer types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t
  • 整数类型:uint8_tint8_tuint16_tint16_tuint32_tint32_tuint64_tint64_t

  • zero-terminated string

  • 以零结尾的字符串

  • variable length binary data (blob)

  • 可变长度二进制数据(blob)

注意

String and blob values are currently limited to 1984 bytes. For strings, this includes the null terminator.

字符串和blob值目前仅限于1984字节。对于字符串,这包括空终止符。

Additional types, such as float and double may be added later.

其他类型,例如floatdouble可能稍后添加。

Keys are required to be unique. Writing a value for a key which already exists behaves as follows:

密钥必须是唯一的。为已存在的键写入值的行为如下所示:

  • if the new value is of the same type as old one, value is updated
  • 如果新值与旧值相同,则更新值

  • if the new value has different data type, an error is returned

  • 如果新值具有不同的数据类型,则返回错误

Data type check is also performed when reading a value. An error is returned if data type of read operation doesn’t match the data type of the value.

读取数值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则会返回错误。

命名空间

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. 15 character maximum length. Namespace name is specified in the nvs_open or nvs_open_from_partcall. This call returns an opaque handle, which is used in subsequent calls to nvs_read_*, nvs_write_*, and nvs_commit functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with same name in different NVS partitions are considered as separate namespaces.

为了缓解不同组件之间的密钥名称之间的潜在冲突,NVS将每个键值对分配给一个名称空间。名称空间名称遵循与键名相同的规则,即最多15个字符。命名空间名称在nvs_opennvs_open_from_part调用中指定。随后调用的nvs_read_*nvs_write_*nvs_commit将返回不透明句柄。这样,句柄与名称空间相关联,并且键名不会与其他名称空间中的相同名称相冲突。请注意,在不同NVS分区中具有相同名称的名称空间被视为单独的名称空间。

安全性,篡改和鲁棒性

NVS library doesn’t implement tamper prevention measures. It is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs.

NVS库不实施防篡改措施。任何人对于芯片的物理访问(擦除或添加键值对)都可能会导致篡改.

NVS is compatible with the ESP32 flash encryption system, and it can store key-value pairs in an encrypted form. Some metadata, like page state and write/erase flags of individual entries can not be encrypted as they are represented as bits of flash memory for efficient access and manipulation. Flash encryption can prevent some forms of modification:

NVS与ESP32flash加密系统兼容,并且可以以加密形式存储键值对。某些元数据(如页面状态和单个条目的写入/擦除标志)不能被加密,因为它们被表示为闪存(flash memory)的位,以便进行有效的访问和操作。Flash加密可以防止某些形式的修改:

  • replacing keys or values with arbitrary data
  • 用任意数据替换键或值
  • changing data types of values
  • 改变数据的数据类型

The following forms of modification are still possible when flash encryption is used:

使用flash加密时,仍可以使用以下形式的修改:

  • erasing a page completely, removing all key-value pairs which were stored in that page
  • 完全删除页面,删除存储在该页面中的所有键值对

  • corrupting data in a page, which will cause the page to be erased automatically when such condition is detected

  • 破坏页面中的数据,这将导致页面在检测到这种情况时被自动擦除

  • rolling back the contents of flash memory to an earlier snapshot

  • 将flash的内容回滚到较早的快照

  • merging two snapshots of flash memory, rolling back some key-value pairs to an earlier state (although this is possible to mitigate with the current design — TODO)

  • 合并flash的两个快照,将一些键值对回滚到较早的状态(尽管这可以通过当前设计缓解 - TODO)

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, expect for the new key-value pair if it was being written at the moment of power off. The library should also be able to initialize properly with any random data present in flash memory.

当flash处于不一致状态时,库会尝试恢复。特别是,应该能够在任何时间点关闭设备,然后重新接通设备。这不应该导致数据丢失,如果新的键值对在关机的时刻被写入,则不会导致数据丢失。该库还应该能在flash中出现随机数据时进行正确初始化。

Internals

键值对的日志

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, new key-value pair is added at the end of the log and old key-value pair is marked as erased.

NVS按顺序存储键值对,并在最后添加新的键值对。当必须更新任何给定密钥的值时,将在日志末尾添加新的键值对,并将旧键值对标记为已擦除。

页面和条目

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:

NVS库在其操作中使用两个主要实体:页面(pages)和条目(entries)。页面是存储整个日志的一部分的逻辑结构。逻辑页面对应于flash的一个物理扇区。正在使用的页面具有与它们关联的序列号。序号会在页面上进行排序。后创建的页面有更高的序号。每个页面可以处于以下状态之一:

  • Empty/uninitialized

    Flash storage for the page is empty (all bytes are 0xff). Page isn’t used to store any data at this point and doesn’t have a sequence number.

  • 空/未初始化

    该页面的Flash存储空白(所有字节都是0xff)。页面此时不用于存储任何数据,也没有序列号。

  • 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. At most one page can be in this state at any given moment.

  • 活跃

    flash已初始化,页头已写入flash,页面具有有效的序列号。页面有一些空的条目,并且可以在那里写入数据。任意时刻,最多只能有一个页面处于此状态。

  • Full

    Flash storage is in a consistent state and is filled with key-value pairs. Writing new key-value pairs into this page is not possible. It is still possible to mark some key-value pairs as erased.

  • flash存储处于一致状态,并充满了键值对。无法将新的键值写入此页面。但可以将一些键值标记为已擦除。

  • 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 when any API call returns. In case of a sudden power off, move-and-erase process will be completed upon next power on.

  • 擦除

    未擦除的键值对正被移入另一个页面,以便当前页面可以被擦除。这是一个暂时的状态,即当任何API调用返回时,页面不应该保持这种状态。在突然断电的情况下,移动和擦除过程将在下次开机时完成。

  • 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. 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.

  • 损坏

    页眉包含无效数据,并且进一步解析页面数据被取消。之前写入此页面的任何项目都将无法访问。相应的flash扇区将不会立即被擦除,并且会与处于未初始化状态的扇区一起保存以备后用。这对于调试可能很有用。

Mapping from flash sectors to logical pages doesn’t have any particular order. Library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers.

从flash扇区到逻辑页面的映射没有任何特定的顺序。库将检查每个flash扇区中找到的序列号,并根据这些数字组织页面。

+--------+     +--------+     +--------+     +--------+
| Page 1 |     | Page 2 |     | Page 3 |     | Page 4 |
| Full   +---> | Full   +---> | Active |     | Empty  |   <- states
| #11    |     | #12    |     | #14    |     |        |   <- sequence numbers
+---+----+     +----+---+     +----+---+     +---+----+
    |               |              |             |
    |               |              |             |
    |               |              |             |
+---v------+  +-----v----+  +------v---+  +------v---+
| Sector 3 |  | Sector 0 |  | Sector 2 |  | Sector 1 |    <- physical sectors
+----------+  +----------+  +----------+  +----------+

页面的结构

For now we assume that flash sector size is 4096 bytes and that ESP32 flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g. via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g. SPI flash driver and SPI flash cache can support these other sizes).

现在我们假设flash扇区大小为4096字节,并且ESP32 flash加密硬件在32字节块上运行。可以在编译时引入一些可配置的设置(例如,通过menuconfig)以适应不同扇区大小的flash芯片(尽管系统中的其他组件(如SPI flash驱动器和SPI flash高速缓存可以支持这些其他组件)还不清楚大小)。

Page consists of three parts: header, entry state bitmap, and entries themselves. To be compatible with ESP32 flash encryption, entry size is 32 bytes. For integer types, entry holds one key-value pair. For strings and blobs, an entry holds part of key-value pair (more on that in the entry structure description).

页面由三部分组成:头部,条目状态位图和条目本身。为了与ESP32flash加密兼容,条目大小为32字节。对于整数类型,条目保存一个键值对。对于字符串和斑点,条目保存键值对的一部分(更多相关信息在条目结构描述的内容)。

The following diagram illustrates page structure. Numbers in parentheses indicate size of each part in bytes.
下图说明了页面结构。括号中的数字表示每个部分的字节大小。

+-----------+--------------+-------------+-----------+
| State (4) | Seq. no. (4) | Unused (20) | CRC32 (4) | Header (32)
+-----------+--------------+-------------+-----------+
|                Entry state bitmap (32)             |
+----------------------------------------------------+
|                       Entry 0 (32)                 |
+----------------------------------------------------+
|                       Entry 1 (32)                 |
+----------------------------------------------------+
/                                                    /
/                                                    /
+----------------------------------------------------+
|                       Entry 125 (32)               |
+----------------------------------------------------+

Page header and entry state bitmap are always written to flash unencrypted. Entries are encrypted if flash encryption feature of the ESP32 is used.
页头和条目状态位图总是以未加密方式写入flash。如果使用ESP32的flash加密功能,则条目将被加密。

Page state values are defined in such a way that changing state is possible by writing 0 into some of the bits. Therefore it not necessary to erase the page to change page state, unless that is a change to erased state.
页面状态值的定义方式是通过将0写入某些位来改变状态。因此没有必要擦除页面来改变页面状态,除非这是对擦除状态的改变。

CRC32 value in header is calculated over the part which doesn’t include state value (bytes 4 to 28). Unused part is currently filled with 0xff bytes. Future versions of the library may store format version there.
头部的CRC32值是在不包含状态值的部分(字节4到28,即Unused+Seq. no.两个部分)上计算的。未使用的部分当前充满0xff字节。该库的未来版本可能会在那里存储格式版本。

The following sections describe structure of entry state bitmap and entry itself.
以下部分描述了条目状态位图和条目本身的结构。

条目和条目状态位图

Each entry can be in one of the following three states. Each state is represented with two bits in the entry state bitmap. Final four bits in the bitmap (256 - 2 * 126) are unused.
每个条目可以处于以下三种状态之一。每个状态在入口状态位图中用两位表示。位图中的最后四位(256-2 * 126)未被使用。

  • Empty (2’b11)

    Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes 0xff).

  • 空(2’b11)

    没有任何内容写入特定条目。它处于未初始化状态(全部字节为0xff)。

  • Written (2’b10)

    A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry.

  • 写(2’b10)

    键值对(或跨越多个条目的键值对的一部分)已被写入条目中。

  • Erased (2’b00)

    A key-value pair in this entry has been discarded. Contents of this entry will not be parsed anymore.

  • 擦除(2’b00)

    此条目中的键值对已被丢弃。此条目的内容将不再被解析。

条目结构

For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. In case when a key-value pair spans multiple entries, all entries are stored in the same page.
对于基本类型的值(当前为1到8个字节的整数),条目保存一个键值对。对于字符串和blob类型,条目保留整个键值对的一部分。如果键值对跨越多个条目,则所有条目都存储在同一页面中。

+--------+----------+----------+---------+-----------+---------------+----------+
| NS (1) | Type (1) | Span (1) | Rsv (1) | CRC32 (4) |    Key (16)   | Data (8) |
+--------+----------+----------+---------+-----------+---------------+----------+

                                               +--------------------------------+
                         +->    Fixed length:  | Data (8)                       |
                         |                     +--------------------------------+
          Data format ---+
                         |                     +----------+---------+-----------+
                         +-> Variable length:  | Size (2) | Rsv (2) | CRC32 (4) |
                                               +----------+---------+-----------+

Individual fields in entry structure have the following meanings:
条目结构中的单个字段具有以下含义:

  • NS

    Namespace index for this entry. See section on namespaces implementation for explanation of this value.

  • 名称空间

    此条目的名称空间索引。有关此值的解释,请参见名称空间实现部分。

  • Type

    One byte indicating data type of value. See ItemType enumeration in nvs_types.h for possible values.

  • 类型

    一个字节表示数据类型的值。请参阅nvs_types.h中的ItemType枚举获取可能的值。

  • Span

    Number of entries used by this key-value pair. For integer types, this is equal to 1. For strings and blobs this depends on value length.

  • 跨度

    此键值对使用的条目数。对于整数类型,这等于1.对于字符串和二进制数据(blob),这取决于值的长度。

  • Rsv

    Unused field, should be 0xff.

  • RSV

    未使用的字段,值应为0xff

  • CRC32

    Checksum calculated over all the bytes in this entry, except for the CRC32 field itself.

  • CRC32

    校验和计算此条目中的所有字节,CRC32字段本身除外。

  • Key

    Zero-terminated ASCII string containing key name. Maximum string length is 15 bytes, excluding zero terminator.

  • 包含’\0’(ASCII的0终止符,Zero-terminated ASCII)的键名。最大字符串长度为15个字节,不包括零终止符(即全长16(15+1))。

  • Data

    For integer types, this field contains the value itself. If the value itself is shorter than 8 bytes it is padded to the right, with unused bytes filled with 0xff. For string and blob values, these 8 bytes hold additional data about the value, described next:

  • 数据

    对于整数类型,此字段包含值本身。如果该值本身小于8个字节,则将其填充到右侧,并将未使用的部分填充0xff。对于字符串和blob值,这8个字节包含有关该值的附加数据,如下所述:

    • Size

    (Only for strings and blobs.) Size, in bytes, of actual data. For strings, this includes zero terminator.

    • 大小

    (仅适用于字符串和blob。)实际数据的大小(以字节为单位)。对于字符串,这包括零终止符。

    • CRC32

    (Only for strings and blobs.) Checksum calculated over all bytes of data.

    • CRC32

    (仅适用于字符串和blob。)在所有数据字节上计算校验和。

Variable length values (strings and blobs) are written into subsequent entries, 32 bytes per entry. Span field of the first entry indicates how many entries are used.
可变长度值(字符串和blob)被写入随后的条目中,每个条目32字节。第一个条目的跨度字段指示使用了多少个条目。

命名空间

As mentioned above, each key-value pair belongs to one of the namespaces. Namespaces identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces.
如上所述,每个键值对都属于其中一个名称空间。命名空间标识符(字符串)作为键值对的键存储在索引为0的命名空间中。与这些键对应的值是这些命名空间的索引。

+-------------------------------------------+
| NS=0 Type=uint8_t Key="wifi" Value=1      |   Entry describing namespace "wifi"
+-------------------------------------------+
| NS=1 Type=uint32_t Key="channel" Value=6  |   Key "channel" in namespace "wifi"
+-------------------------------------------+
| NS=0 Type=uint8_t Key="pwm" Value=2       |   Entry describing namespace "pwm"
+-------------------------------------------+
| NS=2 Type=uint16_t Key="channel" Value=20 |   Key "channel" in namespace "pwm"
+-------------------------------------------+

项目哈希列表

To reduce the number of reads performed from flash memory, each member of Page class maintains a list of pairs: (item index; item hash). This list makes searches much quicker. Instead of iterating over all entries, reading them from flash one at a time, Page::findItem first performs search for item hash in the hash list. This gives the item index within the page, if such an item exists. Due to a hash collision it is possible that a different item will be found. This is handled by falling back to iteration over items in flash.
为了减少从flash执行的读取次数,Page类的每个成员都维护一个对列表(项目索引;项目哈希值)。这个列表使得搜索更快。不是迭代访问所有条目,而是一次从flash中读取它们,Page::findItem首先在哈希列表中执行项目哈希搜索。如果这样的项目存在,这给出页面内的项目索引。由于哈希冲突,可能会找到不同的项目。这个问题通过迭代Flash中的项目来处理。

Each node in hash list contains a 24-bit hash and 8-bit item index. Hash is calculated based on item namespace and key name. CRC32 is used for calculation, result is truncated to 24 bits. To reduce overhead of storing 32-bit entries in a linked list, list is implemented as a doubly-linked list of arrays. Each array holds 29 entries, for the total size of 128 bytes, together with linked list pointers and 32-bit count field. Minimal amount of extra RAM useage per page is therefore 128 bytes, maximum is 640 bytes.
哈希列表中的每个节点都包含一个24位哈希和8位项目索引。哈希值是根据项目名称空间和密钥名称计算的。CRC32用于计算,结果被截断为24位。为了减少在链表中存储32位条目的开销,列表被实现为双向链接的数组列表。每个array包含29个条目,总大小为128个字节,以及链接列表指针和32位计数字段。因此,每页额外的RAM使用量最少为128字节,最大为640字节。

应用示例

Two examples are provided in storage directory of ESP-IDF examples:
ESP-IDF示例的存储目录中提供了两个示例:

storage/nvs_rw_value

Demonstrates how to read and write a single integer value using NVS.

The value holds the number of ESP32 module restarts. Since it is written to NVS, the value is preserved between restarts.

Example also shows how to check if read / write operation was successful, or certain value is not initialized in NVS. Diagnostic is provided in plain text to help track program flow and capture any issues on the way.

演示如何使用NVS读取和写入单个整数值。

该值保存ESP32模块重新启动的次数。由于它被写入到NVS,所以在重新启动之间保留该值。

示例还显示了如何检查读/写操作是否成功,或者某些值未在NVS中初始化。诊断以纯文本形式提供,以帮助跟踪程序流程并捕获途中的任何问题。

storage/nvs_rw_blob

Demonstrates how to read and write a single integer value and a blob (binary large object) using NVS to preserve them between ESP32 module restarts.

  • value - tracks number of ESP32 module soft and hard restarts.
  • blob - contains a table with module run times. The table is read from NVS to dynamically allocated RAM. New run time is added to the table on each manually triggered soft restart and written back to NVS. Triggering is done by pulling down GPIO0.

Example also shows how to implement diagnostics if read / write operation was successful.

演示如何使用NVS读取和写入单个整数值和blob(二进制大对象),以在ESP32模块重新启动之间保留它们。

  • 值 - 跟踪ESP32模块软启动和硬启动的次数。
  • blob - 包含一个包含模块运行时间的表。该表从NVS读取到动态分配的RAM。在每次手动触发的软重新启动时将新的运行时间添加到表中并写回到NVS。通过拉低GPIO0完成触发。

示例还显示了如何在读取/写入操作成功时实施诊断。

API参考

头文件

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值