2、LVGL图片加载基础:lv_img_dsc_t结构体、内置图片与外部图片的区别

好,咱们正式开始聊图片加载。很多新手朋友一上来就急着调缓存、搞DMA,结果连最基础的 lv_img_dsc_t 都没搞明白。我当年也犯过这个错——项目都快交付了,发现图片显示全是花屏,排查了两天才发现是结构体里的 header.always_zero 字段没清零。嗯,这种坑踩一次就够了。

2.1 lv_img_dsc_t:图片描述符的核心

在LVGL里,任何一张图片,不管它是存在内部Flash还是外部存储卡,最终都要通过 lv_img_dsc_t 这个结构体来“描述”自己。说白了,它就是图片的身份证加户口本。

typedef struct {
    lv_img_header_t header;  // 图片头信息
    uint32_t data_size;      // 数据总字节数
    const uint8_t *data;     // 指向像素数据的指针
    lv_img_cf_t cf;          // 颜色格式(已废弃,保留兼容)
} lv_img_dsc_t;

这里面最关键的三个字段:

  • header:包含宽、高、颜色格式、是否启用缓存等标志位。我习惯在初始化时用 lv_img_header_t 的默认值先清零,再逐个赋值,避免遗留脏数据。
  • data_size:注意,这个值是 像素数据的总字节数,不是文件大小。比如一张 100x100 的 RGB565 图片,data_size = 100 * 100 * 2 = 20000 字节。
  • data:指向实际像素数据的指针。内置图片指向Flash地址,外部图片指向RAM中的缓存区或文件系统读取后的内存块。
我的小习惯:在定义 lv_img_dsc_t 变量时,总是用 = {0} 初始化。曾经遇到过因为结构体未清零,导致 header.always_zero 字段非零,LVGL直接拒绝加载图片。这个字段必须为0,否则内部校验会失败。

2.2 内置图片 vs 外部图片:本质区别

你想想看,为什么LVGL要区分这两种加载方式?其实核心就一句话:数据源的位置不同。

对比维度 内置图片 外部图片
数据存储位置 MCU内部Flash(代码段) 外部存储(SD卡、SPI Flash、文件系统)
加载方式 编译时链接,直接引用地址 运行时从文件系统读取到RAM
lv_img_dsc_t.data 指向 Flash中的常量数组地址 RAM中的动态分配缓冲区
内存占用 不占用RAM(直接解码显示) 需要RAM缓存整张图片或分块加载
修改灵活性 需重新编译固件 可动态替换文件
典型场景 图标、Logo、固定背景 用户照片、地图瓦片、动态素材

我记得有一次做智能家居面板,客户要求开机动画能通过SD卡更换。如果全用内置图片,每次换图都得远程OTA升级固件,太折腾了。后来改成外部加载,用户直接把新图片拷进SD卡就行。这就是外部图片的灵活之处。

2.3 内置图片的加载流程

内置图片其实没什么好说的——LVGL在编译时就把图片数据转成C数组,链接到固件里。你调用 lv_img_set_src(img_obj, &my_image) 时,LVGL直接拿着 lv_img_dsc_t 里的 data 指针去Flash里读像素。

流程非常简单:

  1. 使用LVGL的图片转换工具(如 LVGL Image Converter)将PNG/JPG转成C数组。
  2. 把生成的 .c 文件加入工程编译。
  3. 在代码中 extern 声明该 lv_img_dsc_t 变量。
  4. 直接赋值给 lv_img 对象的 src 属性。
注意:内置图片虽然不占RAM,但会消耗Flash空间。一张 320x240 的RGB565图片(约150KB),如果你的MCU只有512KB Flash,放几张就满了。这时候就得考虑外部加载。

2.4 外部图片的加载流程

外部图片就复杂多了。LVGL本身不直接操作文件系统,它通过 lv_fs 驱动层来读写文件。你需要先注册一个文件系统驱动(比如FatFS、LittleFS),然后LVGL才能通过路径字符串打开图片文件。

基本步骤:

  1. 初始化文件系统驱动(挂载SD卡、初始化SPI Flash等)。
  2. 调用 lv_fs_drv_register() 注册你的文件系统驱动。
  3. 使用 lv_img_set_src(img_obj, "S:/images/photo.bin") 加载外部图片。
  4. LVGL内部通过 lv_fs_open() 读取文件,解析图片头,分配RAM缓冲区,将像素数据读入内存。
我曾经踩过的坑:外部图片加载时,如果文件路径格式不对,LVGL会静默失败。比如我用 "0:/pic.bin" 但驱动注册的盘符是 "S:",结果图片显示不出来,调试了半天才发现是路径前缀不匹配。建议统一用宏定义管理盘符。

2.5 两种加载方式的内存对比

咱们用一张图来直观感受一下区别。下面这个SVG展示了内置和外部图片在内存布局上的差异:

内置图片 vs 外部图片 内存布局对比 内置图片(Flash存储) lv_img_dsc_t 结构体(Flash) 像素数据数组(Flash) RAM:0 占用 外部图片(文件系统加载) lv_img_dsc_t 结构体(RAM) 文件系统读取到RAM缓冲区 RAM:图片大小 + 结构体开销 内置图片:Flash换RAM,适合固定资源;外部图片:RAM换灵活性,适合动态更新

从这张图可以看得很清楚:内置图片的 data 指针直接指向Flash地址,CPU读Flash的速度虽然比RAM慢一点,但胜在完全不占RAM。而外部图片需要先把文件从存储介质读到RAM里,LVGL再从这个RAM缓冲区解码显示。这意味着你的RAM里必须留出至少一张图片大小的空间。

2.6 什么时候该用哪种?

我个人总结了一个简单的选择原则:

  • 小于 32KB 的图标、按钮背景 → 内置图片。这点Flash空间还是舍得给的。
  • 大于 64KB 的全屏背景、照片 → 外部图片。RAM不够用,而且用户可能需要换图。
  • 中间尺寸(32KB~64KB) → 看你的Flash剩余空间。如果Flash还够,内置更省心;如果Flash紧张,就外置。
避坑指南:外部图片加载时,如果图片尺寸很大(比如 800x480 的RGB565,约768KB),而你的RAM只有512KB,那就不能一次性加载整张图。这时候需要用到LVGL的 图片缓存分块加载 机制,这个咱们后面章节会详细讲。先记住:外部加载不等于整张图全塞进RAM。