1、缓存基础概念:什么是图像缓存,为什么需要缓存,缓存的核心指标

各位同学,咱们今天聊点实在的。图像缓存,说白了就是一块专门用来暂存图像数据的内存区域。你想想看,LVGL 在屏幕上画一张图,总不能每次都从 Flash 或者 SD 卡里现读吧?那速度慢得你根本没法忍。

我刚开始用 LVGL 做项目时,就犯过这个错。一个 320x240 的 RGB565 图片,没开缓存,结果界面切换卡得像幻灯片。后来加了缓存,丝般顺滑。嗯,这就是缓存的魅力。

1.1 什么是图像缓存

图像缓存,本质上是一块预先分配好的 RAM 区域。LVGL 把解码后的图像数据暂存在这里,下次再显示同一张图时,直接从缓存里拿,不用重新解码。

举个例子:你有一张 PNG 图标,原始大小可能只有 2KB。但解码成 RGB565 后,它占 320×240×2 = 150KB。每次显示都解码一遍?太浪费了。缓存就是用来存这个解码后的“成品”。

核心要点:缓存存的是“解码后”的像素数据,不是原始文件。原始文件在 Flash 里,缓存里的数据是 CPU/GPU 可以直接拿去刷屏的。

1.2 为什么需要缓存

这个问题我问过不少初学者。答案其实就三个字:快、省、稳。

  • 快:从 RAM 读数据比从 Flash/外部存储快 10~100 倍。LVGL 的刷新率全靠这个。
  • 省:减少重复解码,降低 CPU 占用。尤其对于 JPEG/PNG 这种解码开销大的格式,缓存能省下大量算力。
  • 稳:避免频繁访问外部存储导致的延迟抖动。我在一个项目里遇到过,没缓存时,图片切换偶尔会卡半秒,加了缓存后帧率稳如老狗。

我的经验:在 STM32F429 这类不带硬件 JPEG 解码的 MCU 上,一张 800x480 的 JPEG 图片解码需要 200ms 左右。如果每帧都解码,帧率只有 5fps。加上缓存后,直接飙到 30fps。差距就是这么大。

1.3 缓存的核心指标

做嵌入式的人都知道,资源是有限的。缓存不是越大越好,关键看三个指标:命中率、延迟、容量。

指标 定义 对 LVGL 的影响
命中率 缓存命中的次数 / 总访问次数 命中率越高,平均加载时间越短
延迟 从发起请求到获取数据的时间 影响界面响应速度,尤其是动画场景
容量 缓存能存储的最大数据量 决定能缓存多少张图片,影响命中率

命中率

命中率是缓存最核心的指标。说白了,就是“我要的图在不在缓存里”。如果命中率 90%,那平均加载时间就是:0.9 × 缓存延迟 + 0.1 × 原始加载延迟。

我曾经在一个智能家居面板项目里,把缓存命中率从 60% 优化到 95%,界面切换速度提升了 3 倍。怎么优化的?其实就是把最常用的 5 张背景图固定缓存,不让它们被踢出去。

注意:命中率不是越高越好。为了追求 100% 命中率而分配超大缓存,可能反而因为内存不足导致系统不稳定。我见过有人给 512KB RAM 的芯片分配了 400KB 缓存,结果其他任务 OOM 了。

延迟

延迟分两种:缓存命中延迟和缓存未命中延迟。

  • 命中延迟:从缓存读数据的时间。通常几个 CPU 周期,可以忽略不计。
  • 未命中延迟:从原始存储读取 + 解码 + 写入缓存的时间。这才是大头。

你想想看,如果一张图未命中,LVGL 要先去 Flash 读原始数据,然后解码,再写入缓存,最后才能显示。这一套下来,几十到几百毫秒就没了。所以降低未命中延迟的关键,就是提高命中率。

容量

容量是个双刃剑。给多了,内存不够;给少了,命中率低。LVGL 的缓存容量通常由 LV_IMG_CACHE_DEF_SIZE 宏控制,单位是字节。

// 在 lv_conf.h 中配置
#define LV_IMG_CACHE_DEF_SIZE   0    // 0 表示使用默认值(通常为 0,即不限制)
// 实际项目中建议手动设置,例如:
#define LV_IMG_CACHE_DEF_SIZE   (32 * 1024)  // 32KB 缓存

我个人习惯这样估算缓存容量:先统计项目中所有图片的大小,然后取最常用的 3~5 张图的总大小,再加 20% 余量。比如最常用的图加起来 80KB,那就设 100KB 左右。

1.4 缓存的工作流程

说了这么多理论,咱们看看 LVGL 里缓存到底怎么工作的。下面这张图能帮你理清思路。

LVGL 图像缓存工作流程 LVGL 图像缓存工作流程 应用请求显示图片 检查缓存中是否存在 命中:直接从缓存读取 未命中:从存储加载并解码 解码后写入缓存 将像素数据送入显示驱动 命中时直接走绿色路径,延迟极低;未命中时走红色路径,需要解码和写入

从这张图你能看到,LVGL 的缓存逻辑其实很清晰:每次显示图片前,先查缓存。有就直接用,没有就加载解码,然后存一份到缓存里。下次再用就快了。

1.5 缓存的代价

缓存不是免费的午餐。它也有代价,主要体现在三个方面:

  • 内存占用:缓存占用的 RAM 不能被其他模块使用。在资源紧张的 MCU 上,这是个需要权衡的问题。
  • 管理开销:缓存需要维护索引、LRU 链表等数据结构,会消耗少量 CPU 时间。
  • 一致性维护:如果图片文件在外部存储中被修改了,缓存里的旧数据就成了“脏数据”。需要手动刷新缓存。

避坑指南:我曾经在一个 OTA 升级项目里踩过坑。升级后图片文件变了,但缓存没清,结果界面上显示的还是旧图。后来在升级流程里加了 lv_img_cache_invalidate_src(NULL) 清空所有缓存,问题解决。

1.6 什么时候该用缓存

不是所有场景都需要缓存。我总结了几条经验:

场景 建议 原因
频繁切换的图标/背景 必须缓存 减少重复解码,提升流畅度
只显示一次的启动图 不需要缓存 浪费内存,且没有复用机会
大尺寸 JPEG 图片 建议缓存 解码开销大,缓存收益明显
小尺寸 Alpha 图标 视情况而定 解码快,缓存收益有限