LVGL 移植整体流程
一、确定输入、输出设备
输入:触摸屏、鼠标、键盘以及编码器;输出:显示屏。
二、确定所需功能
屏幕数据传输方式、系统、SRAM、内存管理算法。
三、准备 LVGL 库、例程
准备 V8.2 版本的 LVGL 库文件,还有支持所需功能的例程源码。
四、添加 LVGL 库到工程
按需裁剪、修改 LVGL 库文件,并添加到 MDK 工程中。
五、配置输入、输出设备
适配自己的输入和输出设备,添加所需功能(例如系统)。
六、提供心跳、测试
为 LVGL 提供时基,写测试代码检测移植是否成功。移植步骤一到四,略。
移植步骤五、配置输出(LCD屏幕)
-
把 lv_port_disp_templ.c / h 的条件编译#if 0 修改成 #if 1
-
在 lv_port_disp_templ.c 文件里,包含 屏设备驱动的 头文件,比如 #include "lcd.h"
-
在 lv_port_disp_templ.c 文件里,在disp_init()函数中初始化屏幕设备,设置横屏或者竖屏
比如:
void disp_init(void)
{
lcd_init();
}
- 在 lv_port_disp_templ.c 里,配置图形数据缓存模式。
这里有3种缓冲配置:
* 1. 单缓冲区:
* LVGL 会将显示设备的内容绘制到这里,并将他写入显示设备。
*
* 2. 双缓冲区:
* LVGL 会将显示设备的内容绘制到其中一个缓冲区,并将他写入显示设备。
* 需要使用 DMA 将要显示在显示设备的内容写入缓冲区。
* 当数据从第一个缓冲区发送时,它将使 LVGL 能够将屏幕的下一部分绘制到另一个缓冲区。
* 这样使得渲染和刷新可以并行执行。
*
* 3. 全尺寸双缓冲区
* 设置两个屏幕大小的全尺寸缓冲区,并且设置 disp_drv.full_refresh = 1。
* 这样,LVGL将始终以 'flush_cb' 的形式提供整个渲染屏幕,您只需更改帧缓冲区的地址。
根据内存选择,我选择的是 单缓存
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1MY_DISP_HOR_RES \* MY_DISP_BUF_LINES; /* 设置缓冲区的大小 */
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * MY_DISP_BUF_LINES); /* 初始化显示缓冲区 */
说明:
MY_DISP_HOR_RES 屏幕的水平宽度 ,MY_DISP_BUF_LINES 显示10行,如果显示效果不是很好,并且内存够用的话,可以修改大些。
全尺寸缓存区的话,5寸 (800 * 480)内存需要 800*480*2*2(第一个2: 16 bit,所以是2byte。第二个2,2块缓存区) 对内存要求高,如果开在外部内存上,外部读写速度慢,刷新速率不见得快。 所以如果使用外部内存,没必要使用全尺寸,速度不一定快。
- 在 lv_port_disp_templ.c 文件里,设置屏幕尺寸。
竖屏
disp_drv.hor_res = 272;
disp_drv.ver_res = 480;
横屏
disp_drv.hor_res = 480;
disp_drv.ver_res = 272;
- 在 lv_port_disp_templ.c 文件里, disp_flush 函数里,配置打点输出(在指定区域内填充指定颜色块)。
lcd_color_fill(area->x1, area->y1, area->x2, area->y2, (uint16_t *)color_p);
area->x1, area->y1 起始点 ,area->x2, area->y2终点的坐标。
移植步骤五、配置输入(触摸)
-
把 lv_port_indev_templ.c / h 的条件编译#if 0 修改成 #if 1
-
在 lv_port_indev_templ.c里,按需裁剪输入设备,文件里有 触摸屏、鼠标、键盘,编码器,键盘,根据自己的输入设备的类别,留下需要的代码。裁剪后的代码如下:
cpp
/**
* @file lv_port_indev_templ.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev_template.h"
#include "../../lvgl.h"
/* 导入屏幕触摸驱动头文件 */
#include "./BSP/TOUCH/touch.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/* 触摸屏 */
static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_touchpad; // 触摸屏
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* @brief 初始化并注册输入设备
* @param 无
* @retval 无
*/
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
/*------------------
* 触摸屏
* -----------------*/
/* 初始化触摸屏(如果有) */
touchpad_init();
/* 注册触摸屏输入设备 */
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);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* 触摸屏
* -----------------*/
/**
* @brief 初始化触摸屏
* @param 无
* @retval 无
*/
static void touchpad_init(void)
{
/*Your code comes here*/
tp_dev.init();
/* 电阻屏如果发现显示屏XY镜像现象,需要坐标矫正 */
if (0 == (tp_dev.touchtype & 0x80)) {
tp_adjust();
tp_save_adjust_data();
}
}
/**
* @brief 图形库的触摸屏读取回调函数
* @param indev_drv : 触摸屏设备
* @arg data : 输入设备数据结构体
* @retval 无
*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
/* 保存按下的坐标和状态 */
if(touchpad_is_pressed())
{
touchpad_get_xy(&last_x, &last_y);
data->state = LV_INDEV_STATE_PR;
}
else
{
data->state = LV_INDEV_STATE_REL;
}
/* 设置最后按下的坐标 */
data->point.x = last_x;
data->point.y = last_y;
}
/**
* @brief 获取触摸屏设备的状态
* @param 无
* @retval 返回触摸屏设备是否被按下
*/
static bool touchpad_is_pressed(void)
{
/*Your code comes here*/
tp_dev.scan(0);
if (tp_dev.sta & TP_PRES_DOWN)
{
return true;
}
return false;
}
/**
* @brief 在触摸屏被按下的时候读取 x、y 坐标
* @param x : x坐标的指针
* @arg y : y坐标的指针
* @retval 无
*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
/*Your code comes here*/
(*x) = tp_dev.x[0];
(*y) = tp_dev.y[0];
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
- 在 lv_port_indev_templ.c里,包含输入设备驱动头文件。
/* 导入屏幕触摸驱动头文件 */
#include "./BSP/TOUCH/touch.h"
-
在touchpad_init()函数中初始化触摸屏
-
配置触摸检测函数,touchpad_is_pressed(),扫描屏幕,扫描按下,返回真,没有被按下,则返回假。
-
配置坐标获取函数,touchpad_get_xy(),在触摸屏被按下的时候读取 x、y 坐标。
移植步骤六: LVGL 提供时基
-
添加定时器驱动 timer.c
-
在定时器驱动.c 文件中包含 : #include "lvgl.h"
-
在定时器中断函数(回调)中调用: lv_tick_inc(x)
重点:
初始化定时器时,需保证:进入中断的时间间隔=x毫秒
移植步骤六: 测试
LVGL移植 main函数部分 要点整理
4个步骤
1.引入头文件在main.c包含:定时器、lvgl.h、显示驱动模板、输入设备驱动模板。
#include "./BSP/TIMER/btim.h"
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
2.初始化
btim_timx_int_init(10−1,9000−1); 初始化基础定时器(用来提供LVGL毫秒时基,中断内调用lv_tick_inc(1))
lv_init():初始化LVGL内核
lv_port_disp_init():初始化显示设备驱动
lv_port_indev_init():初始化触摸等输入设备驱动
3.主循环调用任务处理
在while(1)循环里每隔5ms调用一次 lv_timer_handler()(LVGL在这里刷新画面、处理控件事件、运行定时器动画)
注意:lv_timer_handler()是LVGL任务调度入口,占用CPU,必须在主大循环执行。
4.编写UI测试代码
创建控件,设置大小、位置,验证移植是否成功。
示例创建开关控件:
lv_obj_t* switch_obj = lv_switch_create(lvscract());
lv_obj_set_size(switchobj,120,60);
lv_obj_align(switchobj,LVALIGNCENTER,0,0);
cpp
完整main简易示例
int main(void)
{
//硬件初始化
btim_timx_int_init(10−1,9000−1);
lv_init();
lv_port_disp_init();
lv_port_indev_init();
//测试UI
lv_obj_t* switch_obj = lv_switch_create(lvscract());
lv_obj_set_size(switchobj,120,60);
lv_obj_align(switchobj,LVALIGNCENTER,0,0);
while(1)
{
lv_timer_handler();
delay_ms(5);
}
}
移植步骤: DMA2D 的使用
要求 mcu芯片 硬件有 DMA2D外设,屏幕必须是 RGB屏幕
- 修改lv_conf.h 中相关的宏定义,开启 DMA2D
cpp
/*-------------
* 2. GPU
*-----------*/
/* 使用STM32的DMA2D(又名Chrom Art) GPU */
#define LV_USE_GPU_STM32_DMA2D 1
#if LV_USE_GPU_STM32_DMA2D
/* 必须定义包括目标处理器的CMSIS头的路径
如。"stm32f769xx.h"或"stm32f429xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE stm32l4r7xx.h
#endif
- 在 MDK 配置中 添加 对应芯片的定义
Edit-> Configuration-> C/C++-> Preprocessor Symbols-> define-> 加上 STM32L4
- 配置 DMA2D 外设
cpp
void lcd_fill(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t color)
{
uint32_t psx, psy, pex, pey;
uint32_t offline, addr, timeout = 0;
if (sx >= LCD_WIDTH || sy >= LCD_HEIGHT) return;
if (ex >= LCD_WIDTH) ex = LCD_WIDTH - 1;
if (ey >= LCD_HEIGHT) ey = LCD_HEIGHT - 1;
if (sx > ex || sy > ey) return;
lcd_coord_convert(sx, sy, ex, ey, &psx, &psy, &pex, &pey);
if (psx > pex || psy > pey) return;
offline = LCD_PANEL_WIDTH - (pex - psx + 1);
addr = LCD_FRAMEBUF + LCD_PIXSIZE * (LCD_PANEL_WIDTH * psy + psx);
__HAL_RCC_DMA2D_CLK_ENABLE();
DMA2D->CR &= ~(DMA2D_CR_START);
DMA2D->CR = DMA2D_R2M;
DMA2D->OPFCCR = LCD_PIXFORMAT;
DMA2D->OOR = offline;
DMA2D->OCOLR = color;
DMA2D->OMAR = addr;
DMA2D->NLR = (pey - psy + 1) | ((pex - psx + 1) << 16);
DMA2D->CR |= DMA2D_CR_START;
while ((DMA2D->ISR & DMA2D_FLAG_TC) == 0)
{
if (++timeout > 0x1FFFFF) break;
}
DMA2D->IFCR |= DMA2D_FLAG_TC;
}