LVGL移植详细教程(基于STM32F407+rt-thread+FSMC接口屏+V9版本)

发布于:2025-03-23 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、LVGL 移植要求

市面上拥有众多的微处理器(MCU) , 但并不是每一个 MCU 都适合移植 LVGL 图形库,
例如传统的 51 单片机,它并不具备移植 LVGL 图形库的条件。 下面我们来看看 LVGL 对硬件
的要求:

1. MCU

LVGL 图形库对微处理器具有一定的要求, 例如主频、内存等, 具体要求如下表所示:

要求 说明
微控制器 16、 32 、 64 位的微控制器或处理器
主控频率(Hz) > 16 MHz 时钟速度
Flash/ROM > 64 kB ,如果使用非常多的部件, 推荐 > 180 kB
内存(RAM) 8kB(建议配置 24kB)

从上表可知: 微处理器至少需要 16 位以上, 所以传统的 51、 52 单片机无法移植 LVGL,
它们都是 8 位的微处理器。 接下来, 我们来看一下正点原子 Mini 板是否满足 LVGL 最低移植
需求,它的 MCU 为 STM32F103RCT6, 其主频率 72MHz, Flash 为 256K, SRAM 为 64K, 显
然 Mini 开发板的 MCU 可以满足 LVGL 图形库的移植要求。

2. 显示屏

LVGL 只需要一个简单的驱动程序函数即可将像素阵列复制到显示器的给定区域中, 其对
显示屏的兼容性很强,具体要求如下(满足其一即可) :
① 具有 8/16 /24/ 32 位色深的显示屏。
② HDMI 端口的显示器。
③ 小型单色显示器。
④ LED 矩阵。
⑤ 其他可以控制像素颜色/状态的显示器。
我们正点原子的 2.8/3.5/4.3/7/10.1 寸 TFTLCD 模块以及 RGBLCD 模块都是 16 位深的显示
屏,这些显示屏皆可满足 LVGL 的要求。

二、具体移植步骤

1、LVGL源码下载

LVGL 相关的源码和工程都是存放在 GitHub 远程仓库中,该 GitHub 远程仓库地址为
https://github.com/lvgl/lvgl/, 用户可以该仓库中下载 LVGL 图形库的源码。 由于 GitHub 仓库的
服务器在国外, 如果用户在国内访问该服务器,可能登录不成功。
在这里插入图片描述

源码下载下来后解压,如下图所示:
在这里插入图片描述
由上图可知, LVGL 源码的目录下有很多文件和文件夹, 但用户并不需要完全了解它们,
我们只需要了解与移植相关的部分即可。 各文件夹和文件的功能如下表所示:

文件 说明
demos LVGL 提供的综合演示源码
docs LVGL 文献,主要说明 LVGL 每个部件的使用方法
env_support 环境的支持(MDK、 ESP、 RTThread)
examples LVGL 例程源码和 LVGL 输入设备驱动,显示屏驱动文件
scripts LVGL 手稿(与 MicroPython 有关)
src LVGL 源文件(LVGL 部件源码、第三方库)
tests 官方人员的测试代码, 该文件夹用户无需了解
lv_conf_template.h LVGL 的剪裁文件
lvgl.h LVGL 包含的头文件

上表中, 与 LVGL 移植相关的有 examples 文件夹、 src 文件夹、 lv_conf_template.h 和 lvgl.h
文件,其他的部分均与移植无关,用户可以选择忽略。 接下来我们分别看一下 examples、 src
这两个文件夹的文件结构:

a、examples 文件夹

该文件夹主要包含 LVGL部件实例、动画实例、其他第三方库实例以及输入设备和显示器
驱动文件等内容, 具体如表 1.3.2 所示:

文件 描述
anim LVGL 动画例程实例
arduino 开源电子平台
assets 图片资源
event LVGL 事件机制实例
get_started LVGL 获取状态实例
layouts LVGL 布局实例
libs LVGL 移植第三方库实例
others LVGL 其他测试
porting LVGL 输入设备驱动、 文件系统驱动以及显示器驱动
scroll LVGL 滚动实例
styles LVGL 对象样式实例
widgets LVGL 部件实例

b 、src 文件夹

文件 描述
core LVGL 核心源码(事件、组、对象、坐标、样式、主题)
draw LVGL 绘画驱动(图片、 解码、 DMA2D、圆、线、圆弧、和文本)
extra LVGL 的拓展内容(布局、第三方库、其他测试、主题以及部件)
font LVGL 字库
gpu LVGL 针对图形加速
hal 硬件抽象层(显示驱动程序、输入设备程序以及 LVGL 系统滴答)
misc 主要描述 LVGL 其他定义(动画、内存管理、 日志)
widgets LVGL 基础部件

2、LVGL源码裁剪

接下来删除移植无关的文件和文件夹,只留下需要的,如下图所示:
在这里插入图片描述
examples文件夹中的porting文件夹拷贝出来,然后删除examples文件夹,并将lv_conf_template.h文件名改为lv_conf.h,如下图所示:
在这里插入图片描述
在这里插入图片描述

3、rt-thread工程搭建

此部分自己完成,并保证能运行。

4、LVGL源码复制

在rt-thread工程新建middlewares文件夹,然后在middlewares文件夹下新建lvgl文件夹,然后在lvgl文件夹下新建guigui_app文件夹,如下图所示:
在这里插入图片描述
然后将上面裁剪好的源码复制到gui文件夹内,如下图所示:

在这里插入图片描述

5、将LVGL源码添加到keil中

对照LVGL源码src里面的文件夹创建keil分组,如下图所示:
在这里插入图片描述
比较特殊一点的就是porting、src、gui文件夹。
创建完成以后将对应文件夹中的c文件添加进来:
在这里插入图片描述
porting文件夹中的lv_port_disp_template.clv_port_disp_template.hlv_port_indev_template.clv_port_indev_template.h文件改名为lv_port_disp.clv_port_disp.hlv_port_indev.clv_port_indev.h并添加到porting分组内:
在这里插入图片描述
src分组内的内容为:
在这里插入图片描述
gui文件夹放我们设计的gui,先不用管。

6、keil中添加头文件

在这里插入图片描述

7、开启显示

打开lv_port_disp.c文件将#if 0改为#if 1。并定义屏幕尺寸,如下图所示:
在这里插入图片描述
打开lv_port_disp.h文件将#if 0改为#if 1。如下图所示:
在这里插入图片描述

8、编译工程,并解决相关错误

编译会出现大量警告,不用管。
在这里插入图片描述

9、添加屏幕显示部分

a、修改lv_port_disp_init函数:

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    lv_display_t *disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_flush_cb(disp, disp_flush);

    /* Example 1
     * One buffer for partial rendering*/
//    LV_ATTRIBUTE_MEM_ALIGN
//    static uint8_t buf_1_1[MY_DISP_HOR_RES * 10 * BYTE_PER_PIXEL];            /*A buffer for 10 rows*/
//    lv_display_set_buffers(disp, buf_1_1, NULL, sizeof(buf_1_1), LV_DISPLAY_RENDER_MODE_PARTIAL);

    /* Example 2
     * Two buffers for partial rendering
     * In flush_cb DMA or similar hardware should be used to update the display in the background.*/
    static uint8_t buf_2_1[MY_DISP_HOR_RES * 10 * BYTE_PER_PIXEL];

    static uint8_t buf_2_2[MY_DISP_HOR_RES * 10 * BYTE_PER_PIXEL] ;
    lv_display_set_buffers(disp, buf_2_1, buf_2_2, sizeof(buf_2_1), LV_DISPLAY_RENDER_MODE_PARTIAL);

    /* Example 3
     * Two buffers screen sized buffer for double buffering.
     * Both LV_DISPLAY_RENDER_MODE_DIRECT and LV_DISPLAY_RENDER_MODE_FULL works, see their comments*/
//    LV_ATTRIBUTE_MEM_ALIGN
//    static uint8_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL];

//    LV_ATTRIBUTE_MEM_ALIGN
//    static uint8_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL];
//    lv_display_set_buffers(disp, buf_3_1, buf_3_2, sizeof(buf_3_1), LV_DISPLAY_RENDER_MODE_DIRECT);

}

LVGL 需要一个缓冲区用来绘制小部件,随后,这个缓冲区的内容会通过显示设备的 flush_cb(显示设备刷新函数) 复制到显示设备上。这个缓冲区的大小需要大于显示设备一行的大小。
这里有 3 中缓冲配置:

    1. 单缓冲区:
      LVGL 会将显示设备的内容绘制到这里,并将他写入显示设备。
    1. 双缓冲区:
      LVGL 会将显示设备的内容绘制到其中一个缓冲区,并将他写入显示设备。
      需要使用 DMA 将要显示在显示设备的内容写入缓冲区。
      当数据从第一个缓冲区发送时,它将使 LVGL 能够将屏幕的下一部分绘制到另一个缓冲区。这样使得渲染和刷新可以并行执行。
    1. 全尺寸双缓冲区
      *设置两个屏幕大小的全尺寸缓冲区,并且设置 disp_drv.full_refresh = 1。 LVGL 将始终以 ‘flush_cb’ 的形式提供整个渲染屏幕,只需更改帧缓冲区的地址。

b、打开lv_port_disp.c文件在disp_init()函数内添加屏幕初始化部分:

static void disp_init(void)
{
    /*You code here*/
	lcd_init();
}

c、打开lv_port_disp.c文件在disp_flush函数内添加我们屏幕的显示部分:

static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    if(disp_flush_enabled) {
        /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

		lcd_color_fill(area->x1,area->y1,area->x2,area->y2,(uint16_t*)px_map);
    }

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_display_flush_ready(disp_drv);
}

10、任务创建

在rt-thread工程内创建一个LVGL任务:

extern "C" void lvgl_handler_task(void *parameter);
Thread  lvglHandlerThread(lvgl_handler_task,RT_NULL,5120,0,5,"lvglHandlerThread");

我的是c++创建方式。
lvgl_handler_task任务函数内添加如下代码:


void lvgl_handler_task(void *parameter)//任务函数
{	
	#define TASK_LOOP_TICK 5
	
	extern void lv_port_disp_init(void);
	extern void lv_port_indev_init(void);
	extern void lv_user_gui_init(void);
	
	lv_init();
    lv_tick_set_cb(&rt_tick_get_millisecond);
    lv_port_disp_init();
    lv_port_indev_init();
    //lv_user_gui_init();
	
		
	lv_obj_t *label = lv_label_create(lv_scr_act());
	lv_label_set_text(label,"Hello Alientek!!!");
	lv_obj_center(label);

	
    while(1)
    { 		

		lv_timer_handler();
		rt_thread_mdelay(TASK_LOOP_TICK);
    }
}

这一段为测试代码:

lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label,"Hello Alientek!!!");
lv_obj_center(label);

将程序下载到开发板,出现如下图所示表示移植正常:
在这里插入图片描述

11、触摸移植

打开lv_port_indev.c文件将#if 0改为#if 1。打开lv_port_indev.h文件将#if 0改为#if 1

a、修改lv_port_indev_init函数:

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
     */

    /*------------------
     * Touchpad
     * -----------------*/

    /*Initialize your touchpad if you have*/
    touchpad_init();

    /*Register a touchpad input device*/
    indev_touchpad = lv_indev_create();
    lv_indev_set_type(indev_touchpad, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev_touchpad, touchpad_read);

    /*------------------
     * Mouse
     * -----------------*/

    /*Initialize your mouse if you have*/
//    mouse_init();

//    /*Register a mouse input device*/
//    indev_mouse = lv_indev_create();
//    lv_indev_set_type(indev_mouse, LV_INDEV_TYPE_POINTER);
//    lv_indev_set_read_cb(indev_mouse, mouse_read);

//    /*Set cursor. For simplicity set a HOME symbol now.*/
//    lv_obj_t * mouse_cursor = lv_image_create(lv_screen_active());
//    lv_image_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*/
//    indev_keypad = lv_indev_create();
//    lv_indev_set_type(indev_keypad, LV_INDEV_TYPE_KEYPAD);
//    lv_indev_set_read_cb(indev_keypad, keypad_read);

    /*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*/
//    indev_encoder = lv_indev_create();
//    lv_indev_set_type(indev_encoder, LV_INDEV_TYPE_ENCODER);
//    lv_indev_set_read_cb(indev_encoder, encoder_read);

    /*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*/
//    indev_button = lv_indev_create();
//    lv_indev_set_type(indev_button, LV_INDEV_TYPE_BUTTON);
//    lv_indev_set_read_cb(indev_button, button_read);

//    /*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);
}

屏蔽其他,只留下触摸部分。

b、修改touchpad_init函数

添加自己的触摸驱动初始化函数

static void touchpad_init(void)
{
    /*Your code comes here*/
	gt9147_init();
}

c、修改touchpad_is_pressed函数

添加触摸屏扫描和返回触摸是否按下:

static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/
	gt9147_read_scan();
	gt9147_get_value(&isPress,(uint16_t *)&xPress,(uint16_t *)&yPress);
    return isPress;
}

d、修改touchpad_get_xy函数

返回按下坐标点

static void touchpad_get_xy(int32_t * x, int32_t * y)
{
    /*Your code comes here*/
    (*x) = yPress;
    (*y) = 480 - xPress;
}

我屏幕做了翻转,所以要对坐标进行变换,你们的根据实际情况来。

至此已移植完成。
觉得有用点个赞呗
完整工程下载:点我