2. 文件系统选择
文件系统选型,说白了就是给存储芯片找个合适的“管家”。LVGL要加载外部图片,必须通过文件系统来读写数据。选错了,轻则性能拉胯,重则直接跑不起来。
我这些年折腾过的方案里,FatFS、LittleFS、SPIFFS这三个最常碰面。它们各有脾气,适配LVGL的方式也不太一样。今天咱们就掰开揉碎聊清楚。
2.1 三大文件系统核心差异
先看一张对比表,心里有个底:
| 特性 | FatFS | LittleFS | SPIFFS |
|---|---|---|---|
| 适用存储 | SD卡、NAND Flash、NOR Flash | NOR Flash、SPI Flash | SPI Flash |
| 磨损均衡 | 无(需硬件或驱动层支持) | 内置 | 内置 |
| 掉电安全 | 依赖上层日志 | 强(写时复制+CRC校验) | 中等 |
| RAM占用 | 约2-4KB | 约4-8KB | 约6-10KB |
| 最大文件数 | 取决于分区大小 | 约1024个 | 受限于目录结构 |
| PC兼容性 | Windows/macOS直接读写 | 需专用工具 | 需专用工具 |
| LVGL适配难度 | 低(标准POSIX接口) | 中(需封装读写回调) | 中(需封装读写回调) |
核心结论:SD卡或大容量存储选FatFS;小容量SPI Flash且要求掉电安全选LittleFS;老项目或资源极度受限选SPIFFS。
2.2 FatFS:最通用的选择
FatFS是我用得最多的方案。为什么?因为它兼容性好,SD卡插电脑上就能读写,调试方便。
LVGL适配FatFS其实很简单。LVGL的文件系统接口是抽象好的,你只需要实现几个回调函数:open、close、read、write、seek、tell。FatFS本身就有这些函数,直接映射过去就行。
// LVGL文件系统回调注册示例
static lv_fs_res_t fs_open(lv_fs_drv_t * drv, void * file_p, const char * path, lv_fs_mode_t mode)
{
FIL * fp = (FIL *)file_p;
BYTE flags = 0;
if (mode == LV_FS_MODE_RD) flags = FA_READ;
else if (mode == LV_FS_MODE_WR) flags = FA_WRITE | FA_CREATE_ALWAYS;
else if (mode == LV_FS_MODE_RDWR) flags = FA_READ | FA_WRITE | FA_OPEN_ALWAYS;
FRESULT res = f_open(fp, path, flags);
return (res == FR_OK) ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN;
}
// 注册驱动
lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv);
fs_drv.letter = 'S'; // 盘符,图片路径如 "S:/images/bg.png"
fs_drv.open_cb = fs_open;
// ... 注册其他回调
lv_fs_drv_register(&fs_drv);
我的习惯:盘符用'S'代表SD卡,用'F'代表Flash。这样图片路径一目了然,比如 "S:/images/logo.png" 就是SD卡上的图片。
2.3 LittleFS:掉电安全的守护者
LittleFS是ARM推出的轻量级文件系统,专为Flash设计。我有个项目是做工业手持设备,设备随时可能断电。用FatFS的话,写一半断电文件就坏了。换成LittleFS后,再也没出过这种问题。
LittleFS的磨损均衡和掉电安全是硬核的。它采用写时复制(Copy-on-Write)策略,每次写入都是先写新位置,再更新元数据。即使写的过程中断电,最多丢最后一次写入,不会破坏已有数据。
适配LVGL时,LittleFS需要自己实现底层Flash读写接口:
// LittleFS配置结构体
static struct lfs_config cfg = {
.read = flash_read, // 你的Flash读函数
.prog = flash_write, // 你的Flash写函数
.erase = flash_erase, // 你的Flash擦除函数
.sync = flash_sync, // 你的Flash同步函数
.read_size = 256,
.prog_size = 256,
.block_size = 4096,
.block_count = 512, // 假设2MB Flash
.cache_size = 256,
.lookahead_size = 16,
.block_cycles = 500,
};
// 初始化LittleFS
lfs_t lfs;
lfs_mount(&lfs, &cfg);
// LVGL回调中调用LittleFS API
static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
lfs_file_t * fp = (lfs_file_t *)file_p;
lfs_ssize_t res = lfs_file_read(&lfs, fp, buf, btr);
*br = (res > 0) ? res : 0;
return (res >= 0) ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN;
}
注意:LittleFS的block_cycles参数很关键。它控制每个块被擦写多少次后触发磨损均衡。设太小会频繁搬移数据,设太大磨损均衡效果差。我一般设500-1000,具体看Flash的擦写寿命。
2.4 SPIFFS:老牌轻量级方案
SPIFFS是ESP8266/ESP32时代的老将了。它专为SPI Flash设计,代码量小,资源占用低。但说实话,现在新项目我基本不推荐它了。
为什么?SPIFFS有几个硬伤:
- 目录支持弱:SPIFFS是扁平结构,不支持子目录。所有文件都在根目录下,文件多了管理起来很痛苦。
- 掉电恢复慢:每次挂载都要扫描整个Flash,2MB的Flash挂载可能要几百毫秒。
- 文件数限制:默认配置下最多支持几十个文件,超过就报错。
不过,如果你的项目是ESP32老代码迁移,或者Flash只有1MB以下,SPIFFS还是能用的。适配方式和LittleFS类似,也是封装回调:
// SPIFFS初始化
spiffs fs;
spiffs_config cfg;
cfg.phys_size = 2 * 1024 * 1024; // 2MB
cfg.phys_addr = 0;
cfg.phys_erase_block = 4096;
cfg.log_block_size = 8192;
cfg.log_page_size = 256;
spiffs_mount(&fs, &cfg, spiffs_flash_read, spiffs_flash_write, spiffs_flash_erase);
// LVGL回调中调用SPIFFS API
static lv_fs_res_t fs_open(lv_fs_drv_t * drv, void * file_p, const char * path, lv_fs_mode_t mode)
{
spiffs_file fd = SPIFFS_open(&fs, path, SPIFFS_RDONLY, 0);
if (fd < 0) return LV_FS_RES_UNKNOWN;
*(spiffs_file *)file_p = fd;
return LV_FS_RES_OK;
}
2.5 选型决策流程
说了这么多,到底怎么选?我画了个决策图,你照着走就行:
2.6 LVGL适配的通用步骤
不管选哪个文件系统,适配LVGL的套路是一样的。我总结成四步:
- 初始化存储介质:SPI Flash初始化、SD卡SPI/SDIO初始化
- 挂载文件系统:调用文件系统的mount函数
- 实现LVGL回调:把文件系统的API映射到LVGL的
lv_fs_drv_t结构体 - 注册驱动:调用
lv_fs_drv_register()注册
注册完成后,LVGL就能通过路径访问文件了。比如:
// 加载图片
lv_img_dsc_t img;
lv_img_set_src(img_obj, "S:/images/background.bin");
// 或者用文件描述符
lv_fs_file_t file;
lv_fs_open(&file, "S:/images/icon.bin", LV_FS_MODE_RD);
避坑指南:我曾经在FatFS上踩过一个坑——文件名大小写。FatFS默认是大小写不敏感的,但LVGL的路径比较是大小写敏感的。所以图片路径一定要和实际文件名完全一致,否则加载失败。建议统一用小写命名。
2.7 性能对比实测数据
最后给一组实测数据,来自我手头一个STM32F429+W25Q64的项目。Flash 8MB,图片是320x240的RGB565位图(约150KB):
| 指标 | FatFS | LittleFS | SPIFFS |
|---|---|---|---|
| 挂载时间 | 5ms | 12ms | 180ms |
| 读取150KB图片 | 320ms | 350ms | 380ms |
| 写入150KB图片 | 410ms | 480ms | 520ms |
| 掉电后文件损坏率 | 约15% | <1% | 约5% |
| Flash寿命(模拟写入10万次) | 部分块提前报废 | 均衡良好 | 均衡一般 |
看到没?FatFS在性能上确实有优势,但掉电安全是硬伤。LittleFS牺牲了一点性能,换来了可靠性和寿命。SPIFFS嘛...挂载时间太长了,新项目真不建议。
嗯,文件系统选型这块就聊到这儿。记住一个原则:没有最好的,只有最合适的。根据你的硬件、可靠性要求和开发习惯来选,别盲目跟风。