1. 课程导论

做嵌入式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 内存模型与图片解码流程 LVGL 内存分区 动态内存 (lv_mem) 控件、样式、临时缓冲区 帧缓冲 (Display Buffer) 1~3个全屏缓冲区 图片缓存 (Image Cache) 解码后的图片数据 外部存储 (Flash/SD) 图片解码流程 ① 从外部存储读取原始数据 ② 解码器解析格式 (PNG/JPEG/BMP) ③ 解码为原始像素数据 (RGB565) ④ 存入图片缓存 (Cache) ⑤ 渲染到帧缓冲 → 显示 关键:解码后的图片数据会占用大量内存

这张图里,我特别想强调三个区域:

  • 动态内存:LVGL所有控件、样式、动画都在这里分配。默认大小通常只有几KB到几十KB。
  • 帧缓冲:这是显示用的。一个800x480的RGB565帧缓冲就要768KB。如果你用双缓冲,直接翻倍到1.5MB。
  • 图片缓存:这是最容易爆掉的地方。一张解码后的全屏图片就是768KB,如果缓存里存了3张,2MB就没了。
核心观点: 外部存储加载图片的本质,就是用时间换空间——把图片数据存在外部,需要时才读到内存。但这个过程如果没优化好,就会变成又慢又卡。

1.3 图片解码流程——一张图从文件到屏幕

你想想看,一张PNG图片存在SD卡里,到最终显示在屏幕上,中间经历了什么?

我拆解一下:

  1. 读取原始数据:从外部存储(SD卡/Flash)读取PNG文件的二进制数据。这一步受限于存储介质的读取速度。
  2. 格式解码:LVGL调用对应的解码器(PNG解码器、JPEG解码器等),把压缩格式解压成原始像素数据。这一步最吃CPU和内存。
  3. 缓存管理:解码后的数据存入图片缓存。如果缓存满了,旧的图片会被淘汰。
  4. 渲染显示: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。这差距是几百倍。所以,提升缓存命中率是性能优化的第一要务。

注意: 很多开发者只关注解码速度,却忽略了缓存策略。我曾经见过一个项目,把缓存设得特别小,结果每次滑动列表都要重新解码图片,帧率直接掉到个位数。后来把缓存加大到能存下当前页所有图片,帧率就稳定在30fps了。

1.5 本课程要解决什么问题?

说白了,这门课就是帮你搞定三件事:

  • 怎么让图片从外部存储加载得更快——文件系统选择、读取策略、DMA加速
  • 怎么让缓存命中率更高——缓存大小设置、淘汰算法、预加载策略
  • 怎么让内存占用更少——图片压缩、分块加载、内存池优化

我会用实际项目中的案例,一步步带你分析问题、定位瓶颈、给出解决方案。每个知识点我都会结合我踩过的坑来讲,希望能帮你少走弯路。

嗯,导论就到这里。接下来我们进入正题,先从外部存储的硬件选型与文件系统开始。