1、课程导论:为什么需要外部存储加载图片?LVGL内存模型与图片解码流程概览
做嵌入式UI开发的朋友,迟早会遇到一个问题:图片多了怎么办?
我刚开始用LVGL做项目时,觉得一切都很美好。直到有一次,客户要求在屏幕上显示几十张产品图,每张都是800x480的全屏照片。板子上的Flash只有16MB,算下来连10张都存不下。那时候我才意识到——内部存储永远不够用。
说白了,这就是我们这门课要解决的核心矛盾:嵌入式设备的存储资源有限,但UI对图片的需求却在无限增长。
1.1 为什么非要用外部存储?
先看一组数据。我随便列几个常见场景:
| 场景 | 单张图片大小(RGB565) | 100张所需空间 | 常见内部Flash |
|---|---|---|---|
| 480x272 全屏 | ~254 KB | ~25 MB | 8~16 MB |
| 800x480 全屏 | ~768 KB | ~75 MB | 8~16 MB |
| 1024x600 全屏 | ~1.2 MB | ~120 MB | 8~16 MB |
看到了吧?内部Flash连10张都塞不下。更别说你还得放代码、字库、配置文件。
所以,外部存储不是可选项,而是必选项。常见的方案有:
- SPI Flash / NOR Flash:容量16MB~128MB,速度尚可
- SD卡 / eMMC:容量GB级别,但体积大、功耗高
- NAND Flash:容量大,但需要坏块管理
- QSPI Flash:速度快,适合内存映射
我个人习惯用QSPI Flash + 内存映射方式。为什么呢?因为这样LVGL可以直接把外部Flash当成内存来读,省去文件系统的开销。不过,每种方案都有坑,后面章节我会一个个讲。
1.2 LVGL的内存模型——你得知道它怎么花钱
LVGL的内存管理,说白了就是两件事:谁在用内存?用了多少?
我画了一张图,帮你快速理解LVGL的内存布局:
这张图里,我特别想强调三个区域:
- 动态内存:LVGL所有控件、样式、动画都在这里分配。默认大小通常只有几KB到几十KB。
- 帧缓冲:这是显示用的。一个800x480的RGB565帧缓冲就要768KB。如果你用双缓冲,直接翻倍到1.5MB。
- 图片缓存:这是最容易爆掉的地方。一张解码后的全屏图片就是768KB,如果缓存里存了3张,2MB就没了。
1.3 图片解码流程——一张图从文件到屏幕
你想想看,一张PNG图片存在SD卡里,到最终显示在屏幕上,中间经历了什么?
我拆解一下:
- 读取原始数据:从外部存储(SD卡/Flash)读取PNG文件的二进制数据。这一步受限于存储介质的读取速度。
- 格式解码:LVGL调用对应的解码器(PNG解码器、JPEG解码器等),把压缩格式解压成原始像素数据。这一步最吃CPU和内存。
- 缓存管理:解码后的数据存入图片缓存。如果缓存满了,旧的图片会被淘汰。
- 渲染显示:LVGL从缓存中取出像素数据,绘制到帧缓冲中,最终显示在屏幕上。
这里有个坑,我踩过好几次——解码过程是同步的。也就是说,如果一张大图解码需要200ms,那这200ms里UI是卡住的。用户滑动列表时突然卡一下,体验就很糟糕。
1.4 缓存命中率——性能的关键指标
说到缓存,就不得不提命中率。这个概念其实很简单:
缓存命中率 = 缓存命中的次数 / 总访问次数
举个例子。你的UI上有10张图片轮播,如果缓存能存下10张,那命中率就是100%。但如果缓存只能存3张,那每次切换图片时,有7次都要重新从外部存储读取并解码——这就是缓存未命中。
未命中的代价有多大?我算一笔账:
| 操作 | 耗时(典型值) | 说明 |
|---|---|---|
| 缓存命中 | < 1 ms | 直接从内存拷贝数据 |
| 从SPI Flash读取+解码 | 50~200 ms | 取决于图片大小和格式 |
| 从SD卡读取+解码 | 100~500 ms | SD卡IO延迟较大 |
看到了吧?命中一次不到1ms,未命中一次可能要几百ms。这差距是几百倍。所以,提升缓存命中率是性能优化的第一要务。
1.5 本课程要解决什么问题?
说白了,这门课就是帮你搞定三件事:
- 怎么让图片从外部存储加载得更快——文件系统选择、读取策略、DMA加速
- 怎么让缓存命中率更高——缓存大小设置、淘汰算法、预加载策略
- 怎么让内存占用更少——图片压缩、分块加载、内存池优化
我会用实际项目中的案例,一步步带你分析问题、定位瓶颈、给出解决方案。每个知识点我都会结合我踩过的坑来讲,希望能帮你少走弯路。
嗯,导论就到这里。接下来我们进入正题,先从外部存储的硬件选型与文件系统开始。