ssESP-IDFを使ってみる

File System


esp-idfでもFlashの一部をFile System(Storage)として使うことができます。
利用できるFile SystemはFAT File System(FatFS)と、SPIFFSの2種類ですが、
ESP-IDF V5.2からはLittleFSが公式に利用できるようになりました。



こ ちらにFatFSのサンプルが公開されています。
ビルドして実行すると以下の様にStorage領域をFatFSとして初期化してファイルの読み書きを行います。


利用できるStorage領域のサイズは起動時に確認する事ができます。
I (48) boot: Partition Table:
I (51) boot: ## Label            Usage          Type ST Offset   Length
I (58) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (73) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot:  3 storage          Unknown data     01 81 00110000 00100000 ----> ここをFatFSとして使う
I (88) boot: End of partition table

パーテイション・サイズは「partitions_example.csv」で指定します。
「partitions_example.csv」の書き方はこ ちらに公開されています。
このファイルを変更してビルドすると、パーティション・サイズが変わります。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage,  data, fat,     ,        1M,

sdkconfig.defaultsに以下を追加すると、「partitions_example.csv」を使ってパーテイションを作成しま す。
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"

パーテイションサイズは10進数、16進数、K単位、M単位の何れかで指定します。
以下の指定はいずれも同じサイズ(1M)となります。
#storage,  data, spiffs,  ,        1M,
#storage,  data, spiffs,  ,        0x100000,
storage,  data, spiffs,  ,        1024K,

デフォルトでは4096バイトセクターが使われます。


menuconfigを使って512バイトセクターに変更することができます。
512バイトセクターにはPerformanceモードとSafetyモードが有ります。


FatFSでは最低でも、128セクターが必要となります。
それ以外に24Kの管理領域が必要になるので、最小パーティションサイズは
128 * セクターのバイト数 + 24K
となります。

512バイトセクターの時は、128 * 0.5K + 24K = 88K となります。
4Kバイトセクターの時は、128 * 4K + 24K = 536K となります。
# 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,
#512 bytes/sector
#storage,  data, fat,     ,        88K,
#4096 bytes/sector
storage,  data, fat,     ,        536K,

これ以下のパーテイションサイズではマウントに失敗します。
W (343) vfs_fat_spiflash: f_mount failed (13)
I (343) vfs_fat_spiflash: Formatting FATFS partition, allocation unit size=4096
E (353) vfs_fat_spiflash: f_mkfs failed (14)
E (353) example: Failed to mount FATFS (ESP_FAIL)

パーテイションの一部を書き込み時のバッファとして使うので、ファイルの書き込みを行うときは、これよりも大きなパーティションが必要です。
パーティションサイズが小さいとファイルの書き込みに失敗します。

各モードの書き込みスピードは以下の様になります。
書き込みスピードには大きな差が出ます。
ファイルシステム バイトセクター モード 書き込みスピード 読み込みスピード
FatFS 512 Safety 2 kB/s 590 kB/s
FatFS 512 Performance 7 kB/s 590 kB/s
FatFS 4096
60 kB/s 722 kB/s

FatFSではディレクトリ操作を行うことができます。
ディレクトリ操作の簡単なサンプルを以下に示します。
    //Create directory
    err = mkdir("/spiflash/sub1", 0755);
    ESP_LOGI(TAG, "mkdir err=%d", err);

    //Create file
    FILE *f = fopen("/spiflash/sub1/test1", "wb");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    fprintf(f, "Hello World");
    fclose(f);

    //Open directory
    DIR *dir;
    dir = opendir ("/spiflash/sub1");
    if (dir == NULL) {
        ESP_LOGE(TAG, "opendir fail");
        return;
    }

    //Read Directory
    struct dirent *de;
    while(1) {
        de = readdir(dir);
        if (de == NULL) break;
        ESP_LOGI(TAG, "readdir de=%p", de);
        ESP_LOGI(TAG, "de->d_name=%s", de->d_name);
    }
    closedir(dir);

    //Remove file
    remove("/spiflash/sub1/test1");

    //Remove directory
    err = rmdir("/spiflash/sub1");
    ESP_LOGI(TAG, "rmdir err=%d", err);



こ ちらにSPIFFSのサンプルが公開されています。
ビルドして実行すると以下の様にStorage領域をSPIFFSとして初期化してファイルの読み書きを行います。


FatFSではディレクトリを作ったり削除したりできますが、SPIFFSではディレクトリ操作はできません。
ただし、従来のファイルシステムとは異なり、ファイル名にはスラッシュ(/)を使用することができます。
これにより、「abc/」で始まるすべてのファイル名を一覧表示することにより、ディレクトリ「abc」内のファイルの一覧表示を模倣します。

パーテイション・サイズはカレントディレクトリにある「partitions_example.csv」で指定します。
このファイルを変更してビルドすると、パーティション・サイズが変わります。
# 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, spiffs,  ,        0xF0000,

SPIFFSを使う場合、esp_spiffs_info()でFile Systemとして実際に使用できる領域のサイズを求めることができます。
管理領域がとられるので、実際に使用できる領域サイズは、パーテイションサイズの90%から80%ぐらいになります。
パーテイションサイズを変えて実測してみました。
パーテイションサイズ(K) パーテイションサイズ esp_spiffs_infoのtotal rate
1M(=1024K) 1,048,576 956,561 91.2%
512K 524,288 474,641 90.5%
256K 262,144 233,681 89.1%
128K 131,072 113,201 86.4%
64K 65,536 52,961 80.8%

SPIFFSとして使う場合の最小パーティションサイズはSoCに関係なく16K(0x4000)の様です。
これ以下のサイズのパーテイションではファイルのオープンに失敗します。
I (501) example: Opening file
E (501) example: Failed to open file for writing

storage領域の開始アドレスが0x110000なので、2M FlashのSoCで指定できる最大のパーティションサイズは0xF0000(=960K)となります。
I (54) boot: Partition Table:
I (57) boot: ## Label            Usage          Type ST Offset   Length
I (65) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (72) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (79) boot:  2 factory          factory app      00 00 00010000 00100000
I (87) boot:  3 storage          Unknown data     01 82 00110000 000f0000
I (94) boot: End of partition table

これ以上の領域を指定した場合、ビルド時に以下のエラーになります。
Partitions defined in '/home/nop/esp-idf/examples/storage/spiffs/partitions_example.csv' occupy 2.1MB of flash (2162688 bytes) which does not fit in configured flash size 2MB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu.

ESP32の様に4M FlashのSoCを使っている場合、Menuconfigを使ってFlash Sizeを4Mに変更することができます。
ESP32-C3の様に、Flash Sizeが2MのSoCではこの指定はエラーになります。




これで1M以上のStorageを確保することができます。
I (48) boot: Partition Table:
I (51) boot: ## Label            Usage          Type ST Offset   Length
I (58) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (73) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot:  3 storage          Unknown data     01 82 00110000 00100000
I (88) boot: End of partition table

storage領域の開始アドレスが0x110000なので、4M FlashのSoCで指定できる最大のパーティションサイズは0x2F0000(=3,008K)となります。
I (48) boot: Partition Table:
I (51) boot: ## Label            Usage          Type ST Offset   Length
I (58) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (73) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot:  3 storage          Unknown data     01 82 00110000 002f0000
I (88) boot: End of partition table

2つ以上のstorage領域を確保することができます。
2つ以上のstorage領域を確保する場合、Partition nameを変更します。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change
the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage0,  data, spiffs, ,        1M,
storage1,  data, spiffs, ,        1M,

これで2つのstorage領域が確保されます。
I (48) boot: Partition Table:
I (51) boot: ## Label            Usage          Type ST Offset   Length
I (58) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (73) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot:  3 storage0         Unknown data     01 82 00110000 00100000
I (88) boot:  4 storage1         Unknown data     01 82 00210000 00100000
I (96) boot: End of partition table

以下の様にするとFatFSとSPIFFSを両方同時に確保することができます。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change
the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage0,  data, spiffs, ,        1M,
storage1,  data, fat,    ,        1M,

I (48) boot: Partition Table:
I (51) boot: ## Label            Usage          Type ST Offset   Length
I (58) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (73) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot:  3 storage0         Unknown data     01 82 00110000 00100000 ---> SPIFFS
I (88) boot:  4 storage1         Unknown data     01 81 00210000 00100000 ---> FatFS
I (96) boot: End of partition table

FatFSに比べ、読み書きのスピードはかなり遅くなります。
ファイルシステム バイトセクター モード 書き込みスピード 読み込みスピード
FatFS 512 Safety 2 kB/s 590 kB/s
FatFS 512 Performance 7 kB/s 590 kB/s
FatFS 4096
60 kB/s 722 kB/s
SPIFFS

35 kB/s 406 kB/s

SPIFFSにはいくつかの設定値が有りますが、何がパフォーマンスに影響するのか分かりません。




ESP-IDF V5.2からLittleFSが正式なコンポーネントとして追加されました。
こ ちらにLittleFSのサンプルが公開されています。
ビルドして実行すると以下の様にStorage領域をLittleFSとして初期化してファイルの読み書きを行います。


SPIFFSと同様にディレクトリ操作はできません。
ただし、FatFSやSPIFFSよりも高速に動作します。
ファイルシステム バイトセクター モード 書き込みスピード 読み込みスピード
FatFS 512 Safety 2 kB/s 590 kB/s
FatFS 512 Performance 7 kB/s 590 kB/s
FatFS 4096
60 kB/s 722 kB/s
SPIFFS

35 kB/s 406 kB/s
LittleFs

103 kB/s 650 kB/s



FatFSではファイル名の大文字、小文字を区別しません。
以下の様にhello.txtのファイルを作成しても、ファイル名は大文字で扱われます。


SPIFFSとLittleFSではファイル名の大文字、小文字を区別します。
また、SPIFFSではファイル名に/を使うことができます。


FatFSのデフォルトでは8文字以上のファイル名を扱うことができません。
以下の設定を変更すると、長いファイル名を扱うことが出来るようになります。


全てのファイルシステムで、以下のコードでディレクトリ内のファイル一覧を取り出すことができます。
#include "esp_vfs.h" // opendir


static void listDir(char * path) {
    DIR* dir = opendir(path);
    assert(dir != NULL);
    while (true) {
        struct dirent*pe = readdir(dir);
        if (!pe) break;
        ESP_LOGI(__FUNCTION__,"d_name=%s d_ino=%d d_type=%x", pe->d_name,pe->d_ino, pe->d_type);
    }
    closedir(dir);
}

FatFS、SPIFFS、LittleFSの違いをまとめると以下の様になります。

FatFS SPIFFS LittleFS
ディレクトリ作成・削除 可能 不可能 不可能
/を含んだファイル名
可能 不可能
ファイル名の大文字小文字の区別 なし あり あり
8文字以上のファイル名 可能(*1) 可能 可能

(*1)デフォルトでは8文字まで。設定変更が必要。

何処にも明確な記載は有りませんが、試した限りSPIFFSもFatFSもThread Safeです。
ファイルを読むタスクと、ファイルを書くタスクを同時に動かしても、問題なく読み書きができますが、
読み込んだデータの保証を行うためには、セマフォーなどで同期を取る必要が有ります。



Partition Tableの定義はCSVファイルに指定します。
書き方として2つの書き方が有ります。
こちらは各パーティションの先頭アドレス(Offset)を指定しています。
# 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,     0x110000,1M,

こちらは各パーティションの先頭アドレス(Offset)を指定していません。
# 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,     , 0x6000,
phy_init, data, phy,     , 0x1000,
factory,  app,  factory, , 1M,
storage,  data, fat,     , 1M,

どちらも同じ位置、同じサイズのパーティションが作られます。
I (58) boot: Partition Table:
I (62) boot: ## Label            Usage          Type ST Offset   Length
I (69) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (77) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (84) boot:  2 factory          factory app      00 00 00010000 00100000
I (91) boot:  3 storage          Unknown data     01 81 00110000 00100000
I (99) boot: End of partition table

Flashの先頭部分(0x0000-0x8FFF)にはBootLoaderなどが書き込まれます。
ファームウェアを暗号化するとBootLoaderのサイズが0x8FFFを超えることが有り、その場合は先頭パーティションの開始位置をずらす 必要が有ります。
先頭パーティションの開始位置をずらす場合は、各パーテイションの先頭オフセットを増やすか、Menuconfigで以下を変更します。


これでパーティションの開始位置が変わります。
I (58) boot: Partition Table:
I (62) boot: ## Label            Usage          Type ST Offset   Length
I (69) boot:  0 nvs              WiFi data        01 02 0000a000 00006000
I (77) boot:  1 phy_init         RF data          01 01 00010000 00001000
I (84) boot:  2 factory          factory app      00 00 00020000 00100000
I (91) boot:  3 storage          Unknown data     01 81 00120000 00100000
I (99) boot: End of partition table



sdkconfig.defaultsに以下を追加すると、「partitions_example.csv」を使ってパーテイションを作成しま す。
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"

「partitions_example.csv」のファイル名は動的に変更することができません。
ESP32シリーズにはFlashメモリーが2Mの製品と4Mの製品が有ります。
もしかしたらこれ以上のFlashサイズの製品も有るかもしれませんが、見たことが有りません。
Flashメモリーサイズの設定は以下のconfigで行います。


Flashメモリが2Mの製品では最大960K、4Mの製品では最大3008Kのパーテイションを作成することができます。
main/CMakeLists.txtでFlashサイズを判定することで、Flashサイズに応じた最大サイズのパーティションを作成するこ とができます。
まず、Flashサイズが2M用のファイル(partitions_example_2M.csv)を作成します。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage,  data, spiffs,  ,        0xF0000,

次に、Flashサイズが4M用のファイル(partitions_example_4M.csv)を作成します。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage,  data, spiffs,  ,        0x2F0000,

そして、main/CMakeLists.txtでpartitions_example.csvを切り替えます。
makeと違ってCmakeはファイル操作ができるのが嬉しいです。
set(TARGET_FILE "${CMAKE_SOURCE_DIR}/partitions_example.csv")
message(STATUS ${TARGET_FILE})
if(CONFIG_ESPTOOLPY_FLASHSIZE_2MB)
    message(STATUS "CONFIG_ESPTOOLPY_FLASHSIZE_2MB")
    set(SOURCE_FILE "${CMAKE_SOURCE_DIR}/partitions_example_2M.csv")
    message(STATUS ${SOURCE_FILE})
    file(COPY_FILE ${SOURCE_FILE} ${TARGET_FILE})
elseif(CONFIG_ESPTOOLPY_FLASHSIZE_4MB)
    message(STATUS "CONFIG_ESPTOOLPY_FLASHSIZE_4MB")
    set(SOURCE_FILE "${CMAKE_SOURCE_DIR}/partitions_example_4M.csv")
    message(STATUS ${SOURCE_FILE})
    file(COPY_FILE ${SOURCE_FILE} ${TARGET_FILE})
endif()
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")

ビルド時のログを見ると、パーティションを作成するよりも早い段階で、main/CMakeLists.txtが実行される事が分かります。
これで、Flashサイズに応じた最大パーテイションを作ることができます。
これが2Mの時のパーティション情報です。
*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
storage,data,spiffs,0x110000,960K,
*******************************************************************************

これが4Mの時のパーテイション情報です。
*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
storage,data,spiffs,0x110000,3008K,
*******************************************************************************



こ ちらにパーテイション情報を取得して表示するサンプルが公開されています。
ビルドして実行すると、パーテイションを定義しているcsvファイルの内容と、現在のパーティション情報を表示します。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,        data, nvs,      0x9000,  0x6000,
phy_init,   data, phy,      0xf000,  0x1000,
factory,    app,  factory,  0x10000, 1M,
storage1,    data, fat,             , 0x40000,
storage2,    data, fat,             , 0x40000,

I (328) example: ----------------Find partitions---------------
I (328) example: Find partition with type ESP_PARTITION_TYPE_DATA, subtype ESP_PARTITION_SUBTYPE_DATA_NVS, label NULL (unspecified)...
I (348) example:        found partition 'nvs' at offset 0x9000 with size 0x6000
I (348) example: Find partition with type ESP_PARTITION_TYPE_DATA, subtype ESP_PARTITION_SUBTYPE_DATA_PHY, label NULL (unspecified)...
I (368) example:        found partition 'phy_init' at offset 0xf000 with size 0x1000
I (368) example: Find partition with type ESP_PARTITION_TYPE_APP, subtype ESP_PARTITION_SUBTYPE_APP_FACTORY, label NULL (unspecified)...
I (388) example:        found partition 'factory' at offset 0x10000 with size 0x100000
I (388) example: Find partition with type ESP_PARTITION_TYPE_DATA, subtype ESP_PARTITION_SUBTYPE_DATA_FAT, label NULL (unspecified)...
I (408) example:        found partition 'storage1' at offset 0x110000 with size 0x40000
I (418) example: Find second FAT partition by specifying the label
I (418) example: Find partition with type ESP_PARTITION_TYPE_DATA, subtype ESP_PARTITION_SUBTYPE_DATA_FAT, label storage2...
I (428) example:        found partition 'storage2' at offset 0x150000 with size 0x40000
I (438) example: ----------------Iterate through partitions---------------
I (448) example: Iterating through app partitions...
I (458) example:        found partition 'factory' at offset 0x10000 with size 0x100000
I (458) example: Iterating through data partitions...
I (468) example:        found partition 'nvs' at offset 0x9000 with size 0x6000
I (478) example:        found partition 'phy_init' at offset 0xf000 with size 0x1000
I (478) example:        found partition 'storage1' at offset 0x110000 with size 0x40000
I (488) example:        found partition 'storage2' at offset 0x150000 with size 0x40000
I (498) example: Example end

「CMakeLists.txt」の中でcsvファイルをバイナリーファイルとしてコードに埋め込んでいます。
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS "."
                    EMBED_TXTFILES ../partitions_example.csv)

ソースコードの中では埋め込まれているバイナリーファイルの先頭ポインターと最終ポインターを求めて、そこから求めたサイズ分のデータを表示 して います。
ちょっとトリッキーなコードです。
extern const char csv_start[] asm("_binary_partitions_example_csv_start");
extern const char csv_end[]   asm("_binary_partitions_example_csv_end");

ESP_LOGI(TAG, "Printing partition table csv file contents for reference...\n\n%.*s", csv_end - csv_start + 1, csv_start);



こ ちらにSD-CARDのサンプルが公開されています。
このサンプルではSDMMCとSDSPIの2つのI/FのSDカードを使うことができます。

SDMMCのカードリーダーを使う場合、1-Line Modeと4-Line modeの2つのモードが有ります。
ESP32で4-Line modeのSDMMCを使うのは少し(かなり)厄介です。
ESP32ではSDMMCとの接続で使用するGPIOは固定されていて、変更することができません。
SDMMCカードリーダーとの接続にGPIO2とGPIO12を使いますが、どちらもPullUpしておく必要が有ります。

ESP32ではGPIO2ピンはブートストラップピンとして使用され、ファームを書き込むときはLowレベルでなければなりません。
GPIO2がPullUpされていると、ファームを書き込むことができなくなります。
これを解決する1つの方法は、ファームを書き込むときにSDMMCカードリーダーへの電源供給を止めることです。
これを解決する別の方法は、ジャンパーを使用してGPIO0とGPIO2を接続することです。
UARTダウンロードモードに入ると、ほとんどの開発ボードの自動リセット回路がGPIO0をLowレベルに引き下げます。
これによりGPIO2もLowレベルに引き下げます。

ESP32ではGPIO12は、内部レギュレータの出力電圧(VDD_SDIO)を選択するためのブートストラップピンとして 使用されます。
リセット時にGPIO12がPullUpされていると、VDD_SDIOには1.8Vが出力されます。
リセット時にGPIO12がPullDownされていると、VDD_SDIOには3.3Vが出力されます。
3.3V仕様のフラッシュチップを使用していて、フラッシュチップへの電力供給に内部レギュレータを使用するモジュールでは、
GPIO12はリセット時にLowレベルである必要があり、この様なモジュールでは、GPIO12のPullUpが必要な4-Line modeは使えません。

GPIO12に関する別のオプションは、フラッシュ電圧選択に関するefusesを固定させることです。
これにより、内部レギュレータの出力電圧が恒久的に3.3Vに固定され、GPIO12をブートストラップピンとして使用しなくなります。
プルアップ抵抗をGPIO12に接続しても安全です。
次のコマンドを使用して、フラッシュ電圧選択に関するefusesを3.3V固定にすることができます。

components/esptool_py/esptool/espefuse.py set_flash_voltage 3.3V

このコマンドは、 `XPD_SDIO_TIEH`、` XPD_SDIO_FORCE`、および `XPD_SDIO_REG`のefusesを書き込みます。
3つのすべての値が1になると、内部VDD_SDIOフラッシュ電圧レギュレータは3.3Vで永続的に有効になります。
詳細については、テクニカルリファレンスマニュアルを参照してください。

と、いうようなことがこ ちらに書かれています。

GPIO12に関する問題はモジュールに依存するところがさらに厄介です。
こ ちらのSD Pull-up Requirementsのドキュメントに以下の記述が有りました。

ESP32-WROVER-Bを除くすべてのESP32-WROVERモジュールは、1.8Vフラッシュを使用し、GPIO12に内部プル アップ を備えています。
3.3 Vフラッシュを使用する他のモジュールには、GPIO12ピンにプルアップがなく、このピンは内部でわずかにプルダウンされています。

ESP32-CAMにはSDMMCのカードリーダーが実装されています。
sdmmcのサンプルが何も変更なしで動きます。
SDカードはあらかじめFAT32で初期化しておかないと認識しません。


ESP32-CAMのSchematicがこ ちらに有ります。
これを見るとGPIO12に繋がっているHS2_DATA2のPullUp抵抗にはN/Cと記載されています。
ESP-32Sには1.8Vフラッシュが使われていて、GPIO12は内部PullUpされているのかと思って、
esptool.pyを使って チップ情報を読み出してみまし た。
esptool.pyの使い方はこちらに 詳 しく紹介されています。
WinbondのW25Q32なので3.3Vフラッシュです。
$ esptool.py flash_id
esptool.py v3.2-dev
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 24:6f:28:0c:a8:04
Uploading stub...
Running stub...
Stub running...
Manufacturer: ef
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

フラッシュ電圧固定化については、こち らに 記事が有りました。
そこで、efuseの情報を読み出してみました。
内部レギュレータの出力電圧(VDD_SDIO)は固定されていません。
なぜ、ESP32-CAMで4-line mde SDMMCが動くのか謎です。
分かる方が居たら教えて欲しいです。
$ espefuse.py -p /dev/ttyUSB0 summary
Connecting....
Detecting chip type... ESP32
espefuse.py v3.2-dev
EFUSE_NAME (Block)                       Description  = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Calibration fuses:
BLK3_PART_RESERVE (BLOCK0):              BLOCK3 partially served for ADC calibration data   = False R/W (0b0)
ADC_VREF (BLOCK0):                       Voltage reference calibration                      = 1079 R/W (0b10011)

Config fuses:
XPD_SDIO_FORCE (BLOCK0):                 Ignore MTDI pin (GPIO12) for VDD_SDIO on reset     = False R/W (0b0)
XPD_SDIO_REG (BLOCK0):                   If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset    = False R/W (0b0)
XPD_SDIO_TIEH (BLOCK0):                  If XPD_SDIO_FORCE & XPD_SDIO_REG                   = 1.8V R/W (0b0)
CLK8M_FREQ (BLOCK0):                     8MHz clock freq override                           = 54 R/W (0x36)
SPI_PAD_CONFIG_CLK (BLOCK0):             Override SD_CLK pad (GPIO6/SPICLK)                 = 0 R/W (0b00000)
SPI_PAD_CONFIG_Q (BLOCK0):               Override SD_DATA_0 pad (GPIO7/SPIQ)                = 0 R/W (0b00000)
SPI_PAD_CONFIG_D (BLOCK0):               Override SD_DATA_1 pad (GPIO8/SPID)                = 0 R/W (0b00000)
SPI_PAD_CONFIG_HD (BLOCK0):              Override SD_DATA_2 pad (GPIO9/SPIHD)               = 0 R/W (0b00000)
SPI_PAD_CONFIG_CS0 (BLOCK0):             Override SD_CMD pad (GPIO11/SPICS0)                = 0 R/W (0b00000)
DISABLE_SDIO_HOST (BLOCK0):              Disable SDIO host                                  = False R/W (0b0)

Efuse fuses:
WR_DIS (BLOCK0):                         Efuse write disable mask                           = 0 R/W (0x0000)
RD_DIS (BLOCK0):                         Efuse read disable mask                            = 0 R/W (0x0)
CODING_SCHEME (BLOCK0):                  Efuse variable block length scheme
   = NONE (BLK1-3 len=256 bits) R/W (0b00)
KEY_STATUS (BLOCK0):                     Usage of efuse block 3 (reserved)                  = False R/W (0b0)

Identity fuses:
MAC (BLOCK0):                            Factory MAC Address
   = 24:6f:28:0c:a8:04 (CRC 0x3a OK) R/W
MAC_CRC (BLOCK0):                        CRC8 for factory MAC address                       = 58 R/W (0x3a)
CHIP_VER_REV1 (BLOCK0):                  Silicon Revision 1                                 = True R/W (0b1)
CHIP_VER_REV2 (BLOCK0):                  Silicon Revision 2                                 = False R/W (0b0)
CHIP_VERSION (BLOCK0):                   Reserved for future chip versions                  = 2 R/W (0b10)
CHIP_PACKAGE (BLOCK0):                   Chip package identifier                            = 0 R/W (0b000)
MAC_VERSION (BLOCK3):                    Version of the MAC field                           = 0 R/W (0x00)

Security fuses:
FLASH_CRYPT_CNT (BLOCK0):                Flash encryption mode counter                      = 0 R/W (0b0000000)
UART_DOWNLOAD_DIS (BLOCK0):              Disable UART download mode (ESP32 rev3 only)       = False R/W (0b0)
FLASH_CRYPT_CONFIG (BLOCK0):             Flash encryption config (key tweak bits)           = 0 R/W (0x0)
CONSOLE_DEBUG_DISABLE (BLOCK0):          Disable ROM BASIC interpreter fallback             = True R/W (0b1)
ABS_DONE_0 (BLOCK0):                     Secure boot V1 is enabled for bootloader image     = False R/W (0b0)
ABS_DONE_1 (BLOCK0):                     Secure boot V2 is enabled for bootloader image     = False R/W (0b0)
JTAG_DISABLE (BLOCK0):                   Disable JTAG                                       = False R/W (0b0)
DISABLE_DL_ENCRYPT (BLOCK0):             Disable flash encryption in UART bootloader        = False R/W (0b0)
DISABLE_DL_DECRYPT (BLOCK0):             Disable flash decryption in UART bootloader        = False R/W (0b0)
DISABLE_DL_CACHE (BLOCK0):               Disable flash cache in UART bootloader             = False R/W (0b0)
BLOCK1 (BLOCK1):                         Flash encryption key
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLOCK2 (BLOCK2):                         Secure boot key
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLOCK3 (BLOCK3):                         Variable Block 3
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W

Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).

1-Line modeならばGPIO12は使わないので、GPIO12に対する考慮は不要になります。
1-Line modeにするためには、こ の部分を変更する必要が有ります

ESP32S3ではSDMMCとの接続で使用するGPIOは、任意のGPIOに変更することができます。
ブートストラップピンを使わないので、単純にPullUpすることができます。

1-Line/4-Line CLK CMD D0 D1 D2 D3
ESP32 1-Line GPIO14 GPIO15 GPIO2


ESP32 4-Line GPIO14 GPIO15 GPIO2 GPIO4 GPIO12 GPIO13
ESP32S3 1-Line GPIO36(*) GPIO35(*) GPIO37(*)


ESP32S3 4-Line GPIO36(*) GPIO35(*) GPIO37(*) GPIO38(*) GPIO33(*) GPIO34(*)

(*)任意のGPIOに変更可能

FLASH上のファイルシステムに比べ、書き込みスピードはかなり速くなります。
ファイルシステム バイトセクター モード 書き込みスピード
FatFS 512 Safety 2KB/Sec
FatFS 512 Performance 7KB/Sec
FatFS 4096
60KB/Sec
SPIFFS

35KB/Sec
LittleFS

103KB/Sec
SDMMC(1Line)

175KB/Sec
SDMMC(4Line)

215KB/Sec



SPI接続のSDカードリーダー(SDSPI)を使う場合の結線は以下の様になります。
使用するGPIOはmenucnfigで変更することができます。
SPIカードリーダー ESP32 備考
3V3 3V3
CS GPIO13
MOSI GPIO15 PullUpが必要
CLK GPIO14
MISO GPIO2
GND GND

デフォルトでは、GPIO2がMISOとして使われますが、GPIO2は起動時のストラップなので、ここに何かを接続すると
ファームが書き込めません。
ファームを書き込む時は、GPIO2を外しておくか、別のGPIOを使う必要が有ります。

こ ちらのREADME.mdには、GPIO15(MOSI)に10k PullUpが必要と書かれています。
PullUp抵抗が無くても動くリーダーと、PullUp抵抗が無いと動かないリーダーが有りました。
なお、Arduino用のSDカードリーダーは5V仕様なので、そのままでは使えません。
5V仕様のSDカードリーダーを使うときは、MOSI、CLK、CSを5V-->3.3Vにレベルシフトする必要が有ります。

ビルドして実行すると以下の様にSDSPIのカードリーダーを使ったファイルの読み書きを行います。
ESP-IDFのSDSPIは20MHzのバスクロックを使用します。


SDSPIはSDSPI_HOST_DEFAULT()を使って初期化します。
この関数はこ ちらに定義されていますが、HSPI_HOST/SPI2_HOSTのSPIバスが使われます。
SDSPIと同じSPIバスに別のSPIデバイスを追加する場合、AC負荷(ピンコンデンサ)とDC負荷(プルアップ)に注意が必要 です。
こちらに 詳 しく紹介されていますが難しくてちゃんと理解できません。
SPIデバイスをSDSPIと同時に使うときは、SDSPIが使うバスとは別のバス(VSPI_HOST/SPI3_HOST)を使った方が 良さ そうです。

ESP-IDF V5.1からSoftSPIがサポートされました。
SPIデバイスをSDSPIと同時に使うときは、SoftSPIを使うことも可能になりました。

余談ですが、ESP-IDFのバージョンアップに伴い、HSPI_HOST/VSPI_HOSTの記述は非推奨になり、SPI2/SPI3の 記述 が推奨されています。
実際、ESP32-S2ではVSPI_HOSTの記述はビルドエラーになり、
ESP32-S3やESP32-C3ではHSPI_HOST/VSPI_HOSTの記述はどちらもビルドエラーになります。
ESP32では以下の様に定義されています。
HSPI_HOST=1 VSPI_HOST=2
SPI2_HOST=1 SPI3_HOST=2

SDSPIは完全なThread Safeではなさそうです。
読み込みタスクと書き込みタスクを同時に実行すると、このようなエラーになる事がごくまれに有ります。
どうもSDカードの容量に依存するみたいなことがこちらに 書か れています。
タイムスタンプを見ても、起動してから15分後に発生しているので、頻繁に発生する現象ではありません。
連続稼働の環境では注意が必要です。
E (872011) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107
E (872021) diskio_sdmmc: sdmmc_read_blocks failed (263)
E (872021) sdmmc_cmd: sdmmc_write_sectors_dma: sdmmc_send_cmd returned 0x105
E (872031) diskio_sdmmc: sdmmc_write_blocks failed (261)
E (872031) READ: Failed to read file
E (872041) WRITE: Failed to write file



ESP-IDE Ver5からUSB HOSTの機能が追加され、ESP32S2/S3でUSB HOSTを使うことができるようになりました。
こ ちらにUSB Flash Drive(USBメモリースティック)を使うサンプルが公開されています。
ESP32とUSBメスコネクターを以下の様に接続し、メスコネクターにUSBメモリーを刺します。
ESP BOARD    USB CONNECTOR (type A)
                  +--+
[5V]      ------> | || VCC
[GPIO19]  ------> | || D-
[GPIO20]  ------> | || D+
[GND]     ------> | || GND
                  +--+

ファームをビルドすると以下の様に表示されます。
サンプルコードを見ると、mkdirしているので、ディレクトリを扱うことができます。
*** Device descriptor ***
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0x0
bDeviceSubClass 0x0
bDeviceProtocol 0x0
bMaxPacketSize0 64
idVendor 0x90c
idProduct 0x2000
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerialNumber 0
bNumConfigurations 1
*** Configuration descriptor ***
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
bMaxPower 500mA
        *** Interface descriptor ***
        bLength 9
        bDescriptorType 4
        bInterfaceNumber 0
        bAlternateSetting 0
        bNumEndpoints 2
        bInterfaceClass 0x50
        iInterface 0
                *** Endpoint descriptor ***
                bLength 7
                bDescriptorType 5
                bEndpointAddress 0x81   EP 1 IN
                bmAttributes 0x2        BULK
                wMaxPacketSize 64
                bInterval 255
                *** Endpoint descriptor ***
                bLength 7
                bDescriptorType 5
                bEndpointAddress 0x2    EP 2 OUT
                bmAttributes 0x2        BULK
                wMaxPacketSize 64
                bInterval 255
Device info:
         Capacity: 14999 MB
         Sector size: 512
         Sector count: 30719999
         PID: 0x2000
         VID: 0x 90C
         iProduct: USB DISK
         iManufacturer: SMI Corporation
         iSerialNumber:
I (6023) example: Writing file
I (6063) example: Reading file
I (6073) example: Read from file: 'Hello World!'
I (6123) example: Done



こちらの コード で書き込み速度を実測してみました。
ESP32-CAMにはSDMMCのカードリーダーが実装されているので、ESP32-CAMに外付けのSPIカードリーダーを付けて、
同じマイクロSDカード、同じ開発ボードを使って、同じ条件で測定しています。

書き込み速度はSDMMCの方が圧倒的に早いです。
SDMMCでも4-Line modeの方が当然速いです。
CPUクロックの影響はあまり受けない様です。
SDSPI @160
write buffer size = 16384
sum=135419110 microseconds, average=13541 microseconds
maximum=293781 microseconds, minimum=12774 microseconds
highest write speed = 1282605 byte/s
average write speed = 1209954 byte/s
lowest write speed = 55769 byte/s

SDSPI @240
write buffer size = 16384
sum=118289791 microseconds, average=11828 microseconds
maximum=63770 microseconds, minimum=11206 microseconds
highest write speed = 1462073 byte/s
average write speed = 1385187 byte/s
lowest write speed = 256923 byte/s

SDMMS(1-line mode) @160
write buffer size = 16384
sum=85988383 microseconds, average=8598 microseconds
maximum=329841 microseconds, minimum=7946 microseconds
highest write speed = 2061917 byte/s
average write speed = 1905559 byte/s
lowest write speed = 49672 byte/s

SDMMS(1-line mode) @240
write buffer size = 16384
sum=85516217 microseconds, average=8551 microseconds
maximum=136091 microseconds, minimum=7892 microseconds
highest write speed = 2076026 byte/s
average write speed = 1916033 byte/s
lowest write speed = 120390 byte/s

SDMMS(4-line mode) @160
write buffer size = 16384
sum=41035462 microseconds, average=4103 microseconds
maximum=134916 microseconds, minimum=3190 microseconds
highest write speed = 5136050 byte/s
average write speed = 3993175 byte/s
lowest write speed = 121438 byte/s

SDMMC(4-line mode) @240
write buffer size = 16384
sum=39638089 microseconds, average=3963 microseconds
maximum=141535 microseconds, minimum=3085 microseconds
highest write speed = 5310858 byte/s
average write speed = 4134241 byte/s
lowest write speed = 115759 byte/s

SDSPIでもSDMMCでも、書き込むサイズの大小はあまり関係ないみたいです。
バッファフラッシュのタイミングなどが影響するのかもしれません。
SDSPI @160
write buffer size = 1024
maximum = 32 millsec.
minimum = 1 millsec.
write buffer size = 2048
maximum = 344 millsec.
minimum = 2 millsec.
write buffer size = 4096
maximum = 69 millsec.
minimum = 3 millsec.
write buffer size = 8192
maximum = 54 millsec.
minimum = 6 millsec.

SDMMS @160
write buffer size = 1024
maximum = 38 millsec.
minimum = 0 millsec.
write buffer size = 2048
maximum = 77 millsec.
minimum = 0 millsec.
write buffer size = 4096
maximum = 67 millsec.
minimum = 1 millsec.
write buffer size = 8192
maximum = 37 millsec.
minimum = 1 millsec.

続く....