SPIFFS_Test

はじめに

SPIFFSをテストするプログラムです。以下の関数を定義しています。

プログラム

定義等

1
2
3
4
5
6
7
#include "FS.h"
#include "SPIFFS.h"

/* You only need to format SPIFFS the first time you run a
   test or else use the SPIFFS plugin to create a partition
   https://github.com/me-no-dev/arduino-esp32fs-plugin */
#define FORMAT_SPIFFS_IF_FAILED true

FORMAT_SPIFFS_IF_FAILEDをtrueとしています。このマクロの利用方法は後述します。

listDir()

 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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();
    }
}
 

この関数では、指定したディレクトリ内のファイルを表示します。

この関数の引数は、以下の通りです。

  • fs: ファイルシステム(SPIFFS)
  • dirname: ディレクトリ名
  • levels: 表示する階層(0の場合は、指定したディレクトリだけ。1の場合は、1階層下のディレクトリまで)

open()を使って、dirnameをオープンします。オープンした結果は、rootに代入されます。

13行目のif文では、open()で返ってきた値が有効かどうかを、operator boolを使って調べています。指定したファイルパスのオープンに失敗していた場合は、ここで処理を終了します。

17行目のif文では、isDirectory()を使い、オープンしたファイルパスがディレクトリかどうかを調べています。ディレクトリでない場合は、ここで処理を終了します。

22行目では、openNextFile()を使い、ディレクトリ内のファイルパスをオープンします。

fileが有効な間、23行目から37行目のwhileループを実行します。

24行目から29行目は、ファイルパスがディレクトリだった場合の処理です。

26行目では、name()によりディレクトリ名を取得し、表示しています。

levelsが0でなければ、levelsから1を引いて、listDir(この関数)を再帰呼び出しします。

ただし、現状のESP32のSPIFFSでは、ディレクトリはサポートされていないようなので、この部分が実行されることはないのではないかと思います。SPIFFS Filesystemには、以下の記述があります。

Currently, SPIFFS does not support directories, it produces a flat structure. If SPIFFS is mounted under /spiffs, then creating a file with the path /spiffs/tmp/myfile.txt will create a file called /tmp/myfile.txt in SPIFFS, instead of myfile.txt in the directory /spiffs/tmp.

30行目から35行目は、ファイルパスがディレクトリではなかった場合の処理です。

32行目では、name()によりファイル名を、34行目では、size()を使って、ファイルサイズを取得して表示しています。

最後に、openNextFile()を使って、次のファイルを取得し、23行目に戻ります。

readFile()

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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();
}

この関数では、指定したファイルの内容を表示します。

この関数の引数は、以下の通りです。

  • fs: ファイルシステム(SPIFFS)
  • path: ファイル名

open()を使って、pathをオープンします。オープンした結果は、fileに代入されます。ファイルのオープンに失敗した場合や、pathで指定したファイルパスがディレクトリだった場合は、終了します。

available()が0になるまで、read()で1バイトずつ読み出し表示します。

最後に、close()で、ファイルパスをクローズします。

writeFile()

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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();
}

この関数では、指定したファイルにメッセージを書き込みます。

この関数の引数は、以下の通りです。

  • fs: ファイルシステム(SPIFFS)
  • path: ファイル名
  • message: ファイルに書き込むメッセージ

open()を使って、pathをオープンします。ファイルに書き込みを行うので、モードにFILE_WRITEを指定します。オープンした結果は、fileに代入されます。ファイルのオープンに失敗した場合や、pathで指定したファイルパスがファイルでなかった場合は、終了します。

Fileクラスは、Printクラスを引き継いだStreamクラスを引き継いでいるので、57行目のprint()はSerial.print()と同じと思います。

最後に、close()で、ファイルパスをクローズします。

appendFile()

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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();
}

基本的には、writeFile()と同じです。ただし、ファイルをopen()する際のモードにFILE_APPEND(追記)を指定しています。

renameFile()

88
89
90
91
92
93
94
95
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");
    }
}

rename()を使って、ファイル名を変更します。

deleteFile()

 97
 98
 99
100
101
102
103
104
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");
    }
}

remove()を使って、指定したファイルを削除します。

testFileIO()

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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 %u 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 %u ms\r\n", flen, end);
        file.close();
    } else {
        Serial.println("- failed to open file for reading");
    }
}

111行目で、pathで指定されたファイルをopenします。openに失敗したときは終了します。

119行目では、ESP32を起動してからの時間をmillis()を使って取得し、startに代入しています。

120行目から125行目のforループで、ファイルに書き込みを行います。今回は、512バイトのデータ(buf[512])を、2048回書き込みます。このため、1048576(=512*2048)バイトを書き込むことになります。書き込みには、write()を使用します。

このループの中では、ループ変数iを2進数で表現したときに、下5桁が全部1(16進数で1F)になった時(=32回に1回)に、Serial.print()を使って、".“を表示します。

ファイルを書き終わると、書き終わった時刻を取得して、書き込み前に取得しておいた自国の差分を計算し、書き込みに要した時間を表示します。私の環境では、73888ミリ秒でした。

その後、close()を使って、ファイルをクローズします。

131行目からは、今書いたファイルからデータを読み出すテストです。136行目で、size()を使ってファイルサイズを取得しています。このプログラムでは、flenをファイル全体のサイズ、lenをまだ読み取っていないサイズ(今後読み取る必要のあるサイズ)としています。

140行目から150行目のwhileループで、ファイルを読み出します。

toReadに、まだ読み取っていないサイズであるlenを代入した後、toReadが512を超える場合は、toReadを512に設定します。これにより、一度に読み取るサイズを、buf[]のサイズである512バイトを超えないようにしています。

read()で、toReadバイトのデータを、bufに読み取ります。その後、lenからいま読み取ったtoReadを減算し、残りのサイズを計算しています。

最後に、所要時間を計算して、表示しています。私の環境では、1024ミリ秒でした。

setup()

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
void setup(){
    Serial.begin(115200);
    if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
        Serial.println("SPIFFS Mount Failed");
        return;
    }
    
    listDir(SPIFFS, "/", 0);
    writeFile(SPIFFS, "/hello.txt", "Hello ");
    appendFile(SPIFFS, "/hello.txt", "World!\r\n");
    readFile(SPIFFS, "/hello.txt");
    renameFile(SPIFFS, "/hello.txt", "/foo.txt");
    readFile(SPIFFS, "/foo.txt");
    deleteFile(SPIFFS, "/foo.txt");
    testFileIO(SPIFFS, "/test.txt");
    deleteFile(SPIFFS, "/test.txt");
    Serial.println( "Test complete" );
}
 

Serial.begin()を使って、シリアル通信の初期化を行います。

次に、SPIFFS::begin()を使って、SPIFFSを初期化します。SPIFFSは、SPIFFSライブラリにより事前定義された変数です。第1引数に、FORMAT_SPIFFS_IF_FAILED(実際の値はtrue)を指定しているので、失敗した場合はSPIFFSのフォーマットを行います。それでも失敗した場合は、プログラムを終了します。

その後は、以下の関数を順に呼び出します。

最後に、Serial.println()を使って、“Test complete"と表示します。

loop()

179
180
181
void loop() {

}

何もしません。

バージョン

Hardware:ESP-WROOM-32
Software:Arduino core for the ESP32 2.0.4

最終更新日

September 4, 2022

inserted by FC2 system