1.4.0
A FAT filesystem on the chip's own flash — and on a USB stick
FatFS.begin(); // FAT on the internal flash partition
FatFSUSB.begin(); // ...and a PC sees it as a removable drive
SDFS.begin(); // the SD card, at the same timeDrag a file onto the drive from a PC and read it back from a sketch. There is
one copy of ChaN's FatFs now, built for two volumes, so the flash filesystem
and the SD card can be mounted together — which is what this release is for.
That differs from arduino-pico, whose FatFS is flash-only and whose SDFS
is a separate FAT implementation (SdFat) entirely. The library names match
upstream; the internals deliberately do not.
Verified on hardware: Windows enumerates the board as a 216 KB drive,
mounts it as a 198 KB FAT volume, and files copied on land in flash. Worst-case
stall inside a mass-storage write is 1.3 ms, so USB transfers absorb it.
New libraries
| FatFS | FAT on the internal flash, behind the same FS/File/Dir API as SDFS and LittleFS |
| FatFSUSB | that volume presented to a host as a USB stick |
SDFS and SD are unchanged for sketches — they simply became FatFs volume 1.
Before you use FatFS
A 256 KB minimum filesystem partition, from the Filesystem Size menu, or
board_build.filesystem_size = 256k. Below that the wear-levelling layer's
fixed reserve leaves under 90 KB, which is a legal but odd FAT12 volume and a
coin-toss on a Windows host. begin() refuses and names the setting.
It shares the partition with LittleFS, and only one filesystem can live
there. Flashing a FatFS sketch over a LittleFS one reformats it, and the
reverse is equally true — the sketch on the board decides which filesystem the
board has. If you want the flash readable from a PC, FAT is the one to pick; if
only sketches ever read it, LittleFS wears better and has no minimum size.
The sketch and a host must never both hold the volume. FatFs caches
directory and allocation sectors, so a host writing underneath that cache
corrupts one or both views silently. Watch FatFSUSB.hostChanged() and call
FatFS.end(); the USBDrive example shows the whole handover.
onPlug/onUnplug exist but are best-effort: they come from SCSI commands a
host is not obliged to send, and Windows sends neither — measured, mounting
and writing and ejecting with the callbacks silent throughout. hostChanged()
also goes true on the first host write, which no host can perform silently.
Only Windows has been tested; macOS and Linux are expected to be better
behaved, but nobody has checked.
Five bugs found porting the wear-levelling layer
All from upstream's flash translation layer writing 4096/512 as a literal
8 — right for the RP2040's 4096-byte erase blocks, wrong for this part's
8192, which hold sixteen 512-byte sectors. Four of them are silent:
- The logical-to-physical map packed a 3-bit slot index, so LBA n aliased
onto n+8: a boot sector appeared twice and a file's directory entry landed
on the allocation table. - The per-block count of live sectors was a nibble, so a full 16-slot block
counted 16, truncated to zero, and the allocator erased a block that still
held data. - Garbage collection stopped filling a reclaimed block at 8 of 16 slots,
freeing half the space it should and wearing the flash about twice as fast.
Invisible to a correctness test; now 15 writes per erase against a
theoretical 16. gcScore()went negative for a block holding more than eight sectors,
mis-ranking collection candidates.- Two loops had no bound — a corrupt block state or inconsistent free-space
accounting spun forever, which on this part wedges the debug probe rather
than merely hanging the sketch. Both now fail the write instead.
docs/hazards.md has the measurements. The regression tests are the point:
the aliasing was missed by the original tests because they used LBAs 0, 1, 17
and 100, and no two of those are eight apart.