Add Chinese translation for the 2nd batch of the files in storage folder in api-reference.
1. The translation should be first reviewed by technical reviewers; 2. and then by language reviewers. For the translation for the first batch of files, please see !MR5620 and !MR5613
This commit is contained in:
parent
96474f33a6
commit
32adfdc307
9 changed files with 774 additions and 15 deletions
|
@ -1,6 +1,8 @@
|
||||||
Non-volatile storage library
|
Non-volatile storage library
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
:link_to_translation:`zh_CN:[中文]`
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
303
components/nvs_flash/README_CN.rst
Normal file
303
components/nvs_flash/README_CN.rst
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
非易失性存储库
|
||||||
|
============================
|
||||||
|
|
||||||
|
:link_to_translation:`en:[English]`
|
||||||
|
|
||||||
|
简介
|
||||||
|
------------
|
||||||
|
|
||||||
|
非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。本文档将详细介绍 NVS 常用的一些概念。
|
||||||
|
|
||||||
|
底层存储
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
NVS 通过调用 ``spi_flash_{read|write|erase}`` API 对主 flash 的部分空间进行读、写、擦除操作,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 ``nvs_open`` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 ``nvs_open_from_part`` API 选择使用指定名称的任意分区。
|
||||||
|
|
||||||
|
NVS 库后续版本可能会增加其他存储器后端,实现将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。
|
||||||
|
|
||||||
|
.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase_flash`` 命令擦除 flash 上的所有内容。
|
||||||
|
|
||||||
|
.. note:: NVS 最适合存储一些较小的数据,而非字符串或二进制大对象 (BLOB) 等较大的数据。如需存储较大的 BLOB 或者字符串,请考虑使用基于磨损均衡库的 FAT 文件系统。
|
||||||
|
|
||||||
|
|
||||||
|
键值对
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型:
|
||||||
|
|
||||||
|
- 整数型:``uint8_t``、``int8_t``、``uint16_t``、``int16_t``、``uint32_t``、``int32_t``、``uint64_t`` 和 ``int64_t``;
|
||||||
|
- 以 ``\0`` 结尾的字符串;
|
||||||
|
- 可变长度的二进制数据 (BLOB)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小减去 4000 字节的 97.6%,以较低值为准。
|
||||||
|
|
||||||
|
后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。
|
||||||
|
|
||||||
|
键必须唯一。为现有的键写入新的值可能产生如下结果:
|
||||||
|
|
||||||
|
- 如果新旧值数据类型相同,则更新值;
|
||||||
|
- 如果新旧值数据类型不同,则返回错误。
|
||||||
|
|
||||||
|
读取值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则返回错误。
|
||||||
|
|
||||||
|
|
||||||
|
命名空间
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,即最多可占 15 个字符。命名空间的名称在调用 ``nvs_open`` 或 ``nvs_open_from_part`` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_read_*``、``nvs_write_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
|
||||||
|
|
||||||
|
|
||||||
|
安全性、篡改性及鲁棒性
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
NVS 与 ESP32 flash 加密系统不直接兼容。但如果 NVS 加密与 ESP32 flash 加密一起使用时,数据仍可以加密形式存储。更多详情请参阅 :ref:`nvs_encryption`。
|
||||||
|
|
||||||
|
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的人都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
|
||||||
|
|
||||||
|
当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应当能对 flash 中的任意数据进行正确初始化。
|
||||||
|
|
||||||
|
|
||||||
|
内部实现
|
||||||
|
---------
|
||||||
|
|
||||||
|
键值对日志
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需更新某一键值对,实际是在日志最后增加一对新的键值对,同时将旧的键值对标记为已擦除。
|
||||||
|
|
||||||
|
页面和条目
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的序列号。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
|
||||||
|
|
||||||
|
空或未初始化
|
||||||
|
页面对应的 flash 扇区为空白状态(所有字节均为 ``0xff``)。此时,页面未存储任何数据且没有关联的序列号。
|
||||||
|
|
||||||
|
活跃状态
|
||||||
|
此时 flash 已完成初始化,页头部写入 flash,页面已具备有效序列号。页面中存在一些空条目,可写入数据。任意时刻,至多有一个页面处于活跃状态。
|
||||||
|
|
||||||
|
写满状态
|
||||||
|
Flash 已写满键值对,状态不再改变。用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
|
||||||
|
|
||||||
|
擦除状态
|
||||||
|
未擦除的键值对将移至其他页面,以便擦除当前页面。这一状态仅为暂时性状态,即 API 调用返回时,页面应脱离这一状态。如果设备突然断电,下次开机时,设备将继续把未擦除的键值对移至其他页面,并继续擦除当前页面。
|
||||||
|
|
||||||
|
损坏状态
|
||||||
|
页头部包含无效数据,无法进一步解析该页面中的数据,因此之前写入该页面的所有条目均无法访问。相应的 flash 扇区并不会被立即擦除,而是与其他处于未初始化状态的扇区一起等待后续使用。这一状态可能对调试有用。
|
||||||
|
|
||||||
|
Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存储在 flash 扇区的页面序列号,并根据序列号组织页面。
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
+--------+ +--------+ +--------+ +--------+
|
||||||
|
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
|
||||||
|
| Full +---> | Full +---> | Active | | Empty | <- 状态
|
||||||
|
| #11 | | #12 | | #14 | | | <- 序列号
|
||||||
|
+---+----+ +----+---+ +----+---+ +---+----+
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
+---v------+ +-----v----+ +------v---+ +------v---+
|
||||||
|
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- 物理扇区
|
||||||
|
+----------+ +----------+ +----------+ +----------+
|
||||||
|
|
||||||
|
页面结构
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
当前,我们假设 flash 扇区大小为 4096 字节,并且 ESP32 flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
|
||||||
|
|
||||||
|
页面由头部、条目状态位图和条目三部分组成。为了实现与 ESP32 flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
|
||||||
|
|
||||||
|
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)::
|
||||||
|
|
||||||
|
+-----------+--------------+-------------+-------------------------+
|
||||||
|
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | 页头部 (32)
|
||||||
|
+-----------+--------------+-------------+-------------------------+
|
||||||
|
| Entry state bitmap (32) |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| Entry 0 (32) |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| Entry 1 (32) |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
/ /
|
||||||
|
/ /
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| Entry 125 (32) |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
|
||||||
|
头部和条目状态位图写入 flash 时不加密。如果启用了 ESP32 flash 加密功能,则条目写入 flash 时将会加密。
|
||||||
|
|
||||||
|
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为擦除状态。
|
||||||
|
|
||||||
|
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减(例如,version-1 为 0xff,version-2 为 0xfe 等)。
|
||||||
|
|
||||||
|
头部中 CRC32 值是由不包含状态值的条目计算所得(4 到 28 字节)。当前未使用的条目用 ``0xff`` 字节填充。
|
||||||
|
|
||||||
|
条目结构和条目状态位图详细信息见下文描述。
|
||||||
|
|
||||||
|
条目和条目状态位图
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
每个条目可处于以下三种状态之一,每个状态在条目状态位图中用两位表示。位图中的最后四位 (256 - 2 * 126) 未使用。
|
||||||
|
|
||||||
|
空 (2'b11)
|
||||||
|
条目还未写入任何内容,处于未初始化状态(全部字节为 ``0xff``)。
|
||||||
|
|
||||||
|
写入(2'b10)
|
||||||
|
一个键值对(或跨多个条目的键值对的部分内容)已写入条目中。
|
||||||
|
|
||||||
|
擦除(2'b00)
|
||||||
|
条目中的键值对已丢弃,条目内容不再解析。
|
||||||
|
|
||||||
|
.. _structure_of_entry:
|
||||||
|
|
||||||
|
条目结构
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
如果键值类型为基础类型,即 1 - 8 个字节长度的整数型,条目将保存一个键值对;如果键值类型为字符串或 BLOB 类型,条目将保存整个键值对的部分内容。另外,如果键值为字符串类型且跨多个条目,则键值所跨的所有条目均保存在同一页面。BLOB 则可以切分为多个块,实现跨多个页面。BLOB 索引是一个附加的固定长度元数据条目,用于追踪 BLOB 块。目前条目仍支持早期 BLOB 格式(可读取可修改),但这些 BLOB 一经修改,即以新格式储存至条目。
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||||
|
| NS (1) | Type (1) | Span (1) | ChunkIndex (1) | CRC32 (4) | Key (16) | Data (8) |
|
||||||
|
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||||
|
|
||||||
|
Primitive +--------------------------------+
|
||||||
|
+--------> | Data (8) |
|
||||||
|
| Types +--------------------------------+
|
||||||
|
+-> Fixed length --
|
||||||
|
| | +---------+--------------+---------------+-------+
|
||||||
|
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
|
||||||
|
Data format ---+ BLOB Index +---------+--------------+---------------+-------+
|
||||||
|
|
|
||||||
|
| +----------+---------+-----------+
|
||||||
|
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
|
||||||
|
(Strings, BLOB Data) +----------+---------+-----------+
|
||||||
|
|
||||||
|
|
||||||
|
条目结构中各个字段含义如下:
|
||||||
|
|
||||||
|
命名空间 (NS, NameSpace)
|
||||||
|
该条目的命名空间索引,详细信息见命名空间实现章节。
|
||||||
|
|
||||||
|
类型 (Type)
|
||||||
|
一个字节表示的值的数据类型,可能的类型见 ``nvs_types.h`` 中 ``ItemType`` 枚举。
|
||||||
|
|
||||||
|
跨度 (Span)
|
||||||
|
该键值对所用的条目数量。如果键值为整数型,条目数量即为 1。如果键值为字符串或 BLOB,则条目数量取决于值的长度。
|
||||||
|
|
||||||
|
块索引 (ChunkIndex)
|
||||||
|
用于存储 BLOB 类型数据块的索引。如果键值为其他数据类型,则此处索引应写入 ``0xff``。
|
||||||
|
|
||||||
|
CRC32
|
||||||
|
对条目下所有字节进行校验,所得的校验和(CRC32 字段不计算在内)。
|
||||||
|
|
||||||
|
键 (Key)
|
||||||
|
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的 NULL (``\0``) 终止符。
|
||||||
|
|
||||||
|
数据 (Data)
|
||||||
|
如果键值类型为整数型,则数据字段仅包含键值。如果键值小于八个字节,使用 ``0xff`` 填充未使用的部分(右侧)。
|
||||||
|
|
||||||
|
如果键值类型为 BLOB 索引条目,则该字段的八个字节将保存以下数据块信息:
|
||||||
|
|
||||||
|
- 块大小
|
||||||
|
整个 BLOB 数据的大小(以字节为单位)。该字段仅用于 BLOB 索引类型条目。
|
||||||
|
|
||||||
|
- ChunkCount
|
||||||
|
存储过程中 BLOB 分成的数据块数量。该字段仅用于 BLOB 索引类型条目。
|
||||||
|
|
||||||
|
- ChunkStart
|
||||||
|
BLOB 第一个数据块的块索引,后续数据块索引依次递增,步长为 1。该字段仅用于 BLOB 索引类型条目。
|
||||||
|
|
||||||
|
如果键值类型为字符串或 BLOB 数据块,数据字段的这八个字节将保存该键值的一些附加信息,如下所示:
|
||||||
|
|
||||||
|
- 数据大小
|
||||||
|
实际数据的大小(以字节为单位)。如果键值类型为字符串,此字段也应将零终止符包含在内。此字段仅用于字符串和 BLOB 类型条目。
|
||||||
|
|
||||||
|
- CRC32
|
||||||
|
数据所有字节的校验和,该字段仅用于字符串和 BLOB 类型条目。
|
||||||
|
|
||||||
|
可变长度值(字符串和 BLOB)写入后续条目,每个条目 32 字节。第一个条目的 span 字段将指明使用了多少条目。
|
||||||
|
|
||||||
|
命名空间
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
如上所述,每个键值对属于一个命名空间。命名空间标识符(字符串)也作为键值对的键,存储在索引为 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"
|
||||||
|
+-------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
条目哈希列表
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
为了减少对 flash 执行的读操作次数,Page 类对象均设有一个列表,包含一对数据:条目索引和条目哈希值。该列表可大大提高检索速度,而无需迭代所有条目并逐个从 flash 中读取。``Page::findItem`` 首先从哈希列表中检索条目哈希值,如果条目存在,则在页面内给出条目索引。由于哈希冲突,在哈希列表中检索条目哈希值可能会得到不同的条目,对 flash 中条目再次迭代可解决这一冲突。
|
||||||
|
|
||||||
|
哈希列表中每个节点均包含一个 24 位哈希值和 8 位条目索引。哈希值根据条目命名空间、键名和块索引由 CRC32 计算所得,计算结果保留 24 位。为减少将 32 位条目存储在链表中的开销,链表采用了数组的双向链表。每个数组占用 128 个字节,包含 29 个条目、两个链表指针和一个 32 位计数字段。因此,每页额外需要的 RAM 最少为 128 字节,最多为 640 字节。
|
||||||
|
|
||||||
|
.. _nvs_encryption:
|
||||||
|
|
||||||
|
NVS 加密
|
||||||
|
--------------
|
||||||
|
|
||||||
|
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密,每个条目被均视为一个扇区,并将条目相对地址(相对于分区开头)传递给加密算法,用作扇区号。NVS 加密所需的密钥存储于其他分区,并进行了 :doc:`flash 加密 <../../security/flash-encryption>`。因此,在使用 NVS 加密前应先启用 :doc:`flash 加密 <../../security/flash-encryption>`。
|
||||||
|
|
||||||
|
.. _nvs_key_partition:
|
||||||
|
|
||||||
|
NVS 密钥分区
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 ``data``,子类型为 ``key`` 的密钥分区。该分区应标记为已加密,且最小为 4096 字节,具体结构见下表。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`。
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
+-----------+--------------+-------------+----+
|
||||||
|
| XTS encryption key(32) |
|
||||||
|
+---------------------------------------------+
|
||||||
|
| XTS tweak key (32) |
|
||||||
|
+---------------------------------------------+
|
||||||
|
| CRC32(4) |
|
||||||
|
+---------------------------------------------+
|
||||||
|
|
||||||
|
使用 NVS 分区生成程序生成上述分区表,并烧录至设备。由于分区已标记为已加密,而且启用了 :doc:`flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。您也可以在设备启动后调用 ``nvs_flash.h`` 提供的 ``nvs_flash_generate_keys`` API 生成加密密钥,然后再将密钥以加密形式写入密钥分区。
|
||||||
|
|
||||||
|
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
|
||||||
|
|
||||||
|
加密读取/写入
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``nvs_read_*`` 和 ``nvs_write_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。但用于初始化 NVS 非加密分区和加密分区的 API 则有所不同:初始化 NVS 非加密分区可以使用 ``nvs_flash_init`` 和 ``nvs_flash_init_partition``,但初始化 NVS 加密分区则需调用 ``nvs_flash_secure_init`` 和 ``nvs_flash_secure_init_partition``。上述 API 函数所需的 ``nvs_sec_cfg_t`` 结构可使用 ``nvs_flash_generate_keys`` 或者 ``nvs_flash_read_security_cfg`` 进行填充。
|
||||||
|
|
||||||
|
应用程序如需在加密状态下执行 NVS 读写操作,应遵循以下步骤:
|
||||||
|
|
||||||
|
1. 使用 ``esp_partition_find*`` API 查找密钥分区和 NVS 数据分区;
|
||||||
|
2. 使用 ``nvs_flash_read_security_cfg`` 或 ``nvs_flash_generate_keys`` API 填充 ``nvs_sec_cfg_t`` 结构;
|
||||||
|
3. 使用 ``nvs_flash_secure_init`` 或 ``nvs_flash_secure_init_partition`` API 初始化 NVS flash 分区;
|
||||||
|
4. 使用 ``nvs_open`` 或 ``nvs_open_from_part`` API 打开命名空间;
|
||||||
|
5. 使用 ``nvs_read_*`` 或 ``nvs_write_*`` API 执行 NVS 读取/写入操作;
|
||||||
|
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
|
||||||
|
|
||||||
|
NVS 迭代器
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。
|
||||||
|
|
||||||
|
您可以使用以下函数,执行相关操作:
|
||||||
|
|
||||||
|
- ``nvs_entry_find``:返回一个不透明句柄,用于后续调用 ``nvs_entry_next`` 和 ``nvs_entry_info`` 函数;
|
||||||
|
- ``nvs_entry_next``:返回指向下一个键值对的迭代器;
|
||||||
|
- ``nvs_entry_info``:返回每个键值对的信息。
|
||||||
|
|
||||||
|
如果未找到符合标准的键值对,``nvs_entry_find`` 和 ``nvs_entry_next`` 将返回 NULL,此时不必释放迭代器。若不再需要迭代器,可使用 ``nvs_release_iterator`` 释放迭代器。
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
SPI Flash API
|
SPI Flash API
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
:link_to_translation:`zh_CN:[中文]`
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
The spi_flash component contains API functions related to reading, writing,
|
The spi_flash component contains API functions related to reading, writing,
|
||||||
|
|
188
components/spi_flash/README_CN.rst
Normal file
188
components/spi_flash/README_CN.rst
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
SPI Flash API
|
||||||
|
=================
|
||||||
|
|
||||||
|
:link_to_translation:`en:[English]`
|
||||||
|
|
||||||
|
概述
|
||||||
|
--------
|
||||||
|
|
||||||
|
SPI Flash 组件提供外部 flash 数据读取、写入、擦除和内存映射相关的 API 函数,同时也提供了更高层级的,面向分区的 API 函数(定义在 :doc:`分区表 </api-guides/partition-tables>` 中)。
|
||||||
|
|
||||||
|
与 ESP-IDF V4.0 之前的 API 不同,这一版 API 功能并不局限于主 SPI Flash 芯片(即运行程序的 SPI Flash 芯片)。使用不同的芯片指针,您可以通过 SPI0/1 或 HSPI/VSPI 总线访问外部 flash。
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
ESP-IDF V4.0 之后的 flash API 不再是原子的。因此,如果 flash 操作地址有重叠,且写操作与读操作同时执行,读操作可能会返回一部分写入之前的数据,返回一部分写入之后的数据。
|
||||||
|
|
||||||
|
Kconfig 选项 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` 可将 ``spi_flash_*`` 函数切换至 ESP-IDF V4.0 之前的实现。但是,如果同时使用新旧 API,代码量可能会增多。
|
||||||
|
|
||||||
|
即便未启用 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL`,加密读取和加密写入操作也均使用旧实现。因此,仅有主 flash 芯片支持加密操作,其他不同片选(经 SPI1 访问的 flash 芯片)则不支持加密操作。
|
||||||
|
|
||||||
|
初始化 Flash 设备
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
在使用 ``esp_flash_*`` API 之前,您需要在 SPI 总线上初始化芯片。
|
||||||
|
|
||||||
|
1. 调用 :cpp:func:`spi_bus_initialize` 初始化 SPI 总线,此函数将初始化总线上设备间共享的资源,如 I/O、DMA 及中断等。
|
||||||
|
|
||||||
|
2. 调用 :cpp:func:`spi_bus_add_flash_device` 将 flash 设备连接到总线上。然后分配内存,填充 ``esp_flash_t`` 结构体,同时初始化 CS I/O。
|
||||||
|
|
||||||
|
3. 调用 :cpp:func:`esp_flash_init` 与芯片进行通信。后续操作会依据芯片类型不同而有差异。
|
||||||
|
|
||||||
|
.. note:: 目前,多个 flash 芯片可连接到同一总线。但尚不支持在同一个 SPI 总线上使用 ``esp_flash_*`` 和 ``spi_device_*`` 设备。
|
||||||
|
|
||||||
|
SPI Flash 访问 API
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
如下所示为处理 flash 中数据的函数集:
|
||||||
|
|
||||||
|
- :cpp:func:`esp_flash_read`:将数据从 flash 读取到 RAM;
|
||||||
|
- :cpp:func:`esp_flash_write`:将数据从 RAM 写入到 flash;
|
||||||
|
- :cpp:func:`esp_flash_erase_region`:擦除 flash 中指定区域的数据;
|
||||||
|
- :cpp:func:`esp_flash_erase_chip`:擦除整个 flash;
|
||||||
|
- :cpp:func:`esp_flash_get_chip_size`:返回 menuconfig 中设置的 flash 芯片容量(以字节为单位)。
|
||||||
|
|
||||||
|
一般来说,请尽量避免对主 SPI flash 芯片直接使用原始 SPI flash 函数,如需对主 SPI flash 芯片进行操作,请使用 :ref:`分区专用函数 <flash-partition-apis>`。
|
||||||
|
|
||||||
|
SPI Flash 容量
|
||||||
|
--------------
|
||||||
|
|
||||||
|
SPI flash 容量存储于引导程序映像头部(烧录偏移量为 0x1000)的一个字段。
|
||||||
|
|
||||||
|
默认情况下,引导程序写入 flash 时,esptool.py 将引导程序写入 flash 时,会自动检测 SPI flash 容量,同时使用正确容量更新引导程序的头部。您也可以在工程配置中设置 :envvar:`CONFIG_ESPTOOLPY_FLASHSIZE`,生成固定的 flash 容量。
|
||||||
|
|
||||||
|
如需在运行时覆盖已配置的 flash 容量,请配置 ``g_rom_flashchip`` 结构中的 ``chip_size``。``esp_flash_*`` 函数使用此容量(于软件和 ROM 中)进行边界检查。
|
||||||
|
|
||||||
|
SPI1 Flash 并发约束
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
由于 SPI1 flash 也被用于执行固件(通过指令 cache 或数据 cache ),因此在执行读取、写入及擦除操作时,必须禁用这些 cache。这意味着在执行 flash 写操作时,两个 CPU 必须从 IRAM 运行代码,且只能从 DRAM 中读取数据。
|
||||||
|
|
||||||
|
如果您使用本文档中 API 函数,上述限制将自动生效且透明(无需您额外关注),但这些限制可能会影响系统中的其他任务的性能。
|
||||||
|
|
||||||
|
除 SPI0/1 以外的 SPI 总线上的其它 flash 芯片则不受这种限制。
|
||||||
|
|
||||||
|
请参阅 :ref:`应用程序内存分布 <memory-layout>`,查看 IRAM、DRAM 和 flash cache 的区别。
|
||||||
|
|
||||||
|
为避免意外读取 flash cache,一个 CPU 在启动 flash 写入或擦除操作时,另一个 CPU 将阻塞,并且在 flash 操作完成前,两个 CPU 上的所有的非 IRAM 安全的中断都会被禁用。
|
||||||
|
|
||||||
|
.. _iram-safe-interrupt-handlers:
|
||||||
|
|
||||||
|
IRAM 安全中断处理程序
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
如果您需要在 flash 操作期间运行中断处理程序(比如低延迟操作),请在 :doc:`注册中断处理程序 </api-reference/system/intr_alloc>` 时设置 ``ESP_INTR_FLAG_IRAM``。
|
||||||
|
|
||||||
|
请确保中断处理程序访问的所有数据和函数(包括其调用的数据和函数)都存储在 IRAM 或 DRAM 中。
|
||||||
|
|
||||||
|
为函数添加 ``IRAM_ATTR`` 属性::
|
||||||
|
|
||||||
|
#include "esp_attr.h"
|
||||||
|
|
||||||
|
void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
为常量添加 ``DRAM_ATTR`` 和 ``DRAM_STR`` 属性::
|
||||||
|
|
||||||
|
void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
|
||||||
|
const static char *MSG = DRAM_STR("I am a string stored in RAM");
|
||||||
|
}
|
||||||
|
|
||||||
|
辨别哪些数据应标记为 ``DRAM_ATTR`` 可能会比较困难,除非明确标记为 ``DRAM_ATTR``,否则编译器依然可能将某些变量或表达式当做常量(即便没有 ``const`` 标记),并将其放入 flash。
|
||||||
|
|
||||||
|
如果函数或符号未被正确放入 IRAM/DRAM 中,当中断处理程序在 flash 操作期间从 flash cache 中读取数据,则会产生非法指令异常(这是因为代码未被正确放入 IRAM)或读取垃圾数据(这是因为常数未被正确放入 DRAM),而导致崩溃。
|
||||||
|
|
||||||
|
.. _flash-partition-apis:
|
||||||
|
|
||||||
|
分区表 API
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序、各种应用程序二进制文件、数据及文件系统等。请参考 :doc:`分区表 </api-guides/partition-tables>`,查看详细信息。
|
||||||
|
|
||||||
|
该组件在 ``esp_partition.h`` 中声明了一些 API 函数,用以枚举在分区表中找到的分区,并对这些分区执行操作:
|
||||||
|
|
||||||
|
- :cpp:func:`esp_partition_find`:在分区表中查找特定类型的条目,返回一个不透明迭代器;
|
||||||
|
- :cpp:func:`esp_partition_get`:返回一个结构,描述给定迭代器的分区;
|
||||||
|
- :cpp:func:`esp_partition_next`:将迭代器移至下一个找到的分区;
|
||||||
|
- :cpp:func:`esp_partition_iterator_release`:释放 ``esp_partition_find`` 中返回的迭代器;
|
||||||
|
- :cpp:func:`esp_partition_find_first`:返回一个结构,描述 ``esp_partition_find`` 中找到的第一个分区;
|
||||||
|
- :cpp:func:`esp_partition_read`、:cpp:func:`esp_partition_write` 和 :cpp:func:`esp_partition_erase_range` 在分区边界内执行,等同于 :cpp:func:`spi_flash_read`、:cpp:func:`spi_flash_write` 和 :cpp:func:`spi_flash_erase_range`。
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
请在应用程序代码中使用上述 ``esp_partition_*`` API 函数,而非低层级的 ``spi_flash_*`` API 函数。分区表 API 函数根据存储在分区表中的数据,进行边界检查并计算在 flash 中的正确偏移量。
|
||||||
|
|
||||||
|
SPI Flash 加密
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
您可以对 SPI flash 内容进行加密,并在硬件层对其进行透明解密。
|
||||||
|
|
||||||
|
请参阅 :doc:`Flash 加密 </security/flash-encryption>`,查看详细信息。
|
||||||
|
|
||||||
|
内存映射 API
|
||||||
|
------------------
|
||||||
|
|
||||||
|
ESP32 内存硬件可以将 flash 部分区域映射到指令地址空间和数据地址空间,此映射仅用于读操作。不能通过写入 flash 映射的存储区域来改变 flash 中内容。
|
||||||
|
|
||||||
|
Flash 以 64 KB 页为单位进行地址映射。内存映射硬件最多可将 4 MB flash 映射到数据地址空间,将 16 MB flash 映射到指令地址空间。请参考《ESP32 技术参考手册》查看内存映射硬件的详细信息。
|
||||||
|
|
||||||
|
请注意,有些 64 KB 页还用于将应用程序映射到内存中,因此实际可用的 64 KB 页会更少一些。
|
||||||
|
|
||||||
|
:doc:`Flash 加密 </security/flash-encryption>` 启用时,使用内存映射区域从 flash 读取数据是解密 flash 的唯一方法,解密需在硬件层进行。
|
||||||
|
|
||||||
|
内存映射 API 在 ``esp_spi_flash.h`` 和 ``esp_partition.h`` 中声明:
|
||||||
|
|
||||||
|
- :cpp:func:`spi_flash_mmap`:将 flash 物理地址区域映射到 CPU 指令空间或数据空间;
|
||||||
|
- :cpp:func:`spi_flash_munmap`:取消上述区域的映射;
|
||||||
|
- :cpp:func:`esp_partition_mmap`:将分区的一部分映射至 CPU 指令空间或数据空间;
|
||||||
|
|
||||||
|
:cpp:func:`spi_flash_mmap` 和 :cpp:func:`esp_partition_mmap` 的区别如下:
|
||||||
|
|
||||||
|
- :cpp:func:`spi_flash_mmap`:需要给定一个 64 KB 对齐的物理地址;
|
||||||
|
- :cpp:func:`esp_partition_mmap`:给定分区内任意偏移量即可,此函数根据需要将返回的指针调整至指向映射内存。
|
||||||
|
|
||||||
|
内存映射在 64 KB 块中进行,如果分区已传递给 ``esp_partition_mmap``,则可读取分区外数据。
|
||||||
|
|
||||||
|
实现
|
||||||
|
--------------
|
||||||
|
|
||||||
|
``esp_flash_t`` 结构包含芯片数据和该 API 的三个重要部分:
|
||||||
|
|
||||||
|
1. 主机驱动,为访问芯片提供硬件支持;
|
||||||
|
2. 芯片驱动,为不同芯片提供兼容性服务;
|
||||||
|
3. OS 函数,在不同阶段(一级或二级 Boot 或者应用程序阶段)为部分 OS 函数提供支持(如一些锁、延迟)。
|
||||||
|
|
||||||
|
主机驱动
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
主机驱动依赖 ``soc/include/hal`` 文件夹下 ``spi_flash_host_drv.h`` 定义的 ``spi_flash_host_driver_t`` 接口。该接口提供了一些与芯片通信常用的函数。
|
||||||
|
|
||||||
|
在 SPI HAL 文件中,有些函数是基于现有的 ESP32 memory-spi 来实现的。但是,由于 ESP32 速度限制,HAL 层无法提供某些读命令的高速实现(所以这些命令根本没有在 HAL 的文件中被实现)。``memspi_host_driver.h`` 和 ``.c`` 文件使用 HAL 提供的 ``common_command`` 函数实现上述读命令的高速版本,并将所有它实现的及 HAL 函数封装为 ``spi_flash_host_driver_t`` 供更上层调用。
|
||||||
|
|
||||||
|
您也可以实现自己的主机驱动,甚至只通过简单的 GPIO。只要实现了 ``spi_flash_host_driver_t`` 中所有函数,不管底层硬件是什么,esp_flash API 都可以访问 flash。
|
||||||
|
|
||||||
|
芯片驱动
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
芯片驱动在 ``spi_flash_chip_driver.h`` 中进行定义,并将主机驱动提供的基本函数进行封装以供 API 层使用。
|
||||||
|
|
||||||
|
有些操作需在执行前先发送命令,或在执行后读取状态,因此有些芯片需要不同的命令或值以及通信方式。
|
||||||
|
|
||||||
|
``generic chip`` 芯片代表了常见的 flash 芯片,其他芯片驱动可以在通用芯片的基础上进行开发。
|
||||||
|
|
||||||
|
芯片驱动依赖主机驱动。
|
||||||
|
|
||||||
|
OS 函数
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
OS 函数层提供访问锁和延迟的方法。
|
||||||
|
|
||||||
|
该锁定用于解决 SPI Flash 芯片访问和其他函数之间的冲突。例如,经 SPI0/1 访问 flash 芯片时,应当禁用 cache(平时用于取代码和 PSRAM 数据)。另一种情况是,一些没有 CS 线或者 CS 线受软件控制的设备(如通过 SPI 接口的 SD 卡控制)需要在一段时间内独占总线。
|
||||||
|
|
||||||
|
延时则用于某些长时操作,需要主机处于等待状态或执行轮询。
|
||||||
|
|
||||||
|
顶层 API 将芯片驱动和 OS 函数封装成一个完整的组件,并提供参数检查。
|
|
@ -1,6 +1,8 @@
|
||||||
Virtual filesystem component
|
Virtual filesystem component
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
:link_to_translation:`zh_CN:[中文]`
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
167
components/vfs/README_CN.rst
Normal file
167
components/vfs/README_CN.rst
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
虚拟文件系统组件
|
||||||
|
============================
|
||||||
|
|
||||||
|
:link_to_translation:`en:[English]`
|
||||||
|
|
||||||
|
概述
|
||||||
|
--------
|
||||||
|
|
||||||
|
虚拟文件系统 (VFS) 组件可为一些驱动提供一个统一接口。有了该接口,用户可像操作普通文件一样操作虚拟文件。这类驱动程序可以是 FAT、SPIFFS 等真实文件系统,也可以是有文件类接口的设备驱动程序。
|
||||||
|
|
||||||
|
VFS 组件支持 C 库函数(如 fopen 和 fprintf 等)与文件系统 (FS) 驱动程序协同工作。在高层级,每个 FS 驱动程序均与某些路径前缀相关联。当一个 C 库函数需要打开文件时,VFS 组件将搜索与该文件所在文件路径相关联的 FS 驱动程序,并将调用传递给该驱动程序。针对该文件的读取、写入等其他操作的调用也将传递给这个驱动程序。
|
||||||
|
|
||||||
|
例如,您可以使用 ``/fat`` 前缀注册 FAT 文件系统驱动,之后即可调用 ``fopen("/fat/file.txt", "w")``。之后,VFS 将调用 FAT 驱动的 ``open`` 函数,并将参数 ``/file.txt`` 和合适的打开模式传递给 ``open`` 函数;后续对返回的 ``FILE*`` 数据流调用 C 库函数也同样会传递给 FAT 驱动。
|
||||||
|
|
||||||
|
注册 FS 驱动程序
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
如需注册 FS 驱动程序,首先要定义一个 :cpp:type:`esp_vfs_t` 结构体实例,并用指向 FS API 的函数指针填充它。
|
||||||
|
|
||||||
|
.. highlight:: c
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
esp_vfs_t myfs = {
|
||||||
|
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||||
|
.write = &myfs_write,
|
||||||
|
.open = &myfs_open,
|
||||||
|
.fstat = &myfs_fstat,
|
||||||
|
.close = &myfs_close,
|
||||||
|
.read = &myfs_read,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
|
||||||
|
|
||||||
|
在上述代码中需要用到 ``read``、 ``write`` 或 ``read_p``、 ``write_p``,具体使用哪组函数由 FS 驱动程序 API 的声明方式决定。
|
||||||
|
|
||||||
|
示例 1:声明 API 函数时不带额外的上下文指针参数,即 FS 驱动程序为单例模式,此时使用 ``write`` ::
|
||||||
|
|
||||||
|
ssize_t myfs_write(int fd, const void * data, size_t size);
|
||||||
|
|
||||||
|
// In definition of esp_vfs_t:
|
||||||
|
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||||
|
.write = &myfs_write,
|
||||||
|
// ... other members initialized
|
||||||
|
|
||||||
|
// When registering FS, context pointer (third argument) is NULL:
|
||||||
|
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
|
||||||
|
|
||||||
|
示例 2:声明 API 函数时需要一个额外的上下文指针作为参数,即可支持多个 FS 驱动程序实例,此时使用 ``write_p`` ::
|
||||||
|
|
||||||
|
ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);
|
||||||
|
|
||||||
|
// In definition of esp_vfs_t:
|
||||||
|
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
|
||||||
|
.write_p = &myfs_write,
|
||||||
|
// ... other members initialized
|
||||||
|
|
||||||
|
// When registering FS, pass the FS context pointer into the third argument
|
||||||
|
// (hypothetical myfs_mount function is used for illustrative purposes)
|
||||||
|
myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
|
||||||
|
ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1));
|
||||||
|
|
||||||
|
// Can register another instance:
|
||||||
|
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
|
||||||
|
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));
|
||||||
|
|
||||||
|
同步输入/输出多路复用
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
如需通过 :cpp:func:`select` 使用同步输入/输出多路复用,首先需要把 :cpp:func:`start_select` 和 :cpp:func:`end_select` 注册到 VFS,如下所示:
|
||||||
|
|
||||||
|
.. highlight:: c
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
// In definition of esp_vfs_t:
|
||||||
|
.start_select = &uart_start_select,
|
||||||
|
.end_select = &uart_end_select,
|
||||||
|
// ... other members initialized
|
||||||
|
|
||||||
|
调用 :cpp:func:`start_select` 设置环境,用以检测某一 VFS 文件描述符的读取/写入/错误条件。调用 :cpp:func:`end_select` 终止、析构或释放 :cpp:func:`start_select` 设置的资源。请在 :component_file:`vfs/vfs_uart.c` 中查看 UART 外设参考实现、:cpp:func:`esp_vfs_dev_uart_register`、:cpp:func:`uart_start_select` 和 :cpp:func:`uart_end_select` 函数。
|
||||||
|
|
||||||
|
请参考以下示例,查看如何使用 VFS 文件描述符调用 :cpp:func:`select`:
|
||||||
|
|
||||||
|
- :example:`peripherals/uart_select`
|
||||||
|
- :example:`system/select`
|
||||||
|
|
||||||
|
如果 :cpp:func:`select` 用于套接字文件描述符,您可以启用 :envvar:`CONFIG_LWIP_USE_ONLY_LWIP_SELECT` 选项来减少代码量,提高性能。
|
||||||
|
|
||||||
|
路径
|
||||||
|
-----
|
||||||
|
|
||||||
|
已注册的 FS 驱动程序均有一个路径前缀与之关联,此路径前缀即为分区的挂载点。
|
||||||
|
|
||||||
|
如果挂载点中嵌套了其他挂载点,则在打开文件时使用具有最长匹配路径前缀的挂载点。例如,假设以下文件系统已在 VFS 中注册:
|
||||||
|
|
||||||
|
- 在 /data 下注册 FS 驱动程序 1
|
||||||
|
- 在 /data/static 下注册 FS 驱动程序 2
|
||||||
|
|
||||||
|
那么:
|
||||||
|
|
||||||
|
- 打开 ``/data/log.txt`` 会调用驱动程序 FS 1;
|
||||||
|
- 打开 ``/data/static/index.html`` 需调用 FS 驱动程序 2;
|
||||||
|
- 即便 FS 驱动程序 2 中没有 ``/index.html``,也不会在 FS 驱动程序 1 中查找 ``/static/index.html``。
|
||||||
|
|
||||||
|
挂载点名称必须以路径分隔符 (``/``) 开头,且分隔符后至少包含一个字符。但在以下情况中,VFS 同样支持空的挂载点名称:1. 应用程序需要提供一个”最后方案“下使用的文件系统;2. 应用程序需要同时覆盖 VFS 功能。如果没有与路径匹配的前缀,就会使用到这种文件系统。
|
||||||
|
|
||||||
|
VFS 不会对路径中的点 (``.``) 进行特殊处理,也不会将 ``..`` 视为对父目录的引用。在上述示例中,使用 ``/data/static/../log.txt`` 路径不会调用 FS 驱动程序 1 打开 ``/log.txt``。特定的 FS 驱动程序(如 FATFS)可能以不同的方式处理文件名中的点。
|
||||||
|
|
||||||
|
执行打开文件操作时,FS 驱动程序仅得到文件的相对路径(挂载点前缀已经被去除):
|
||||||
|
|
||||||
|
1. 以 ``/data`` 为路径前缀注册 ``myfs`` 驱动;
|
||||||
|
2. 应用程序调用 ``fopen("/data/config.json", ...)``;
|
||||||
|
3. VFS 调用 ``myfs_open("/config.json", ...)``;
|
||||||
|
4. ``myfs`` 驱动打开 ``/config.json`` 文件。
|
||||||
|
|
||||||
|
VFS 对文件路径长度没有限制,但文件系统路径前缀受 ``ESP_VFS_PATH_MAX`` 限制,即路径前缀上限为 ``ESP_VFS_PATH_MAX``。各个文件系统驱动则可能会对自己的文件名长度设置一些限制。
|
||||||
|
|
||||||
|
|
||||||
|
文件描述符
|
||||||
|
----------------
|
||||||
|
|
||||||
|
文件描述符是一组很小的正整数,从 ``0`` 到 ``FD_SETSIZE - 1``,``FD_SETSIZE`` 在 newlib ``sys/types.h`` 中定义。最大文件描述符由 ``CONFIG_LWIP_MAX_SOCKETS`` 定义,且为套接字保留。VFS 中包含一个名为 ``s_fd_table`` 的查找表,用于将全局文件描述符映射至 ``s_vfs`` 数组中注册的 VFS 驱动索引。
|
||||||
|
|
||||||
|
|
||||||
|
标准 IO 流 (stdin, stdout, stderr)
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
如果 menuconfig 中 ``UART for console output`` 选项没有设置为 ``None``,则 ``stdin``、 ``stdout`` 和 ``stderr`` 将默认从 UART 读取或写入。UART0 或 UART1 可用作标准 IO。默认情况下,UART0 使用 115200 波特率,TX 管脚为 GPIO1,RX 管脚为 GPIO3。您可以在 menuconfig 中更改上述参数。
|
||||||
|
|
||||||
|
对 ``stdout`` 或 ``stderr`` 执行写入操作将会向 UART 发送 FIFO 发送字符,对 ``stdin`` 执行读取操作则会从 UART 接收 FIFO 中取出字符。
|
||||||
|
|
||||||
|
默认情况下,VFS 使用简单的函数对 UART 进行读写操作。在所有数据放进 UART FIFO 之前,写操作将处于 busy-wait 状态,读操处于非阻塞状态,仅返回 FIFO 中已有数据。由于读操作为非阻塞,高层级 C 库函数调用(如 ``fscanf("%d\n", &var);``)可能获取不到所需结果。
|
||||||
|
|
||||||
|
如果应用程序使用 UART 驱动,则可以调用 ``esp_vfs_dev_uart_use_driver`` 函数来指导 VFS 使用驱动中断、读写阻塞功能等。您也可以调用 ``esp_vfs_dev_uart_use_nonblocking`` 来恢复非阻塞函数。
|
||||||
|
|
||||||
|
VFS 还为输入和输出提供换行符转换功能(可选)。多数应用程序在程序内部发送或接收以 LF (''\n'') 结尾的行,但不同的终端程序可能需要不同的换行符,比如 CR 或 CRLF。应用程序可以通过 menuconfig 或者调用 ``esp_vfs_dev_uart_set_rx_line_endings`` 和 ``esp_vfs_dev_uart_set_tx_line_endings`` 为输入输出配置换行符。
|
||||||
|
|
||||||
|
|
||||||
|
标准流和 FreeRTOS 任务
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``stdin``、``stdout`` 和 ``stderr`` 的 ``FILE`` 对象在所有 FreeRTOS 任务之间共享,指向这些对象的指针分别存储在每个任务的 ``struct _reent`` 中。
|
||||||
|
|
||||||
|
预处理器把如下代码:
|
||||||
|
|
||||||
|
.. highlight:: c
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
fprintf(stderr, "42\n");
|
||||||
|
|
||||||
|
解释为:
|
||||||
|
|
||||||
|
.. highlight:: c
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
fprintf(__getreent()->_stderr, "42\n");
|
||||||
|
|
||||||
|
其中 ``__getreent()`` 函数将为每个任务返回一个指向 ``struct _reent`` 的指针 (:component_file:`newlib/include/sys/reent.h#L370-L417`)。每个任务的 TCB 均拥有一个 ``struct _reent`` 结构体,任务初始化后,``struct _reent`` 结构体中的 ``_stdin``、``_stdout`` 和 ``_stderr`` 将会被赋予 ``_GLOBAL_REENT`` 中 ``_stdin``、 ``_stdout`` 和 ``_stderr`` 的值,``_GLOBAL_REENT`` 即为 FreeRTOS 启动之前所用结构体。
|
||||||
|
|
||||||
|
这样设计带来的结果是:
|
||||||
|
|
||||||
|
- 允许重定向给定任务的 ``stdin``、 ``stdout`` 和 ``stderr``,而不影响其他任务,例如通过 ``stdin = fopen("/dev/uart/1", "r")``;
|
||||||
|
- 但使用 ``fclose`` 关闭默认 ``stdin``、 ``stdout`` 或 ``stderr`` 将同时关闭相应的 ``FILE`` 流对象,因此会影响其他任务;
|
||||||
|
- 如需更改新任务的默认 ``stdin``、 ``stdout`` 和 ``stderr`` 流,请在创建新任务之前修改 ``_GLOBAL_REENT->_stdin`` (``_stdout``、``_stderr``)。
|
|
@ -1 +1,36 @@
|
||||||
.. include:: ../../../en/api-reference/storage/nvs_flash.rst
|
.. include:: ../../../../components/nvs_flash/README_CN.rst
|
||||||
|
|
||||||
|
NVS 分区生成程序
|
||||||
|
------------------
|
||||||
|
|
||||||
|
NVS 分区生成程序帮助生成 NVS 分区二进制文件,可使用烧录程序将二进制文件单独烧录至特定分区。烧录至分区上的键值对由 CSV 文件提供,详情请参考 :doc:`NVS 分区生成程序 <nvs_partition_gen>`。
|
||||||
|
|
||||||
|
应用示例
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
ESP-IDF :example:`storage` 目录下提供了两个代码示例:
|
||||||
|
|
||||||
|
:example:`storage/nvs_rw_value`
|
||||||
|
|
||||||
|
演示如何读取及写入 NVS 单个整数值。
|
||||||
|
|
||||||
|
此示例中的值表示 ESP32 模组重启次数。NVS 中数据不会因为模组重启而丢失,因此只有将这一值存储于 NVS 中,才能起到重启次数计数器的作用。
|
||||||
|
|
||||||
|
该示例也演示了如何检测读取/写入操作是否成功,以及某个特定值是否在 NVS 中尚未初始化。诊断程序以纯文本形式提供,帮助您追踪程序流程,及时发现问题。
|
||||||
|
|
||||||
|
:example:`storage/nvs_rw_blob`
|
||||||
|
|
||||||
|
演示如何读取及写入 NVS 单个整数值和 Blob(二进制大对象),并在 NVS 中存储这一数值,即便 ESP32 模组重启也不会消失。
|
||||||
|
|
||||||
|
* value - 记录 ESP32 模组软重启次数和硬重启次数。
|
||||||
|
* blob - 内含记录模组运行次数的表格。此表格将被从 NVS 读取至动态分配的 RAM 上。每次手动软重启后,表格内运行次数即增加一次,新加的运行次数被写入 NVS。下拉 GPIO0 即可手动软重启。
|
||||||
|
|
||||||
|
该示例也演示了如何执行诊断程序以检测读取/写入操作是否成功。
|
||||||
|
|
||||||
|
|
||||||
|
API 参考
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. include:: /_build/inc/nvs_flash.inc
|
||||||
|
|
||||||
|
.. include:: /_build/inc/nvs.inc
|
||||||
|
|
|
@ -1 +1,47 @@
|
||||||
.. include:: ../../../en/api-reference/storage/spi_flash.rst
|
.. include:: ../../../../components/spi_flash/README_CN.rst
|
||||||
|
|
||||||
|
另请参考
|
||||||
|
------------
|
||||||
|
|
||||||
|
- :doc:`分区表 <../../api-guides/partition-tables>`
|
||||||
|
- :doc:`OTA API <../system/ota>` 提供了高层 API 用于更新存储在 flash 中的 app 固件。
|
||||||
|
- :doc:`NVS API <nvs_flash>` 提供了结构化 API 用于存储 SPI flash 中的碎片数据。
|
||||||
|
|
||||||
|
.. _spi-flash-implementation-details:
|
||||||
|
|
||||||
|
实现细节
|
||||||
|
------------
|
||||||
|
|
||||||
|
必须确保操作期间,两个 CPU 均未从 flash 运行代码,实现细节如下:
|
||||||
|
|
||||||
|
- 单核模式下,SDK 在执行 flash 操作前将禁用中断或调度算法。
|
||||||
|
- 双核模式下,实现细节更为复杂,SDK 需确保两个 CPU 均未运行 flash 代码。
|
||||||
|
|
||||||
|
如果有 SPI flash API 在 CPU A(PRO 或 APP)上调用,它使用 ``esp_ipc_call`` API 在 CPU B 上运行 ``spi_flash_op_block_func`` 函数。``esp_ipc_call`` API 在 CPU B 上唤醒一个高优先级任务,即运行 ``spi_flash_op_block_func`` 函数。运行该函数将禁用 CPU B 上的 cache,并使用 ``s_flash_op_can_start`` 旗帜来标志 cache 已禁用。然后,CPU A 上的任务也会禁用 cache 并继续执行 flash 操作。
|
||||||
|
|
||||||
|
执行 flash 操作时,CPU A 和 CPU B 仍然可以执行中断操作。默认中断代码均存储于 RAM 中,如果新添加了中断分配 API,则应添加一个标志位以请求在 flash 操作期间禁用该新分配的中断。
|
||||||
|
|
||||||
|
Flash 操作完成后,CPU A 上的函数将设置另一标志位,即 ``s_flash_op_complete``,用以通知 CPU B 上的任务可以重新启用 cache 并释放 CPU。接着,CPU A 上的函数也重新启用 cache,并将控制权返还给调用者。
|
||||||
|
|
||||||
|
另外,所有 API 函数均受互斥量 ``s_flash_op_mutex`` 保护。
|
||||||
|
|
||||||
|
在单核环境中(启用 :ref:`CONFIG_FREERTOS_UNICORE`),您需要禁用上述两个 cache 以防发生 CPU 间通信。
|
||||||
|
|
||||||
|
SPI Flash API 参考
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. include:: /_build/inc/esp_flash_spi_init.inc
|
||||||
|
.. include:: /_build/inc/esp_flash.inc
|
||||||
|
.. include:: /_build/inc/spi_flash_types.inc
|
||||||
|
|
||||||
|
分区表 API 参考
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. include:: /_build/inc/esp_partition.inc
|
||||||
|
|
||||||
|
Flash 加密 API 参考
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. include:: /_build/inc/esp_flash_encrypt.inc
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1,15 @@
|
||||||
.. include:: ../../../en/api-reference/storage/vfs.rst
|
.. include:: ../../../../components/vfs/README_CN.rst
|
||||||
|
|
||||||
|
应用示例
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
`指南`_ (未完成)
|
||||||
|
|
||||||
|
.. _指南: ../../template.html
|
||||||
|
|
||||||
|
API 参考
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. include:: /_build/inc/esp_vfs.inc
|
||||||
|
|
||||||
|
.. include:: /_build/inc/esp_vfs_dev.inc
|
||||||
|
|
Loading…
Reference in a new issue