ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MicroPython存储与文件系统底层原理:Flash、LittleFS与VFS完全指南

MicroPython存储与文件系统底层原理:Flash、LittleFS与VFS完全指南 先说个真实经历。早年刚玩 ESP8266 的时候我为了一个配置参数要把整份脚本重新烧进 Flash改一个 IP 地址都要重新擦除再上传那阵子真是改到崩溃。后来我终于摸清楚了 MicroPython 设备上的存储和文件系统机制才明白问题是出在“它到底把文件放在哪”这件事上。MicroPython 烧进板子之后你就拥有了一台带文件系统的小电脑固件占掉一部分 Flash剩下的一块 Flash 空间会被格式化成文件系统挂载成“/”目录。你可以像在电脑上一样读写文件、建目录、存日志、存配置但是这套机制的底层跟普通电脑上的 NTFS/ext4 完全不是一回事。很多新手文章会把 open()、write()、close() 讲得跟 PC 编程一样结果一断电、一擦写、一格式化就原形毕露。这篇文章我要讲的是 MicroPython 存储和文件系统的底层原理不是让你背概念而是把这些知识点揉进实际场景里Flash 是怎么被切分的、文件系统在 Flash 上是怎么存数据的、为什么经常掉电会导致文件损坏、怎么用 os.mount() 把 SD 卡或外部 SPI Flash 接入系统以及我踩过的一些坑。全程不用太高深的硬件知识跟着走一遍你会比大多数只会写 open() 的人都理解得更深。1. MicroPython 存储体系全景固件、Flash、RAM 文件系统1.1 一块 Flash 是怎么被切分的大部分常见的开发板比如 ESP32、ESP8266、RP2040、STM32F4 系列核心存储介质都是 NOR Flash只是位置不同。ESP32 的 Flash 在芯片外部通过 SPI 接口连接RP2040 的 Flash 在芯片外面QSPI 接口STM32 则很多把 Flash 做到芯片内部。MicroPython 固件烧录的时候并不是把整个 Flash 都拿来当文件系统。以 ESP32 为例Flash 顶部或底部的某个区域放着 bootloader、分区表、MicroPython 固件本体剩下的空间才会被划分出来作为“VFS 存储区”也就是我们平常在 mpremote 或 Thonny 里看到的那几个文件存放的地方。这个区域在不同固件版本里可能叫 “vfs”“spiffs”“littlefs”命名已经不重要了你只需要知道它是一块被格式化成文件系统的 Flash 分区。打个比方这就像你买了一块 4GB 的手机存储卡但系统占了 3GB剩下的 1GB 才是你能用来拍照的“用户空间”。MicroPython 设备也一样芯片标注的 Flash 大小不等于你能用的存储大小固件越大、分区越多可用空间就越小。实测一块标注 4MB Flash 的 ESP32 模块MicroPython 默认格式化后文件系统空间常见只有 1.5MB 到 2MB。1.2 RAM 盘和临时文件系统的角色除了 Flash 分区部分 MicroPython 移植版还支持 RAM 盘如 STM32 某些移植版把一部分内存当作块设备挂载后可以直接在上面读写临时文件。RAM 盘的特点是读写快、掉电即失适合保存运行时的临时数据、缓存或者需要频繁擦写的中间文件不适合存放配置和日志。在电脑上你不能把“C 盘满了”和“内存不够用”混为一谈因为两者的寻址方式、读写速度、掉电行为完全不同。MicroPython 设备上也是一样Flash 上的文件相当于持久化存储RAM 盘上的文件是临时存储。很多人把大量日志直接写到 Flash 上一天下来就可能把 2MB 空间写满时间一长 Flash 的磨损问题也会暴露出来。如果你只是暂存几个变量没必要动用文件系统用 ujson 序列化后放进 RAM 还是更合适。1.3 不同开发板的存储布局有什么差异下面是我用过的一些常见板子上的存储情况开发芯片/板卡Flash 来源典型文件系统用户可用空间特点ESP32外部 SPI NOR FlashLittleFS分区大小由固件分区表决定4MB 芯片约 1.5MBESP8266外部 SPI NOR FlashLittleFS 或 SPIFFS1MB~2MB老固件是 SPIFFSRP2040树莓派 Pico外部 QSPI NOR Flash标准 MicroPython 文件系统1MB~2MB取决于固件STM32F4 系列内部 FlashFAT/内部 Flash 文件系统空间受芯片容量限制各种开发板 SD 卡SD 卡FAT32从几十 MB 到几十 GB 都能用RP2040 和 ESP32 的另一个关键是它们都支持在运行时把外部存储设备如 SD 卡、SPI Flash 模块挂到 VFS 上这就是我们在后续章节要重点讲的 os.mount()。理解了这个机制你就不会纠结为什么板子 Flash 看起来很大但实际能用很少因为你可以用一块 SD 卡把它“扩容”成几百 MB 的文件系统。2. 文件系统的底层到底在干什么VFS、块设备和目录树2.1 VFS 是什么它解决了什么问题MicroPython 官方文档里有一个概念叫 VFSVirtual File System直译是“虚拟文件系统”。它定义了一套统一的接口不管是 Flash 上的 LittleFS、SD 卡上的 FAT还是 RAM 盘最终都通过同一套 read()、write()、open()、listdir() 来访问。这有点像是电器上的 USB 接口标准不管你插的是 U 盘、移动硬盘还是读卡器电脑只要识别出它是 USB 设备就能统一读写。VFS 让 MicroPython 也能做到“插上什么文件系统都统一处理”。当你调用 open(/sd/data.txt, w) 时路径最前面的“/sd”会告诉 VFS 去哪个挂载点找对应的块设备。VFS 的关键概念有挂载点mount point一个路径比如 “/”、“/sd”代表某个文件系统被挂载到目录树的哪个位置。块设备block device提供读、写、擦除、同步等基本操作的低层存储单元。文件系统驱动filesystem driver在块设备之上实现目录、文件、权限管理比如 LittleFS 驱动、FAT 驱动。很多教程直接教你怎么用 open()但对 VFS 这个概念一笔带过。如果不懂 VFS你在换了一块 SD 卡或者想用外部 Flash 扩展存储的时候会四处碰壁。因为多了一个设备你要做的事不是“在代码里写路径”而是先把设备挂载进 VFS 的目录树里然后才能通过路径去访问。2.2 块设备为什么是文件系统的地基文件系统最终要落在某种存储介质上而存储介质的访问方式通常被抽象成一块一块的区域而不是一个一个字节。传统机械硬盘的最小读写单位是扇区512 字节或 4096 字节Flash 甚至更特殊擦除的基本单位是“块”Block读取和编程的最小单位可能是“页”Page。MicroPython 里的块设备必须实现 readblocks()、writeblocks() 和 ioctl()。ioctl() 告诉上层文件系统这个块设备有多少块、每块多大是否支持擦除等。这很像电脑上操作系统通过磁盘驱动去读取硬盘的参数开始扇区、扇区大小、总扇区数。我在 ESP32 上用外部 SPI Flash 时第一步就是写一个块设备驱动类。刚开始我没有真正实现 ioctl() 里查询块大小的逻辑直接写死成 4096结果挂载 LittleFS 后只要写入数据就报错后来才发现是块大小和实际 Flash 参数不一致。这个问题在 PC 开发中几乎不会遇到因为你不会自己写硬盘驱动但在嵌入式开发里块设备就是文件系统的地基地基参数错了上面的大楼必塌。2.3 路径、目录和挂载点是如何关联的MicroPython 的根目录是“/”默认挂载的是板载 Flash 上的文件系统。你写 open(config.json) 其实等价于 open(/config.json)VFS 会在全局挂载表里找到“/”对应的文件系统然后在这个文件系统里查找 config.json。如果外部 SD 卡被挂在“/sd”那你访问 SD 卡上的文件就必须完整写清路径比如 open(/sd/data.csv, a)。反过来如果你把 SD 卡挂到“/”上就会把板载的文件系统“盖住”但原来的文件系统并没有被删除只是暂时无法从这个挂载点访问。这种机制听上去简单实际操作时很容易搞混我就干过把 SD 卡挂到“/” 之后以为自己把固件文件覆盖了其实是旧的 root 文件系统被隐藏了换掉挂载点马上又冒出来。3. 深入底层LittleFS 是怎么在 Flash 上存文件的3.1 为什么默认选择 LittleFS而不是 FATMicroPython 的很多移植版尤其是 ESP32、LuatOS、RP2040默认文件系统已经逐步转向 LittleFS。LittleFS 是 ARM 设计的一个专门用于嵌入式设备的文件系统设计目标非常贴合 Flash 的特性掉电安全突然断电不会导致整个文件系统崩溃。磨损均衡写入负载会尽量平均分配到 Flash 各块避免某一块被写穿。有限的 RAM/ROM 占用适合单片机上那点可怜的内存。元数据可靠性文件的大小、时间戳、名称等信息采用日志式或 COW写时复制方式保存。而 FAT 是 PC 上传统的文件系统比如 SD 卡出厂常是 FAT32。FAT 的优点是兼容性极强电脑上能直接识别但缺点是它设计给磁盘用不是给 Flash 用的。FAT 写一个文件经常要反复修改 FAT 表文件分配表和目录项每次修改都会造成 Flash 上的同一块区域被反复擦除长久下去磨损会特别不均匀很可能把小范围 Flash 提前写坏。所以 MicroPython 在设计上做了个很聪明的分层板载 Flash 一般用 LittleFSSD 卡保留 FAT各自发挥优势。如果你硬要把 SD 卡格式化成 LittleFS那性能可能不好电脑也不认这类卡。3.2 LittleFS 的“日志式”写入和掉电保护LittleFS 内部是一种 copy-on-write 的设计严格来说叫“写时复制 日志结构”的组合。你要修改一个文件的内容它不会直接把原数据覆盖掉而是在 Flash 上分配新的块写入新内容然后再更新元数据指向新的位置最后回收旧块。这么做的最大好处是哪怕你在“更新元数据”这步之前突然断电旧文件的完整内容依然还在只是新内容没写进去文件不会变成一半新一半旧的“杂交怪物”。很多新手容易把“掉电安全”理解成“我随便断电都不会丢数据”这是不对的。掉电安全指的是文件系统的结构不会崩溃不会导致目录损坏、整张卡打不开。但如果你写了个文件只关了文件没调 fsyncMicroPython 里对应 os.sync()数据可能还在内存缓冲区里这时断电这些数据就是真丢了。文件系统保护的是“结构完整性”不保证“应用层数据不丢”。我之前测试过一个日志模块掉电之后总发现最后几条日志不见了。后来我打开一个文件写满一行然后立刻断电再上电反复测了几轮确认这种“少了几条”的丢数据不是文件系统崩溃而是我根本没有调用 flush() 或 os.sync()。断电前的一瞬间数据还躺在 RAM 的缓冲区里。3.3 擦除块、页和写入放大的基本关系NOR Flash 的物理特性决定了它不能随便覆盖写已经写了 1 的区域想重新写 0必须先擦除整个块把块内的所有字节变成 0xFF也就是“1”状态然后才能编程写入。这个“先擦除再写”的过程比普通读写慢得多而且擦除次数是有限的一般几千到十万次不等。LittleFS 为了减少擦除次数会把小文件的写入尽量合并到一个块里并且在生命周期内动态移动块的位置这就是磨损均衡。听起来很高端实际效果是怎样的你在 MicroPython 里写一个 100 字节的文件底层可能并不是整个 4KB 的擦除块都被写了一遍而是 LittleFS 会挑选一个适合的块把新数据追加到块内未使用的区域并记录相应的元数据。但如果文件频繁追加你会发现写入会变得慢或出现卡顿。因为 LittleFS 一旦发现当前块的空间不够必须找到或者擦出新的块这个过程涉及元数据更新、块分配和擦除操作。我在实践里经常看见有人用文件来存传感器历史数据每秒一次 open()/write()/close()很快 Flash 空间见底、写入变慢。这种场景的正确做法其实是先把数据攒在内存列表中凑够 1KB 或 4KB 再一次写入或者用多文件轮转写减少文件系统的分配开销。4. 实操解析把存储能力用出来4.1 基础文件读取不要小看 flush() 和 sync()先看 MicroPython 里最经典的一段日志代码import os def append_log(path, line): with open(path, a) as f: f.write(line \n) f.flush() os.sync()很多人写日志时只写 open() 和 close()忘了 flush() 和 os.sync()。这两个调用有什么区别flush()把 Python 缓冲区里的数据推送到操作系统/底层文件系统。但如果文件系统本身也有缓存flush() 不一定保证数据已经落到 Flash。os.sync()同步文件系统把文件系统缓存中的数据真正写到块设备上。在 MicroPython 环境里有些移植版对 flush() 和 os.sync() 的实现并不完全一致但稳妥的做法是重要的数据写入后两者都调用。代价是每次 sync 都会引起 Flash 擦写写入速度会变慢。所以“重要程度不高”的数据比如临时缓存可以不 sync但在掉电关键场景必须 sync。我自己的习惯是配置文件这种改动频率低、但丢失后果严重的必须 sync日志文件这种高频追加的改为在 write 之后只 flush每隔 N 行或者定时调用 os.sync()平衡性能和可靠性。4.2 外部存储挂载SD 卡的完整接入方式多数开发板没有自带 SD 卡槽需要你自己接一个 SPI 模式的 SD 卡模块。在 MicroPython 中接入 SD 卡通常分三步初始化 SPI 和 SD 卡对象、检查是否已有文件系统、挂载到挂载点。import machine, os # 以 ESP32 为例SPI 引脚按你实际接线修改 spi machine.SPI(2, baudrate40000000, polarity0, phase0, sckmachine.Pin(18), mosimachine.Pin(23), misomachine.Pin(19)) cs machine.Pin(5, machine.Pin.OUT) # 旧版本使用 sd 模块新版本使用 sdcard 模块 try: import sdcard sd sdcard.SDCard(spi, cs) except ImportError: import sd sd sd.SDCard(spi, cs) # 如果 SD 卡是新的需要先格式化这是危险操作确认不要数据再执行 # os.VfsFat.mkfs(sd) os.mount(sd, /sd) print(os.listdir(/sd))这段代码里最关键的一步是 os.mount()。如果你只初始化了 SD 卡没有挂载那无论你怎么 open(/sd/a.txt) 都是找不到路径的。我见过不少人把 SD 卡模块接好后直接写 open(/sd/test.txt, w)结果报 “OSError: [Errno 2] ENOENT”问题就是少了 mount 这一步。SD 卡的默认文件系统一般是 FAT32MicroPython 能原生识别。如果你想在 SPI Flash 上使用 LittleFS则需要用 os.VfsLfs2.mkfs() 进行格式化然后再 mount。示例import os from machine import SPI, Pin # 假设外部 Flash 块设备实例为 dev # dev FlashDevice(...) # os.VfsLfs2.mkfs(dev) # 第一次使用才需要 os.mount(dev, /ext)格式化是一锤子买卖会把设备上所有旧数据清掉。新手刚拿到外部 Flash 模块如果没有老数据可以直接 mkfs但如果是买了二手模块或者反复测试过的模块mkfs 前先确认没有重要数据。4.3 用 RAM 盘做临时存储有些移植版支持 RAM 盘比如 ESP32 上你可以用 machine.RTC().memory() 保存一小块数据或者用 bytearray 自己实现一个内存块设备。不过最简单的实际用法是先把需要高频读写的临时配置或者缓冲结果放在内存里不碰文件系统等最后确认要持久化时才一次性写入 Flash。比如你要保存一组 WiFi 扫描结果import ujson, time scan_data [{ssid: A, rssi: -40}, {ssid: B, rssi: -55}] json_str ujson.dumps(scan_data) # 先放内存里等扫码完成或定期再写 buffer json_str ... # 确认写入时才调用 with open(/wifi_scan.json, w) as f: f.write(buffer) f.flush() os.sync()注意MicroPython 的字符串拼接和 list 操作都在 RAM 中进行如果数据量太大比如几十 KBESP32 的 RAM 可能扛不住。这时候还是要直接流式写入文件每攒一小段 buffer 就写入一次但不要每次 write 都 sync。4.4 配置文件的读写与默认值处理配置文件是嵌入式设备最常用的存储场景。启动时读配置没有文件则写默认值。常见做法是DEFAULT_CONFIG { ssid: mywifi, password: 12345678, interval: 30, } def load_config(path/config.json): try: with open(path, r) as f: data ujson.load(f) except (OSError, ValueError): # 文件不存在或格式损坏回退到默认配置 data {} cfg dict(DEFAULT_CONFIG) cfg.update(data) return cfg def save_config(cfg, path/config.json): with open(path, w) as f: ujson.dump(cfg, f) f.flush() os.sync()这里有两个容易出错的地方。第一如果断电导致配置文件只写了一半ujson.load() 会抛 ValueError这时程序必须能回退到默认配置否则就会启动失败。第二不要把密码之类敏感信息明文存储这只是个开发板最好加密或至少混淆不过我主要从功能完整性角度提醒一下。5. 常见问题与排查实录5.1 文件写不完断电后整个文件消失或打不开这个现象我几乎每周都能在论坛上看到。出现这种问题首先检查你是不是只调用 close()没有 flush() 或 os.sync()。close() 会关闭文件句柄但不保证把文件系统缓存刷到 Flash。其次检查代码逻辑如果写入过程中抛出异常文件是否没有正常 closeMicroPython 的 with 语句可以保证退出时 close但 close 不等于 sync这是两码事。注意重要数据写入后先 f.flush() 再 os.sync()。如果程序崩溃或断电最坏情况是丢失最近几次未同步的数据但不会导致文件系统整个挂掉。5.2 删除文件后空间没有立刻变多你可能遇到过这样的情况删掉一个大文件后调用 os.statvfs(/) 或者查看剩余空间发现空间并没有立刻完全恢复或者隔一段时间才恢复。这跟文件系统的垃圾回收和 COW 设计有关。删除一个文件实际上是把文件的数据块标记为“可回收”而不是立刻擦除。LittleFS 会在后续写入时按需回收和擦除块。所以你删除后马上看到空间不变是很正常的等下一次写入或者文件系统执行后台填充时可用空间会恢复过来。这在电脑上也很常见Windows 删除文件后磁盘空间经常不会立刻 1:1 恢复因为文件系统可能有延迟回收、卷影副本或者预分配机制。嵌入式文件系统也一样只是延迟时间可能更长。针对这一点做空间管理时不要用“删掉一个文件就重新计算空间剩余”的逻辑最好在写入前检查剩余空间而不是依赖删除后的即时恢复。5.3 大量小文件写入越来越慢如果你的应用频繁地在根目录直接写入很多小文件比如每秒往 /data 目录下新建一个 100 字节的文件文件系统会越来越慢。LittleFS 处理小文件的方式本来是为了节省空间的但如果文件数量太多元数据的查找、目录树的更新都会变慢。我的解决办法是设计一个“分区目录策略”把最高频的文件放在一个固定的轮转文件里而不是每个周期新建一个文件。例如日志文件只保留一个 data.log写满到一定大小后重命名成 data.old再新建 data.log。这样整个过程中目录项的数量基本不变写入负载也集中一些。5.4 Flash 生命周期耗尽还能救吗NOR Flash 的擦写次数有限典型寿命在 100k 次左右。如果你每秒钟都写一次文件并 sync寿命短得吓人。一些工业级模块的 Flash 可以撑更久但也不是无限的。在 MicroPython 中做高频数据记录时务必考虑把数据先缓存在 RAM再批量化写入或者使用专门的外部 SD 卡 / 外部 SPI Flash 记录数据让主控芯片自带 Flash 只保留固件和配置。SD 卡的磨损均衡通常也由卡内主控负责承载大数据写入更合适。如果必须频繁写板载 Flash建议加一个上传机制把本地 Flash 仅仅当作缓冲数据上传到服务器后就删除这样能显著延长 Flash 寿命。5.5 路径大小写和文件命名MicroPython 的 LittleFS 是大小写敏感的这一点和 Windows FAT 习惯不同。你写入一个叫 Data.txt 的文件另一个文件叫 data.txt这两个可以同时存在。新手最容易踩的坑是代码里写 open(/sd/config.txt)但实际生成的是 CONFIG.TXT结果怎么都打不开。遇到奇怪的文件找不到问题优先用 os.listdir() 打印一下目录里的真实文件名。5.6 固件更新、main.py 和文件系统被重置很多时候你改了 main.py却发现上电后运行的是旧代码。常见原因有两个一是 Thonny 之类的工具实际保存到了别的路径或者没有真正写进去二是固件更新后文件系统被重新格式化旧文件丢失。MicroPython 在烧写固件时不一定每次都会保留 VFS 分区。如果你 update 固件后发现文件全没了优先检查固件烧录工具有没有勾选“擦除 Flash”选项。为了避免 main.py 在开发中途崩溃导致设备变砖严格说是反复重启我会先删除 main.py只保留 boot.py等所有功能在脚本里稳定运行后再重新创建 main.py 设为主程序。不要在 main.py 中做危险的无限循环也不做任何异常捕获否则你连重新接入 REPL 都可能变得很麻烦。6. 一些特殊的存储玩法从源码到 schema 的扩展思路6.1 把文件系统当配置数据库用有些项目需要保存多个传感器校准参数或者一组设备配置。最简单的办法是用 JSON 文件但如果你有几十上百条记录建议按 ID 拆分文件比如 /cfg/device_001.json。这种做法的好处是写入一个文件不会影响其他文件修改某一条配置时不用把整个大文件读出来再写回去。不过文件数量太多也有目录项开销的问题。折中方案是“分桶”把 ID 换算成目录层级比如 /cfg/00/device_001.json每个目录只放少量文件避免根目录堆积大量 Term 项。6.2 启动时检查并修复文件系统MicroPython 本身一般不会像桌面系统那样提供 fsck 工具但你可以写一个启动自检逻辑import os, machine def check_fs(): try: f open(/health_check, w) f.write(ok) f.close() os.remove(/health_check) print(filesystem ok) except OSError as e: print(filesystem error:, e) # 根据具体错误决定是否格式化或使用备用方案这个“健康检查”文件的方法能快速发现文件系统是否只读或者已损坏。如果输出 OSError 且无法解决最后的手段是重新格式化整个 Flash 分区。格式化之前确认固件本身还是好的因为格式化会清空你所有脚本包括 boot.py 和 main.py如果格式化后没法写脚本进去设备就只能重新烧固件。注意不要轻易格式化。格式化前先把 boot.py/main.py 备份到电脑否则设备重新上电后可能连 REPL 都进不去通常是能进但应用功能全部消失。6.3 与 PC 端同步文件的工具链开发时经常需要在电脑和板子之间同步代码。常用的 mpremote 命令mpremote cp main.py : mpremote cp /sd/data.csv : mpremote lsmpremote 支持直接操作文件和目录比 Thonny 命令行方式更灵活。这里有一个小技巧别用 os.rename() 去覆盖正在运行的 main.py如果你在脚本运行期间想更新 main.py最好先写到 main.new.py再用 mpremote 之类的工具在复位后替换。6.4 文件系统事件的利用MicroPython 没有完整的事件通知机制但你可以利用文件是否存在作为状态机标志。比如设备开机时检测 /flag_boot 是否存在存在说明上次可能没有正常关机从而触发自检或日志检查。这是一种非常朴素的“崩溃标记”做法。import os FLAG_PATH /flag_boot if os.path.exists(FLAG_PATH): print(previous boot was not clean) # 进入安全模式或错误处理 else: with open(FLAG_PATH, w) as f: f.write(booted)这种方案简单有效但要注意Flag 文件的写入时机最好放在 boot.py 或者 main.py 最前面而且要在主要逻辑执行前完成。如果主要逻辑在写 Flag 前就崩溃了那下一次仍会认为上一次是干净启动导致漏报。7. 几个值得记住的数值和选择建议我把日常调参过程中比较有用的几个经验数值整理成表格方便大家参考参数/场景推荐值/做法理由日志写入频率每秒最多 1 次推荐 10 秒以上一次减少擦写延长 Flash 寿命单次写入数据量尽量凑到 1KB~4KB匹配 Flash 页大小减少写放大文件数量级单目录下不超过 100 个文件避免目录查找和元数据开销过大配置文件格式JSON 或简单键值文本易解析不易像二进制那样出错外部存储SD 卡 FAT32SPI Flash LittleFS各自发挥兼容性/掉电安全优势掉电保护诉求高每次写完调用 flush os.sync尽可能降低数据丢失窗口这些数值不是绝对标准具体还要看你的 Flash 芯片型号和 MicroPython 移植版本。但有一个原则可以通用离 Flash 物理擦除边界越远应用越不容易踩坑。8. 关于“存储还是不够用”的扩展思路如果你的项目对存储容量需求很大比如要保存几十 MB 的传感数据或图片板载 Flash 显然不够。我的次选方案是直接用 SD 卡SD 卡容量大、FAT32 兼容性好但要注意 SPI 模式下的写入速度通常比较慢实测大约几十 KB/s 到几百 KB/s取决于卡本身和接线质量。如果对速度要求高可以考虑 SDIO 模式或者更高速的 SPI 接口但代码复杂度和硬件连线难度都会上升。还有一个思路是把数据通过 Wi-Fi/BLE 上传到服务器或 NAS本地 Flash 只做缓冲。这类场景下Flash 上的文件系统更像是“临时缓冲区”而不是最终数据仓库。这样即使 Flash 空间被写满也不会造成不可恢复的损失。我在做一个环境监测项目时就是把数据先写到本地的 small buffer 文件每 5 分钟 FTP 上传一次上传成功后立刻删除本地文件板载 Flash 空间一直很稳定。9. 我在实际项目中的一点体会做了几年 MicroPython 开发我最大的体会是文件系统不是“用一下就行”的黑盒子它跟底层 Flash 的配合关系决定了你项目的长期稳定性。早期我只知道 open() 和 write()只要偶尔出错就怀疑是固件问题后来搞懂 VFS、块设备、LittleFS 的原理之后再遇到奇怪的问题至少知道该往哪个方向排查是挂载问题、同步问题、还是 Flash 磨损问题。如果你也是新手我建议不要一上来就去读 LittleFS 的源码先把这把“存储钥匙”装进脑子里文件系统是构建在块设备之上的块设备负责物理读、写、擦除文件系统负责目录、文件和安全。MicroPython 帮你把这两层封装成了 os.mount() 和 open()但当你遇到“文件打不开”“空间不释放”“写入变慢”这类问题时一定要回到块设备、挂载、同步和磨损这几个维度去想。这个思路一旦建立起来后面不管是玩 ESP32、RP2040 还是 STM32你都会非常从容。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进