Filesystem on flash
There is a small disk inside the chip, and it behaves nothing like a disk. Files round up to 4 kB blocks, a reflash can wipe it, and the write that fails returns zero instead of raising anything.
A disk that charges by the block
The chip's flash has a region set aside for files. It is around 1.4 MB on a standard 4 MB board, and it is handed out in 4 kB blocks — so a 40-byte reading saved as its own file costs 4 kB, not 40 bytes.
LittleFS, not SPIFFS
SPIFFS is the older one, it has no directories, and it degrades badly when full. LittleFS is what to use for anything new: same API, real directories, and it survives power loss mid-write, which SPIFFS does not.
What belongs here, and what does not
Good: a web page and its CSS, a configuration file, a certificate, a calibration table. Things written rarely and read often.
Bad: a data logger sampling every second, anything larger than a megabyte, anything you would be upset to lose. Flash wears out at around 100,000 erases per block, and a reflash with a different partition scheme erases the lot. For data that matters, use a microSD card.
The code
Mount, append, read back. The two lines people leave out are the format-on-failure flag and the check on the write, and both of them are the difference between a logger and a device that has been silently discarding data.
#include <LittleFS.h>
void setup() {
Serial.begin(115200);
if (!LittleFS.begin(true)) { // true = format if unmountable
Serial.println("no filesystem");
return;
}
File f = LittleFS.open("/log.csv", FILE_APPEND);
if (!f) { Serial.println("open failed"); return; }
size_t n = f.printf("%lu,%.2f\n", millis(), 21.4);
if (n == 0) Serial.println("write failed - probably full");
f.close();
Serial.printf("%u of %u bytes used\n",
LittleFS.usedBytes(), LittleFS.totalBytes());
}
void loop() {}LittleFS.begin(true) formats the partition if it will not mount. Useful on a new board, dangerous later — it is how a firmware update quietly erases a customer's data.
MicroPython mounts a filesystem for you at boot — the files you upload live on it, so you are already using it. Writing to it is ordinary Python.
import os, time
with open('log.csv', 'a') as f:
f.write('{},{:.2f}\n'.format(time.ticks_ms(), 21.4))
st = os.statvfs('/')
free = st[0] * st[3]
print(free, 'bytes free')os.statvfs gives you free space. Check it before a long run rather than after, because a full filesystem also stops you uploading the fix.
When it does not work
The partition table moved. Changing the partition scheme in the IDE moves every boundary, and the filesystem region ends up somewhere else. Keep the scheme fixed for the life of a project.
The partition is full. file.print returns the number of bytes written and everyone ignores it. Check usedBytes against totalBytes before a write, and check the return after.
That is SPIFFS above about 80% full. It has no real garbage collection. LittleFS is the drop-in replacement and it also gives you directories.
Not a bug. Every file occupies at least one 4 kB block. Append to one file rather than writing a file per reading.
Mounting it is half the job. Getting your own files in there is a second, separate upload to a second, separate partition.
Uploading files to LittleFS →Edit this page — content/esp32/filesystem-on-flash.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.