大家好,我是杂烩君。
SPI Flash 读写会了,配置、小日志、网页资源往哪放?自己按扇区管偏移,加两个文件就乱。有没有轻一点的文件系统?
MCU 上这类需求很常见。FAT 能用,RAM 和代码量往往先涨一截;LittleFS 是后来社区里讨论更多的方向。再往前翻几年,很多板子——尤其 ESP8266、早期 ESP32 资料里——碰到的常常是 SPIFFS。
https://github.com/pellepl/spiffs
SPIFFS解决什么问题?
SPI NOR Flash 有几条硬约束,SPIFFS 几乎是围着它们写的:
- 只能按块(或扇区)擦除擦除把这一片全置成 1写只能把 1 拉成 0想把 0 变回 1,只能再擦
MCU 还常常没有像样的 heap。TECH_SPEC 写得很直白:不能假设目标机有堆,文件系统只能靠你交给它的 work 缓冲干活。这块缓冲固定是 2 倍逻辑页大小。
作者说思路借鉴过 YAFFS,但 YAFFS 面向 NAND、面向 RAM 更宽裕的机器。SPIFFS 更像是把「按文件名读写」这件事,裁到几 KB~几十 KB RAM 的 NOR 场景里。
它不是桌面文件系统的缩小版。更现实的切口是:你已经有一块 SPI NOR,不想自己维护扇区分配。
块、页,对象查找表
集成时要在 spiffs_config 里填几何参数,别跟芯片手册上的 Block / Sector 混着记:
phys_erase_block:芯片一次能擦多大
log_block_size:SPIFFS 自己的逻辑块,必须 ≥ 擦除块,并且对齐
log_page_size:逻辑页,块内最小数据单元
页太小,页头(大约 5~9 字节)占比高,work 缓冲也窄,扫描会多读几次 Flash。页太大,一个 1 字节文件也要占两页(一页索引 + 一页数据),小文件一多就浪费。TECH_SPEC 给了个起步公式,不是最优解:
逻辑页大小 = 逻辑块大小 / 256
逻辑块 64KB,页可以先按 256 字节试;128KB 块可以先试 512。最终还是得按文件大小和 RAM 预算拧。
每个逻辑块内部大致长这样:
开头若干页:Object Lookup(LU),没有页头,就是一串 obj_id后面才是数据页和索引页,带页头找文件时不必把块里每个页头都读一遍,先扫各块的 LU。这是用一点冗余元数据,换更少的短随机读——SPI 交易本身就有命令和地址开销。
obj_id 有几个特殊值,排障时有用:
0xFFFF:空闲页
0x0000
- :已删除(NOR 只能 1→0,删除就是把全 1 写成全 0)最高位为 1:索引页最高位为 0:数据页
一个文件在 SPIFFS 里叫 object。最小开销就是 2 页。配置如果拆成几十个几十字节的小文件,Flash 会先被索引吃掉一截。这点比 API 清单更值得提前算。
集成要点
源码可以按三层记:
spiffs_hydrogen.c:你直接调的 POSIX 味 API
spiffs_nucleus.c:查找表、页分配、对象读写
spiffs_gc.c / spiffs_cache.c / spiffs_check.c:回收、缓存、一致性检查
你要自己实现的,其实就三个 HAL 回调:read / write / erase。擦除的地址和长度必须对齐 phys_erase_block。写路径要尊重 NOR 语义,别假设能把 0 写回 1。
datasheet 上 4KB sector、64KB block 经常混着写,phys_erase_block 填错,format 时 erase 回调会直接失败,或者只擦了一半。phys_addr 必须落在块边界上。和固件、OTA 挤在同一颗 Flash 时,先把分区表算清楚再 mount,别拿应用区去覆盖引导区。
GC 不一定要你手动调。创建、写入、追加时,内部会在分配页之前检查空间。串口日志里突然卡住一下,有时就是它在搬页。
GC 和磨损
候选块打分大致是:删除页多加分,还活着的页要搬家就减分,擦得越老越倾向回收。空间已经很挤时,擦除年龄这项会先丢掉,优先腾地方。
SPIFFS_gc_quick 只擦「几乎全是删除页」的块,不搬页,也不做磨损均衡。空闲时偶尔收拾可以,别当成日常写路径上的默认动作。
README 表明:它不是实时栈。一次 write 可能很快,下一次可能跟着一轮 GC。日志、配置这种可以接受抖动的数据还行;硬实时控制环里塞 SPIFFS_write,后面排查延时会很烦。
内部会尽量留出大约 2 个空闲块。看起来 SPIFFS_info 还有空间,create 却返回 SPIFFS_ERR_FULL,有时就是这个余量,不是 API 坏了。调试阶段可以把 SPIFFS_GC_DBG 打开,串口里能看到它在打分、搬页,比猜「系统卡死」靠谱。
注意事项
没有真正的目录。tmp/config.json
是一个文件名,不是 tmp 目录下的文件。
readdir 能列文件,但别按树状路径去想。
规模不适合很大。
-
- 设计目标是常见容量的 SPI Flash。往 128MB 以上堆,LU 扫描会跟着块数涨,RAM 省下来的优势就不好看了。
不管坏块。
-
- NOR 一般也不按 NAND 那套 ECC 来设计。
掉电不是零成本。SPIFFS_info 出现 used 比 total 还大,先跑 SPIFFS_check()。更糟的情况(GC 中途反复掉电),可能得手动删文件才能缓过来。
一份配置对应一份二进制。
- 没有通吃所有几何参数的通用库。
AI 帮忙搜 API、对着 TECH_SPEC 解释结构,会快一些。页大小合不合适、HAL 擦写是否对齐、掉电后 check 修不回来,还是得实际调试确认。
有没有用过SPIFFS的朋友?欢迎评论区分享经验
295