1. 课程导学:为什么需要外部存储?LVGL图片加载机制概述
大家好,欢迎来到这门课。我是你们的老朋友,一个在嵌入式UI和底层驱动之间来回折腾了十来年的工程师。
咱们这门课叫《LVGL外部存储加载图片:SPI Flash驱动封装与读写》。名字挺长,但核心就一件事——教你怎么让LVGL流畅地显示存在外部Flash里的图片。
你可能会问:图片直接放MCU内部Flash不就行了?干嘛要折腾外部存储?
嗯,这个问题问得好。我刚开始做LVGL项目时也这么想,直到被现实狠狠教育了一回。
1.1 为什么需要外部存储?
咱们先算一笔账。假设你做一个智能家居面板,屏幕分辨率是480×320,用RGB565格式(每个像素2字节)。
| 图片类型 | 单张大小 | 10张图片 | 50张图片 |
|---|---|---|---|
| 480×320 RGB565 | 300 KB | 3 MB | 15 MB |
| 800×480 RGB565 | 750 KB | 7.5 MB | 37.5 MB |
| 1024×600 RGB565 | 1.2 MB | 12 MB | 60 MB |
你看,主流MCU内部Flash一般就512KB到2MB。放个Bootloader、放个RTOS、放个LVGL库,再放点字体文件……剩下的空间,塞几张图就满了。
更致命的是:内部Flash是用来放代码的。你把图片资源塞进去,以后OTA升级怎么办?代码和资源混在一起,每次改个图标都得重新烧录整个固件。我在一个量产项目上吃过这个亏——客户要换三张启动图,结果得发新版固件,折腾了整整两周。
所以,外部存储几乎是必然选择。常见方案有几种:
- SPI Flash:成本低、容量大(16MB~128MB)、接口简单,适合存图片和字库
- SD卡:容量更大,但体积大、功耗高,不适合小设备
- NAND Flash:容量超大,但需要坏块管理,驱动复杂
我个人最常用的是SPI Flash。为什么?因为它在成本、容量、驱动复杂度之间取得了很好的平衡。咱们这门课就围绕它展开。
核心结论:外部存储解决三个问题——容量不够、代码资源分离、方便更新。SPI Flash是嵌入式UI场景下性价比最高的选择。
1.2 LVGL图片加载机制概述
好,现在你知道为什么需要外部存储了。但光有硬件不行,你得让LVGL能从外部存储里把图片读出来、显示出来。
LVGL加载图片,说白了就两条路:
- 内部加载:图片数据直接编译进固件,存在内部Flash或RAM里
- 外部加载:图片数据存在外部存储设备上,运行时动态读取
内部加载很简单,用lv_img_dsc_t结构体把图片数据包起来,直接传给lv_img_set_src()就行。但缺点也很明显——占内部Flash空间。
外部加载就复杂一些。LVGL本身不直接操作硬件,它通过一个叫文件系统接口的抽象层来读写外部存储。这个接口长这样:
// LVGL文件系统驱动结构体
typedef struct _lv_fs_drv_t {
char letter; // 驱动器盘符,如'F'
uint16_t cache_size; // 缓存大小
bool (*ready_cb)(void); // 设备是否就绪
void *(*open_cb)(void *file, const char *path, lv_fs_mode_t mode);
lv_fs_res_t (*close_cb)(void *file);
lv_fs_res_t (*read_cb)(void *file, void *buf, uint32_t size, uint32_t *bytes_read);
lv_fs_res_t (*seek_cb)(void *file, uint32_t pos, lv_fs_whence_t whence);
// ... 还有其他回调
} lv_fs_drv_t;
你只需要实现这几个回调函数,LVGL就能通过"F:/images/bg.bin"这样的路径来访问外部存储里的文件。
但这里有个坑——性能问题。SPI Flash的读取速度一般在10~50MB/s,看起来不慢,但LVGL刷新一帧画面可能需要读取几百KB甚至几MB的数据。如果每次读取都走文件系统、经过层层封装,帧率会惨不忍睹。
我曾经在一个项目上直接用标准文件系统接口加载图片,结果刷新一帧花了800多毫秒。用户点一下按钮,等将近1秒才看到画面变化……那体验,别提了。
所以,真正工程化的做法是:
- 底层:封装SPI Flash的读写驱动,保证硬件层面的吞吐量
- 中间层:实现LVGL文件系统接口,让LVGL能识别外部存储
- 上层:使用LVGL的图片缓存机制(
lv_img_cache),减少重复读取
这三层缺一不可。咱们这门课就是带你一步步把这三层都搭起来。
1.3 本章知识体系
为了让你对整个课程结构有个清晰的认识,我画了一张图:
从这张图你能看到,整个课程是自底向上搭建的。咱们不会一上来就讲LVGL怎么用,而是先从最底层的SPI Flash驱动开始,一步步往上封装。
1.4 你需要准备什么?
在正式开始之前,我建议你准备好以下东西:
| 类别 | 具体内容 | 备注 |
|---|---|---|
| 硬件 | STM32F4/F7/H7 开发板(或其他带SPI接口的MCU) | 我课程里用STM32F429演示 |
| 硬件 | W25Q64 或 W25Q128 SPI Flash模块 | 淘宝几块钱一个 |
| 硬件 | 带屏幕的LVGL开发板(480×320以上) | 没有的话用模拟器也行 |
| 软件 | Keil MDK 或 STM32CubeIDE | 看你习惯 |
| 软件 | LVGL 8.3 或更高版本源码 | 建议用v8.3,稳定 |
| 工具 | 逻辑分析仪或示波器(可选) | 调试SPI时序用 |
个人建议:如果你手头没有现成的硬件,可以先在PC上用LVGL的模拟器(SDL或Windows模拟器)跑通文件系统接口。等逻辑通了再移植到板子上。我刚开始学的时候就这么干,省了不少调试时间。
1.5 避坑指南
最后,分享几个我踩过的坑,你提前知道,能少走弯路:
- SPI时钟不要拉太高:很多Flash芯片标称支持104MHz,但实际布线不好、线太长,跑高频会丢数据。我习惯先跑20MHz,稳定了再往上提。
- 注意Flash的页大小和扇区大小:W25Q64每页256字节,每扇区4KB。写数据前必须先擦除,擦除以扇区为单位。这个特性会直接影响你的缓存策略。
- LVGL的图片缓存要开:默认情况下LVGL每次显示图片都会重新读取。打开
LV_IMG_CACHE_DEF_SIZE,设置一个合适的缓存大小(比如16~32KB),能显著提升性能。 - 文件系统选型要慎重:如果只是存图片,用raw分区(直接按地址读写)比用FATFS更高效。FATFS有文件分配表、目录项等开销,在小容量Flash上不划算。