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

STM32之LVGL移植

  1. 2.8寸TFT-LCD屏原理与应用
  • 基本参数

  • 引脚说明

  • 硬件接线

  • 程序移植

  1. 2.8寸LCD移植LVGL流程分析
  • 基本概念

LVGL的英文全称为Light and Versatile Graphics Library,翻译为中文就是轻便而多功能的图形库,LVGL是目前最流行的免费开源的嵌入式图形库之一,可以为任意一款MCU、MPU以及显示器创建漂亮的UI界面。

  • 硬件要求

  • 支持平台

  • 版本说明

LVGL目前主流的版本有LVGL8和LVGL9,如果打算在STM32上移植LVGL,推荐大家选择LVGL8,原因如下:

  1. 资源占用

STM32F407系列MCU拥有2MB Flash和192KB RAM,虽然这在嵌入式系统中算是相对丰富的资源,但是与一些更高级别的微控制器相比仍然有限。LVGL 8相较于LVGL 9可会有更低的资源需求,这对于资源受限的设备来说是一个重要的考量点。

  1. 高稳定性

LVGL 8作为一个成熟的版本,已经被广泛使用,并且经过了时间的考验。对于那些需要高稳定性的应用,选择一个成熟的版本可以减少潜在的风险。

  • 源码下载
  1. 进入源码页面后,选择要下载的源码版本,目前稳定版为8.3.11,选中release/v8.3即可,尝鲜的同学可以选择v8.4或v9.0版本(特别是v9版本要求MCU的主频及内存资源更高,目前流畅度稍低于v8版本)。

  1. 选中源码版本为8.3后,点击右侧的【< >code】按钮执行源码下载,选择下载ZIP格式即可。

  • 移植文件
  1. 打开移植好的2.8英寸电阻屏的工程,在该工程目录下创建LVGL文件夹,并将LVGL所有源码解压到该文件夹。

  1. 将带有_template后缀的c与h文件,删除“_template”文字,如lv_conf_template.h的名字修改为lv_conf.h,详细如下:

路径

文件名

修改后的文件名

LVGL\

lv_conf_template.h

lv_conf.h

LVGL\examples\porting\

lv_port_disp_template.c

lv_port_disp.c

lv_port_disp_template.h

lv_port_disp.h

lv_port_fs_template.c

lv_port_fs.c

lv_port_fs_template.h

lv_port_fs.h

lv_port_indev_template.c

lv_port_indev.c

lv_port_indev_template.h

lv_port_indev.h

  1. 进入管理项目项界面,选中【Project Items】标签页,创建LVGL_CORE、LVGL_DRAW……LVGL_DEMOKEYPAD等多个Group(组),并为每个组添加对应的c文件,详细如下图:

Groups对应添加文件路径如下:

Groups

文件路径

LVGL_CORE

LVGL\src\core

LVGL_DRAW

LVGL\src\draw LVGL\src\draw\子文件夹

LVGL_EXTRA

LVGL\src\extra LVGL\src\extra\子文件夹

LVGL_FONT

LVGL\src\font

LVGL_HAL

LVGL\src\hal

LVGL_MISC

LVGL\src\misc

LVGL_WIDGETS

LVGL\src\widgets

LVGL_PORTING

LVGL\examples\porting

LVGL_CONFIG

LVGL\lvgl.h LVGL\lvgl_conf.h

LVGL_DEMO_BENCHMARK

LVGL\demos\benchmark LVGL\demos\benchmark\子文件夹

LVGL_DEMO_STRESS

LVGL\demos\stress

LVGL_DEMO_WIDGETS

LVGL\demos\widgets LVGL\demos\widgets\子文件夹

LVGL_DEMO_KEYPAD

LVGL\demos\keypad_encoder

所有文件添加完成后,工程目录树结构如下:

  • 配置工程
  1. 修改启动文件中的堆栈大小,在startup_stm32f40_41xxx.s文件中检查栈大小至少为0x1000,若无,请设置。

  1. 进入Target的options,选中【C/C++】标签页,在Define中添加宏LV_LVGL_H_INCLUDE_SIMPLE,并勾选C99 Mode。

添加宏LV_LVGL_H_INCLUDE_SIMPLE原因是在不少文件都存在这个宏定义,若不使能,则要再新建lvgl目录,为了方便访问到lvgl.h头文件,则在MDK的target的options中添加定义LV_LVGL_H_INCLUDE_SIMPLE该宏 。

#if defined(LV_LVGL_H_INCLUDE_SIMPLE)

#include "lvgl.h"

#else

#include "lvgl/lvgl.h"

#endif

另外,LVGL源码的编译需要C99模式的支持,不然会出现大量的报错,所以大家需要启用C99模式。

  1. 包含头文件路径,点击“魔术棒”,然后选择C/C++,在Include Paths添加新的头文件路径,详细如下。

  • 配置文件
  1. 启用LVGL\lv_conf.h这个头文件,因为默认该文件是不参与编译,必须将#if 0 修改为 #if 1,如下图。

  1. 根据当前显示设备支持的颜色深度设置宏LV_COLOR_DEPTH,对于SPI TFT屏且每8位的传输,确定是否要交换16位色的高低字节,当前显示设备是高字节优先传输,若使能了DMA传输,要设置宏LV_COLOR_16_SWAP为1。

/*====================

COLOR SETTINGS

*====================*/

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/

#define LV_COLOR_DEPTH 16

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/

#define LV_COLOR_16_SWAP 0

  1. 需要使能LVGL配置文件中的宏定义LV_USE_DEMO_WIDGETS,这样可以使用LVGL提供的demo验证显示效果。

#define LV_USE_DEMO_WIDGETS 1

#if LV_USE_DEMO_WIDGETS

#define LV_DEMO_WIDGETS_SLIDESHOW 0

#endif

  • 显示设备
  1. 启用LVGL\examples\porting\lv_port_disp.c,默认该文件是不参与编译,必须将#if 0 修改为 #if 1,另外,“lv_port_disp_template.h”要修改为“lv_port_disp.h”,详细如下图。

  1. 当前文件会涉及到显示设备的初始化、颜色填充等函数,需要引入相关支撑tft函数的头文件,并补充增加"lvgl.h"头文件。

  1. 启用LVGL\examples\porting\lv_port_disp.h,默认该文件是不参与编译,必须将#if 0 修改为 #if 1,如下图。

  1. 修改分辨率,根据用到的屏幕分辨率修改宏MY_DISP_HOR_RES(屏幕宽度)与MY_DISP_VER_RES(屏幕高度)。

/*********************

* DEFINES

*********************/

#ifndef MY_DISP_HOR_RES

#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.

#define MY_DISP_HOR_RES 320

#endif

#ifndef MY_DISP_VER_RES

#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.

#define MY_DISP_VER_RES 240

#endif

  1. 修改lv_port_disp_init函数,lv_port_disp_init 函数是LVGL在特定平台或模拟器上初始化显示驱动的接口函数。在实际嵌入式项目中,需要根据硬件平台编写或修改这个函数来适配显示控制器。

此函数通常包含以下内容:

初始化硬件:配置和打开与显示屏连接的硬件接口(disp_init)。

创建显示器描述符:定义一个 lv_disp_drv_t 结构体实例,该结构体包含了LVGL需要的所有关于显示控制器的信息,例如:一个回调函数用于写像素数据到屏幕(disp_flush)。

注册显示器驱动:调用 lv_disp_drv_register 函数,将创建好的显示器驱动结构体注册给LVGL核心库,使其能够通过该驱动与实际硬件进行通信。

LVGL需要使用缓冲区,用于在内部绘制小部件。此缓冲区将传递给显示器驱动程序的“flush_cb”(copy the buffer's content to the display),以将其内容复制到显示器,另外缓冲区必须大于1个显示行。

LVGL有3种缓冲配置:

(1)创建一个行缓冲区(移植时使用该缓冲配置)

LVGL将在此处绘制显示器的内容,并将其写入显示器。

(2)创建两个行缓冲区(有DMA外设则使用该缓冲配置,能大幅提高显示帧率)

LVGL将绘制显示的内容到缓冲区并将其写入显示,并使用DMA将缓冲区的内容写入显示器,它将使LVGL绘制屏幕的下一部分到另一个缓冲区同时数据从第一个缓冲区发送。它使渲染和并行刷新。

(3)双重帧缓冲(有DMA且大内存则推荐使用该缓冲配置,进一步提高显示帧率)

设置2个屏幕大小的缓冲区,并设置disp_drv.full_refresh=1。这样,LVGL将始终在flush_cb中提供整个渲染屏幕,并且只需要更改帧缓冲区的地址。

使用第一种缓冲配置,需注释掉Example for 2 与 Example for 3代码。

void lv_port_disp_init(void)

{

    /*-------------------------

     * Initialize your display

     * -----------------------*/

    disp_init();

    /*-----------------------------

     * Create a buffer for drawing

     *----------------------------*/

    /**

     * LVGL requires a buffer where it internally draws the widgets.

     * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.

     * The buffer has to be greater than 1 display row

     *

     * There are 3 buffering configurations:

     * 1. Create ONE buffer:

     *      LVGL will draw the display's content here and writes it to your display

     *

     * 2. Create TWO buffer:

     *      LVGL will draw the display's content to a buffer and writes it your display.

     *      You should use DMA to write the buffer's content to the display.

     *      It will enable LVGL to draw the next part of the screen to the other buffer while

     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.

     *

     * 3. Double buffering

     *      Set 2 screens sized buffers and set disp_drv.full_refresh = 1.

     *      This way LVGL will always provide the whole rendered screen in `flush_cb`

     *      and you only need to change the frame buffer's address.

     */

    /* Example for 1) */

    static lv_disp_draw_buf_t draw_buf_dsc_1;

    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                             /*A buffer for 10 rows*/

    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/

#if 0

    /* Example for 2) */

    //    static lv_disp_draw_buf_t draw_buf_dsc_2;

    //    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                                /*A buffer for 10 rows*/

    //    static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                                /*An other buffer for 10 rows*/

    //    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/

    /* Example for 3) also set disp_drv.full_refresh = 1 below*/

    //    static lv_disp_draw_buf_t draw_buf_dsc_3;

    //    static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*A screen sized buffer*/

    //    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*Another screen sized buffer*/

    //    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,

//                          MY_DISP_VER_RES * LV_VER_RES_MAX);   /*Initialize the display buffer*/

#endif

    /*-----------------------------------

     * Register the display in LVGL

     *----------------------------------*/

    static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/

    lv_disp_drv_init(&disp_drv);   /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/

    disp_drv.hor_res = MY_DISP_HOR_RES;

    disp_drv.ver_res = MY_DISP_VER_RES;

    /*Used to copy the buffer's content to the display*/

    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/

    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Required for Example 3)*/

    // disp_drv.full_refresh = 1;

    /* Fill a memory array with a color if you have GPU.

     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.

     * But if you have a different GPU you can use with this callback.*/

    // disp_drv.gpu_fill_cb = gpu_fill;

    /*Finally register the driver*/

    lv_disp_drv_register(&disp_drv);

}

  1. 编写disp_init函数,该函数初始化显示器和所需的外围设备,并被lv_port_disp_init函数所调用。

/*Initialize your display and the required peripherals.*/

static void disp_init(void)

{

LCD_Init();

LCD_Display_Dir(0);

LCD_Scan_Dir(U2D_R2L);

lcddev.width=320;

lcddev.height=240;

}

  1. 编写disp_flush函数,将内部缓冲区的内容刷新到显示器上的特定区域,并被disp_drv.flush_cb进行回调。

/*Flush the content of the internal buffer the specific area on the display

 *You can use DMA or any hardware acceleration to do this operation in the background but

 *'lv_disp_flush_ready()' has to be called when finished.*/

static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)

{

int32_t x;

int32_t y;

if(disp_flush_enabled)

{

LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(uint16_t *)color_p);

lv_disp_flush_ready(disp_drv);

}

}

  • 输入设备
  1. 启用examples\porting\lv_port_indev.c,默认该文件是不参与编译,必须将#if 0 修改为 #if 1 ,另外,必须把“lv_port_indev_template.h”要修改为“lv_port_indev.h”,详细如下图。

另外额外增加以下头文件,支持触摸检测。

#include "touch.h"

#include "tft.h"

  1. 启用examples\porting\lv_port_indev.h,默认该文件是不参与编译,必须将#if 0 修改为 #if 1,如下图。

把该头文件中的

#include "lvgl/lvgl.h"

修改为

#include "lvgl.h"

  1. 在lv_port_indev_init函数中屏蔽鼠标、键盘、编码器等相关代码,只保留Touchpad(触摸板)设备。

void lv_port_indev_init(void)

{

/**

* Here you will find example implementation of input devices supported by LittelvGL:

* - Touchpad

* - Mouse (with cursor support)

* - Keypad (supports GUI usage only with key)

* - Encoder (supports GUI usage only with: left, right, push)

* - Button (external buttons to press points on the screen)

*

* The `..._read()` function are only examples.

* You should shape them according to your hardware

*/

static lv_indev_drv_t indev_drv;

/*------------------

* Touchpad

* -----------------*/

/*Initialize your touchpad if you have*/

touchpad_init();

/*Register a touchpad input device*/

lv_indev_drv_init(&indev_drv);

indev_drv.type = LV_INDEV_TYPE_POINTER;

indev_drv.read_cb = touchpad_read;

indev_touchpad = lv_indev_drv_register(&indev_drv);

#if 0

/*------------------

* Mouse

* -----------------*/

/*Initialize your mouse if you have*/

mouse_init();

/*Register a mouse input device*/

lv_indev_drv_init(&indev_drv);

indev_drv.type = LV_INDEV_TYPE_POINTER;

indev_drv.read_cb = mouse_read;

indev_mouse = lv_indev_drv_register(&indev_drv);

/*Set cursor. For simplicity set a HOME symbol now.*/

lv_obj_t * mouse_cursor = lv_img_create(lv_scr_act());

lv_img_set_src(mouse_cursor, LV_SYMBOL_HOME);

lv_indev_set_cursor(indev_mouse, mouse_cursor);

/*------------------

* Keypad

* -----------------*/

/*Initialize your keypad or keyboard if you have*/

keypad_init();

/*Register a keypad input device*/

lv_indev_drv_init(&indev_drv);

indev_drv.type = LV_INDEV_TYPE_KEYPAD;

indev_drv.read_cb = keypad_read;

indev_keypad = lv_indev_drv_register(&indev_drv);

/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,

*add objects to the group with `lv_group_add_obj(group, obj)`

*and assign this input device to group to navigate in it:

*`lv_indev_set_group(indev_keypad, group);`*/

/*------------------

* Encoder

* -----------------*/

/*Initialize your encoder if you have*/

encoder_init();

/*Register a encoder input device*/

lv_indev_drv_init(&indev_drv);

indev_drv.type = LV_INDEV_TYPE_ENCODER;

indev_drv.read_cb = encoder_read;

indev_encoder = lv_indev_drv_register(&indev_drv);

/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,

*add objects to the group with `lv_group_add_obj(group, obj)`

*and assign this input device to group to navigate in it:

*`lv_indev_set_group(indev_encoder, group);`*/

/*------------------

* Button

* -----------------*/

/*Initialize your button if you have*/

button_init();

/*Register a button input device*/

lv_indev_drv_init(&indev_drv);

indev_drv.type = LV_INDEV_TYPE_BUTTON;

indev_drv.read_cb = button_read;

indev_button = lv_indev_drv_register(&indev_drv);

/*Assign buttons to points on the screen*/

static const lv_point_t btn_points[2] = {

{10, 10}, /*Button 0 -> x:10; y:10*/

{40, 100}, /*Button 1 -> x:40; y:100*/

};

lv_indev_set_button_points(indev_button, btn_points);

#endif

}

  1. 修改touchpad_init函数,该函数会被lv_port_indev_init调用,并需增加初始化触摸板设备的代码。

/*Initialize your touchpad*/

static void touchpad_init(void)

{

    /*Your code comes here*/

    TP_Init();

}

  1. 修改touchpad_is_pressed函数,该函数会被touchpad_read调用,并需增加触摸板设备按压检测的代码。

/*Return true is the touchpad is pressed*/

static bool touchpad_is_pressed(void)

{

    /*Your code comes here*/

if(PEN)

return false;

return true;

}

  1. 修改touchpad_get_xy函数,该函数会被touchpad_read调用,并需增加触摸板设备获取x、y坐标值的代码。

/*Get the x and y coordinates if the touchpad is pressed*/

static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y)

{

    /*Your code comes here*/

if(TP_Scan(0))

{

(*x) = tp_dev.x[0];

(*y) = tp_dev.y[0];

}

}

  • 提供心跳

lv_tick_inc()是LVGL中的一个函数,主要用于模拟系统时钟的滴答(tick)更新。在LVGL中,系统时钟被用于动画、延时处理等定时任务。

该函数的主要作用是将LVGL内部维护的系统 tick 计数器加1,表示系统时间已经向前推进了一个单位的时间间隔(通常是毫秒级别)。在实际使用中,用户需要在合适的时机(例如硬件定时器中断服务程序中如TIM3_IRQHandler)调用此函数,以便LVGL能够正确地进行定时相关的操作,参考代码如下:

void TIM3_IRQHandler(void)

{

    /* 检测时间更新中断的标志位是否置位 */

    if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)

    {

/* 系统时间向前推进一个单位的时间间隔 */

        lv_tick_inc(1);

        /* 清空标志位 */

        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

    }

}

  • 任务处理

要处理 LVGL 的任务,我们需要定期通过以下方式之一调用 lv_task_handler() ,提供以下方案:

  1. main 函数中设置 while(1) 调用
  2. 定期定时中断(低优先级然后是 lv_tick_inc()) 中调用
  3. 定期执行的 OS 任务中调用

计时并不严格,但应保持大约5毫秒以保持系统响应。

while(1)

{

lv_task_handler();

delay_ms(5);

}

  • 案例演示
  1. 在工程中创建includes.h头文件,在该头文件中包含以下lvgl相关头文件,如下图所示:

  1. 在main函数中,调用lv_init、lv_port_disp_init、lv_port_indev_init、lv_demo_widgets、tim3_init等相关初始化函数,在while(1)不可退出循环中添加lv_task_handler函数,该函数为图形库中的一个核心函数,它负责处理所有LVGL的任务和事件。

#include "includes.h"

// 主函数

int main(void)

{

    // 初始化lvgl

    lv_init();

    // 初始化lvgl显示设备

    lv_port_disp_init();

    // 初始化lvgl输入设备

    lv_port_indev_init();

    // 初始化lvgl demo

    lv_demo_widgets();

    // tim3初始化,定时周期为1ms

    tim3_init();

    while (1)

    {

        lv_task_handler();

    }

    return 0;

}

  1. 将编译好的程序下载到开发板,可以看到开发板的LCD屏上成功显示LVGL提供的demo。

http://www.dtcms.com/a/275641.html

相关文章:

  • 详解缓存淘汰策略:LRU
  • python-enumrate函数
  • NO.3数据结构栈和队列|顺序栈|共享栈|链栈|顺序队|循环队列|链队|双端队列|括号匹配|中缀表达式转后缀|后缀表达式求值
  • JavaScript代码段注入:动态抓取DOM元素的原理与实践
  • GitHub 操作指南:项目协作与自动化工作流实践
  • 【第五节】部署http接口到ubuntu server上的docker内
  • 开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库
  • OSPFv3-一二类LSA
  • 创建 UIKit 项目教程
  • 前端开发中的输出问题
  • 新手向:使用Python将多种图像格式统一转换为JPG
  • 【学习笔记】Linux命令
  • JavaWeb(苍穹外卖)--学习笔记05(MD5)
  • LangChain极速入门:用Python构建AI应用的新范式
  • [特殊字符]LabelMe标注转PaddleSeg数据集:多类掩码自动生成+配置文件输出(附完整Python脚本)
  • Apache Iceberg数据湖高级特性及性能调优
  • 玩转rhel9 Apache
  • linux 系统找出磁盘IO占用元凶 —— 筑梦之路
  • Java零基础笔记12(Java编程核心:面向对象编程高级{常用API、GUI编程})
  • PyTorch多层感知机模型构建与MNIST分类训练
  • 【BurpSuite 2025最新版插件开发】基础篇10(完):日志记录与调试
  • 请求服务端获取broker的机房归属信息异常
  • 剑指offer56_数组中唯一只出现一次的数字
  • JavaScript加强篇——第七章 浏览器对象与存储要点
  • NLP:RNN文本生成案例分享
  • 关于 验证码系统 详解
  • S7-200 SMART PLC:硬件、原理及接线特点全解析
  • Transformer基础
  • Linux驱动09 --- 环境搭建
  • 零基础 “入坑” Java--- 九、类和对象(二)