You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: add a LittleFS FsImpl backend — on-chip flash storage is unreachable today #3984
FastLED has no LittleFS (or SPIFFS) filesystem backend. fl::FileSystem can only reach an SD card over SPI, the WASM browser VFS, or a host stub — so on-chip flash storage is unreachable from FastLED code.
Verified current state
FsImpl has exactly three implementations:
Backend
File
Arduino SD over SPI
src/platforms/fs_sdcard_arduino.hpp:200
WASM browser VFS
src/platforms/wasm/fs_wasm.cpp.hpp:358
Host stub
src/platforms/stub/fs_stub.cpp.hpp:28
The only mention of LittleFS in the tree is aspirational — src/fl/stl/detail/file_io.h:19: "Embedded (future): Can map to LittleFS, SPIFFS, SD card APIs, etc."
Consequence: any sketch needing on-device file storage must use a physical SD card. examples/Fx/FxLedmapper32x32/FxLedmapper32x32.ino:64 calls filesystem.beginSd(CHIP_SELECT_PIN) for exactly this reason — it is the only hardware option available.
Why this is a clean drop-in
FileSystem::begin(FsImplPtr) (src/fl/system/file_system.h:55) already accepts an arbitrary backend, alongside the SD-specific beginSd(int cs_pin). A LittleFS FsImpl needs no core API change — it implements the existing virtual interface and is passed to begin().
Scope is contained: implement FsImpl's virtuals over the platform LittleFS API, plus a make_littlefs_filesystem() factory mirroring make_sdcard_filesystem(int cs_pin).
Platform coverage to decide
Platform
LittleFS availability
ESP32 (Arduino-ESP32)
LittleFS.h in core
ESP8266
LittleFS.h in core (SPIFFS deprecated)
RP2040/RP2350 (arduino-pico)
LittleFS.h in core
Teensy 4.x
LittleFS_Program library — program flash, wiped on re-upload
AVR
none
The Teensy wipe-on-upload behavior is a genuine semantic difference worth surfacing to users rather than papering over.
Why now
This is the blocker for FastLED/fbuild's filesystem-image work: a buildfs-generated LittleFS image would be unreadable by FastLED without this backend. The two should land together, or buildfs ships pointing at an API gap.
Related
FastLED/fbuild — buildfs/uploadfs (linked in the meta issue)
I started this — FsLittleFs : FsImpl over the Arduino <LittleFS.h> API, mirroring fl/system/sd/, with a make_littlefs_filesystem(bool format_on_fail) factory and its own translation unit so the linker can dead-strip it the same way the SD chain is.
The natural next step was FileSystem::beginLittleFs() / FileSystem::littleFs() alongside beginSd() / sd() — and that is what surfaced the problem. Adding a method per backend widens a class that is already carrying a method per format (openVideo, loadJpeg, openMp3, readJson, readScreenMap, loadFled), in a header that re-exports video.h and jpeg.h to every consumer.
I backed the work out rather than leave a half-built backend in the tree or commit to a shape that #4003 may change. The generic seam (FileSystem::begin(FsImplPtr) plus a free factory) is probably the right minimal answer and needs no new class methods at all — but that should follow the audit rather than pre-empt it.
Implementation notes worth keeping for whoever picks this up:
FsImpl is a small interface — begin(), end(), openRead(), optional ls() — so the backend itself is straightforward.
The filebuf handle can be nearly identical to SDFileHandle; the LittleFS File API matches Arduino SD's (available(), size(), read(), seek(), position(), close()).
LittleFS.begin() differs by core: ESP32 takes (formatOnFail, basePath, maxOpenFiles, partitionLabel), while ESP8266 and RP2040 expose a no-argument begin(). Defaulting format_on_fail to false matters — silently reformatting flash loses whatever was there.
Guard on FL_HAS_INCLUDE(<LittleFS.h>) && FL_HAS_INCLUDE(<FS.h>), matching how the SD backend probes for <SD.h> / <fs.h> / <SPI.h>.
FastLED can now reach on-chip flash storage. FsImpl previously had three implementations — Arduino SD over SPI, the WASM browser VFS, and a host stub — so a sketch wanting files on an ESP32 needed a physical SD card.
fl::FileSystem fs;
if (fs.begin(fl::getEmbeddedFs())) {
fl::ifstream f = fs.openRead("data/frame.rgb");
}
What landed
fl/fs/embedded/embedded_fs.h fl::getEmbeddedFs()
fl/fs/embedded/embedded_fs.cpp.hpp body
platforms/embedded_fs.h dispatch
platforms/esp/fs/embedded_fs_esp.hpp LittleFS (ESP32 / ESP8266)
platforms/shared/embedded_fs_noop.hpp null elsewhere
fl/build/fl.fs.embedded+.cpp own translation unit
Two design corrections along the way
The API names the medium, not the driver. The first version exposed make_littlefs_filesystem(). That forces every platform — including the host stub, which just maps to a directory on disk — to hold an opinion about LittleFS, and ESP32 can back flash with LittleFS, SPIFFS or FatFS anyway. getEmbeddedFs() matches make_sdcard_filesystem(), which likewise is not called make_sdfat_filesystem(). Recorded in #4007.
Entry point and backend share a translation unit. An intermediate version had the backend reached through platforms/**/_build.cpp.hpp, which lands it in platforms+.cpp — an object every sketch links, so the LittleFS chain could no longer be dropped. The final shape follows fl/system/delay.h: API header, matching .cpp.hpp, and a platforms/*.h dispatch header selecting a plain-header fragment, all composing into one TU. That preserves the tree-shaking fl.fs.sd+.cpp relies on (#2773 item 1.2).
Verification
A green ESP32 build proves nothing here, because the no-op fragment compiles identically — three green builds were consistent with the backend never being compiled at all. Putting #error in the LittleFS branch made esp32devfail, which is the actual proof the real backend compiles against arduino-esp32. Every occurrence traced to a single include chain through fl.fs.embedded+.cpp, confirming the backend stays inside the tree-shakable TU.
With the probe removed, bash compile esp32dev --examples Blink succeeds. Four host tests cover the null contract — the factory links with no backend present, returns null, begin() reports failure rather than crashing, and a failed mount leaves the FileSystem safe to call.
Not included
format_on_fail is honored on ESP32 and ignored on ESP8266, which has no such hook. RP2040 ships LittleFS and should gain a fragment; Teensy's LittleFS_Program is wiped on re-upload and deserves its own decision — both noted in #4007.
This unblocks FastLED/fbuild#1358 (buildfs/uploadfs), which was explicitly waiting on a backend so a generated image would be readable.
Activity
zackees commentedon Aug 22, 2026
Tracked in the meta issue: #3986 — remote assets: hosting, build-time resolution, and on-device filesystem deployment.
zackees commentedon Aug 23, 2026
Blocked pending #4003.
I started this —
FsLittleFs : FsImplover the Arduino<LittleFS.h>API, mirroringfl/system/sd/, with amake_littlefs_filesystem(bool format_on_fail)factory and its own translation unit so the linker can dead-strip it the same way the SD chain is.The natural next step was
FileSystem::beginLittleFs()/FileSystem::littleFs()alongsidebeginSd()/sd()— and that is what surfaced the problem. Adding a method per backend widens a class that is already carrying a method per format (openVideo,loadJpeg,openMp3,readJson,readScreenMap,loadFled), in a header that re-exportsvideo.handjpeg.hto every consumer.I backed the work out rather than leave a half-built backend in the tree or commit to a shape that #4003 may change. The generic seam (
FileSystem::begin(FsImplPtr)plus a free factory) is probably the right minimal answer and needs no new class methods at all — but that should follow the audit rather than pre-empt it.Implementation notes worth keeping for whoever picks this up:
FsImplis a small interface —begin(),end(),openRead(), optionalls()— so the backend itself is straightforward.filebufhandle can be nearly identical toSDFileHandle; the LittleFSFileAPI matches Arduino SD's (available(),size(),read(),seek(),position(),close()).LittleFS.begin()differs by core: ESP32 takes(formatOnFail, basePath, maxOpenFiles, partitionLabel), while ESP8266 and RP2040 expose a no-argumentbegin(). Defaultingformat_on_failto false matters — silently reformatting flash loses whatever was there.FL_HAS_INCLUDE(<LittleFS.h>) && FL_HAS_INCLUDE(<FS.h>), matching how the SD backend probes for<SD.h>/<fs.h>/<SPI.h>.fl/system/sd/file_system_sd.cpp.hpp(FastLED Meta: ESP32-S3 binary-size reduction — Batch 1 (~26 KB) + Batch 2 (~26–50 KB) (supersedes #2771) #2773 item 1.2) — that is a hard constraint, not a nicety.Settling #4003 first means this lands in the intended structure. The implementation is small; the interface question is the real decision.
refactor(fs): expose embedded storage generically as fl::getEmbeddedFs()
refactor(fs): expose embedded storage generically as fl::getEmbeddedF…
zackees commentedon Aug 23, 2026
Resolved — merged in #4006
FastLED can now reach on-chip flash storage.
FsImplpreviously had three implementations — Arduino SD over SPI, the WASM browser VFS, and a host stub — so a sketch wanting files on an ESP32 needed a physical SD card.What landed
Two design corrections along the way
The API names the medium, not the driver. The first version exposed
make_littlefs_filesystem(). That forces every platform — including the host stub, which just maps to a directory on disk — to hold an opinion about LittleFS, and ESP32 can back flash with LittleFS, SPIFFS or FatFS anyway.getEmbeddedFs()matchesmake_sdcard_filesystem(), which likewise is not calledmake_sdfat_filesystem(). Recorded in #4007.Entry point and backend share a translation unit. An intermediate version had the backend reached through
platforms/**/_build.cpp.hpp, which lands it inplatforms+.cpp— an object every sketch links, so the LittleFS chain could no longer be dropped. The final shape followsfl/system/delay.h: API header, matching.cpp.hpp, and aplatforms/*.hdispatch header selecting a plain-header fragment, all composing into one TU. That preserves the tree-shakingfl.fs.sd+.cpprelies on (#2773 item 1.2).Verification
A green ESP32 build proves nothing here, because the no-op fragment compiles identically — three green builds were consistent with the backend never being compiled at all. Putting
#errorin the LittleFS branch madeesp32devfail, which is the actual proof the real backend compiles against arduino-esp32. Every occurrence traced to a single include chain throughfl.fs.embedded+.cpp, confirming the backend stays inside the tree-shakable TU.With the probe removed,
bash compile esp32dev --examples Blinksucceeds. Four host tests cover the null contract — the factory links with no backend present, returns null,begin()reports failure rather than crashing, and a failed mount leaves theFileSystemsafe to call.Not included
format_on_failis honored on ESP32 and ignored on ESP8266, which has no such hook. RP2040 ships LittleFS and should gain a fragment; Teensy'sLittleFS_Programis wiped on re-upload and deserves its own decision — both noted in #4007.This unblocks FastLED/fbuild#1358 (
buildfs/uploadfs), which was explicitly waiting on a backend so a generated image would be readable.