mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
docs:update CN translation for API reference storage(fatfs and index)
This commit is contained in:
parent
ae87b280bc
commit
3ffc5f0c67
@ -84,4 +84,3 @@ They provide implementation of disk I/O functions for SD/MMC cards and can be re
|
||||
.. doxygenfunction:: ff_diskio_register_wl_partition
|
||||
.. doxygenfunction:: ff_diskio_register_raw_partition
|
||||
|
||||
|
||||
|
@ -14,7 +14,8 @@ Storage API
|
||||
SPI Flash and Partition APIs <spi_flash>
|
||||
SPIFFS Filesystem <spiffs>
|
||||
Virtual Filesystem <vfs>
|
||||
Wear Levelling <wear-levelling>
|
||||
Wear Levelling API <wear-levelling>
|
||||
|
||||
|
||||
|
||||
Code examples for this API section are provided in the :example:`storage` directory of ESP-IDF examples.
|
||||
|
@ -8,7 +8,6 @@ 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
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -32,14 +31,14 @@ NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length
|
||||
|
||||
.. note::
|
||||
|
||||
String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower.
|
||||
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.
|
||||
|
||||
Additional types, such as ``float`` and ``double`` might be added later.
|
||||
|
||||
Keys are required to be unique. Assigning a new value to an existing key works as follows:
|
||||
|
||||
- if the new value is of the same type as the old one, value is updated
|
||||
- if the new value has a different data type, an error is returned
|
||||
- If the new value is of the same type as the old one, value is updated.
|
||||
- If the new value has a different data type, an error is returned.
|
||||
|
||||
Data type check is also performed when reading a value. An error is returned if the data type of the read operation does not match the data type of the value.
|
||||
|
||||
@ -80,7 +79,7 @@ NVS Encryption
|
||||
|
||||
Data stored in NVS partitions can be encrypted using AES-XTS in the manner similar to the one mentioned in disk encryption standard IEEE P1619. For the purpose of encryption, each entry is treated as one `sector` and relative address of the entry (w.r.t. partition-start) is fed to the encryption algorithm as `sector-number`. The NVS Encryption can be enabled by enabling :ref:`CONFIG_NVS_ENCRYPTION`. The keys required for NVS encryption are stored in yet another partition, which is protected using :doc:`Flash Encryption <../../security/flash-encryption>`. Therefore, enabling :doc:`Flash Encryption <../../security/flash-encryption>` is a prerequisite for NVS encryption.
|
||||
|
||||
The NVS Encryption is enabled by default when :doc:`Flash Encryption <../../security/flash-encryption>` is enabled. This is done because WiFi driver stores credentials (like SSID and passphrase) in the default NVS partition. It is important to encrypt them as default choice if platform level encryption is already enabled.
|
||||
The NVS Encryption is enabled by default when :doc:`Flash Encryption <../../security/flash-encryption>` is enabled. This is done because Wi-Fi driver stores credentials (like SSID and passphrase) in the default NVS partition. It is important to encrypt them as default choice if platform level encryption is already enabled.
|
||||
|
||||
For using NVS encryption, the partition table must contain the :ref:`nvs_key_partition`. Two partition tables containing the :ref:`nvs_key_partition` are provided for NVS encryption under the partition table option (menuconfig->Partition Table). They can be selected with the project configuration menu (``idf.py menuconfig``). Please refer to the example :example:`security/flash_encryption` for how to configure and use NVS encryption feature.
|
||||
|
||||
@ -96,18 +95,18 @@ NVS key partition
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key(32) |
|
||||
| XTS encryption key (32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32(4) |
|
||||
| CRC32 (4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
The XTS encryption keys in the :ref:`nvs_key_partition` can be generated in one of the following two ways.
|
||||
|
||||
1. Generate the keys on the ESP chip:
|
||||
|
||||
When NVS encryption is enabled the :cpp:func:`nvs_flash_init` API function can be used to initialize the encrypted default NVS partition. The API function internally generates the XTS encryption keys on the ESP chip. The API function finds the first :ref:`nvs_key_partition`. Then the API function automatically generates and stores the nvs keys in that partition by making use of the :cpp:func:`nvs_flash_generate_keys` API function provided by :component_file:`nvs_flash/include/nvs_flash.h`. New keys are generated and stored only when the respective key partiton is empty. The same key partition can then be used to read the security configurations for initializing a custom encrypted NVS partition with help of :cpp:func:`nvs_flash_secure_init_partition`.
|
||||
When NVS encryption is enabled the :cpp:func:`nvs_flash_init` API function can be used to initialize the encrypted default NVS partition. The API function internally generates the XTS encryption keys on the ESP chip. The API function finds the first :ref:`nvs_key_partition`. Then the API function automatically generates and stores the NVS keys in that partition by making use of the :cpp:func:`nvs_flash_generate_keys` API function provided by :component_file:`nvs_flash/include/nvs_flash.h`. New keys are generated and stored only when the respective key partiton is empty. The same key partition can then be used to read the security configurations for initializing a custom encrypted NVS partition with help of :cpp:func:`nvs_flash_secure_init_partition`.
|
||||
|
||||
The API functions :cpp:func:`nvs_flash_secure_init` and :cpp:func:`nvs_flash_secure_init_partition` do not generate the keys internally. When these API functions are used for initializing encrypted NVS partitions, the keys can be generated after startup using the :cpp:func:`nvs_flash_generate_keys` API function provided by ``nvs_flash.h``. The API function will then write those keys onto the key-partition in encrypted form.
|
||||
|
||||
@ -178,7 +177,7 @@ You can find code examples in the :example:`storage` directory of ESP-IDF exampl
|
||||
|
||||
:example:`storage/nvs_rw_value_cxx`
|
||||
|
||||
This example does exactly the same as :example:`storage/nvs_rw_value`, except that it uses the C++ nvs handle class.
|
||||
This example does exactly the same as :example:`storage/nvs_rw_value`, except that it uses the C++ NVS handle class.
|
||||
|
||||
Internals
|
||||
---------
|
||||
@ -378,5 +377,3 @@ API Reference
|
||||
.. include-build-file:: inc/nvs_flash.inc
|
||||
|
||||
.. include-build-file:: inc/nvs.inc
|
||||
|
||||
|
||||
|
@ -74,13 +74,22 @@ An example which combines the SDMMC driver with the FATFS library is provided in
|
||||
|
||||
For card configuration and data transfer, choose the pair of functions relevant to your case from the table below.
|
||||
|
||||
========================================================================= ================================= =================================
|
||||
Action Read Function Write Function
|
||||
========================================================================= ================================= =================================
|
||||
Read and write a single byte using IO_RW_DIRECT (CMD52) :cpp:func:`sdmmc_io_read_byte` :cpp:func:`sdmmc_io_write_byte`
|
||||
Read and write multiple bytes using IO_RW_EXTENDED (CMD53) in byte mode :cpp:func:`sdmmc_io_read_bytes` :cpp:func:`sdmmc_io_write_bytes`
|
||||
Read and write blocks of data using IO_RW_EXTENDED (CMD53) in block mode :cpp:func:`sdmmc_io_read_blocks` :cpp:func:`sdmmc_io_write_blocks`
|
||||
========================================================================= ================================= =================================
|
||||
.. list-table::
|
||||
:widths: 55 25 20
|
||||
:header-rows: 1
|
||||
|
||||
* - Action
|
||||
- Read Function
|
||||
- Write Function
|
||||
* - Read and write a single byte using IO_RW_DIRECT (CMD52)
|
||||
- :cpp:func:`sdmmc_io_read_byte`
|
||||
- :cpp:func:`sdmmc_io_write_byte`
|
||||
* - Read and write multiple bytes using IO_RW_EXTENDED (CMD53) in byte mode
|
||||
- :cpp:func:`sdmmc_io_read_bytes`
|
||||
- :cpp:func:`sdmmc_io_write_bytes`
|
||||
* - Read and write blocks of data using IO_RW_EXTENDED (CMD53) in block mode
|
||||
- :cpp:func:`sdmmc_io_read_blocks`
|
||||
- :cpp:func:`sdmmc_io_write_blocks`
|
||||
|
||||
SDIO interrupts can be enabled by the application using the function :cpp:func:`sdmmc_io_enable_int`. When using SDIO in 1-line mode, the D1 line also needs to be connected to use SDIO interrupts.
|
||||
|
||||
@ -88,8 +97,7 @@ An example which combines the SDMMC driver with the FATFS library is provided in
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
There is a component ESSL (ESP Serial Slave Link) to use if you are communicating with an ESP32
|
||||
SDIO slave. See :doc:`/api-reference/protocols/esp_serial_slave_link` and example :example:`peripherals/sdio/host`.
|
||||
There is a component ESSL (ESP Serial Slave Link) to use if you are communicating with an ESP32 SDIO slave. See :doc:`/api-reference/protocols/esp_serial_slave_link` and example :example:`peripherals/sdio/host`.
|
||||
|
||||
Combo (memory + IO) cards
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -7,7 +7,7 @@ Overview
|
||||
--------
|
||||
The spi_flash component contains API functions related to reading, writing, erasing, memory mapping for data in the external flash. The spi_flash component also has higher-level API functions which work with partitions defined in the :doc:`partition table </api-guides/partition-tables>`.
|
||||
|
||||
Different from the API before IDF v4.0, the functionality of esp_flash_* APIs is not limited to the "main" SPI flash chip (the same SPI flash chip from which program runs). With different chip pointers, you can access to external flashes chips connected to not only SPI0/1 but also other SPI buses like SPI2.
|
||||
Different from the API before IDF v4.0, the functionality of `esp_flash_*` APIs is not limited to the "main" SPI flash chip (the same SPI flash chip from which program runs). With different chip pointers, you can access to external flash chips connected to not only SPI0/1 but also other SPI buses like SPI2.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -20,7 +20,7 @@ Different from the API before IDF v4.0, the functionality of esp_flash_* APIs is
|
||||
Flash APIs after IDF v4.0 are no longer *atomic*. A writing operation during another on-going read operation, on the overlapped flash address, may cause the return data from the read operation to be partly same as before, and partly updated as new written.
|
||||
|
||||
|
||||
Kconfig option :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` can be used to switch ``spi_flash_*`` functions back to the implementation before IDF v4.0. However, the code size may get bigger if you use the new API and the old API the same time.
|
||||
Kconfig option :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` can be used to switch ``spi_flash_*`` functions back to the implementation before ESP-IDF v4.0. However, the code size may get bigger if you use the new API and the old API at the same time.
|
||||
|
||||
Encrypted reads and writes use the old implementation, even if :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` is not enabled. As such, encrypted flash operations are only supported with the main flash chip (and not with other flash chips, that is on SPI1 with different CS, or on other SPI buses). Reading through cache is only supported on the main flash, which is determined by the HW.
|
||||
|
||||
@ -96,7 +96,7 @@ Concurrency Constraints for flash on SPI1
|
||||
|
||||
.. attention::
|
||||
|
||||
The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Hence, calling SPI Flash API on SPI1 bus (including the main flash) will cause significant influence to the whole system. See :doc:`spi_flash_concurrency` for more details.
|
||||
The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI flash driver). Hence, calling SPI Flash API on SPI1 bus (including the main flash) will cause significant influence to the whole system. See :doc:`spi_flash_concurrency` for more details.
|
||||
|
||||
.. _flash-partition-apis:
|
||||
|
||||
@ -147,11 +147,12 @@ Memory mapping API are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
|
||||
Differences between :cpp:func:`spi_flash_mmap` and :cpp:func:`esp_partition_mmap` are as follows:
|
||||
|
||||
- :cpp:func:`spi_flash_mmap` must be given a {IDF_TARGET_CACHE_SIZE} aligned physical address.
|
||||
- :cpp:func:`esp_partition_mmap` may be given any arbitrary offset within the partition, it will adjust the returned pointer to mapped memory as necessary
|
||||
- :cpp:func:`esp_partition_mmap` may be given any arbitrary offset within the partition, it will adjust the returned pointer to mapped memory as necessary.
|
||||
|
||||
Note that since memory mapping happens in pages, it may be possible to read data outside of the partition provided to ``esp_partition_mmap``, regardless of the partition boundary.
|
||||
|
||||
.. note:: mmap is supported by cache, so it can only be used on main flash.
|
||||
.. note::
|
||||
mmap is supported by cache, so it can only be used on main flash.
|
||||
|
||||
SPI Flash Implementation
|
||||
------------------------
|
||||
@ -219,13 +220,13 @@ In order to perform some flash operations, it is necessary to make sure that bot
|
||||
- In a single-core setup, the SDK does it by disabling interrupts/scheduler before performing the flash operation.
|
||||
- In a dual-core setup, this is slightly more complicated as the SDK needs to make sure that the other CPU is not running any code from flash.
|
||||
|
||||
When SPI flash API is called on CPU A (can be PRO or APP), start the spi_flash_op_block_func function on CPU B using the esp_ipc_call API. This API wakes up a high priority task on CPU B and tells it to execute a given function, in this case, spi_flash_op_block_func. This function disables cache on CPU B and signals that the cache is disabled by setting the s_flash_op_can_start flag. Then the task on CPU A disables cache as well and proceeds to execute flash operation.
|
||||
When SPI flash API is called on CPU A (can be PRO or APP), start the ``spi_flash_op_block_func`` function on CPU B using the ``esp_ipc_call`` API. This API wakes up a high priority task on CPU B and tells it to execute a given function, in this case, ``spi_flash_op_block_func``. This function disables cache on CPU B and signals that the cache is disabled by setting the ``s_flash_op_can_start`` flag. Then the task on CPU A disables cache as well and proceeds to execute flash operation.
|
||||
|
||||
While a flash operation is running, interrupts can still run on CPUs A and B. It is assumed that all interrupt code is placed into RAM. Once the interrupt allocation API is added, a flag should be added to request the interrupt to be disabled for the duration of a flash operations.
|
||||
|
||||
Once the flash operation is complete, the function on CPU A sets another flag, s_flash_op_complete, to let the task on CPU B know that it can re-enable cache and release the CPU. Then the function on CPU A re-enables the cache on CPU A as well and returns control to the calling code.
|
||||
Once the flash operation is complete, the function on CPU A sets another flag, ``s_flash_op_complete``, to let the task on CPU B know that it can re-enable cache and release the CPU. Then the function on CPU A re-enables the cache on CPU A as well and returns control to the calling code.
|
||||
|
||||
Additionally, all API functions are protected with a mutex (s_flash_op_mutex).
|
||||
Additionally, all API functions are protected with a mutex (``s_flash_op_mutex``).
|
||||
|
||||
In a single core environment (:ref:`CONFIG_FREERTOS_UNICORE` enabled), you need to disable both caches, so that no inter-CPU communication can take place.
|
||||
|
||||
|
@ -112,7 +112,6 @@ To flash the image onto {IDF_TARGET_NAME} at offset 0x110000, run::
|
||||
|
||||
python esptool.py --chip {IDF_TARGET_PATH_NAME} --port [port] --baud [baud] write_flash -z 0x110000 spiffs.bin
|
||||
|
||||
|
||||
Notes on which SPIFFS tool to use
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -7,38 +7,41 @@ ESP-IDF 使用 `FatFs <http://elm-chan.org/fsw/ff/00index_e.html>`_ 库来实现
|
||||
|
||||
此外,我们对 FatFs 库进行了扩展,新增了支持可插拔磁盘 I/O 调度层,从而允许在运行时将 FatFs 驱动映射到物理磁盘。
|
||||
|
||||
|
||||
FatFs 与 VFS 配合使用
|
||||
----------------------------
|
||||
|
||||
:component_file:`fatfs/vfs/esp_vfs_fat.h` 头文件定义了连接 FatFs 和 VFS 的函数。
|
||||
头文件 :component_file:`fatfs/vfs/esp_vfs_fat.h` 定义了连接 FatFs 和 VFS 的函数。
|
||||
|
||||
函数 :cpp:func:`esp_vfs_fat_register` 分配一个 ``FATFS`` 结构,并在 VFS 中注册特定路径前缀。如果文件路径以此前缀开头,则对此文件的后续操作将转至 FatFs API。函数 :cpp:func:`esp_vfs_fat_unregister_path` 删除在 VFS 中的注册,并释放 ``FATFS`` 结构。
|
||||
函数 :cpp:func:`esp_vfs_fat_register` 分配一个 ``FATFS`` 结构,并在 VFS 中注册特定路径前缀。如果文件路径以此前缀开头,则对此文件的后续操作将转至 FatFs API。
|
||||
函数 :cpp:func:`esp_vfs_fat_unregister_path` 删除在 VFS 中的注册,并释放 ``FATFS`` 结构。
|
||||
|
||||
多数应用程序在使用 ``esp_vfs_fat_`` 函数时,采用如下步骤:
|
||||
|
||||
1. 调用 :cpp:func:`esp_vfs_fat_register`,指定:
|
||||
|
||||
- 挂载文件系统的路径前缀(例如,``"/sdcard"`` 或 ``"/spiflash"``)
|
||||
- FatFs 驱动编号
|
||||
- 一个用于接收指向 ``FATFS`` 结构指针的变量
|
||||
|
||||
2. 调用 :cpp:func:`ff_diskio_register` 为上述步骤中的驱动编号注册磁盘 I/O 驱动;
|
||||
2. 调用 :cpp:func:`ff_diskio_register` 为步骤 1 中的驱动编号注册磁盘 I/O 驱动;
|
||||
|
||||
3. 调用 FatFs 函数 ``f_mount``,或 ``f_fdisk``, ``f_mkfs``,并使用与传递到 :cpp:func:`esp_vfs_fat_register` 相同的驱动编号挂载文件系统。请参考 `FatFs 文档 <http://www.elm-chan.org/fsw/ff/doc/mount.html>`_,查看更多信息;
|
||||
3. 调用 FatFs 函数 ``f_mount``,随后调用 ``f_fdisk`` 或 ``f_mkfs``,并使用与传递到 :cpp:func:`esp_vfs_fat_register` 相同的驱动编号挂载文件系统。请参考 `FatFs 文档 <http://www.elm-chan.org/fsw/ff/doc/mount.html>`_,查看更多信息;
|
||||
|
||||
4. 调用 C 标准库和 POSIX API 对路径中带有步骤 1 中所述前缀的文件(例如,``"/sdcard/hello.txt"``)执行打开、读取、写入、擦除、复制等操作。
|
||||
4. 调用 C 标准库和 POSIX API 对路径中带有步骤 1 中所述前缀的文件(例如,``"/sdcard/hello.txt"``)执行打开、读取、写入、擦除、复制等操作。文件系统默认使用 `8.3 文件名 <https://en.wikipedia.org/wiki/8.3_filename>`_ 格式 (SFN)。若您需要使用长文件名 (LFN),启用 :ref:`CONFIG_FATFS_LONG_FILENAMES` 选项。请参考 `here <http://elm-chan.org/fsw/ff/doc/filename.html>`_,查看更多信息;
|
||||
|
||||
5. 您可以选择直接调用 FatFs 库函数,但需要使用没有 VFS 前缀的路径(例如,``"/hello.txt"``);
|
||||
5. 您可以选择启用 :ref:`CONFIG_FATFS_USE_FASTSEEK` 选项,使用 POSIX lseek 来快速执行。快速查找不适用于编辑模式下的文件,所以,使用快速查找时,应在只读模式下打开(或者关闭然后重新打开)文件;
|
||||
|
||||
6. 关闭所有打开的文件;
|
||||
6. 您也可以选择直接调用 FatFs 库函数,但需要使用没有 VFS 前缀的路径(例如,``"/hello.txt"``);
|
||||
|
||||
7. 调用 ``f_mount`` 并使用 NULL ``FATFS*`` 参数为与上述编号相同的驱动卸载文件系统;
|
||||
7. 关闭所有打开的文件;
|
||||
|
||||
8. 调用 FatFs 函数 :cpp:func:`ff_diskio_register` 并使用 NULL ``ff_diskio_impl_t*`` 参数和相同的驱动编号来释放注册的磁盘 I/O 驱动。
|
||||
8. 调用 FatFs 函数 ``f_mount`` 并使用 NULL ``FATFS*`` 参数,为与上述编号相同的驱动卸载文件系统;
|
||||
|
||||
9. 调用 :cpp:func:`esp_vfs_fat_unregister_path` 并使用文件系统挂载的路径将 FatFs 从 NVS 中移除,并释放步骤 1 中分配的 FatFs 结构。
|
||||
9. 调用 FatFs 函数 :cpp:func:`ff_diskio_register` 并使用 NULL ``ff_diskio_impl_t*`` 参数和相同的驱动编号,来释放注册的磁盘 I/O 驱动。
|
||||
|
||||
``esp_vfs_fat_sdmmc_mount`` 和 ``esp_vfs_fat_sdmmc_unmount`` 这两个便捷函数对上述步骤进行了封装,并加入对 SD 卡初始化的处理,非常便捷。我们将在下一章节详细介绍这两个函数。
|
||||
10. 调用 :cpp:func:`esp_vfs_fat_unregister_path` 并使用文件系统挂载的路径将 FatFs 从 VFS 中移除,并释放步骤 1 中分配的 ``FATFS`` 结构。
|
||||
|
||||
便捷函数 ``esp_vfs_fat_sdmmc_mount``, ``esp_vfs_fat_sdspi_mount``, 和 ``esp_vfs_fat_sdmmc_unmount`` 对上述步骤进行了封装,并加入了对 SD 卡初始化的处理。我们将在下一章节详细介绍这两个函数。
|
||||
|
||||
.. doxygenfunction:: esp_vfs_fat_register
|
||||
.. doxygenfunction:: esp_vfs_fat_unregister_path
|
||||
@ -47,20 +50,21 @@ FatFs 与 VFS 配合使用
|
||||
FatFs 与 VFS 和 SD 卡配合使用
|
||||
---------------------------------
|
||||
|
||||
:component_file:`fatfs/vfs/esp_vfs_fat.h` 头文件定义了两个便捷函数 :cpp:func:`esp_vfs_fat_sdmmc_mount` 和 :cpp:func:`esp_vfs_fat_sdmmc_unmount`。这两个函数分别执行上一章节的步骤 1-3 和步骤 7-9,并初始化 SD 卡,但仅提供有限的错误处理功能。我们鼓励开发人员查看源代码并将更多高级功能集成到产品应用中。
|
||||
头文件 :component_file:`fatfs/vfs/esp_vfs_fat.h` 定义了两个便捷函数 :cpp:func:`esp_vfs_fat_sdmmc_mount` 和 :cpp:func:`esp_vfs_fat_sdmmc_unmount`。这两个函数分别执行上一章节的步骤 1-3 和步骤 7-9,并初始化 SD 卡,但仅提供有限的错误处理功能。我们鼓励开发人员查看源代码并将更多高级功能集成到产品应用中。
|
||||
|
||||
:cpp:func:`esp_vfs_fat_sdmmc_unmount` 函数用于卸载文件系统并释放从 :cpp:func:`esp_vfs_fat_sdmmc_mount` 函数获取的资源。
|
||||
便捷函数 :cpp:func:`esp_vfs_fat_sdmmc_unmount` 用于卸载文件系统并释放从 :cpp:func:`esp_vfs_fat_sdmmc_mount` 函数获取的资源。
|
||||
|
||||
.. doxygenfunction:: esp_vfs_fat_sdmmc_mount
|
||||
.. doxygenfunction:: esp_vfs_fat_sdspi_mount
|
||||
.. doxygenstruct:: esp_vfs_fat_mount_config_t
|
||||
:members:
|
||||
.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount
|
||||
.. doxygenfunction:: esp_vfs_fat_sdcard_unmount
|
||||
|
||||
|
||||
FatFs 与 VFS 配合使用(只读模式下)
|
||||
--------------------------------------
|
||||
|
||||
:component_file:`fatfs/vfs/esp_vfs_fat.h` 头文件也定义了两个便捷函数 :cpp:func:`esp_vfs_fat_rawflash_mount` 和 :cpp:func:`esp_vfs_fat_rawflash_unmount`。上述两个函数分别对 FAT 只读分区执行步骤 1-3 和步骤 7-9。有些数据分区仅在工厂时写入一次,之后在整个硬件生命周期内都不会再有任何改动。利用上述两个函数处理这种数据分区非常方便。
|
||||
头文件 :component_file:`fatfs/vfs/esp_vfs_fat.h` 也定义了两个便捷函数 :cpp:func:`esp_vfs_fat_rawflash_mount` 和 :cpp:func:`esp_vfs_fat_rawflash_unmount`。上述两个函数分别对 FAT 只读分区执行步骤 1-3 和步骤 7-9。有些数据分区仅在工厂配置时写入一次,之后在整个硬件生命周期内都不会再有任何改动。利用上述两个函数处理这种数据分区非常方便。
|
||||
|
||||
.. doxygenfunction:: esp_vfs_fat_rawflash_mount
|
||||
.. doxygenfunction:: esp_vfs_fat_rawflash_unmount
|
||||
@ -71,7 +75,7 @@ FatFs 磁盘 I/O 层
|
||||
|
||||
我们对 FatFs API 函数进行了扩展,实现了运行期间注册磁盘 I/O 驱动。
|
||||
|
||||
上述 API 为 SD/MMC 卡提供了磁盘 I/O 函数实现方式,可使用 :cpp:func:`ff_diskio_register_sdmmc` 注册指定的 FatFs 驱动编号。
|
||||
上述 API 为 SD/MMC 卡提供了磁盘 I/O 函数实现方式,可使用 :cpp:func:`ff_diskio_register_sdmmc` 函数注册指定的 FatFs 驱动编号。
|
||||
|
||||
.. doxygenfunction:: ff_diskio_register
|
||||
.. doxygenstruct:: ff_diskio_impl_t
|
||||
@ -80,4 +84,3 @@ FatFs 磁盘 I/O 层
|
||||
.. doxygenfunction:: ff_diskio_register_wl_partition
|
||||
.. doxygenfunction:: ff_diskio_register_raw_partition
|
||||
|
||||
|
||||
|
@ -6,15 +6,16 @@
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
SPI Flash 和分区 API <spi_flash>
|
||||
SD/SDIO/MMC 驱动程序 <sdmmc>
|
||||
非易失性存储 <nvs_flash>
|
||||
NVS 分区生成程序 <nvs_partition_gen.rst>
|
||||
虚拟文件系统 <vfs>
|
||||
FAT 文件系统 <fatfs>
|
||||
磨损均衡 <wear-levelling>
|
||||
SPIFFS 文件系统 <spiffs>
|
||||
量产程序 <mass_mfg.rst>
|
||||
非易失性存储库 <nvs_flash>
|
||||
NVS 分区生成程序 <nvs_partition_gen.rst>
|
||||
SD/SDIO/MMC 驱动程序 <sdmmc>
|
||||
SPI Flash API <spi_flash>
|
||||
SPIFFS 文件系统 <spiffs>
|
||||
虚拟文件系统组件 <vfs>
|
||||
磨损均衡 API <wear-levelling>
|
||||
|
||||
|
||||
|
||||
此部分 API 代码示例详见 ESP-IDF 项下 :example:`storage` 目录。
|
||||
|
@ -11,9 +11,9 @@
|
||||
底层存储
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 通过调用 ``spi_flash_{read|write|erase}`` API 对主 flash 的部分空间进行读、写、擦除操作,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 ``nvs_open`` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 ``nvs_open_from_part`` API 选择使用指定名称的任意分区。
|
||||
NVS 库通过调用 :ref:`esp_partition <flash-partition-apis>` API 使用主 flash 的部分空间,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 :cpp:func:`nvs_open` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 :cpp:func:`nvs_open_from_partition` API 选择使用指定名称的任意分区。
|
||||
|
||||
NVS 库后续版本可能会增加其他存储器后端,实现将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。
|
||||
NVS 库后续版本可能会增加其他存储器后端,来将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。
|
||||
|
||||
.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase-flash`` 命令擦除 flash 上的所有内容。
|
||||
|
||||
@ -23,15 +23,15 @@ NVS 库后续版本可能会增加其他存储器后端,实现将数据保存
|
||||
键值对
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型:
|
||||
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的最大键长为 15 个字符。值可以为以下几种类型:
|
||||
|
||||
- 整数型:``uint8_t``、``int8_t``、``uint16_t``、``int16_t``、``uint32_t``、``int32_t``、``uint64_t`` 和 ``int64_t``;
|
||||
- 以 ``\0`` 结尾的字符串;
|
||||
- 以 0 结尾的字符串;
|
||||
- 可变长度的二进制数据 (BLOB)
|
||||
|
||||
.. note::
|
||||
|
||||
字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小减去 4000 字节的 97.6%,以较低值为准。
|
||||
字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小的 97.6% 减去 4000 字节,以较低值为准。
|
||||
|
||||
后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。
|
||||
|
||||
@ -46,19 +46,139 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
|
||||
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,即最多可占 15 个字符。命名空间的名称在调用 ``nvs_open`` 或 ``nvs_open_from_part`` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
|
||||
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,例如,最多可占 15 个字符。命名空间的名称在调用 :cpp:func:`nvs_open` 或 :cpp:type:`nvs_open_from_partition` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 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`` 释放迭代器。
|
||||
|
||||
|
||||
安全性、篡改性及鲁棒性
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 与 ESP32 flash 加密系统不直接兼容。但如果 NVS 加密与 ESP32 flash 加密一起使用时,数据仍可以加密形式存储。更多详情请参阅 :ref:`nvs_encryption`。
|
||||
NVS 与 {IDF_TARGET_NAME} flash 加密系统不直接兼容。但如果 NVS 加密与 {IDF_TARGET_NAME} flash 加密一起使用时,数据仍可以加密形式存储。详情请参阅 :ref:`nvs_encryption`。
|
||||
|
||||
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的人都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
|
||||
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的用户都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值对。但是,针对擦除操作没有相应的防篡改功能。
|
||||
|
||||
当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应当能对 flash 中的任意数据进行正确初始化。
|
||||
|
||||
|
||||
.. _nvs_encryption:
|
||||
|
||||
NVS 加密
|
||||
--------------
|
||||
|
||||
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密,每个条目被均视为一个扇区,并将条目相对地址(相对于分区开头)传递给加密算法,用作扇区号。可通过 :ref:`CONFIG_NVS_ENCRYPTION` 启用 NVS 加密。NVS 加密所需的密钥存储于其他分区,并进行了 :doc:`Flash 加密 <../../security/flash-encryption>`。因此,在使用 NVS 加密前应先启用 :doc:`Flash 加密 <../../security/flash-encryption>`。
|
||||
|
||||
启用 :doc:`Flash 加密 <../../security/flash-encryption>` 时,默认启用 NVS 加密。这是因为 Wi-Fi 驱动在默认的 NVS 分区中存储了凭证(如 SSID 和口令)。启用平台级加密后,仍需将它们作为默认选项进行加密。
|
||||
|
||||
使用 NVS 加密,分区表必须包含 :ref:`nvs_key_partition`。在分区表选项 (menuconfig->Partition Table) 下,为 NVS 加密提供了两个包含 :ref:`nvs_key_partition` 的分区表,您可以通过工程配置菜单 (``idf.py menuconfig``) 进行选择。请参考 :example:`security/flash_encryption` 中的例子,了解如何配置和使用 NVS 加密功能。
|
||||
|
||||
.. _nvs_key_partition:
|
||||
|
||||
NVS 密钥分区
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 `data`,子类型为 `key` 的密钥分区。该分区应标记为 `已加密` 且最小为 4096 字节。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`。在分区表选项 (menuconfig->Partition Table) 下提供了两个包含 :ref:`nvs_key_partition` 的额外分区表,可以直接用于 :ref:`nvs_encryption`。这些分区的具体结构见下表:
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key (32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32 (4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
可以通过以下两种方式生成 :ref:`nvs_key_partition` 中的 XTS 加密密钥:
|
||||
|
||||
1. 在 ESP 芯片上生成密钥:
|
||||
|
||||
启用 NVS 加密时,可用 :cpp:func:`nvs_flash_init` API 函数来初始化加密的默认 NVS 分区,在内部生成 ESP 芯片上的 XTS 加密密钥。在找到 :ref:`nvs_key_partition` 后,API 函数利用 :component_file:`nvs_flash/include/nvs_flash.h` 提供的 :cpp:func:`nvs_flash_generate_keys` 函数,自动生成并存储该分区中的 NVS 密钥。只有当各自的密钥分区为空时,才会生成并存储新的密钥。可以借助 :cpp:func:`nvs_flash_secure_init_partition` 用同一个密钥分区来读取安全配置,以初始化一个定制的加密 NVS 分区。
|
||||
|
||||
API 函数 :cpp:func:`nvs_flash_secure_init` 和 :cpp:func:`nvs_flash_secure_init_partition` 不在内部产生密钥。当这些 API 函数用于初始化加密的 NVS 分区时,可以在启动后使用 `nvs_flash.h` 提供的 :cpp:func:`nvs_flash_generate_keys` API 函数生成密钥,以加密的形式把密钥写到密钥分区上。
|
||||
|
||||
2. 使用预先生成的密钥分区:
|
||||
|
||||
若 :ref:`nvs_key_partition` 中的密钥不是由应用程序生成,则需要使用预先生成的密钥分区。可以使用 :doc:`NVS 分区生成工具 </api-reference/storage/nvs_partition_gen>` 生成包含 XTS 加密密钥的 :ref:`nvs_key_partition`。用户可以借助以下两个命令,将预先生成的密钥分区储存在 flash 上:
|
||||
|
||||
i) 建立并烧录分区表
|
||||
::
|
||||
|
||||
idf.py partition_table partition_table-flash
|
||||
|
||||
ii) 调用 :component_file:`parttool.py<partition_table/parttool.py>`,将密钥存储在 flash 上的 :ref:`nvs_key_partition` 中。详见 :doc:` 分区表 </api-guides/partition-tables>` 的分区工具部分。
|
||||
|
||||
|
||||
parttool.py --port /dev/ttyUSB0 --partition-table-offset "nvs_key partition offset" write_partition --partition-name="name of nvs_key partition" --input "nvs_key partition"
|
||||
|
||||
由于分区已标记为 `已加密`,而且启用了 :doc:`Flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。
|
||||
|
||||
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
|
||||
|
||||
加密读取/写入
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``nvs_get_*`` 和 ``nvs_set_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。
|
||||
|
||||
**加密默认的 NVS 分区:**
|
||||
无需额外步骤即可启用默认 NVS 分区的加密。启用 :ref:`CONFIG_NVS_ENCRYPTION` 时, :cpp:func:`nvs_flash_init` API 函数会在内部使用找到的第一个 :ref:`nvs_key_partition` 执行额外步骤,以启用默认 NVS 分区的加密(详情请参考 API 文档)。另外,:cpp:func:`nvs_flash_secure_init` API 函数也可以用来启用默认 NVS 分区的加密。
|
||||
|
||||
**加密一个自定义的 NVS 分区:**
|
||||
使用 :cpp:func:`nvs_flash_secure_init_partition` API 函数启用自定义 NVS 分区的加密,而非 :cpp:func:`nvs_flash_init_partition`。
|
||||
|
||||
使用 :cpp:func:`nvs_flash_secure_init` 和 :cpp:func:`nvs_flash_secure_init_partition` API 函数时,应用程序如需在加密状态下执行 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_partition`` API 打开命名空间;
|
||||
5. 使用 ``nvs_get_*`` 或 ``nvs_set_*`` API 执行 NVS 读取/写入操作;
|
||||
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
|
||||
|
||||
NVS 分区生成程序
|
||||
------------------
|
||||
|
||||
NVS 分区生成程序帮助生成 NVS 分区二进制文件,可使用烧录程序将二进制文件单独烧录至特定分区。烧录至分区上的键值对由 CSV 文件提供,详情请参考 :doc:`NVS 分区生成程序 <nvs_partition_gen>`。
|
||||
|
||||
应用示例
|
||||
-------------------
|
||||
|
||||
ESP-IDF :example:`storage` 目录下提供了数个代码示例:
|
||||
|
||||
:example:`storage/nvs_rw_value`
|
||||
|
||||
演示如何读取及写入 NVS 单个整数值。
|
||||
|
||||
此示例中的值表示 {IDF_TARGET_NAME} 模组重启次数。NVS 中数据不会因为模组重启而丢失,因此只有将这一值存储于 NVS 中,才能起到重启次数计数器的作用。
|
||||
|
||||
该示例也演示了如何检测读取/写入操作是否成功,以及某个特定值是否在 NVS 中尚未初始化。诊断程序以纯文本形式提供,帮助您追踪程序流程,及时发现问题。
|
||||
|
||||
:example:`storage/nvs_rw_blob`
|
||||
|
||||
演示如何读取及写入 NVS 单个整数值和 Blob(二进制大对象),并在 NVS 中存储这一数值,即便 {IDF_TARGET_NAME} 模组重启也不会消失。
|
||||
|
||||
* value - 记录 {IDF_TARGET_NAME} 模组软重启次数和硬重启次数。
|
||||
* blob - 内含记录模组运行次数的表格。此表格将被从 NVS 读取至动态分配的 RAM 上。每次手动软重启后,表格内运行次数即增加一次,新加的运行次数被写入 NVS。下拉 GPIO0 即可手动软重启。
|
||||
|
||||
该示例也演示了如何执行诊断程序以检测读取/写入操作是否成功。
|
||||
|
||||
:example:`storage/nvs_rw_value_cxx`
|
||||
|
||||
这个例子与 :example:`storage/nvs_rw_value` 完全一样,只是使用了 C++ 的 NVS 处理类。
|
||||
|
||||
内部实现
|
||||
---------
|
||||
|
||||
@ -70,7 +190,7 @@ NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需
|
||||
页面和条目
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的序列号。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
|
||||
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的 *序列号*。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
|
||||
|
||||
空或未初始化
|
||||
页面对应的 flash 扇区为空白状态(所有字节均为 ``0xff``)。此时,页面未存储任何数据且没有关联的序列号。
|
||||
@ -79,7 +199,8 @@ NVS 库在其操作中主要使用两个实体:页面和条目。页面是一
|
||||
此时 flash 已完成初始化,页头部写入 flash,页面已具备有效序列号。页面中存在一些空条目,可写入数据。任意时刻,至多有一个页面处于活跃状态。
|
||||
|
||||
写满状态
|
||||
Flash 已写满键值对,状态不再改变。用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
|
||||
Flash 已写满键值对,状态不再改变。
|
||||
用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
|
||||
|
||||
擦除状态
|
||||
未擦除的键值对将移至其他页面,以便擦除当前页面。这一状态仅为暂时性状态,即 API 调用返回时,页面应脱离这一状态。如果设备突然断电,下次开机时,设备将继续把未擦除的键值对移至其他页面,并继续擦除当前页面。
|
||||
@ -106,11 +227,13 @@ Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存
|
||||
页面结构
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
当前,我们假设 flash 扇区大小为 4096 字节,并且 ESP32 flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
|
||||
当前,我们假设 flash 扇区大小为 4096 字节,并且 {IDF_TARGET_NAME} flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
|
||||
|
||||
页面由头部、条目状态位图和条目三部分组成。为了实现与 ESP32 flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
|
||||
页面由头部、条目状态位图和条目三部分组成。为了实现与 {IDF_TARGET_NAME} flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
|
||||
|
||||
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)::
|
||||
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)。
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | 页头部 (32)
|
||||
@ -127,15 +250,15 @@ Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存
|
||||
| Entry 125 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
|
||||
头部和条目状态位图写入 flash 时不加密。如果启用了 ESP32 flash 加密功能,则条目写入 flash 时将会加密。
|
||||
头部和条目状态位图写入 flash 时不加密。如果启用了 {IDF_TARGET_NAME} flash 加密功能,则条目写入 flash 时将会加密。
|
||||
|
||||
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为擦除状态。
|
||||
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为 *擦除* 状态。
|
||||
|
||||
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减(例如,version-1 为 0xff,version-2 为 0xfe 等)。
|
||||
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减(例如,version-1 为 0xff,version-2 为 0xfe,以此类推)。
|
||||
|
||||
头部中 CRC32 值是由不包含状态值的条目计算所得(4 到 28 字节)。当前未使用的条目用 ``0xff`` 字节填充。
|
||||
|
||||
条目结构和条目状态位图详细信息见下文描述。
|
||||
条目结构和条目状态位图的详细信息见下文描述。
|
||||
|
||||
条目和条目状态位图
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -151,6 +274,7 @@ Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存
|
||||
擦除(2'b00)
|
||||
条目中的键值对已丢弃,条目内容不再解析。
|
||||
|
||||
|
||||
.. _structure_of_entry:
|
||||
|
||||
条目结构
|
||||
@ -180,10 +304,10 @@ Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存
|
||||
条目结构中各个字段含义如下:
|
||||
|
||||
命名空间 (NS, NameSpace)
|
||||
该条目的命名空间索引,详细信息见命名空间实现章节。
|
||||
该条目的命名空间索引,详细信息参见命名空间实现章节。
|
||||
|
||||
类型 (Type)
|
||||
一个字节表示的值的数据类型,可能的类型见 ``nvs_types.h`` 中 ``ItemType`` 枚举。
|
||||
一个字节表示的值的数据类型,:component_file:`nvs_flash/include/nvs_handle.hpp` 下的 :cpp:type:`ItemType` 枚举了可能的类型。
|
||||
|
||||
跨度 (Span)
|
||||
该键值对所用的条目数量。如果键值为整数型,条目数量即为 1。如果键值为字符串或 BLOB,则条目数量取决于值的长度。
|
||||
@ -192,10 +316,10 @@ Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存
|
||||
用于存储 BLOB 类型数据块的索引。如果键值为其他数据类型,则此处索引应写入 ``0xff``。
|
||||
|
||||
CRC32
|
||||
对条目下所有字节进行校验,所得的校验和(CRC32 字段不计算在内)。
|
||||
对条目下所有字节进行校验后,所得的校验和(CRC32 字段不计算在内)。
|
||||
|
||||
键 (Key)
|
||||
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的 NULL (``\0``) 终止符。
|
||||
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的零终止符。
|
||||
|
||||
数据 (Data)
|
||||
如果键值类型为整数型,则数据字段仅包含键值。如果键值小于八个字节,使用 ``0xff`` 填充未使用的部分(右侧)。
|
||||
@ -206,7 +330,7 @@ CRC32
|
||||
整个 BLOB 数据的大小(以字节为单位)。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkCount
|
||||
存储过程中 BLOB 分成的数据块数量。该字段仅用于 BLOB 索引类型条目。
|
||||
存储过程中 BLOB 分成的数据块总量。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkStart
|
||||
BLOB 第一个数据块的块索引,后续数据块索引依次递增,步长为 1。该字段仅用于 BLOB 索引类型条目。
|
||||
@ -219,7 +343,8 @@ CRC32
|
||||
- CRC32
|
||||
数据所有字节的校验和,该字段仅用于字符串和 BLOB 类型条目。
|
||||
|
||||
可变长度值(字符串和 BLOB)写入后续条目,每个条目 32 字节。第一个条目的 span 字段将指明使用了多少条目。
|
||||
可变长度值(字符串和 BLOB)写入后续条目,每个条目 32 字节。第一个条目的 `Span` 字段将指明使用了多少条目。
|
||||
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
@ -246,89 +371,6 @@ CRC32
|
||||
|
||||
哈希列表中每个节点均包含一个 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_get_*`` 和 ``nvs_set_*`` 等 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_get_*`` 或 ``nvs_set_*`` 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`` 释放迭代器。
|
||||
|
||||
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 参考
|
||||
-------------
|
||||
|
||||
|
@ -3,18 +3,12 @@ SD/SDIO/MMC 驱动程序
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
.. only:: esp32c3
|
||||
|
||||
.. warning::
|
||||
|
||||
本文档尚未针对 ESP32-C3 进行更新。
|
||||
|
||||
概述
|
||||
--------
|
||||
|
||||
SD/SDIO/MMC 驱动是一种基于 SDMMC 和 SD SPI 主机驱动的协议级驱动程序,目前已支持 SD 存储器、SDIO 卡和 eMMC 芯片。
|
||||
|
||||
SDMMC 主机驱动和 SD SPI 主机驱动(:component_file:`driver/include/driver/sdmmc_host.h`)为以下功能提供 API:
|
||||
SDMMC 主机驱动和 SD SPI 主机驱动(:component_file:`driver/include/driver/sdmmc_host.h` 和 :component_file:`driver/include/driver/sdspi_host.h`)为以下功能提供 API:
|
||||
|
||||
- 发送命令至从设备
|
||||
- 接收和发送数据
|
||||
@ -22,76 +16,93 @@ SDMMC 主机驱动和 SD SPI 主机驱动(:component_file:`driver/include/driv
|
||||
|
||||
初始化函数及配置函数:
|
||||
|
||||
.. only:: esp32
|
||||
.. list::
|
||||
|
||||
- 如需初始化和配置 SDMMC 主机,请参阅 :doc:`SDMMC 主机 API <../peripherals/sdmmc_host>`
|
||||
:SOC_SDMMC_HOST_SUPPORTED: - 如需初始化和配置 SDMMC 主机,请参阅 :doc:`SDMMC 主机 API <../peripherals/sdmmc_host>`
|
||||
- 如需初始化和配置 SD SPI 主机,请参阅 :doc:`SD SPI 主机 API <../peripherals/sdspi_host>`
|
||||
|
||||
|
||||
- 如需初始化和配置 SD SPI 主机,请参阅 :doc:`SD SPI 主机 API <../peripherals/sdspi_host>`
|
||||
.. only:: SOC_SDMMC_HOST_SUPPORTED
|
||||
|
||||
本文档中所述的 SDMMC 协议层仅处理 SD 协议相关事项,例如卡初始化和数据传输命令。
|
||||
本文档中所述的 SDMMC 协议层仅处理 SD 协议相关事项,例如卡初始化和数据传输命令。
|
||||
|
||||
协议层通过 :cpp:class:`sdmmc_host_t` 结构体和主机协同工作,该结构体包含指向主机各类函数的指针。
|
||||
|
||||
协议层通过 :cpp:class:`sdmmc_host_t` 结构体和主机协同工作,该结构体包含指向主机各类函数的指针。
|
||||
|
||||
应用示例
|
||||
-------------------
|
||||
|
||||
ESP-IDF :example:`storage/sd_card` 目录下提供了 SDMMC 驱动与 FatFs 库组合使用的示例,演示了先初始化卡,然后使用 POSIX 和 C 库 API 向卡读写数据。请参考示例目录下 README.md 文件,查看更多详细信息。
|
||||
|
||||
协议层 API
|
||||
------------------
|
||||
.. only:: SOC_SDMMC_HOST_SUPPORTED
|
||||
|
||||
协议层具备 :cpp:class:`sdmmc_host_t` 结构体,此结构体描述了 SD/MMC 主机驱动,列出了其功能,并提供指向驱动程序函数的指针。协议层将卡信息储存于 :cpp:class:`sdmmc_card_t` 结构体中。向 SD/MMC 主机发送命令时,协议层调用时需要一个 :cpp:class:`sdmmc_command_t` 结构体来描述命令、参数、预期返回值和需传输的数据(如有)。
|
||||
协议层 API
|
||||
------------------
|
||||
|
||||
用于 SD 存储卡的 API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
协议层具备 :cpp:class:`sdmmc_host_t` 结构体,此结构体描述了 SD/MMC 主机驱动,列出了其功能,并提供指向驱动程序函数的指针。协议层将卡信息储存于 :cpp:class:`sdmmc_card_t` 结构体中。向 SD/MMC 主机发送命令时,协议层调用时需要一个 :cpp:class:`sdmmc_command_t` 结构体来描述命令、参数、预期返回值和需传输的数据(如有)。
|
||||
|
||||
1. 初始化主机,请调用主机驱动函数,例如 :cpp:func:`sdmmc_host_init` 和 :cpp:func:`sdmmc_host_init_slot`;
|
||||
2. 初始化卡,请调用 :cpp:func:`sdmmc_card_init`,并将参数 ``host`` (即主机驱动信息)和参数 ``card`` (指向 :cpp:class:`sdmmc_card_t` 结构体的指针)传递给此函数。函数运行结束后,将会向 :cpp:class:`sdmmc_card_t` 结构体填充该卡的信息;
|
||||
3. 读取或写入卡的扇区,请分别调用 :cpp:func:`sdmmc_read_sectors` 和 :cpp:func:`sdmmc_write_sectors`,并将参数 ``card`` (指向卡信息结构的指针)传递给函数;
|
||||
4. 如果不再使用该卡,请调用主机驱动函数,例如 :cpp:func:`sdmmc_host_deinit`,以禁用主机外设,并释放驱动程序分配的资源。
|
||||
|
||||
用于 SD 存储卡的 API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
用于 eMMC 芯片的 API
|
||||
1. 初始化主机,请调用主机驱动函数,例如 :cpp:func:`sdmmc_host_init` 和 :cpp:func:`sdmmc_host_init_slot`;
|
||||
2. 初始化卡,请调用 :cpp:func:`sdmmc_card_init`,并将参数 ``host`` (主机驱动信息)和参数 ``card`` (指向 :cpp:class:`sdmmc_card_t` 结构体的指针)传递给此函数。函数运行结束后,将会向 :cpp:class:`sdmmc_card_t` 结构体填充该卡的信息;
|
||||
3. 读取或写入卡的扇区,请分别调用 :cpp:func:`sdmmc_read_sectors` 和 :cpp:func:`sdmmc_write_sectors`,并将参数 ``card`` (指向卡信息结构的指针)传递给函数;
|
||||
4. 如果不再使用该卡,请调用主机驱动函数,例如 :cpp:func:`sdmmc_host_deinit`,以禁用主机外设,并释放驱动程序分配的资源。
|
||||
|
||||
|
||||
用于 eMMC 芯片的 API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
从协议层的角度而言,eMMC 存储芯片与 SD 存储卡相同。尽管 eMMC 是芯片,不具备卡的外形,但由于协议相似 (`sdmmc_card_t`, `sdmmc_card_init`),用于 SD 卡的一些概念同样适用于 eMMC 芯片。注意,eMMC 芯片不可通过 SPI 使用,因此它与 SD API 主机驱动不兼容。
|
||||
|
||||
如需初始化 eMMC 内存并执行读/写操作,请参照上一章节 SD 卡操作步骤。
|
||||
|
||||
|
||||
用于 SDIO 卡的 API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
SDIO 卡初始化和检测过程与 SD 存储卡相同,唯一的区别是 SDIO 模式下数据传输命令不同。
|
||||
|
||||
在卡初始化和卡检测(通过运行 :cpp:func:`sdmmc_card_init`)期间,驱动仅配置 IO 卡如下寄存器:
|
||||
|
||||
1. I/O 中止 (0x06) 寄存器:在该寄存器中设置 RES 位可重置卡的 IO 部分;
|
||||
2. 总线接口控制 (0x07) 寄存器:如果主机和插槽配置中启用 4 线模式,则驱动程序会尝试在该寄存器中设置总线宽度字段。如果字段设置成功,则从机支持 4 线模式,主机也切换至 4 线模式;
|
||||
3. 高速(0x13)寄存器:如果主机配置中启用高速模式,则会在该寄存器中设置 SHS 位。
|
||||
|
||||
注意,驱动程序不会在 (1) I/O 使能寄存器和 Int 使能寄存器,及 (2) I/O 块大小中,设置任何位。应用程序可通过调用 :cpp:func:`sdmmc_io_write_byte` 来设置相关位。
|
||||
|
||||
如需设置卡配置或传输数据,请根据您的具体情况选择下表中的函数:
|
||||
|
||||
.. list-table::
|
||||
:widths: 55 25 20
|
||||
:header-rows: 1
|
||||
|
||||
* - Action
|
||||
- Read Function
|
||||
- Write Function
|
||||
* - Read and write a single byte using IO_RW_DIRECT (CMD52)
|
||||
- :cpp:func:`sdmmc_io_read_byte`
|
||||
- :cpp:func:`sdmmc_io_write_byte`
|
||||
* - Read and write multiple bytes using IO_RW_EXTENDED (CMD53) in byte mode
|
||||
- :cpp:func:`sdmmc_io_read_bytes`
|
||||
- :cpp:func:`sdmmc_io_write_bytes`
|
||||
* - Read and write blocks of data using IO_RW_EXTENDED (CMD53) in block mode
|
||||
- :cpp:func:`sdmmc_io_read_blocks`
|
||||
- :cpp:func:`sdmmc_io_write_blocks`
|
||||
|
||||
使用 :cpp:func:`sdmmc_io_enable_int` 函数,应用程序可启用 SDIO 中断。在单线模式下使用 SDIO 时,还需要连接 D1 线来启用 SDIO 中断。
|
||||
|
||||
如果您需要应用程序保持等待直至发生 SDIO 中断,请使用 :cpp:func:`sdmmc_io_wait_int` 函数。
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
如果您需要与 ESP32 的 SDIO 从设备通信,请使用 ESSL 组件(ESP 串行从设备链接)。请参阅 :doc:`/api-reference/protocols/esp_serial_slave_link` and example :example:`peripherals/sdio/host`
|
||||
|
||||
复合卡(存储 + IO)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
从协议层的角度而言,eMMC 存储芯片与 SD 存储卡相同。尽管 eMMC 是芯片,不具备卡的外形,但由于协议相似 (`sdmmc_card_t`, `sdmmc_card_init`),用于 SD 卡的一些概念同样适用于 eMMC 芯片。注意,eMMC 芯片不可通过 SPI 使用,因此它与 SD API 主机驱动不兼容。
|
||||
|
||||
如需初始化 eMMC 内存并执行读/写操作,请参照上一章节 SD 卡操作步骤。
|
||||
|
||||
用于 SDIO 卡的 API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
SDIO 卡初始化和检测过程与 SD 存储卡相同,唯一的区别是 SDIO 模式下数据传输命令不同。
|
||||
|
||||
在卡初始化和卡检测(通过运行 :cpp:func:`sdmmc_card_init`)期间,驱动仅配置 SDIO 卡如下寄存器:
|
||||
|
||||
1. I/O 中止 (0x06) 寄存器:在该寄存器中设置 RES 位可重置卡的 I/O 部分;
|
||||
2. 总线接口控制 (0x07) 寄存器:如果主机和插槽配置中启用 4 线模式,则驱动程序会尝试在该寄存器中设置总线宽度字段。如果字段设置成功,则从机支持 4 线模式,主机也切换至 4 线模式;
|
||||
3. 高速(0x13)寄存器:如果主机配置中启用高速模式,则会在该寄存器中设置 SHS 位。
|
||||
|
||||
注意,驱动程序不会在 (1) I/O 使能寄存器和 Int 使能寄存器,及 (2) I/O 块大小中,设置任何位。应用程序可通过调用 :cpp:func:`sdmmc_io_write_byte` 来设置相关位。
|
||||
|
||||
如需设置卡配置或传输数据,请根据您的具体情况选择下表中的函数:
|
||||
|
||||
========================================================================= ================================= =================================
|
||||
操作 读函数 写函数
|
||||
========================================================================= ================================= =================================
|
||||
使用 IO_RW_DIRECT (CMD52) 读写单个字节。 :cpp:func:`sdmmc_io_read_byte` :cpp:func:`sdmmc_io_write_byte`
|
||||
使用 IO_RW_EXTENDED (CMD53) 的字节模式读写多个字节。 :cpp:func:`sdmmc_io_read_bytes` :cpp:func:`sdmmc_io_write_bytes`
|
||||
块模式下,使用 IO_RW_EXTENDED (CMD53) 读写数据块。 :cpp:func:`sdmmc_io_read_blocks` :cpp:func:`sdmmc_io_write_blocks`
|
||||
========================================================================= ================================= =================================
|
||||
|
||||
使用 :cpp:func:`sdmmc_io_enable_int` 函数,应用程序可启用 SDIO 中断。
|
||||
|
||||
在单线模式下使用 SDIO 时,还需要连接 D1 线来启用 SDIO 中断。
|
||||
|
||||
如果您需要应用程序保持等待直至发生 SDIO 中断,请使用 :cpp:func:`sdmmc_io_wait_int` 函数。
|
||||
|
||||
|
||||
复合卡(存储 + SDIO)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
该驱动程序不支持 SDIO 复合卡,复合卡会被视为 SDIO 卡。
|
||||
该驱动程序不支持 SD 复合卡,复合卡会被视为 IO 卡。
|
||||
|
||||
|
||||
线程安全
|
||||
@ -99,6 +110,7 @@ SDIO 卡初始化和检测过程与 SD 存储卡相同,唯一的区别是 SDIO
|
||||
|
||||
多数应用程序仅需在一个任务中使用协议层。因此,协议层在 :cpp:class:`sdmmc_card_t` 结构体或在访问 SDMMC 或 SD SPI 主机驱动程序时不使用任何类型的锁。这种锁通常在较高层级实现,例如文件系统驱动程序。
|
||||
|
||||
|
||||
API 参考
|
||||
-------------
|
||||
|
||||
|
@ -5,19 +5,20 @@ SPI Flash API
|
||||
|
||||
概述
|
||||
--------
|
||||
Spi_flash 组件提供外部 flash 数据读取、写入、擦除和内存映射相关的 API 函数,同时也提供了更高层级的,面向分区的 API 函数(定义在 :doc:`分区表 </api-guides/partition-tables>` 中)。
|
||||
|
||||
SPI Flash 组件提供外部 flash 数据读取、写入、擦除和内存映射相关的 API 函数,同时也提供了更高层级的,面向分区的 API 函数(定义在 :doc:`分区表 </api-guides/partition-tables>` 中)。
|
||||
|
||||
与 ESP-IDF V4.0 之前的 API 不同,这一版 `esp_flash_*` API 功能并不局限于主 SPI Flash 芯片(即运行程序的 SPI Flash 芯片)。使用不同的芯片指针,您可以通过 SPI0/1 或 SPI2/SPI3 总线访问外部 flash。
|
||||
与 ESP-IDF V4.0 之前的 API 不同,这一版 `esp_flash_*` API 功能并不局限于主 SPI Flash 芯片(即运行程序的 SPI Flash 芯片)。使用不同的芯片指针,您可以访问连接到 SPI0/1 或 SPI2 总线的外部 flash 芯片。
|
||||
|
||||
.. note::
|
||||
|
||||
大多数 `esp_flash_*` API 使用 SPI1,SPI2 等外设而非通过 SPI0 上的 cache。这使得它们不仅能访问主 flash,也能访问外部 flash 。
|
||||
而由于 cache 的限制,所有经过 cache 的操作都只能对 flash 进行。这些操作的地址同样受到 cache 能力的限制。 Cache 无法访问外部 flash 或者高于它能力的地址段。这些 cache 操作包括:mmap ,加密读写,执行代码或者访问在 flash 中的变量。
|
||||
|
||||
而由于 cache 的限制,所有经过 cache 的操作都只能对主 flash 进行。这些操作的地址同样受到 cache 能力的限制。Cache 无法访问外部 flash 或者高于它能力的地址段。这些 cache 操作包括:mmap ,加密读写,执行代码或者访问在 flash 中的变量。
|
||||
|
||||
.. note::
|
||||
|
||||
ESP-IDF V4.0 之后的 flash API 不再是原子的。因此,如果 flash 操作地址有重叠,且写操作与读操作同时执行,读操作可能会返回一部分写入之前的数据,返回一部分写入之后的数据。
|
||||
ESP-IDF V4.0 之后的 flash API 不再是原子的。因此,如果 flash 操作地址有重叠,且写操作与读操作同时执行,读操作可能会返回一部分写入之前的数据和一部分写入之后的数据。
|
||||
|
||||
|
||||
Kconfig 选项 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` 可将 ``spi_flash_*`` 函数切换至 ESP-IDF V4.0 之前的实现。但是,如果同时使用新旧 API,代码量可能会增多。
|
||||
|
||||
@ -26,9 +27,9 @@ Kconfig 选项 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` 可将 ``spi_flash_*``
|
||||
Flash 特性支持情况
|
||||
-----------------------------------
|
||||
|
||||
不同厂家的 Flash 特性通过不同的方式来操作,因此需要特殊的驱动支持。当前驱动支持大多数厂家 Flash 24 位地址范围内的快速/慢速读,以及二线模式( DIO / DOUT ),因为他们不需要任何厂家自定义的命令。
|
||||
不同厂家的 flash 特性有不同的操作方式,因此需要特殊的驱动支持。当前驱动支持大多数厂家 Flash 24 位地址范围内的快速/慢速读,以及二线模式 (DIO / DOUT),因为他们不需要任何厂家的自定义命令。
|
||||
|
||||
当前驱动支持以下厂家/型号的 Flash 的四线模式( QIO / QOUT ):
|
||||
当前驱动支持以下厂家/型号的 flash 的四线模式 (QIO/QOUT):
|
||||
|
||||
1. ISSI
|
||||
2. GD
|
||||
@ -38,17 +39,17 @@ Flash 特性支持情况
|
||||
6. XMC
|
||||
7. BOYA
|
||||
|
||||
当前驱动支持以下厂家/型号的 Flash 的 32 位地址范围的访问:
|
||||
当前驱动支持以下厂家/型号的 flash 的 32 位地址范围的访问:
|
||||
|
||||
1. W25Q256
|
||||
2. GD25Q256
|
||||
|
||||
如果有需要,也可以自定义 Flash 芯片驱动,参见 :doc:`spi_flash_override_driver` 。但此功能仅供专业用户使用。
|
||||
如果有需要,也可以自定义 flash 芯片驱动,参见 :doc:`spi_flash_override_driver` 。
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
自定义 Flash 芯片驱动 <spi_flash_override_driver>
|
||||
自定义 flash 芯片驱动 <spi_flash_override_driver>
|
||||
|
||||
初始化 Flash 设备
|
||||
---------------------------
|
||||
@ -61,7 +62,7 @@ Flash 特性支持情况
|
||||
|
||||
3. 调用 :cpp:func:`esp_flash_init` 与芯片进行通信。后续操作会依据芯片类型不同而有差异。
|
||||
|
||||
.. note:: 目前,多个 flash 芯片可连接到同一总线。但尚不支持在同一个 SPI 总线上使用 ``esp_flash_*`` 和 ``spi_device_*`` 设备。
|
||||
.. note:: 当前,多个 flash 芯片可连接到同一总线。但尚不支持在同一个 SPI 总线上使用 ``esp_flash_*`` 和 ``spi_device_*`` 设备。
|
||||
|
||||
SPI Flash 访问 API
|
||||
--------------------
|
||||
@ -95,14 +96,14 @@ SPI1 Flash 并发约束
|
||||
|
||||
.. attention::
|
||||
|
||||
指令/数据 cache(用以执行固件)与 SPI1 外设(由像 SPI Flash 驱动一样的驱动程序控制)共享 SPI0/1 总线。因此,在 SPI1 总线上调用 SPI Flash API (包括访问主 flash )会对整个系统造成显著的影响。更多细节,参见 :doc:`spi_flash_concurrency` 。
|
||||
指令/数据 cache(用以执行固件)与 SPI1 外设(由像 SPI flash 驱动一样的驱动程序控制)共享 SPI0/1 总线。因此,在 SPI1 总线上调用 SPI Flash API(包括访问主 flash)会对整个系统造成显著的影响。更多细节,参见 :doc:`spi_flash_concurrency`。
|
||||
|
||||
.. _flash-partition-apis:
|
||||
|
||||
分区表 API
|
||||
-------------------
|
||||
|
||||
ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序、各种应用程序二进制文件、数据及文件系统等。请参考 :doc:`分区表 </api-guides/partition-tables>`,查看详细信息。
|
||||
ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序、各种应用程序二进制文件、数据及文件系统等。请参考 :doc:`here </api-guides/partition-tables>`,查看详细信息。
|
||||
|
||||
该组件在 ``esp_partition.h`` 中声明了一些 API 函数,用以枚举在分区表中找到的分区,并对这些分区执行操作:
|
||||
|
||||
@ -110,31 +111,32 @@ ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序
|
||||
- :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`。
|
||||
- :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 函数,而非低层级的 ``esp_flash_*`` API 函数。分区表 API 函数根据存储在分区表中的数据,进行边界检查并计算在 flash 中的正确偏移量。
|
||||
|
||||
|
||||
SPI Flash 加密
|
||||
--------------------
|
||||
|
||||
您可以对 SPI flash 内容进行加密,并在硬件层对其进行透明解密。
|
||||
|
||||
请参阅 :doc:`Flash 加密 </security/flash-encryption>`,查看详细信息。
|
||||
请参阅 :doc:`Flash 加密文档 </security/flash-encryption>`,查看详细信息。
|
||||
|
||||
内存映射 API
|
||||
------------------
|
||||
|
||||
{IDF_TARGET_CACHE_SIZE:default="64 KB"}
|
||||
|
||||
{IDF_TARGET_NAME} 内存硬件可以将 flash 部分区域映射到指令地址空间和数据地址空间,此映射仅用于读操作。不能通过写入 flash 映射的存储区域来改变 flash 中内容。
|
||||
{IDF_TARGET_NAME} 内存硬件可以将 flash 部分区域映射到指令地址空间和数据地址空间,此映射仅用于读操作。不能通过写入 flash 映射的存储区域来改变 flash 中的内容。
|
||||
|
||||
Flash 以 {IDF_TARGET_CACHE_SIZE} 页为单位进行地址映射。内存映射硬件既可将 flash 映射到数据地址空间,也能映射到指令地址空间。请参考《 {IDF_TARGET_NAME} 技术参考手册》查看内存映射硬件的详细信息及有关限制。
|
||||
Flash 在 {IDF_TARGET_CACHE_SIZE} 页进行映射。内存映射硬件既可将 flash 映射到数据地址空间,也能映射到指令地址空间。请查看技术参考手册,了解内存映射硬件的详细信息及有关限制。
|
||||
|
||||
请注意,有些页被用于将应用程序映射到内存中,因此实际可用的页会少于硬件提供的总数。
|
||||
|
||||
:doc:`Flash 加密 </security/flash-encryption>` 启用时,使用内存映射区域从 flash 读取数据是解密 flash 的唯一方法,解密需在硬件层进行。
|
||||
启用 :doc:`Flash 加密 </security/flash-encryption>` 时,使用内存映射区域从 flash 读取数据是解密 flash 的唯一方法,解密需在硬件层进行。
|
||||
|
||||
内存映射 API 在 ``esp_spi_flash.h`` 和 ``esp_partition.h`` 中声明:
|
||||
|
||||
@ -152,23 +154,23 @@ Flash 以 {IDF_TARGET_CACHE_SIZE} 页为单位进行地址映射。内存映射
|
||||
.. note::
|
||||
由于 mmap 是由 cache 支持的,因此,mmap 也仅能用在主 flash 上。
|
||||
|
||||
实现
|
||||
SPI Flash 实现
|
||||
--------------
|
||||
|
||||
``esp_flash_t`` 结构包含芯片数据和该 API 的三个重要部分:
|
||||
|
||||
1. 主机驱动,为访问芯片提供硬件支持;
|
||||
2. 芯片驱动,为不同芯片提供兼容性服务;
|
||||
3. OS 函数,在不同阶段(一级或二级 Boot 或者应用程序阶段)为部分 OS 函数提供支持(如一些锁、延迟)。
|
||||
3. OS 函数,在不同阶段(一级或二级 Boot 或者应用程序阶段)为部分 OS 函数(如锁、延迟)提供支持。
|
||||
|
||||
主机驱动
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
主机驱动依赖 ``hal/include/hal`` 文件夹下 ``spi_flash_types.h`` 定义的 ``spi_flash_host_driver_t`` 接口。该接口提供了一些与芯片通信常用的函数。
|
||||
主机驱动依赖 ``hal/include/hal`` 文件夹下 ``spi_flash_types.h`` 定义的 ``spi_flash_host_driver_t`` 接口。该接口提供了一些常用的函数,用于与芯片通信。
|
||||
|
||||
在 SPI HAL 文件中,有些函数是基于现有的 {IDF_TARGET_NAME} memory-spi 来实现的。但是,由于 {IDF_TARGET_NAME} 速度限制,HAL 层无法提供某些读命令的高速实现(所以这些命令根本没有在 HAL 的文件中被实现)。``memspi_host_driver.h`` 和 ``.c`` 文件使用 HAL 提供的 ``common_command`` 函数实现上述读命令的高速版本,并将所有它实现的及 HAL 函数封装为 ``spi_flash_host_driver_t`` 供更上层调用。
|
||||
在 SPI HAL 文件中,有些函数是基于现有的 {IDF_TARGET_NAME} memory-spi 来实现的。但是,由于 {IDF_TARGET_NAME} 的速度限制,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。
|
||||
您甚至可以仅通过 GPIO 来实现自己的主机驱动。只要实现了 ``spi_flash_host_driver_t`` 中所有函数,不管底层硬件是什么,esp_flash API 都可以访问 flash。
|
||||
|
||||
芯片驱动
|
||||
^^^^^^^^^^^
|
||||
@ -177,7 +179,7 @@ Flash 以 {IDF_TARGET_CACHE_SIZE} 页为单位进行地址映射。内存映射
|
||||
|
||||
有些操作需在执行前先发送命令,或在执行后读取状态,因此有些芯片需要不同的命令或值以及通信方式。
|
||||
|
||||
``generic chip`` 芯片代表了常见的 flash 芯片,其他芯片驱动可以在通用芯片的基础上进行开发。
|
||||
``generic chip`` 芯片代表了常见的 flash 芯片,其他芯片驱动可以在这种通用芯片的基础上进行开发。
|
||||
|
||||
芯片驱动依赖主机驱动。
|
||||
|
||||
@ -186,9 +188,15 @@ Flash 以 {IDF_TARGET_CACHE_SIZE} 页为单位进行地址映射。内存映射
|
||||
OS 函数
|
||||
^^^^^^^^^^^^
|
||||
|
||||
OS 函数层提供访问锁和延迟的方法。
|
||||
OS 函数层目前提供访问锁和延迟的方法。
|
||||
|
||||
该锁定用于解决 SPI Flash 芯片访问和其他函数之间的冲突。例如,经 SPI0/1 访问 flash 芯片时,应当禁用 cache(平时用于取代码和 PSRAM 数据)。另一种情况是,一些没有 CS 线或者 CS 线受软件控制的设备(如通过 SPI 接口的 SD 卡控制)需要在一段时间内独占总线。
|
||||
锁(见 :ref:`spi_bus_lock`)用于解决同一 SPI 总线上的设备访问和 SPI Flash 芯片访问之间的冲突。例如:
|
||||
|
||||
1. 经 SPI1 总线访问 flash 芯片时,应当禁用 cache(平时用于取代码和 PSRAM 数据)。
|
||||
|
||||
2. 经其他总线访问 flash 芯片时,应当禁用 flash 上 SPI 主驱动器注册的 ISR 以避免冲突。
|
||||
|
||||
3. SPI 主驱动器上某些没有 CS 线或者 CS 线受软件(如 SDSPI)控制的设备需要在一段时间内独占总线。
|
||||
|
||||
延时则用于某些长时操作,需要主机处于等待状态或执行轮询。
|
||||
|
||||
@ -202,17 +210,17 @@ OS 函数层提供访问锁和延迟的方法。
|
||||
- :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 代码。
|
||||
- 双核模式下,实现细节较为复杂,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 操作。
|
||||
如果有 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 操作期间禁用该新分配的中断。
|
||||
|
||||
@ -229,7 +237,6 @@ SPI Flash API 参考
|
||||
.. include-build-file:: inc/esp_flash.inc
|
||||
.. include-build-file:: inc/spi_flash_types.inc
|
||||
|
||||
|
||||
.. _api-reference-partition-table:
|
||||
|
||||
分区表 API 参考
|
||||
|
@ -14,6 +14,9 @@ SPIFFS 是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨
|
||||
- 目前,SPIFFS 尚不支持目录,但可以生成扁平结构。如果 SPIFFS 挂载在 ``/spiffs`` 下,在 ``/spiffs/tmp/myfile.txt`` 路径下创建一个文件则会在 SPIFFS 中生成一个名为 ``/tmp/myfile.txt`` 的文件,而不是在 ``/spiffs/tmp`` 下生成名为 ``myfile.txt`` 的文件;
|
||||
- SPIFFS 并非实时栈,每次写操作耗时不等;
|
||||
- 目前,SPIFFS 尚不支持检测或处理已损坏的块。
|
||||
- SPIFFS 只能稳定地使用约 75% 的指定分区容量。
|
||||
- 当文件系统空间不足时,垃圾收集器会尝试多次扫描文件系统来寻找可用空间。根据所需空间的不同,写操作会被调用多次,每次函数调用将花费几秒。同一操作可能会花费不同时长的问题缘于 SPIFFS 的设计,且已在官方的 `SPIFFS github 仓库 <https://github.com/pellepl/spiffs/issues/>`_ 或是 <https://github.com/espressif/esp-idf/issues/1737>`_ 中被多次报告。这个问题可以通过 `SPIFFS 配置 <https://github.com/pellepl/spiffs/wiki/Configure-spiffs>`_ 部分缓解。
|
||||
- 被删除文件通常不会被完全清除,会在文件系统中遗留下无法使用的部分。
|
||||
|
||||
工具
|
||||
-----
|
||||
@ -51,7 +54,7 @@ spiffsgen.py
|
||||
|
||||
spiffs_create_partition_image(<partition> <base_dir> [FLASH_IN_PROJECT] [DEPENDS dep dep dep...])
|
||||
|
||||
在构建系统中使用 ``spiffsgen.py`` 更为方便,构建配置自动传递给 ``spiffsgen.py`` 工具,确保生成的镜像可用于构建。比如,单独调用 ``spiffsgen.py`` 时需要用到 *image_size* 参数,但在构建系统中调用 ``spiffs_create_partition_image`` 时,仅需要 *partition* 参数,镜像大小将直接从工程分区表中获取。
|
||||
在构建系统中使用 ``spiffsgen.py`` 更为方便,构建配置会自动传递给 ``spiffsgen.py`` 工具,确保生成的镜像可用于构建。比如,单独调用 ``spiffsgen.py`` 时需要用到 *image_size* 参数,但在构建系统中调用 ``spiffs_create_partition_image`` 时,仅需要 *partition* 参数,镜像大小将直接从工程分区表中获取。
|
||||
|
||||
Make 构建系统和 CMake 构建系统结构有所不同,请注意以下几点:
|
||||
|
||||
@ -105,11 +108,10 @@ mkspiffs
|
||||
|
||||
mkspiffs -c [src_folder] -b 4096 -p 256 -s 0x100000 spiffs.bin
|
||||
|
||||
运行以下命令,将镜像烧录到 ESP32(偏移量:0x110000)::
|
||||
运行以下命令,将镜像烧录到 {IDF_TARGET_NAME}(偏移量:0x110000)::
|
||||
|
||||
python esptool.py --chip {IDF_TARGET_PATH_NAME} --port [port] --baud [baud] write_flash -z 0x110000 spiffs.bin
|
||||
|
||||
|
||||
选择合适的 SPIFFS 工具
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -130,6 +132,7 @@ mkspiffs
|
||||
|
||||
- :doc:`分区表 <../../api-guides/partition-tables>`
|
||||
|
||||
|
||||
应用示例
|
||||
-------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user