当前位置: 首页 > news >正文

STM32移植LVGL 8.0.2保姆级教程:从工程搭建到优化实战

1. 项目概述:为什么要在STM32上折腾LVGL?

如果你正在用STM32做带屏幕的产品,比如智能手表、工业HMI或者家用电器面板,那你大概率绕不开一个选择:用哪种GUI库?是继续在LCD上画点画线,还是用更高级的图形库?我当年就是从自己写驱动、画UI走过来的,深知其中的痛苦:改个按钮位置要调半天坐标,加个动画效果得写一堆状态机,UI和业务逻辑搅在一起,代码越写越乱。

后来接触到LVGL,感觉像是打开了新世界的大门。LVGL(Light and Versatile Graphics Library)是一个用C语言编写的开源图形库,它提供了按钮、标签、滑块、图表等丰富的控件,支持动画、主题、多语言,甚至还有GPU加速的接口。最关键的是,它资源占用相当友好,在只有几十KB RAM的MCU上也能跑起来。版本8.0.2是一个相当成熟稳定的版本,功能完善,社区资料也多,非常适合作为在STM32上的入门和商用选择。

所以,这个“保姆级教程”的目的,就是帮你把LVGL 8.0.2这颗“心脏”成功地移植到你的STM32“身体”里。我会从最基础的工程准备开始,一步步带你走过配置、适配、调试的全过程,直到屏幕上出现第一个LVGL的“Hello World”。更重要的是,我会分享我踩过的所有坑和对应的填坑方法,并提供一份我亲自移植、测试通过的工程文件作为参考和起点。无论你是刚接触嵌入式GUI的初学者,还是想为现有项目升级UI的开发者,这篇教程都能让你少走弯路。

2. 移植前的核心准备与工程框架搭建

在动手写代码之前,充分的准备工作能避免一半的麻烦。这一部分我们搞清楚需要什么,以及如何搭建一个干净的基础工程。

2.1 硬件与软件环境清单

首先,确认你的“战场”装备是否齐全。

硬件部分:

  • 主控芯片:任意一款STM32系列MCU。本教程以常见的STM32F103C8T6(蓝色药丸核心板)为例,因为它资源适中(64KB Flash,20KB RAM),价格便宜,读者持有量大。但原理适用于F1、F4、H7等全系列。
  • 显示屏:一款支持SPI或8080/6800并行接口的TFT液晶屏。我使用的是1.44寸的ST7735S驱动芯片的SPI屏幕。请务必找到你屏幕的数据手册(Datasheet)和初始化代码序列。
  • 触摸屏(可选):如果你需要交互,需要电阻式或电容式触摸屏。常见的是XPT2046电阻触摸芯片(SPI接口)。本教程会包含触摸移植部分。
  • 调试器:ST-Link V2或J-Link等,用于下载和调试。
  • 接线:确保屏幕的SPI或FSMC引脚、背光控制引脚、触摸芯片引脚正确连接到STM32。

软件部分:

  • 开发环境:Keil MDK-ARM(我用的V5.36),当然你也可以用IAR或者STM32CubeIDE,但本文指令以Keil为主。
  • STM32固件库/ HAL库:为了方便,我们直接使用STM32CubeMX生成基于HAL库的初始化代码。这能节省大量配置GPIO、时钟、外设的时间。
  • LVGL源码:从GitHub官方仓库(https://github.com/lvgl/lvgl)下载版本8.0.2的ZIP包,或者使用Git克隆。注意:不要用master分支,确保是release/v8.0.2标签下的代码。
  • 文件系统(可选):如果你打算从SD卡加载图片、字体,需要FATFS的中间件。

2.2 使用STM32CubeMX创建基础工程

这是构建“地基”的关键一步,正确的初始化能避免后续很多底层驱动问题。

  1. 选择芯片:打开CubeMX,选择你的芯片型号(STM32F103C8T6)。
  2. 配置时钟树:根据你的外部晶振(如8MHz),配置系统时钟到最高频率(对于F103,通常是72MHz)。LVGL的动画流畅度与系统时钟直接相关。
  3. 配置调试接口:在System Core->SYS里,将Debug改为Serial Wire,否则可能无法使用ST-Link调试。
  4. 配置显示接口
    • 对于SPI屏幕:在Connectivity下启用一个SPI,模式为Full-Duplex Master,硬件NSS选Disable(我们通常用软件控制片选)。根据数据手册设置预分频器,确保时钟(如Baud Rate)在屏幕规格范围内(ST7735通常支持到几十MHz)。然后配置对应的GPIO:SCK、MOSI、RES(复位)、DC(命令/数据)、CS(片选)、BLK(背光)。
    • 对于8080并口屏幕:通常使用FSMC(F4/F7/H7)或GPIO模拟。使用FSMC性能更好,需要在Connectivity中配置FSMC,选择LCD Interface,并根据屏幕时序参数调整地址建立、数据建立等时间。
  5. 配置触摸接口(如果有时):如果触摸芯片是XPT2046(SPI),需要再启用一个SPI(或与屏幕共用,但需注意片选隔离),并配置IRQBUSY引脚(如果有)。
  6. 配置FreeRTOS(可选但推荐):在Middleware中启用FREERTOS,使用CMSIS_V2接口。LVGL推荐在操作系统的线程中运行其任务句柄(lv_timer_handler),这样不阻塞其他任务。我们将创建一个低优先级的任务专门用于LVGL。
  7. 生成工程:在Project Manager选项卡,设置工程名、路径,选择MDK-ARM作为Toolchain。在Code Generator里,选择“Copy only necessary library files”以保持工程简洁,并勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files”。最后点击GENERATE CODE

注意:CubeMX生成的gpio.c里会对所有用到的GPIO进行初始化。请务必检查屏幕和触摸的引脚初始化模式(推挽输出、上拉输入等)是否正确,不正确的模式会导致通信失败。

2.3 获取与整理LVGL源码

解压下载的LVGL 8.0.2源码包,你会发现里面有很多文件夹。我们不需要全部放进工程。

  1. 核心必选文件:将lvgl文件夹整个复制到你的Keil工程目录下(例如,与CoreDrivers文件夹同级)。这个文件夹里包含src(核心源码)、examples(示例)、demos(演示)等。
  2. 关键配置文件:在lvgl目录下,找到lv_conf_template.h,将其复制一份到你的工程Core/Inc目录下,并重命名为lv_conf.h。这个文件是LVGL的“大脑”,所有配置开关都在这里。
  3. 端口文件:在lvgl目录下的examples/porting文件夹里,找到我们需要移植实现的几个关键文件模板:
    • lv_port_disp_template.c/.h-> 显示接口
    • lv_port_indev_template.c/.h-> 输入设备(触摸、按键)接口
    • lv_port_fs_template.c/.h-> 文件系统接口(可选) 同样,将它们复制到你的工程目录(比如新建一个LVGL_Port文件夹),并移除文件名中的_template,例如lv_port_disp.c

至此,你的工程目录结构应该大致如下:

My_LVGL_Project/ ├── Core/ │ ├── Inc/ │ │ ├── lv_conf.h <-- LVGL主配置文件 │ │ └── ... │ └── Src/ │ └── ... ├── Drivers/ ├── LVGL/ │ ├── src/ <-- LVGL核心源码 │ ├── examples/ │ └── ... ├── LVGL_Port/ │ ├── lv_port_disp.c │ ├── lv_port_indev.c │ └── ... └── MDK-ARM/ <-- Keil工程文件

3. LVGL核心配置与显示接口移植详解

这是移植最核心的一步,我们要告诉LVGL如何驱动你的屏幕进行绘制。

3.1 深度定制lv_conf.h配置文件

打开lv_conf.h,开头的#if 0需要改为#if 1来启用本文件。然后开始关键配置:

  • 颜色深度LV_COLOR_DEPTH设置为16或32。对于大多数TFT屏,16位色深(RGB565)足以满足需求且节省内存。如果你的屏幕是RGB888且内存充足,可以设32。
    #define LV_COLOR_DEPTH 16
  • 堆内存大小LV_MEM_SIZE是LVGL内部动态分配内存的大小。对于中等复杂度的界面,建议设置为32KB ~ 64KB (即 32 * 1024UL)。如果开了很多缓冲或控件,需要加大。
    #define LV_MEM_SIZE (48 * 1024UL)
  • Tick来源LV_TICK_CUSTOM需要启用,并实现lv_tick_get()函数。我们将用SysTick中断来提供1ms的心跳。
    #define LV_TICK_CUSTOM 1 #if LV_TICK_CUSTOM #define LV_TICK_CUSTOM_INCLUDE "main.h" // 提供HAL_GetTick()声明 #define LV_TICK_CUSTOM_SYS_TIME_EXPR (HAL_GetTick()) #endif
  • 显示缓冲区LV_USE_DISP_BUFFER是性能关键。有三种模式:
    1. 单缓冲LV_DISP_BUF_FULL,只有一个缓冲区,绘制完成直接刷屏,期间屏幕会撕裂。
    2. 双缓冲LV_DISP_BUF_FULL,两个缓冲区,一个用于LVGL绘制,一个用于DMA传输到屏幕,无撕裂但耗内存。
    3. 部分缓冲LV_DISP_BUF_PARTIAL,只分配一小块缓冲区(如屏幕的1/10高度),LVGL分块绘制,DMA分块传输。这是资源紧张MCU的推荐方案,能极大降低RAM需求。
    #define LV_USE_DISP_BUFFER_PARTIAL 1 #define LV_DISP_DEF_REFR_PERIOD 30 // 默认刷新周期30ms #define LV_INDEV_DEF_READ_PERIOD 30 // 默认输入设备读取周期30ms
  • 启用功能:根据需求打开控件和功能,如标签、按钮、滑块、图表、动画、文件系统等。初期可以保持默认,后续按需开启。
    #define LV_USE_LABEL 1 #define LV_USE_BTN 1 #define LV_USE_ANIMATION 1

3.2 实现显示驱动接口 lv_port_disp.c

现在打开lv_port_disp.c,进行实质性移植。

  1. 初始化函数:在disp_init(void)函数里,你需要完成硬件初始化。这部分代码通常就是你屏幕厂家提供的LCD_Init()函数。确保SPI/FSMC、GPIO、背光都已正确配置。
    static void disp_init(void) { // 你实际的屏幕初始化代码 LCD_Init(); // 这个函数需要你根据屏幕驱动芯片编写或已有 // 例如:设置屏幕旋转方向、清屏等 LCD_SetRotation(0); LCD_Clear(0x0000); }
  2. 刷新回调函数:这是最关键的disp_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)
    • area:定义了屏幕上需要更新的矩形区域 (x1, y1, x2, y2)。
    • color_map:是一个数组,存储了该区域内每个像素点的颜色值(格式由LV_COLOR_DEPTH决定)。
    • 你的任务:将color_map中的数据,写入到屏幕的area区域。对于SPI屏幕,通常需要设置光标地址(LCD_SetWindow),然后连续发送颜色数据(LCD_WriteData_Color)。
    static void disp_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map) { // 1. 设置屏幕的显示窗口为 area 区域 LCD_SetWindow(area->x1, area->y1, area->x2, area->y2); // 2. 准备发送数据(例如,拉低片选,写命令等) LCD_Write_Cmd(0x2C); // 假设0x2C是内存写命令 // 3. 将color_map中的数据连续发送到屏幕 uint32_t size = lv_area_get_width(area) * lv_area_get_height(area); LCD_Write_Data_Color((uint16_t*)color_map, size); // 你的SPI发送函数 // 4. 重要!告诉LVGL刷新已完成 lv_disp_flush_ready(drv); }

    实操心得disp_flush函数会被LVGL频繁调用。为了提升性能,务必使用DMA进行数据传输,避免在SPI发送循环中阻塞CPU。对于SPI,可以配置为DMA模式,在disp_flush中启动DMA传输,然后在DMA传输完成的中断回调里调用lv_disp_flush_ready(drv)。这是流畅与否的关键。

  3. 注册显示驱动:在lv_port_disp_init(void)函数中,分配一个显示缓冲,并初始化驱动。
    void lv_port_disp_init(void) { // 1. 初始化硬件 disp_init(); // 2. 分配显示缓冲区(部分缓冲示例) static lv_disp_buf_t disp_buf; static lv_color_t buf[LV_HOR_RES_MAX * 10]; // 缓冲区:屏幕宽度 * 10行像素 lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10); // 3. 注册驱动 lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.buffer = &disp_buf; disp_drv.flush_cb = disp_flush; disp_drv.hor_res = 160; // 你的屏幕实际宽度 disp_drv.ver_res = 128; // 你的屏幕实际高度 // disp_drv.full_refresh = 1; // 如果需要全屏刷新则启用 lv_disp_drv_register(&disp_drv); }

4. 输入设备(触摸)与操作系统适配

一个没有交互的GUI是残缺的。接下来我们让屏幕能响应你的触摸。

4.1 实现触摸驱动接口 lv_port_indev.c

打开lv_port_indev.c,我们以电阻触摸XPT2046为例。

  1. 读取触摸数据:核心是实现touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)函数。
    • 你需要编写一个TP_Read()函数,来读取触摸芯片的ADC值,并转换为屏幕坐标(x, y)
    • 同时,需要检测触摸状态:是按下(LV_INDEV_STATE_PR)还是释放(LV_INDEV_STATE_REL)。
    static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { static int16_t last_x = 0, last_y = 0; uint8_t tp_state = 0; int16_t x = 0, y = 0; // 你的触摸读取函数,返回状态和坐标 tp_state = TP_Read(&x, &y); if(tp_state == 1) { >void lv_port_indev_init(void) { // 1. 初始化触摸硬件 TP_Init(); // 你的触摸初始化函数 // 2. 注册输入设备驱动 static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; // 指针设备(触摸、鼠标) indev_drv.read_cb = touchpad_read; lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv); // 3. (可选)将输入设备与特定显示关联 lv_disp_t * my_disp = lv_disp_get_default(); lv_indev_set_display(my_indev, my_disp); }

4.2 在FreeRTOS中集成LVGL任务句柄

如果你在CubeMX中启用了FreeRTOS,那么让LVGL在其中运行是最佳实践。

  1. 创建LVGL任务:在main.cStartDefaultTask(或你创建的任务中),初始化LVGL和端口,然后创建一个低优先级的任务来周期性调用lv_timer_handler()
    void StartDefaultTask(void *argument) { // 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); // ... 其他外设初始化 // LVGL初始化 lv_init(); lv_port_disp_init(); lv_port_indev_init(); // 创建LVGL任务 xTaskCreate(lvgl_task, "LVGL Task", 512, NULL, 1, NULL); // 优先级较低 // 创建你的应用任务 xTaskCreate(app_task, "APP Task", 512, NULL, 2, NULL); vTaskStartScheduler(); while (1) {} } void lvgl_task(void *pvParameters) { TickType_t last_wake_time = xTaskGetTickCount(); const TickType_t period = pdMS_TO_TICKS(5); // 5ms执行一次,即200Hz for(;;) { lv_task_handler(); // 在v8.0.2中,定时器处理已集成到lv_task_handler() vTaskDelayUntil(&last_wake_time, period); } }
  2. 提供系统Tick:在stm32f1xx_it.c的SysTick中断服务函数中,调用lv_tick_inc(1),为LVGL提供时间基准。
    void SysTick_Handler(void) { HAL_IncTick(); lv_tick_inc(1); // 增加1毫秒计数 }

    实操心得lv_timer_handler()(在v8中已合并)的执行周期建议在1-10ms。太慢会导致UI响应迟钝,动画卡顿;太快会浪费CPU资源。5ms是一个不错的平衡点。确保这个任务的堆栈空间足够(512字可能只是起点,复杂界面需要更多)。

5. 工程集成、编译与第一个UI测试

现在,我们把所有模块像拼图一样组合起来,并点亮屏幕。

5.1 在Keil工程中添加文件与头文件路径

  1. 添加文件分组:在Keil的Project窗口中,创建几个组,例如:
    • LVGL_Core:添加lvgl/src下的所有.c文件。注意,lvgl/src下的子文件夹(如drawwidgetsmisc等)里的.c文件也要按结构添加进去。一个技巧是使用*.c通配符添加整个文件夹。
    • LVGL_Port:添加你修改好的lv_port_disp.clv_port_indev.c
    • LVGL_App:存放你后续的应用程序文件。
  2. 添加头文件路径:在Options for Target->C/C++->Include Paths中,添加以下路径:
    • ../Core/Inc(包含lv_conf.h)
    • ../LVGL(LVGL根目录)
    • ../LVGL/src(LVGL核心头文件)
    • ../LVGL_Port(你的端口文件目录)
    • ../Drivers/STM32F1xx_HAL_Driver/Inc等HAL库路径。

5.2 编写主函数与第一个界面

main.c或你的应用任务中,创建第一个简单的界面。

void app_task(void *pvParameters) { // 等待LVGL初始化完成,可以加个延时或信号量同步 vTaskDelay(pdMS_TO_TICKS(100)); // 1. 创建一个基础对象(屏幕) lv_obj_t * scr = lv_scr_act(); // 2. 创建一个按钮 lv_obj_t * btn = lv_btn_create(scr); lv_obj_set_size(btn, 100, 50); // 宽100像素,高50像素 lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 居中显示 // 3. 给按钮加个标签 lv_obj_t * label = lv_label_create(btn); lv_label_set_text(label, "Click Me!"); lv_obj_center(label); // 4. 给按钮添加一个点击事件 lv_obj_add_event_cb(btn, btn_event_handler, LV_EVENT_CLICKED, NULL); for(;;) { // 你的其他应用逻辑 vTaskDelay(pdMS_TO_TICKS(100)); } } static void btn_event_handler(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); if(code == LV_EVENT_CLICKED) { // 改变按钮标签文字 lv_obj_t * btn = lv_event_get_target(e); lv_obj_t * label = lv_obj_get_child(btn, 0); lv_label_set_text(label, "Clicked!"); } }

5.3 编译、下载与调试

  1. 编译:点击Build。你可能会遇到大量错误,通常是头文件找不到、函数未声明。请仔细检查头文件路径是否添加完整,以及lv_conf.h是否被正确包含(#if 1)。
  2. 解决常见编译错误
    • undefined symbol lv_xxx:通常是相应的源文件(.c)没有添加到工程中。去lvgl/src下找到对应的文件添加。
    • lv_conf.hnot found:确保路径正确,且文件在Core/Inc里。
    • HAL库相关错误:确保CubeMX生成的HAL库文件已正确添加到工程。
  3. 下载与调试:连接ST-Link,下载程序到芯片。打开串口助手(如果你有打印调试信息)。使用Keil的调试功能,单步执行,查看LVGL初始化、刷屏函数是否被调用。

6. 高级优化、问题排查与资源管理

当第一个按钮显示在屏幕上并能响应点击时,恭喜你,移植成功了90%。剩下的10%是优化和打磨,这对产品化至关重要。

6.1 性能优化技巧

  1. 使用DMA进行刷屏:这是提升流畅度的最有效手段。将disp_flush函数中的SPI发送循环改为启动DMA传输。在SPI的DMA传输完成中断(HAL_SPI_TxCpltCallback)中,调用lv_disp_flush_ready(drv)
    // 在disp_flush中 HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)color_map, size * 2); // 16位色,所以size*2 // 不要在这里调用 lv_disp_flush_ready // 在SPI发送完成回调函数中 void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->Instance == SPI1.Instance) { lv_disp_flush_ready(&disp_drv); // 通知LVGL本块刷新完成 } }
  2. 调整缓冲区策略
    • 部分缓冲:如果内存紧张,坚持使用部分缓冲。可以尝试调整缓冲区行数。行数越多,一次传输数据量越大,DMA效率可能更高,但内存占用也大。需要平衡。
    • 全屏双缓冲:如果内存充足(比如STM32F429有256KB RAM),使用全屏双缓冲可以获得最流畅的体验,完全无撕裂。
  3. 优化LVGL配置
    • lv_conf.h中关闭你不需要的控件、特效和功能,以节省Flash和RAM。
    • 调整LV_DISP_DEF_REFR_PERIOD(刷新周期)和LV_INDEV_DEF_READ_PERIOD(输入读取周期)。在保证流畅的前提下尽量调大,降低CPU负载。
    • 使用LV_USE_PERF_MONITORLV_USE_MEM_MONITOR来在屏幕上实时显示帧率和内存使用情况,帮助定位性能瓶颈。

6.2 典型问题排查实录

以下是我在多次移植中遇到的“坑”及其解决方案:

问题现象可能原因排查步骤与解决方案
屏幕白屏或花屏1. 屏幕初始化序列错误。
2. 刷屏函数disp_flush中设置窗口或发送数据命令错误。
3. 颜色格式不匹配(RGB565 vs RGB888)。
1. 用逻辑分析仪或示波器抓取SPI时序,与数据手册对比初始化命令和数据。
2. 在disp_flush开头用HAL_GPIO_WritePin点亮一个LED,确认函数被调用。
3. 尝试发送固定的纯色(如红色0xF800)测试刷屏通路。
4. 检查lv_conf.hLV_COLOR_DEPTH与屏幕驱动芯片要求的格式是否一致。
触摸坐标完全不准1. 触摸芯片初始化或读取函数有误。
2. 坐标未校准。
3. X, Y轴方向反了或镜像了。
1. 读取原始ADC值,通过串口打印出来,看按压时是否有变化。
2. 实现一个简单的校准程序,在屏幕四个角显示十字,让用户点击,计算校准系数。
3. 在touchpad_read中尝试交换xy,或对坐标进行x = 屏幕宽度 - x等变换。
UI非常卡顿,动画掉帧1.lv_timer_handler()调用周期太长。
2. 刷屏未使用DMA,CPU被SPI阻塞。
3. 缓冲区太小,导致分块太多,开销大。
4. 系统时钟频率太低。
1. 在lvgl_task中减少vTaskDelay的时间,比如从10ms改为5ms。
2.必须启用DMA
3. 在内存允许范围内增大部分缓冲区的大小。
4. 检查MCU主频是否配置到最高。
点击按钮等控件无反应1. 触摸坐标正确,但未与显示驱动关联。
2. 控件本身未启用点击事件。
3. 输入设备读取周期LV_INDEV_DEF_READ_PERIOD设置过长。
1. 检查lv_port_indev_init中是否调用了lv_indev_set_display将输入设备与显示关联。
2. 确保按钮等控件使用了lv_obj_add_event_cb添加了事件回调。
3. 在lv_conf.h中减小LV_INDEV_DEF_READ_PERIOD的值。
编译后RAM或Flash溢出1. 启用了太多LVGL功能。
2. 堆栈设置太小。
3. 缓冲区开得太大。
1. 在lv_conf.h中关闭不用的控件和功能(如LV_USE_CANVAS,LV_USE_CHART)。
2. 在Keil的Target选项和FreeRTOS的任务创建中增加堆栈大小。
3. 改用部分缓冲,并减少缓冲区行数。使用LV_MEM_SIZE监控内存使用。

6.3 字体、图片与文件系统集成

  1. 添加自定义字体:LVGL支持从C数组或外部文件加载字体。使用官方工具LVGL Font Converter在线转换.ttf字体为C文件。将生成的.c文件加入工程,并在代码中声明并使用。
    LV_FONT_DECLARE(my_font_20); // 声明字体 lv_obj_set_style_text_font(label, &my_font_20, 0); // 应用字体
  2. 显示图片:同样使用LVGL Image Converter工具将.png.jpg图片转换为C数组或二进制文件。如果使用文件系统,可以保存为.bin文件,通过lv_img_set_src(img, “S:/path/to/img.bin”)加载。
  3. 集成文件系统(FATFS):如果通过CubeMX启用了FATFS和SD卡,需要在lv_port_fs.c中实现open,read,close等接口的映射,将LVGL的文件操作指向FATFS。这样LVGL就能从SD卡读取字体、图片和主题文件,极大节省MCU的Flash空间。

移植完成后,你的工程就成为了一个强大的GUI应用平台。你可以基于此,利用LVGL丰富的控件和样式系统,设计出美观、交互流畅的用户界面。这份移植好的工程文件,不仅是一个起点,更是一个经过验证的稳定基础,你可以将其应用到不同的STM32项目中去。

http://www.jsqmd.com/news/1351106/

相关文章:

  • SQL注入攻击原理与防御实战指南
  • 8款AI工具提升学术写作效率:从目录生成到格式规范
  • AI图像生成与3D建模:从零上手虚拟角色创作与Stable Diffusion实践
  • JavaScript全栈性能优化实战指南
  • 算法面试实战:分类体系与解题五步法详解
  • 潮州本地防水补漏怎么选?屋顶卫生间外墙地下室阳台渗水检测大盘点(2026年8月新) - 屋工匠
  • Kotro:为AI编码智能体构建安全可控的本地控制平面
  • 电子商务网站建设与维护致谢词:致每一位在数字化浪潮中并肩同行的伙伴
  • React 不让你碰 DOM?3 个实战场景搞懂 useRef + Web Worker,性能直接拉满
  • Web开发与API:现代应用架构与实战指南
  • 潍坊本地防水补漏哪家专业?屋顶、卫生间、外墙、地下室、阳台漏水师傅测评(2026年8月新) - 金信达
  • 天梯赛备赛指南:题目解析与高效训练方法
  • AI绘画提示词工程实战:从“四百只兔子”案例解析Stable Diffusion复杂场景生成
  • 数字IC/FPGA工程师求职:结构化简历模板与STAR法则实战指南
  • 2026亲测有效教程:照片改成一寸二寸用什么工具最省事 - 效率工具研究所
  • dbt+SQLServer构建数据仓库(3):dbt_project.yml配置精讲
  • Matplotlib二维绘图核心技巧:从面向对象架构到专业图表定制
  • DeepML每日一题:机器学习算法与工程实践精要
  • RAG文档处理实战:从加载到智能切割,构建LLM高效知识库
  • 后端开发入门:先搞懂这些核心概念
  • 散货港口智能优化:状态监测与群智能算法实践
  • 2026年保温汤车厂家实力甄选:上海界文实业有限公司在宴会保温车、食品保温箱及抽屉式保温柜领域的专业洞察 - 卓企推荐
  • Java工程师转型AI实战:RAG检索优化三大技巧提升准确率至90%
  • Claude Code本地模型接入指南:Ollama与DeepSeek API实战配置
  • Unity游戏开发全类型资源实战合集:从框架选型到性能调优
  • 硕士论文高效写作四步法:从选题到查重全流程解析
  • 从SpaceX AI算力服务看大规模AI集群的工程化实践
  • ArcGIS点线距离计算:垂直距离与路径距离的选型与实战
  • MySQL数据可视化:从SQL查询到动态图表的实战指南
  • RabbitMQ本地消息表实现分布式事务最终一致性