diff --git a/docs/en/api-guides/file-system-considerations.rst b/docs/en/api-guides/file-system-considerations.rst index bef83ad776..839d6f0939 100644 --- a/docs/en/api-guides/file-system-considerations.rst +++ b/docs/en/api-guides/file-system-considerations.rst @@ -105,7 +105,7 @@ The most supported file system, recommended for common applications - file/direc **Examples:** * :example:`storage/sd_card`: access the SD card which uses the FAT file system -* :example:`storage/ext_flash_fatfs`: access the external flash chip which uses the FAT file system +* :example:`storage/fatfs/ext_flash`: access the external flash chip which uses the FAT file system .. _spiffs-fs-section: diff --git a/docs/en/api-guides/performance/speed.rst b/docs/en/api-guides/performance/speed.rst index 0cce05a571..c374a32746 100644 --- a/docs/en/api-guides/performance/speed.rst +++ b/docs/en/api-guides/performance/speed.rst @@ -301,9 +301,9 @@ Improving I/O Performance Using standard C library functions like ``fread`` and ``fwrite`` instead of platform-specific unbuffered syscalls such as ``read`` and ``write``, may result in slower performance. -The ``fread`` and ``fwrite`` functions are designed for portability rather than speed, introducing some overhead due to their buffered nature. Check the example :example:`storage/fatfsgen` to see how to use these two functions. +The ``fread`` and ``fwrite`` functions are designed for portability rather than speed, introducing some overhead due to their buffered nature. Check the example :example:`storage/fatfs/getting_started` to see how to use these two functions. -In contrast, the ``read`` and ``write`` functions are standard POSIX APIs that can be used directly when working with FatFs through VFS, with ESP-IDF handling the underlying implementation. Check the example :example:`storage/perf_benchmark` to see how to use the two functions. +In contrast, the ``read`` and ``write`` functions are standard POSIX APIs that can be used directly when working with FatFs through VFS, with ESP-IDF handling the underlying implementation. Check the example :example:`storage/fatfs/fs_operations` to see how to use the two functions. Additional tips are provided below, and further details can be found in :doc:`/api-reference/storage/fatfs`. diff --git a/docs/en/api-reference/storage/fatfs.rst b/docs/en/api-reference/storage/fatfs.rst index 4c6520eb27..025b48bc33 100644 --- a/docs/en/api-reference/storage/fatfs.rst +++ b/docs/en/api-reference/storage/fatfs.rst @@ -123,7 +123,7 @@ If you decide for any reason to use ``fatfs_create_rawflash_image`` (without wea The arguments of the function are as follows: -#. partition - the name of the partition as defined in the partition table (e.g., :example_file:`storage/fatfsgen/partitions_example.csv`). +#. partition - the name of the partition as defined in the partition table (e.g., :example_file:`storage/fatfs/fatfsgen/partitions_example.csv`). #. base_dir - the directory that will be encoded to FatFs partition and optionally flashed into the device. Beware that you have to specify the suitable size of the partition in the partition table. @@ -139,7 +139,7 @@ For example:: If FLASH_IN_PROJECT is not specified, the image will still be generated, but you will have to flash it manually using ``esptool.py`` or a custom build system target. -For an example, see :example:`storage/fatfsgen`. +For an example, see :example:`storage/fatfs/fatfsgen`. .. _fatfs-partition-analyzer: diff --git a/docs/en/api-reference/storage/index.rst b/docs/en/api-reference/storage/index.rst index 19bf5b6b92..f3f184270f 100644 --- a/docs/en/api-reference/storage/index.rst +++ b/docs/en/api-reference/storage/index.rst @@ -37,22 +37,15 @@ For information about storage security, please refer to :doc:`Storage Security < wear-levelling storage-security.rst -.. list-table:: Code Examples for Storage API +Examples +-------- + +.. list-table:: NVS API examples :widths: 25 75 :header-rows: 0 * - **Code Example** - **Description** - * - :doc:`fatfs` - - - * - :example:`wear_leveling ` - - Demonstrates using FATFS over wear leveling on internal flash. - * - :example:`ext_flash_fatfs ` - - Demonstrates using FATFS over wear leveling on external flash. - * - :example:`fatfsgen ` - - Demonstrates the capabilities of Python-based tooling for FATFS images available on host computers. - * - :doc:`nvs_flash` - - * - :example:`nvs_rw_blob ` - Shows the use of the C-style API to read and write blob data types in NVS flash. * - :example:`nvs_rw_value ` @@ -61,20 +54,59 @@ For information about storage security, please refer to :doc:`Storage Security < - Shows the use of the C++-style API to read and write integer data types in NVS flash. * - :example:`nvsgen ` - Demonstrates how to use the Python-based NVS image generation tool to create an NVS partition image from the contents of a CSV file. - * - :doc:`spiffs` - - + +.. list-table:: Common Filesystem API + :widths: 25 75 + :header-rows: 0 + + * - **Code Example** + - **Description** + * - :example:`fatfs/getting_started ` + - Demonstrates basic common file API (stdio.h) usage over internal flash using FATFS. + * - :example:`fatfs/fs_operations ` + - Demonstrates POSIX API for filesystem manipulation, such as moving, removing and renaming files. + +.. list-table:: FATFS API examples + :widths: 25 75 + :header-rows: 0 + + * - **Code Example** + - **Description** + * - :example:`fatfsgen ` + - Demonstrates the capabilities of Python-based tooling for FATFS images available on host computers. + * - :example:`ext_flash_fatfs ` + - Demonstrates using FATFS over wear leveling on external flash. + * - :example:`wear_leveling ` + - Demonstrates using FATFS over wear leveling on internal flash. + +.. list-table:: SPIFFS API examples + :widths: 25 75 + :header-rows: 0 + + * - **Code Example** + - **Description** * - :example:`spiffs ` - Shows the use of the SPIFFS API to initialize the filesystem and work with files using POSIX functions. * - :example:`spiffsgen ` - Demonstrates the capabilities of Python-based tooling for SPIFFS images available on host computers. - * - :doc:`partition` - - + +.. list-table:: Partition API examples + :widths: 25 75 + :header-rows: 0 + + * - **Code Example** + - **Description** * - :example:`partition_api ` - Provides an overview of API functions to look up particular partitions, perform basic I/O operations, and use partitions via CPU memory mapping. * - :example:`parttool ` - Demonstrates the capabilities of Python-based tooling for partition images available on host computers. - * - :doc:`vfs` - - + +.. list-table:: VFS related examples + :widths: 25 75 + :header-rows: 0 + + * - **Code Example** + - **Description** * - :example:`littlefs ` - Shows the use of the LittleFS component to initialize the filesystem and work with a file using POSIX functions. * - :example:`semihost_vfs ` diff --git a/docs/zh_CN/api-guides/performance/speed.rst b/docs/zh_CN/api-guides/performance/speed.rst index 25601b4dad..55f0a2da74 100644 --- a/docs/zh_CN/api-guides/performance/speed.rst +++ b/docs/zh_CN/api-guides/performance/speed.rst @@ -301,9 +301,9 @@ ESP-IDF 支持动态 :doc:`/api-reference/system/intr_alloc` 和中断抢占。 使用标准 C 库函数,如 ``fread`` 和 ``fwrite``,相较于使用平台特定的不带缓冲系统调用,如 ``read`` 和 ``write``,可能会导致 I/O 性能下降。 -``fread`` 与 ``fwrite`` 函数是为可移植性而设计的,而非速度,其缓冲性质会引入一些额外的开销。关于如何使用这两个函数,请参考示例 :example:`storage/fatfsgen`。 +``fread`` 与 ``fwrite`` 函数是为可移植性而设计的,而非速度,其缓冲性质会引入一些额外的开销。关于如何使用这两个函数,请参考示例 :example:`storage/fatfs/getting_started`。 -与之相比,``read`` 与 ``write`` 函数是标准的 POSIX API,可直接通过 VFS 处理 FatFs,由 ESP-IDF 负责底层实现。关于如何使用这两个函数,请参考示例 :example:`storage/perf_benchmark`。 +与之相比,``read`` 与 ``write`` 函数是标准的 POSIX API,可直接通过 VFS 处理 FatFs,由 ESP-IDF 负责底层实现。关于如何使用这两个函数,请参考示例 :example:`storage/fatfs/fs_operations`。 下面提供了一些提示,更多信息请见 :doc:`/api-reference/storage/fatfs`。 diff --git a/docs/zh_CN/api-reference/storage/fatfs.rst b/docs/zh_CN/api-reference/storage/fatfs.rst index e11a39a170..830d30f6c3 100644 --- a/docs/zh_CN/api-reference/storage/fatfs.rst +++ b/docs/zh_CN/api-reference/storage/fatfs.rst @@ -123,7 +123,7 @@ FatFs 分区生成器 该函数的参数如下: -#. partition - 分区的名称,需要在分区表中定义(如 :example_file:`storage/fatfsgen/partitions_example.csv`)。 +#. partition - 分区的名称,需要在分区表中定义(如 :example_file:`storage/fatfs/fatfsgen/partitions_example.csv`)。 #. base_dir - 目录名称,该目录会被编码为 FatFs 分区,也可以选择将其被烧录进设备。但注意必须在分区表中指定合适的分区大小。 @@ -139,7 +139,7 @@ FatFs 分区生成器 没有指定 FLASH_IN_PROJECT 时也可以生成分区镜像,但是用户需要使用 ``esptool.py`` 或自定义的构建系统目标对其手动烧录。 -相关示例请查看 :example:`storage/fatfsgen`。 +相关示例请查看 :example:`storage/fatfs/fatfsgen`。 .. _fatfs-partition-analyzer: diff --git a/docs/zh_CN/api-reference/storage/index.rst b/docs/zh_CN/api-reference/storage/index.rst index 0687d98c05..3b71db1be0 100644 --- a/docs/zh_CN/api-reference/storage/index.rst +++ b/docs/zh_CN/api-reference/storage/index.rst @@ -37,44 +37,76 @@ wear-levelling storage-security.rst +示例 +---- + .. list-table:: 存储 API 相关例程 :widths: 25 75 :header-rows: 0 * - **例程** - **描述** - * - :doc:`fatfs` - - - * - :example:`wear_leveling ` - - 演示了如何在内部 flash 上使用 FATFS 磨损均衡。 - * - :example:`ext_flash_fatfs ` - - 演示了如何在外部 flash 上使用 FATFS 磨损均衡。 - * - :example:`fatfsgen ` - - 演示了在主机计算机上使用 Python 工具生成 FATFS 镜像的功能。 - * - :doc:`nvs_flash` - - * - :example:`nvs_rw_blob ` - 演示了如何在 NVS flash 中使用 C 语言 API 读写 blob 数据类型。 * - :example:`nvs_rw_value ` - 演示了如何在 NVS flash 中使用 C 语言 API 读写整数数据类型。 - * - :example:`nvs_rw_value_cxx ` + * - :example:`nvs_rw_value ` - 演示了如何在 NVS flash 中使用 C++ 语言 API 读写整数数据类型。 * - :example:`nvsgen ` - 演示了如何使用基于 Python 的 NVS 镜像生成工具,根据 CSV 文件内容创建 NVS 分区镜像。 - * - :doc:`spiffs` - - + +.. list-table:: 常用文件系统 API + :widths: 25 75 + :header-rows: 0 + + * - **代码示例** + - **描述** + * - :example:`fatfs/getting_started ` + - 演示了如何使用 FATFS 库在内部 flash 上应用标准文件 API (stdio.h)。 + * - :example:`fatfs/fs_operations ` + - 演示了如何使用 POSIX API 进行文件系统操作,如移动、删除和重命名文件等。 + +.. list-table:: FATFS API 示例 + :widths: 25 75 + :header-rows: 0 + + * - **代码示例** + - **描述** + * - :example:`fatfsgen ` + - 演示了在主机上使用 Python 工具生成 FATFS 镜像的相关功能。 + * - :example:`ext_flash_fatfs ` + - 演示了在外部 flash 上使用带有磨损均衡功能的 FATFS。 + * - :example:`wear_leveling ` + - 演示了在内部 flash 上使用带有磨损均衡功能的 FATFS。 + +.. list-table:: SPIFFS API 示例 + :widths: 25 75 + :header-rows: 0 + + * - **代码示例** + - **描述** * - :example:`spiffs ` - 演示了如何使用 SPIFFS API 初始化文件系统,并使用 POSIX 函数处理文件。 * - :example:`spiffsgen ` - 演示了在主机计算机上使用 Python 工具生成 SPIFFS 镜像的功能。 - * - :doc:`partition` - - + +.. list-table:: 分区 API 示例 + :widths: 25 75 + :header-rows: 0 + + * - **代码示例** + - **描述** * - :example:`partition_api ` - 介绍了用于查找特定分区、执行基本 I/O 操作以及通过 CPU 内存映射使用分区的 API 函数。 * - :example:`parttool ` - 演示了在主机计算机上使用 Python 工具生成分区镜像的功能。 - * - :doc:`vfs` - - + +.. list-table:: VFS 相关示例 + :widths: 25 75 + :header-rows: 0 + + * - **代码示例** + - **描述** * - :example:`littlefs ` - 演示了如何使用 LittleFS 组件初始化文件系统,并使用 POSIX 函数处理文件。 * - :example:`semihost_vfs ` diff --git a/examples/storage/.build-test-rules.yml b/examples/storage/.build-test-rules.yml index 4e7e8c66cb..967d8ed5b8 100644 --- a/examples/storage/.build-test-rules.yml +++ b/examples/storage/.build-test-rules.yml @@ -19,29 +19,6 @@ examples/storage/emmc: - if: IDF_TARGET == "esp32s3" reason: only support on esp32s3 -examples/storage/ext_flash_fatfs: - depends_components: - - fatfs - - vfs - - spi_flash - - driver - disable: - - if: IDF_TARGET in ["esp32p4", "esp32c5", "esp32c61"] - temporary: true - reason: not supported on p4 and c5 # TODO: [ESP32C5] IDF-8715, [ESP32C61] IDF-9314 - disable_test: - - if: IDF_TARGET not in ["esp32"] - temporary: true - reason: lack of runners - -examples/storage/fatfsgen: - depends_components: - - fatfs - - vfs - disable_test: - - if: IDF_TARGET != "esp32" - reason: only one target needed - examples/storage/nvs_rw_blob: depends_components: - nvs_flash diff --git a/examples/storage/README.md b/examples/storage/README.md index bb5c821fe8..ebfb75833d 100644 --- a/examples/storage/README.md +++ b/examples/storage/README.md @@ -7,10 +7,11 @@ This directory contains a range of examples ESP-IDF projects. These are intended The examples are grouped into sub-directories by category. Each category directory contains one or more example projects: +* `fatfs_basic` minimal example of FatFS usage on SPI FLASH +* `fatfs_advanced` example demonstrates how to use advanced features for working with FatFS such as automatic partition generation * `custom_flash_driver` example demonstrates how to implement your own flash chip driver by overriding the default driver. * `emmc` example demonstrates how to use an eMMC chip with an ESP device. * `ext_flash_fatfs` example demonstrates how to use FATFS partition with external SPI FLASH chip. -* `fatfsgen` example demonstrates how to use FATFS partition * `nvs_rw_blob` example demonstrates how to read and write a single integer value and a blob (binary large object) using NVS to preserve them between ESP module restarts. * `nvs_rw_value` example demonstrates how to read and write a single integer value using NVS. * `nvs_rw_value_cxx` example demonstrates how to read and write a single integer value using NVS (it uses the C++ NVS handle API). diff --git a/examples/storage/ext_flash_fatfs/main/CMakeLists.txt b/examples/storage/ext_flash_fatfs/main/CMakeLists.txt deleted file mode 100644 index c8a5c30e46..0000000000 --- a/examples/storage/ext_flash_fatfs/main/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -idf_component_register(SRCS "ext_flash_fatfs_example_main.c") diff --git a/examples/storage/fatfs/.build-test-rules.yml b/examples/storage/fatfs/.build-test-rules.yml new file mode 100644 index 0000000000..4f3ae3aa16 --- /dev/null +++ b/examples/storage/fatfs/.build-test-rules.yml @@ -0,0 +1,24 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +examples/storage/fatfs: + depends_components: + - fatfs + - vfs + disable_test: + - if: IDF_TARGET != "esp32" + reason: only one target needed + +examples/storage/fatfs/ext_flash: + depends_components: + - fatfs + - vfs + - spi_flash + - driver + disable: + - if: IDF_TARGET in ["esp32p4", "esp32c5", "esp32c61"] + temporary: true + reason: not supported on p4 and c5 # TODO: [ESP32C5] IDF-8715, [ESP32C61] IDF-9314 + disable_test: + - if: IDF_TARGET not in ["esp32"] + temporary: true + reason: lack of runners diff --git a/examples/storage/ext_flash_fatfs/CMakeLists.txt b/examples/storage/fatfs/ext_flash/CMakeLists.txt similarity index 91% rename from examples/storage/ext_flash_fatfs/CMakeLists.txt rename to examples/storage/fatfs/ext_flash/CMakeLists.txt index 93841c0e29..66233663cd 100644 --- a/examples/storage/ext_flash_fatfs/CMakeLists.txt +++ b/examples/storage/fatfs/ext_flash/CMakeLists.txt @@ -4,4 +4,4 @@ cmake_minimum_required(VERSION 3.16) # external SPI flash driver not currently supported for ESP32-S2 include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(ext_flash_fatfs) +project(fatfs_ext_flash) diff --git a/examples/storage/ext_flash_fatfs/README.md b/examples/storage/fatfs/ext_flash/README.md similarity index 94% rename from examples/storage/ext_flash_fatfs/README.md rename to examples/storage/fatfs/ext_flash/README.md index 0014bd8d31..349fd1ba3e 100644 --- a/examples/storage/ext_flash_fatfs/README.md +++ b/examples/storage/fatfs/ext_flash/README.md @@ -5,7 +5,7 @@ (See the README.md file in the upper level 'examples' directory for more information about examples.) -This example is similar to the [wear levelling](../wear_levelling/README.md) example, except that it uses an external SPI Flash chip. This can be useful if you need to add more storage to a module with only 4 MB flash size. +This example is similar to the [wear levelling](../../wear_levelling/README.md) example, except that it uses an external SPI Flash chip. This can be useful if you need to add more storage to a module with only 4 MB flash size. The flow of the example is as follows: diff --git a/examples/storage/fatfs/ext_flash/main/CMakeLists.txt b/examples/storage/fatfs/ext_flash/main/CMakeLists.txt new file mode 100644 index 0000000000..dea52d1cb4 --- /dev/null +++ b/examples/storage/fatfs/ext_flash/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "fatfs_ext_flash_example_main.c") diff --git a/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c b/examples/storage/fatfs/ext_flash/main/fatfs_ext_flash_example_main.c similarity index 98% rename from examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c rename to examples/storage/fatfs/ext_flash/main/fatfs_ext_flash_example_main.c index aa2a2c0990..4e10057d10 100644 --- a/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c +++ b/examples/storage/fatfs/ext_flash/main/fatfs_ext_flash_example_main.c @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ /* Example of FAT filesystem on external Flash. This example code is in the Public Domain (or CC0 licensed, at your option.) diff --git a/examples/storage/ext_flash_fatfs/pytest_ext_flash_fatfs.py b/examples/storage/fatfs/ext_flash/pytest_fatfs_ext_flash.py similarity index 99% rename from examples/storage/ext_flash_fatfs/pytest_ext_flash_fatfs.py rename to examples/storage/fatfs/ext_flash/pytest_fatfs_ext_flash.py index afe81763a3..56dd334a49 100644 --- a/examples/storage/ext_flash_fatfs/pytest_ext_flash_fatfs.py +++ b/examples/storage/fatfs/ext_flash/pytest_fatfs_ext_flash.py @@ -1,7 +1,5 @@ # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 - - import pytest from pytest_embedded import Dut diff --git a/examples/storage/fatfs/fatfsgen/CMakeLists.txt b/examples/storage/fatfs/fatfsgen/CMakeLists.txt new file mode 100644 index 0000000000..0399bf40c5 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(fatfs_fatfsgen) diff --git a/examples/storage/fatfs/fatfsgen/README.md b/examples/storage/fatfs/fatfsgen/README.md new file mode 100644 index 0000000000..772cf9cd8e --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/README.md @@ -0,0 +1,69 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# FATFS partition generation example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates how to use the FATFS partition +generation tool [fatfsgen.py](../../../../components/fatfs/fatfsgen.py) to automatically create a FATFS +filesystem image from the contents of a host folder during build, with an option of +automatically flashing the created image on invocation of `idf.py -p PORT flash`. +Beware that the minimal required size of the flash is 4 MB. +You can specify using menuconfig weather example will use read-only or read-write mode. The default option is read-write mode. +To change it just use menuconfig: + +```shell +idf.py menuconfig +``` + +Then select `Example Configuration` a chose `Mode for generated FATFS image` either `Read-Write Mode` or `Read-Only Mode`. +`Read-Only` option indicates generating raw fatfs image without wear levelling support. +On the other hand, for `Read-Write` the generated fatfs image will support wear levelling thus can be mounted in read-write mode. + + +The following gives an overview of the example: + +1. There is a directory `fatfs_long_name_image` from which the FATFS filesystem image will be created. + +2. Based on the RO/RW configuration either `fatfs_create_rawflash_image` or `fatfs_create_spiflash_image` respectively, +is used to specify that a FATFS image should be created during build for the `storage` partition. +For CMake, it is called from [the main component's CMakeLists.txt](./main/CMakeLists.txt). +`FLASH_IN_PROJECT` specifies that the created image +should be flashed on invocation of `idf.py -p PORT flash` together with app, bootloader, partition table, etc. +The image is created on the example's build directory with the output filename `storage.bin`. + +3. Upon invocation of `idf.py -p PORT flash monitor`, application loads and +finds there is already a valid FATFS filesystem in the `storage` partition with files same as those in `fatfs_image` directory. The application is then +able to read those files. + +## How to use example + +### Build and flash + +To run the example, type the following command: + +```CMake +# CMake +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example output + +Here is the example's console output: + +``` +... +I (322) example: Mounting FAT filesystem +I (332) example: Reading file +I (332) example: Read from file: 'this is test' +I (332) example: Unmounting FAT filesystem +I (342) example: Done +``` + +The logic of the example is contained in a [single source file](./main/fatfsgen_example_main.c), +and it should be relatively simple to match points in its execution with the log outputs above. diff --git a/examples/storage/fatfsgen/fatfs_long_name_image/hellolongname.txt b/examples/storage/fatfs/fatfsgen/fatfs_image/hellolongname.txt similarity index 100% rename from examples/storage/fatfsgen/fatfs_long_name_image/hellolongname.txt rename to examples/storage/fatfs/fatfsgen/fatfs_image/hellolongname.txt diff --git a/examples/storage/fatfsgen/fatfs_long_name_image/sublongnames/testlongfilenames.txt b/examples/storage/fatfs/fatfsgen/fatfs_image/subdir/testlongfilenames.txt similarity index 100% rename from examples/storage/fatfsgen/fatfs_long_name_image/sublongnames/testlongfilenames.txt rename to examples/storage/fatfs/fatfsgen/fatfs_image/subdir/testlongfilenames.txt diff --git a/examples/storage/fatfs/fatfsgen/main/CMakeLists.txt b/examples/storage/fatfs/fatfsgen/main/CMakeLists.txt new file mode 100644 index 0000000000..1b625a3722 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/main/CMakeLists.txt @@ -0,0 +1,18 @@ +idf_component_register(SRCS "fatfsgen_example_main.c" + INCLUDE_DIRS ".") + +# Create a FATFS image from the contents of the 'fatfs_long_name_image' directory +# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that +# the generated image should be flashed when the entire project is flashed to +# the target with 'idf.py -p PORT flash'. +# If read-only mode is set (CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY) +# the generated image will be raw without wear levelling support. +# Otherwise it will support wear levelling and thus enable read-write mounting of the image in the device. + +set(image ../fatfs_image) + +if(CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY) + fatfs_create_rawflash_image(storage ${image} FLASH_IN_PROJECT PRESERVE_TIME) +else() + fatfs_create_spiflash_image(storage ${image} FLASH_IN_PROJECT PRESERVE_TIME) +endif() diff --git a/examples/storage/fatfs/fatfsgen/main/Kconfig.projbuild b/examples/storage/fatfs/fatfsgen/main/Kconfig.projbuild new file mode 100644 index 0000000000..ef79d39456 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/main/Kconfig.projbuild @@ -0,0 +1,10 @@ +menu "Example Configuration" + + config EXAMPLE_FATFS_MODE_READ_ONLY + bool "Read only mode for generated FATFS image" + default y + help + If read-only mode is set, the generated fatfs image will be raw (without wear levelling support). + Otherwise it will support wear levelling that enables read-write mounting. + +endmenu diff --git a/examples/storage/fatfs/fatfsgen/main/fatfsgen_example_main.c b/examples/storage/fatfs/fatfsgen/main/fatfsgen_example_main.c new file mode 100644 index 0000000000..abc01a900a --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/main/fatfsgen_example_main.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "esp_err.h" +#include "sdkconfig.h" + +static const char *TAG = "example"; + +// Mount path for the partition +const char *base_path = "/spiflash"; + + +void app_main(void) +{ + // To mount device we need name of device partition, define base_path + // and allow format partition in case if it is new one and was not formatted before + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = false, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, + .use_one_fat = false, + }; + +#if CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY + ESP_LOGI(TAG, "Mounting FAT filesystem in read-only mode"); + esp_err_t err = esp_vfs_fat_spiflash_mount_ro(base_path, "storage", &mount_config); +#else + ESP_LOGI(TAG, "Mounting FAT filesystem in read/write mode"); + static wl_handle_t wl_handle = WL_INVALID_HANDLE; + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &wl_handle); +#endif + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); + return; + } + + char line[128]; + + FILE *f; + char *pos; + ESP_LOGI(TAG, "Reading file"); + + const char *host_filename1 = "/spiflash/subdir/testlongfilenames.txt"; + + struct stat info; + struct tm timeinfo; + char buffer[32]; + + if(stat(host_filename1, &info) < 0){ + ESP_LOGE(TAG, "Failed to read file stats"); + return; + } + localtime_r(&info.st_mtime, &timeinfo); + strftime(buffer, sizeof(buffer), "%Y-%m-%d", &timeinfo); + + ESP_LOGI(TAG, "The file '%s' was modified at date: %s", host_filename1, buffer); + + + f = fopen(host_filename1, "rb"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + fgets(line, sizeof(line), f); + fclose(f); + // strip newline + pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + ESP_LOGI(TAG, "Read from file: '%s'", line); + + // Unmount FATFS + ESP_LOGI(TAG, "Unmounting FAT filesystem"); + +#if CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY + ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_ro(base_path, "storage")); +#else + ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, wl_handle)); +#endif + + ESP_LOGI(TAG, "Done"); +} diff --git a/examples/storage/fatfs/fatfsgen/partitions_example.csv b/examples/storage/fatfs/fatfsgen/partitions_example.csv new file mode 100644 index 0000000000..a3fee302e2 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/examples/storage/fatfs/fatfsgen/pytest_fatfs_fatfsgen_example.py b/examples/storage/fatfs/fatfsgen/pytest_fatfs_fatfsgen_example.py new file mode 100644 index 0000000000..e0966604eb --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/pytest_fatfs_fatfsgen_example.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import os +import re +from datetime import datetime +from typing import List + +import pytest +from pytest_embedded import Dut + +idf_path = os.environ['IDF_PATH'] # get value of IDF_PATH from environment +parttool_dir = os.path.join(idf_path, 'components', 'partition_table') + + +@pytest.mark.esp32 +@pytest.mark.generic +@pytest.mark.parametrize('config', ['test_read_only_partition_gen_ln', + 'test_read_write_partition_gen_ln', + ], indirect=True) +def test_examples_fatfs_fatfsgen(config: str, dut: Dut) -> None: + # Expects list of strings sequentially + def expect_all(msg_list: List[str], to: int) -> None: + for msg in msg_list: + dut.expect(msg, timeout=to) + + # Expects prefix string followed by date in the format 'yyyy-mm-dd' + def expect_date(prefix: str, to: int) -> datetime: + expect_str = prefix + '(\\d+)-(\\d+)-(\\d+)' + match_ = dut.expect(re.compile(str.encode(expect_str)), timeout=to) + year_ = int(match_[1].decode()) + month_ = int(match_[2].decode()) + day_ = int(match_[3].decode()) + return datetime(year_, month_, day_) + + # Calculates absolute difference in days between date_reference and date_actual. + # Raises exception if difference exceeds tolerance + def evaluate_dates(date_reference: datetime, date_actual: datetime, days_tolerance: int) -> None: + td = date_actual - date_reference + if abs(td.days) > days_tolerance: + raise Exception(f'Too big date difference. Actual: {date_actual}, reference: {date_reference}, tolerance: {days_tolerance} day(s)') + + # Expect timeout + timeout = 20 + + # We tolerate 30 days difference between actual file creation and date when test was executed. + tolerance = 30 + filename = 'sublongnames/testlongfilenames.txt' + date_ref = datetime.today() + + if config in ['test_read_write_partition_gen']: + filename_expected = f'/spiflash/{filename}' + expect_all(['example: Mounting FAT filesystem', + 'example: Opening file', + 'example: File written', + 'example: Reading file', + 'example: Read from file: \'This is written by the device\'', + 'example: Reading file'], timeout) + date_act = expect_date(f'The file \'{filename_expected}\' was modified at date: ', timeout) + evaluate_dates(date_ref, date_act, tolerance) + expect_all(['example: Read from file: \'This is generated on the host\'', + 'example: Unmounting FAT filesystem', + 'example: Done'], timeout) + + elif config in ['test_read_only_partition_gen']: + filename_expected = f'/spiflash/{filename}' + expect_all(['example: Mounting FAT filesystem', + 'example: Reading file'], timeout) + date_act = expect_date(f'The file \'{filename_expected}\' was modified at date: ', timeout) + evaluate_dates(date_ref, date_act, tolerance) + expect_all(['example: Read from file: \'this is test\'', + 'example: Unmounting FAT filesystem', + 'example: Done'], timeout) diff --git a/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln b/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln new file mode 100644 index 0000000000..5122126dcb --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_LFN_NONE=n +CONFIG_FATFS_LFN_STACK=n diff --git a/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln b/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln new file mode 100644 index 0000000000..33a0ccfad3 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_LFN_NONE=n +CONFIG_FATFS_LFN_STACK=n diff --git a/examples/storage/fatfs/fatfsgen/sdkconfig.defaults b/examples/storage/fatfs/fatfsgen/sdkconfig.defaults new file mode 100644 index 0000000000..7dd3c63608 --- /dev/null +++ b/examples/storage/fatfs/fatfsgen/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_FATFS_LFN_HEAP=y diff --git a/examples/storage/fatfs/fs_operations/CMakeLists.txt b/examples/storage/fatfs/fs_operations/CMakeLists.txt new file mode 100644 index 0000000000..2f478de10d --- /dev/null +++ b/examples/storage/fatfs/fs_operations/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(fatfs_fs_operations) diff --git a/examples/storage/fatfs/fs_operations/README.md b/examples/storage/fatfs/fs_operations/README.md new file mode 100644 index 0000000000..e11f3077b5 --- /dev/null +++ b/examples/storage/fatfs/fs_operations/README.md @@ -0,0 +1,94 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# FATFS Filesystem Operations Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates some of the POSIX functions available for working with the FATFS filesystem. +Including basic read and write operations, as well as creating moving, and deleting files and directories. + +## Overview + +1. Partition labeled `storage` is mounted (and formatted if necessary) as FATFS filesystem to `/spiflash` mountpoint. + +2. All existing files and directories in the root directory are deleted. + +3. File `hello.txt` is created and written to. + +4. File `hello.txt` is inspected using `fstat` function showing file size and last modification time. + +5. File `hello.txt` is written to again, appending to the end of the file. + +6. File `hello.txt` is read from and the contents are printed to the console. + +7. New directory `new_dir` is created. + +8. All files and directories in the root directory are listed. + +9. File `hello.txt` is moved and renamed to `new_dir/hello_renamed.txt`. + +## How to use example + +### Build and flash + +To run the example, type the following command: + +```CMake +# CMake +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example output + +Here is the example's console output: + +``` +... +I (323) example: Mounting FAT filesystem +I (333) example: Deleting everything in /spiflash: +I (333) example: Deleting everything in /spiflash/new_dir: +I (673) example: Creating a file +I (683) example: Writing to the file +I (733) example: File stats: + File size: 13 bytes + File modification time: Thu Jan 1 00:00:00 1970 + +I (743) example: Wait for 1 seconds +I (1743) example: Write more to the file +I (1743) example: File stats: + File size: 26 bytes + File modification time: Thu Jan 1 00:00:00 1970 + +I (1743) example: Go to the beginning of the file +I (1753) example: Reading from file: +Hello World! +Hello World! + +I (1753) example: Closing file +I (1993) example: Listing files in /spiflash: +/spiflash: + file : hello.txt +I (1993) example: Creating a new directory +I (2383) example: Listing files in /spiflash: +/spiflash: + file : hello.txt + directory: new_dir +I (2383) example: Rename a file +I (2503) example: Listing files in /spiflash: +/spiflash: + directory: new_dir +I (2503) example: Listing files in /spiflash/new_dir: +/spiflash/new_dir: + file : hello_renamed.txt +I (2513) example: Unmounting FAT filesystem +I (2643) example: Done +... +``` + +The logic of the example is contained in a [single source file](./main/fatfs_fs_operations_example_main.c), +and it should be relatively simple to match points in its execution with the log outputs above. diff --git a/examples/storage/fatfs/fs_operations/main/CMakeLists.txt b/examples/storage/fatfs/fs_operations/main/CMakeLists.txt new file mode 100644 index 0000000000..a5ae965fc4 --- /dev/null +++ b/examples/storage/fatfs/fs_operations/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "fatfs_fs_operations_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/storage/fatfs/fs_operations/main/fatfs_fs_operations_example_main.c b/examples/storage/fatfs/fs_operations/main/fatfs_fs_operations_example_main.c new file mode 100644 index 0000000000..c2dd5bc226 --- /dev/null +++ b/examples/storage/fatfs/fs_operations/main/fatfs_fs_operations_example_main.c @@ -0,0 +1,207 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "sdkconfig.h" + +static const char *TAG = "example"; + +// Mount path for the partition +static const char *base_path = "/spiflash"; + +// File name +static const char *filename = "/spiflash/hello.txt"; + +// Function to dump contents of a directory +static void list_dir(const char *path); + +// Best effort recursive function to clean a directory +static void clean_dir(const char *path); + +void app_main(void) +{ + ESP_LOGI(TAG, "Mounting FAT filesystem"); + + // To mount device we need name of device partition, define base_path + // and allow format partition in case if it is new one and was not formatted before + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = true, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, + .use_one_fat = false, + }; + + wl_handle_t wl_handle = WL_INVALID_HANDLE; + + esp_err_t err = ESP_OK; + + err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); + return; + } + + // Ensure the working directory is empty + clean_dir(base_path); + + ESP_LOGI(TAG, "Creating a file"); + + // Unlike C standard library which uses FILE*, POSIX API uses file descriptors for file operations + int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0); + if (fd < 0) { + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + + ESP_LOGI(TAG, "Writing to the file"); + const char *text = "Hello World!\n"; + write(fd, text, strlen(text)); + + struct stat info; + // We have to use `stat` instead of `fstat`, because `fstat` currently isn't fully supported + if (stat(filename, &info) < 0) { + ESP_LOGE(TAG, "Failed to stat file: %s", strerror(errno)); + close(fd); + return; + } + + ESP_LOGI( + TAG, + "File stats:\n" + "\tFile size: %ld bytes\n" + "\tFile modification time: %s", + info.st_size, + ctime(&info.st_mtime) + ); + + ESP_LOGI(TAG, "Wait for 3 seconds"); + sleep(3); + + ESP_LOGI(TAG, "Write more to the file"); + write(fd, text, strlen(text)); + + ESP_LOGI(TAG, "Force cached data and metadata to the filesystem"); + fsync(fd); + + if (stat(filename, &info) < 0) { + ESP_LOGE(TAG, "Failed to stat file: %s", strerror(errno)); + close(fd); + return; + } + + ESP_LOGI( + TAG, + "File stats:\n" + "\tFile size: %ld bytes\n" + "\tFile modification time: %s", + info.st_size, + ctime(&info.st_mtime) + ); + + ESP_LOGI(TAG, "Go to the beginning of the file"); + lseek(fd, 0, SEEK_SET); + + ESP_LOGI(TAG, "Reading from file:"); + + char buf[128] = {0}; + + ssize_t len = read(fd, buf, sizeof(buf) - 1); + if (len < 0) { + ESP_LOGE(TAG, "Failed to read file: %s", strerror(errno)); + close(fd); + return; + } + + printf("%.*s\n", len, buf); + + ESP_LOGI(TAG, "Closing file"); + close(fd); + + // List files in the directory + list_dir(base_path); + + ESP_LOGI(TAG, "Creating a new directory"); + if (mkdir("/spiflash/new_dir", 0777) < 0) { + ESP_LOGE(TAG, "Failed to create a new directory: %s", strerror(errno)); + return; + } + + // List files in the directory + list_dir(base_path); + + ESP_LOGI(TAG, "Rename a file"); + + if (rename(filename, "/spiflash/new_dir/hello_renamed.txt") < 0) { + ESP_LOGE(TAG, "Failed to rename file: %s", strerror(errno)); + return; + } + + // List files in the directory + list_dir(base_path); + list_dir("/spiflash/new_dir"); + + ESP_LOGI(TAG, "Unmounting FAT filesystem"); + ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, wl_handle)); + + ESP_LOGI(TAG, "Done"); +} + +void list_dir(const char *path) +{ + ESP_LOGI(TAG, "Listing files in %s:", path); + + DIR *dir = opendir(path); + if (!dir) { + ESP_LOGE(TAG, "Failed to open directory: %s", strerror(errno)); + return; + } + + printf("%s:\n", path); + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + printf( + " %s: %s\n", + (entry->d_type == DT_DIR) + ? "directory" + : "file ", + entry->d_name + ); + } + + closedir(dir); +} + +void clean_dir(const char *path) +{ + ESP_LOGI(TAG, "Deleting everything in %s:", path); + + DIR *dir = opendir(path); + if (!dir) { + ESP_LOGE(TAG, "Failed to open directory: %s", strerror(errno)); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + char full_path[64] = {0}; + snprintf(full_path, sizeof(full_path), "%.20s/%.40s", path, entry->d_name); + if (entry->d_type == DT_DIR) + clean_dir(full_path); + if (remove(full_path) != 0) { + ESP_LOGE(TAG, "Failed to remove %s: %s", full_path, strerror(errno)); + } + } + + closedir(dir); +} diff --git a/examples/storage/fatfsgen/partitions_example.csv b/examples/storage/fatfs/fs_operations/partitions_example.csv similarity index 100% rename from examples/storage/fatfsgen/partitions_example.csv rename to examples/storage/fatfs/fs_operations/partitions_example.csv diff --git a/examples/storage/fatfs/fs_operations/pytest_fatfs_fs_operations_example.py b/examples/storage/fatfs/fs_operations/pytest_fatfs_fs_operations_example.py new file mode 100644 index 0000000000..4067c80d13 --- /dev/null +++ b/examples/storage/fatfs/fs_operations/pytest_fatfs_fs_operations_example.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +from datetime import datetime +from typing import List + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.generic +def test_examples_fatfs_fs_operations(config: str, dut: Dut) -> None: + # Expects list of strings sequentially + def expect_all(msg_list: List[str], to: int) -> None: + for msg in msg_list: + dut.expect(msg, timeout=to) + + def parse_date() -> datetime: + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + + pattern = r'([A-Z][a-z]{2}) ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})' + + match = dut.expect(pattern) + return datetime( + month=months.index(match[2].decode('utf-8')) + 1, + day=int(match[3]), + hour=int(match[4]), + minute=int(match[5]), + second=int(match[6]), + year=int(match[7]), + ) + + expect_all( + [ + 'example: Mounting FAT filesystem', + 'example: Creating a file', + 'example: Writing to the file', + 'example: File stats:', + 'File size:', + ], + 5 + ) + + original = parse_date() + + expect_all( + [ + 'example: Wait for 3 seconds', + 'example: Write more to the file', + 'example: Force cached data and metadata to the filesystem', + 'File size:', + ], + 5 + ) + + updated = parse_date() + + assert updated > original + + expect_all( + [ + 'example: Go to the beginning of the file', + 'example: Reading from file', + 'Hello World!', + 'Hello World!', + 'example: Closing file', + 'example: Listing files in /spiflash:', + 'hello.txt', + 'example: Creating a new directory', + 'example: Listing files in /spiflash:', + 'hello.txt', + 'new_dir', + 'example: Rename a file', + 'example: Listing files in /spiflash:', + 'new_dir', + 'example: Listing files in /spiflash/new_dir:', + 'hello_renamed.txt', + ], + 5 + ) diff --git a/examples/storage/fatfs/fs_operations/sdkconfig.defaults b/examples/storage/fatfs/fs_operations/sdkconfig.defaults new file mode 100644 index 0000000000..e5ac937533 --- /dev/null +++ b/examples/storage/fatfs/fs_operations/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_LFN_NONE=n +CONFIG_FATFS_LFN_STACK=n diff --git a/examples/storage/fatfs/getting_started/CMakeLists.txt b/examples/storage/fatfs/getting_started/CMakeLists.txt new file mode 100644 index 0000000000..b595983679 --- /dev/null +++ b/examples/storage/fatfs/getting_started/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(fatfs_getting_started) diff --git a/examples/storage/fatfs/getting_started/README.md b/examples/storage/fatfs/getting_started/README.md new file mode 100644 index 0000000000..626f9323cb --- /dev/null +++ b/examples/storage/fatfs/getting_started/README.md @@ -0,0 +1,44 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# FATFS minimal example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates the minimal setup required to store persistent data on SPI Flash using the FAT filesystem. +Beware that the minimal required size of the flash is 4 MB. + +## How to use example + +### Build and flash + +To run the example, type the following command: + +```CMake +# CMake +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example output + +Here is the example's console output: + +``` +... +I (321) example: Mounting FAT filesystem +I (331) example: Filesystem mounted +I (331) example: Opening file +I (731) example: File written +I (731) example: Reading file +I (741) example: Read from file: 'Hello World!' +I (741) example: Unmounting FAT filesystem +I (851) example: Done +... +``` + +The logic of the example is contained in a [single source file](./main/fatfs_getting_started_main.c), +and it should be relatively simple to match points in its execution with the log outputs above. diff --git a/examples/storage/fatfs/getting_started/main/CMakeLists.txt b/examples/storage/fatfs/getting_started/main/CMakeLists.txt new file mode 100644 index 0000000000..fec5d19cad --- /dev/null +++ b/examples/storage/fatfs/getting_started/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "fatfs_getting_started_main.c" + INCLUDE_DIRS ".") diff --git a/examples/storage/fatfs/getting_started/main/fatfs_getting_started_main.c b/examples/storage/fatfs/getting_started/main/fatfs_getting_started_main.c new file mode 100644 index 0000000000..ae8529160e --- /dev/null +++ b/examples/storage/fatfs/getting_started/main/fatfs_getting_started_main.c @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "sdkconfig.h" + +static const char *TAG = "example"; + +// Mount path for the partition +const char *base_path = "/spiflash"; + +// Handle of the wear levelling library instance +static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; + +void app_main(void) +{ + ESP_LOGI(TAG, "Mounting FAT filesystem"); + // To mount device we need name of device partition, define base_path + // and allow format partition in case if it is new one and was not formatted before + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, // Number of files that can be open at a time + .format_if_mount_failed = true, // If true, try to format the partition if mount fails + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, // Size of allocation unit, cluster size. + .use_one_fat = false, // Use only one FAT table (reduce memory usage), but decrease reliability of file system in case of power failure. + }; + + // Mount FATFS filesystem located on "storage" partition in read-write mode + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &s_wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); + return; + } + + ESP_LOGI(TAG, "Filesystem mounted"); + + ESP_LOGI(TAG, "Opening file"); + + const char *filename = "/spiflash/example.txt"; + + FILE *f = fopen(filename, "wb"); + if (f == NULL) { + perror("fopen"); // Print reason why fopen failed + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + + fprintf(f, "Hello World!\n"); + fclose(f); + + ESP_LOGI(TAG, "File written"); + + // Open file for reading + ESP_LOGI(TAG, "Reading file"); + + f = fopen(filename, "r"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + + char line[128]; + + fgets(line, sizeof(line), f); + fclose(f); + + // strip newline + char *pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + + ESP_LOGI(TAG, "Read from file: '%s'", line); + + // Unmount FATFS + ESP_LOGI(TAG, "Unmounting FAT filesystem"); + + ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_wl_handle)); + + ESP_LOGI(TAG, "Done"); +} diff --git a/examples/storage/fatfs/getting_started/partitions_example.csv b/examples/storage/fatfs/getting_started/partitions_example.csv new file mode 100644 index 0000000000..1c79321a10 --- /dev/null +++ b/examples/storage/fatfs/getting_started/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/examples/storage/fatfs/getting_started/pytest_fatfs_getting_started_example.py b/examples/storage/fatfs/getting_started/pytest_fatfs_getting_started_example.py new file mode 100644 index 0000000000..e7f9d7d78a --- /dev/null +++ b/examples/storage/fatfs/getting_started/pytest_fatfs_getting_started_example.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.generic +def test_examples_fatfs_getting_started(dut: Dut) -> None: + dut.expect('example: Mounting FAT filesystem', timeout=90) + dut.expect('example: Filesystem mounted', timeout=90) + dut.expect('example: Opening file', timeout=90) + dut.expect('example: File written', timeout=90) + dut.expect('example: Reading file', timeout=90) + dut.expect('example: Read from file: \'Hello World!\'', timeout=90) + dut.expect('example: Unmounting FAT filesystem', timeout=90) + dut.expect('example: Done', timeout=90) diff --git a/examples/storage/fatfsgen/sdkconfig.defaults b/examples/storage/fatfs/getting_started/sdkconfig.defaults similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.defaults rename to examples/storage/fatfs/getting_started/sdkconfig.defaults diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 8fc70c2ab7..d2df877be7 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -941,7 +941,6 @@ examples/security/flash_encryption/main/flash_encrypt_main.c examples/storage/custom_flash_driver/components/custom_chip_driver/chip_drivers.c examples/storage/custom_flash_driver/components/custom_chip_driver/spi_flash_chip_eon.c examples/storage/custom_flash_driver/main/main.c -examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c examples/storage/nvs_rw_blob/main/nvs_blob_example_main.c examples/storage/nvs_rw_value/main/nvs_value_example_main.c examples/storage/nvs_rw_value_cxx/main/nvs_value_example_main.cpp diff --git a/tools/test_apps/storage/.build-test-rules.yml b/tools/test_apps/storage/.build-test-rules.yml index d27c0ab893..f0ab55097c 100644 --- a/tools/test_apps/storage/.build-test-rules.yml +++ b/tools/test_apps/storage/.build-test-rules.yml @@ -1,5 +1,13 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps +tools/test_apps/storage/fatfsgen: + depends_components: + - fatfs + - vfs + disable_test: + - if: IDF_TARGET != "esp32" + reason: only one target needed + tools/test_apps/storage/partition_table_readonly: disable_test: - if: IDF_TARGET not in ["esp32", "esp32c3"] diff --git a/examples/storage/fatfsgen/CMakeLists.txt b/tools/test_apps/storage/fatfsgen/CMakeLists.txt similarity index 100% rename from examples/storage/fatfsgen/CMakeLists.txt rename to tools/test_apps/storage/fatfsgen/CMakeLists.txt diff --git a/examples/storage/fatfsgen/README.md b/tools/test_apps/storage/fatfsgen/README.md similarity index 100% rename from examples/storage/fatfsgen/README.md rename to tools/test_apps/storage/fatfsgen/README.md diff --git a/examples/storage/fatfsgen/fatfs_image/hello.txt b/tools/test_apps/storage/fatfsgen/fatfs_image/hello.txt similarity index 100% rename from examples/storage/fatfsgen/fatfs_image/hello.txt rename to tools/test_apps/storage/fatfsgen/fatfs_image/hello.txt diff --git a/examples/storage/fatfsgen/fatfs_image/sub/test.txt b/tools/test_apps/storage/fatfsgen/fatfs_image/sub/test.txt similarity index 100% rename from examples/storage/fatfsgen/fatfs_image/sub/test.txt rename to tools/test_apps/storage/fatfsgen/fatfs_image/sub/test.txt diff --git a/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/hellolongname.txt b/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/hellolongname.txt new file mode 100644 index 0000000000..45caad2725 --- /dev/null +++ b/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/hellolongname.txt @@ -0,0 +1 @@ +This is generated on the host; long name it has diff --git a/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/sublongnames/testlongfilenames.txt b/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/sublongnames/testlongfilenames.txt new file mode 100644 index 0000000000..e8c8c6d51f --- /dev/null +++ b/tools/test_apps/storage/fatfsgen/fatfs_long_name_image/sublongnames/testlongfilenames.txt @@ -0,0 +1 @@ +this is test; long name it has diff --git a/examples/storage/fatfsgen/main/CMakeLists.txt b/tools/test_apps/storage/fatfsgen/main/CMakeLists.txt similarity index 100% rename from examples/storage/fatfsgen/main/CMakeLists.txt rename to tools/test_apps/storage/fatfsgen/main/CMakeLists.txt diff --git a/examples/storage/fatfsgen/main/Kconfig.projbuild b/tools/test_apps/storage/fatfsgen/main/Kconfig.projbuild similarity index 100% rename from examples/storage/fatfsgen/main/Kconfig.projbuild rename to tools/test_apps/storage/fatfsgen/main/Kconfig.projbuild diff --git a/examples/storage/fatfsgen/main/fatfsgen_example_main.c b/tools/test_apps/storage/fatfsgen/main/fatfsgen_example_main.c similarity index 100% rename from examples/storage/fatfsgen/main/fatfsgen_example_main.c rename to tools/test_apps/storage/fatfsgen/main/fatfsgen_example_main.c diff --git a/tools/test_apps/storage/fatfsgen/partitions_example.csv b/tools/test_apps/storage/fatfsgen/partitions_example.csv new file mode 100644 index 0000000000..1c79321a10 --- /dev/null +++ b/tools/test_apps/storage/fatfsgen/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/examples/storage/fatfsgen/pytest_fatfsgen_example.py b/tools/test_apps/storage/fatfsgen/pytest_fatfsgen_example.py similarity index 100% rename from examples/storage/fatfsgen/pytest_fatfsgen_example.py rename to tools/test_apps/storage/fatfsgen/pytest_fatfsgen_example.py diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt b/tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt similarity index 100% rename from examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt rename to tools/test_apps/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt diff --git a/tools/test_apps/storage/fatfsgen/sdkconfig.defaults b/tools/test_apps/storage/fatfsgen/sdkconfig.defaults new file mode 100644 index 0000000000..47363c32d5 --- /dev/null +++ b/tools/test_apps/storage/fatfsgen/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y