ESP8266/ESP32向けのファイルシステム「LittleFS」は、これらのマイクロコントローラーに内蔵されているフラッシュメモリを、パソコンのファイルシステムのように利用できる軽量で堅牢なファイルシステムとなります。特に、従来使用されていたSPIFFSと同様に推奨されるファイルシステムになります。 主な特徴と利点は以下に示します。

耐障害性
ランダムな停電やクラッシュが発生しても、データの破損を防ぐように設計されています。コピーオンライト方式を採用しており、データの整合性を保証します。
効率的なメモリ管理
フラッシュメモリの書き込み/消去回数を均等化するウェアレベリング機能をサポートしており、フラッシュメモリの寿命を延ばします。
ディレクトリサポート
SPIFFSとは異なり、ネストされたディレクトリ(フォルダ)の作成と管理をサポートしているため、ファイルの整理が容易になります。
低RAM要件
リソースが限られた組み込みデバイス向けに設計されているため、RAMの使用量が非常に少なくなります。

LittleFS オブジェクトの主要関数

LittleFS オブジェクトの主要関数を次に示します。詳細については「Arduino (ESP8266/ESP32) 向け LittleFS 関数一覧」を参照します。

LittleFS 関数一覧
関数名 説明
LittleFS.begin(formatOnFail) ファイルシステムをマウント(初期化)します。formatOnFailをtrueに設定すると、マウント失敗時に自動的にフォーマットを試みます。
LittleFS.format() フラッシュメモリ上のLittleFS領域をフォーマットし、空のファイルシステムを作成します。
LittleFS.end() ファイルシステムをアンマウントします。
LittleFS、open(path、mode) 指定されたパスのファイルを開きます。成功するとFileオブジェクトを返します。modeには"r" (読み取り)、"w" (書き込み)、 "a" (追記)などを指定します。
LittleFS.exists(path) 指定されたパスのファイルまたはディレクトリが存在するかどうかを確認します。
LittleFS.remove(path) 指定されたファイルまたは空のディレクトリを削除します。
LittleFS.rename(pathFrom、pathTo) ファイルまたはディレクトリの名前を変更します。
LittleFS.mkdir(path) 新しいディレクトリを作成します。
LittleFS.rmdir(path) ディレクトリを削除します(空である必要があります)。
LittleFS.info(fs_info) ファイルシステムの使用領域やブロックサイズなどの情報をFSInfo構造体に取得します。
LittleFS.totalBytes() ファイルシステムの合計サイズをバイト単位で取得します。
LittleFS.usedBytes() 使用されているバイト数を取得します。
LittleFS.gcfs() ガベージコレクション(領域の再利用)を手動で実行します。

LittleFS のスケッチ例

LittleFS のスケッチ例を次に示します。

#include <Arduino.h>
#include "FS.h"
#include <LittleFS.h>

/* You only need to format LittleFS the first time you run a
   test or else use the LITTLEFS plugin to create a partition
   https://github.com/lorol/arduino-esp32littlefs-plugin

   If you test two partitions, you need to use a custom
   partition.csv file, see in the sketch folder */

//#define TWOPART

#define FORMAT_LITTLEFS_IF_FAILED true

void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\r\n", dirname);

  File root = fs.open(dirname);
  if (!root) {
    Serial.println("- failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println(" - not a directory");
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name());
      if (levels) {
        listDir(fs, file.path(), levels - 1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("\tSIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

void createDir(fs::FS &fs, const char *path) {
  Serial.printf("Creating Dir: %s\n", path);
  if (fs.mkdir(path)) {
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed");
  }
}

void removeDir(fs::FS &fs, const char *path) {
  Serial.printf("Removing Dir: %s\n", path);
  if (fs.rmdir(path)) {
    Serial.println("Dir removed");
  } else {
    Serial.println("rmdir failed");
  }
}

void readFile(fs::FS &fs, const char *path) {
  Serial.printf("Reading file: %s\r\n", path);

  File file = fs.open(path);
  if (!file || file.isDirectory()) {
    Serial.println("- failed to open file for reading");
    return;
  }

  Serial.println("- read from file:");
  while (file.available()) {
    Serial.write(file.read());
  }
  file.close();
}

void writeFile(fs::FS &fs, const char *path, const char *message) {
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("- file written");
  } else {
    Serial.println("- write failed");
  }
  file.close();
}

void appendFile(fs::FS &fs, const char *path, const char *message) {
  Serial.printf("Appending to file: %s\r\n", path);

  File file = fs.open(path, FILE_APPEND);
  if (!file) {
    Serial.println("- failed to open file for appending");
    return;
  }
  if (file.print(message)) {
    Serial.println("- message appended");
  } else {
    Serial.println("- append failed");
  }
  file.close();
}

void renameFile(fs::FS &fs, const char *path1, const char *path2) {
  Serial.printf("Renaming file %s to %s\r\n", path1, path2);
  if (fs.rename(path1, path2)) {
    Serial.println("- file renamed");
  } else {
    Serial.println("- rename failed");
  }
}

void deleteFile(fs::FS &fs, const char *path) {
  Serial.printf("Deleting file: %s\r\n", path);
  if (fs.remove(path)) {
    Serial.println("- file deleted");
  } else {
    Serial.println("- delete failed");
  }
}

// SPIFFS-like write and delete file, better use #define CONFIG_LITTLEFS_SPIFFS_COMPAT 1

void writeFile2(fs::FS &fs, const char *path, const char *message) {
  if (!fs.exists(path)) {
    if (strchr(path, '/')) {
      Serial.printf("Create missing folders of: %s\r\n", path);
      char *pathStr = strdup(path);
      if (pathStr) {
        char *ptr = strchr(pathStr, '/');
        while (ptr) {
          *ptr = 0;
          fs.mkdir(pathStr);
          *ptr = '/';
          ptr = strchr(ptr + 1, '/');
        }
      }
      free(pathStr);
    }
  }

  Serial.printf("Writing file to: %s\r\n", path);
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("- file written");
  } else {
    Serial.println("- write failed");
  }
  file.close();
}

void deleteFile2(fs::FS &fs, const char *path) {
  Serial.printf("Deleting file and empty folders on path: %s\r\n", path);

  if (fs.remove(path)) {
    Serial.println("- file deleted");
  } else {
    Serial.println("- delete failed");
  }

  char *pathStr = strdup(path);
  if (pathStr) {
    char *ptr = strrchr(pathStr, '/');
    if (ptr) {
      Serial.printf("Removing all empty folders on path: %s\r\n", path);
    }
    while (ptr) {
      *ptr = 0;
      fs.rmdir(pathStr);
      ptr = strrchr(pathStr, '/');
    }
    free(pathStr);
  }
}

void testFileIO(fs::FS &fs, const char *path) {
  Serial.printf("Testing file I/O with %s\r\n", path);

  static uint8_t buf[512];
  size_t len = 0;
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }

  size_t i;
  Serial.print("- writing");
  uint32_t start = millis();
  for (i = 0; i < 2048; i++) {
    if ((i & 0x001F) == 0x001F) {
      Serial.print(".");
    }
    file.write(buf, 512);
  }
  Serial.println("");
  uint32_t end = millis() - start;
  Serial.printf(" - %u bytes written in %lu ms\r\n", 2048 * 512, end);
  file.close();

  file = fs.open(path);
  start = millis();
  end = start;
  i = 0;
  if (file && !file.isDirectory()) {
    len = file.size();
    size_t flen = len;
    start = millis();
    Serial.print("- reading");
    while (len) {
      size_t toRead = len;
      if (toRead > 512) {
        toRead = 512;
      }
      file.read(buf, toRead);
      if ((i++ & 0x001F) == 0x001F) {
        Serial.print(".");
      }
      len -= toRead;
    }
    Serial.println("");
    end = millis() - start;
    Serial.printf("- %u bytes read in %lu ms\r\n", flen, end);
    file.close();
  } else {
    Serial.println("- failed to open file for reading");
  }
}

void setup() {
  Serial.begin(115200);

#ifdef TWOPART
  if (!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED, "/lfs2", 5, "part2")) {
    Serial.println("part2 Mount Failed");
    return;
  }
  appendFile(LittleFS, "/hello0.txt", "World0!\r\n");
  readFile(LittleFS, "/hello0.txt");
  LittleFS.end();

  Serial.println("Done with part2, work with the first lfs partition...");
#endif

  if (!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
    Serial.println("LittleFS Mount Failed");
    return;
  }
  Serial.println("SPIFFS-like write file to new path and delete it w/folders");
  writeFile2(LittleFS, "/new1/new2/new3/hello3.txt", "Hello3");
  listDir(LittleFS, "/", 3);
  deleteFile2(LittleFS, "/new1/new2/new3/hello3.txt");

  listDir(LittleFS, "/", 3);
  createDir(LittleFS, "/mydir");
  writeFile(LittleFS, "/mydir/hello2.txt", "Hello2");
  listDir(LittleFS, "/", 1);
  deleteFile(LittleFS, "/mydir/hello2.txt");
  removeDir(LittleFS, "/mydir");
  listDir(LittleFS, "/", 1);
  writeFile(LittleFS, "/hello.txt", "Hello ");
  appendFile(LittleFS, "/hello.txt", "World!\r\n");
  readFile(LittleFS, "/hello.txt");
  renameFile(LittleFS, "/hello.txt", "/foo.txt");
  readFile(LittleFS, "/foo.txt");
  deleteFile(LittleFS, "/foo.txt");
  testFileIO(LittleFS, "/test.txt");
  deleteFile(LittleFS, "/test.txt");

  Serial.println("Test complete");
}

void loop() {}