LVGL移植详细教程(基于STM32F407+rt-thread+FSMC接口屏+V9版本)
- 一、LVGL 移植要求
- 1. MCU
- 2. 显示屏
- 二、具体移植步骤
- 1、LVGL源码下载
- a、examples 文件夹
- b 、src 文件夹
- 2、LVGL源码裁剪
- 3、rt-thread工程搭建
- 4、LVGL源码复制
- 5、将LVGL源码添加到keil中
- 6、keil中添加头文件
- 7、开启显示
- 8、编译工程,并解决相关错误
- 9、添加屏幕显示部分
- a、修改`lv_port_disp_init`函数:
- b、打开`lv_port_disp.c`文件在`disp_init()`函数内添加屏幕初始化部分:
- c、打开`lv_port_disp.c`文件在`disp_flush`函数内添加我们屏幕的显示部分:
- 10、任务创建
- 11、触摸移植
- a、修改`lv_port_indev_init`函数:
- b、修改`touchpad_init`函数
- c、修改`touchpad_is_pressed`函数
- d、修改`touchpad_get_xy`函数
一、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
文件夹下新建gui
和gui_app
文件夹,如下图所示:
然后将上面裁剪好的源码复制到gui文件夹内,如下图所示:
5、将LVGL源码添加到keil中
对照LVGL源码src里面的文件夹创建keil分组,如下图所示:
比较特殊一点的就是porting、src、gui文件夹。
创建完成以后将对应文件夹中的c文件添加进来:
将porting
文件夹中的lv_port_disp_template.c
、lv_port_disp_template.h
、lv_port_indev_template.c
、lv_port_indev_template.h
文件改名为lv_port_disp.c
、lv_port_disp.h
、lv_port_indev.c
、lv_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 中缓冲配置:
-
- 单缓冲区:
LVGL 会将显示设备的内容绘制到这里,并将他写入显示设备。
- 单缓冲区:
-
- 双缓冲区:
LVGL 会将显示设备的内容绘制到其中一个缓冲区,并将他写入显示设备。
需要使用 DMA 将要显示在显示设备的内容写入缓冲区。
当数据从第一个缓冲区发送时,它将使 LVGL 能够将屏幕的下一部分绘制到另一个缓冲区。这样使得渲染和刷新可以并行执行。
- 双缓冲区:
-
- 全尺寸双缓冲区
*设置两个屏幕大小的全尺寸缓冲区,并且设置 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 5extern 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;
}
我屏幕做了翻转,所以要对坐标进行变换,你们的根据实际情况来。
至此已移植完成。
觉得有用点个赞呗
完整工程下载:点我