目录
1. 下载LVGL源代码
2. 平台
3. 导入到工程
3.1 配置头文件
3.2 src文件夹
4. 移植
4.1 显示接口部分
4.1.1 disp_init
4.1.2 lv_port_disp_init
4.1.3 disp_flush
4.2 IPA部分
4.2.1 lv_draw_gd32_ipa_init
4.2.2 lv_draw_gd32_ipa_blend_fill
4.2.3 lv_draw_gd32_ipa_blend_map
4.2.3 lv_gpu_gd32_ipa_wait_cb
4.3 tick
5. 初始化
LVGL是Light and Versatile Graphics Library(轻量级通用型图形库)的简称,遵循MIT开源许可协议。
LVGL的官网地址如下:
LVGL - Light and Versatile Embedded Graphics Library
LVGL中文资料:
http://lvgl.z.net
1. 下载LVGL源代码
源代码在Github上。
LVGL部分:
GitHub - lvgl/lvgl: Powerful and easy-to-use embedded GUI library with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash).https://github.com/lvgl/lvgl
驱动部分(这部分和外部驱动芯片有关,例如ST7565之类的,而GD32F450自带驱动,所以实际不需要):
GitHub - lvgl/lv_drivers: TFT and touch pad drivers for LVGL embedded GUI libraryhttps://github.com/lvgl/lv_drivers
2. 平台
平台1:
GD的开发板GD32450I_EVAL,即GD32GD32F450 + 4.3寸480x272。
编译环境是MDK + GCC。
平台2:
Visual Studio模拟器,下载地址:
GitHub - lvgl/lv_port_win_visual_studio: LVGL Windows Simulator Visual Studio Edition
Visual Studio的版本要求是2019版及以上,下载后打开工程LVGL.Simulator.sln,将默认的平台改为X64(根据自己的Windows配置修改),编译后可以运行看看效果。如果只用这个模拟器学习,可以不看后面的GD32F450的移植。
3. 导入到工程
3.1 配置头文件
根目录下的lv_conf_template.h和lvgl.h拷贝到工程中,并将lv_conf_template.h改名为lv_conf.h。
lv_conf.h是会随项目情况改动到的,放在项目相关的目录内,这里做个另外的lv_config.h,里面只是include真正的lv_config.h文件。而lvgl.h不会更改,而且和代码内的包含路径有关,可以保留在原来的相对路径内。
其中lv_config.h的内容:
#ifndef LV_CONF_H
#define LV_CONF_H
#include "..\..\include.h"
#endif
其中include.h中会include真正的配置文件。
把配置文件中的配置打开,默认有一个宏定义设置为0的,改成1。
3.2 src文件夹
把文件加入MDK的工程,draw文件夹里面还有一些文件夹,可以先不加,和平台移植有关。
4. 移植
draw文件夹中有部分代码和平台有关,这里拷贝分和平台有关的代码,例如stm32_dma2d这个文件夹并改名字为gd32_ipa,里面的文件名改为lv_gpu_gd323_ipa.c和lv_gpu_gd323_ipa.h,将所有的stm32和dma2d的字符串改为gd32和ipa。
搜索LV_USE_GPU_STM32_DMA2D,参照改一个LV_USE_GPU_GD32_IPA,将lv_gpu_gd323_ipa.c中的函数内有关STM32的部分删除。
4.1 显示接口部分
在之前下载的lvgl文件夹里面\examples\porting文件夹下有对应的硬件接口文件lv_port_disp_template.c和lv_port_disp_template.h。这个文件主要要实现函数disp_init、lv_port_disp_init和disp_flush。
4.1.1 disp_init
显示初始化,主要是硬件配置和TLI接口初始化。
要使用到SDRAM,初始化好TLI接口,并且定义好一个Layer。
定义指针变量pTFTBuf指向SDRAM,因为这里定义格式为RGB565,大小为TFT_WIDTH * TFT_HEIGHT * 2字节。
#define GUI_TFT_BUF_START EXMC_SDRAM_ADDR0
#define GUI_TFT_BUF_LEN (TFT_WIDTH * TFT_HEIGHT * sizeof(lv_color_t))
uint16_t *pTFTBuf = (uint16_t *)GUI_TFT_BUF_START;
选择Layer 0作为显示层,初始化这个层。
tliLayer_t layer;
layer.alpha = 0xFF;
layer.bufAddr = (uint32_t)pTFTBuf;
layer.defalutColor = 0x000000FF;//0x00FFFFFF;
layer.format = FORMAT_RGB565;
layer.x = 0;
layer.y = 0;
layer.w = TFT_WIDTH;
layer.h = TFT_HEIGHT;
tliLayerInit(0, layer, 1);
4.1.2 lv_port_disp_init
这个函数里面需要选择1种显示缓冲,文件提供了3种方式的缓冲方式,方式1/2/3对应的刷新速度分别是慢/中/快,而RAM占用是少/中/多。这里选择方式3,分配的空间放在SDRAM中(这里注意,如果RAM不够会导致Hard Fault中断,我选择方式2会在第四次调用disp_flush后出错)。
#define GUI_DISP_BUF1_START (GUI_TFT_BUF_START + GUI_TFT_BUF_LEN)
#define GUI_DISP_BUF1_LEN GUI_TFT_BUF_LEN
#define GUI_DISP_BUF2_START (GUI_DISP_BUF1_START + GUI_DISP_BUF1_LEN)
#define GUI_DISP_BUF2_LEN GUI_TFT_BUF_LEN
static lv_disp_draw_buf_t draw_buf_dsc;
static lv_color_t *dispbuf_1 = (lv_color_t *)GUI_DISP_BUF1_START;
static lv_color_t *dispbuf_2 = (lv_color_t *)GUI_DISP_BUF2_START;
lv_disp_draw_buf_init(&draw_buf_dsc, dispbuf_1, dispbuf_2, GUI_DISP_BUF1_LEN); /*Initialize the display buffer*/
4.1.3 disp_flush
对于GD32F450的TLI来说,flush就是对Layer对应的buffer更新数据。
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
int32_t x;
int32_t y;
uint16_t *pTFTBuf = (uint16_t *)GUI_TFT_BUF_START;
//Printf("draw:(%d, %d) - (%d, %d)\n", area->x1, area->y1, area->x2, area->y2);
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
/*Put a pixel to the display. For example:*/
/*put_px(x, y, *color_p)*/
*(uint16_t *)(pTFTBuf + y * TFT_WIDTH + x) = (*color_p).full;
color_p++;
}
}
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
4.2 IPA部分
4.2.1 lv_draw_gd32_ipa_init
初始化ipa,并设置颜色模式。
void lv_draw_gd32_ipa_init(void)
{
RCU_AHB1EN |= ((uint32_t)1 << 23);
//IPA Reset
RCU_AHB1RST |= ((uint32_t)1 << 23);
RCU_AHB1RST &= ~((uint32_t)1 << 23);
/*set output colour mode*/
IPA_CTL |= ((uint32_t)1 << 2);
IPA_DPCTL = LV_IPA_COLOR_FORMAT;
}
4.2.2 lv_draw_gd32_ipa_blend_fill
这个函数的作用是用指定的颜色刷新显存。函数原型:
static void lv_draw_gd32_ipa_blend_fill(lv_color_t * dest_buf, lv_coord_t dest_stride,
const lv_area_t * fill_area, lv_color_t color)
参数含义:
dest_buf - 显存地址
dest_stride - 显存地址偏移量
fill_area - 填充的区域,长方形(x1,y1)- (x2, y2)
color - 填充的颜色
static void lv_draw_gd32_ipa_blend_fill(lv_color_t * dest_buf, lv_coord_t dest_stride, const lv_area_t * fill_area,
lv_color_t color)
{
/*Simply fill an area*/
int32_t area_w = lv_area_get_width(fill_area);
int32_t area_h = lv_area_get_height(fill_area);
invalidate_cache();
IPA_CTL |= ((uint32_t)1 << 2); //Stop IPA
IPA_CTL &= ~((uint32_t)0x3 << 16);
IPA_CTL |= ((uint32_t)0x3 << 16); //Specific color fill
IPA_DMADDR = (uint32_t)dest_buf;
IPA_DPV = color.full;
IPA_DLOFF = dest_stride - area_w;
IPA_IMS = (area_w << 16) | (area_h << 0);
IPA_CTL |= ((uint32_t)1 << 0);
}
4.2.3 lv_draw_gd32_ipa_blend_map
这个函数的作用是指定buffer更新显存。
static void lv_draw_gd32_ipa_blend_map(
lv_color_t * dest_buf,
const lv_area_t * dest_area,
lv_coord_t dest_stride,
const lv_color_t * src_buf,
lv_coord_t src_stride,
lv_opa_t opa)
参数含义:
dest_buf - 显存地址
dest_area - 填充的区域
dest_stride - 显存地址偏移量
src_buf - 源数据地址
dest_stride - 源数据地址偏移量
opa - alpha值
static void lv_draw_gd32_ipa_blend_map(lv_color_t * dest_buf, const lv_area_t * dest_area, lv_coord_t dest_stride,
const lv_color_t * src_buf, lv_coord_t src_stride, lv_opa_t opa)
{
/*Simple copy*/
int32_t dest_w = lv_area_get_width(dest_area);
int32_t dest_h = lv_area_get_height(dest_area);
invalidate_cache();
if(opa >= LV_OPA_MAX) {
IPA_CTL |= ((uint32_t)1 << 2); //Stop IPA
IPA_CTL &= ~((uint32_t)0x3 << 16);
IPA_CTL = ((uint32_t)0x0 << 16);
IPA_FPCTL = LV_IPA_COLOR_FORMAT;
IPA_FMADDR = (uint32_t)src_buf;
IPA_FLOFF = src_stride - dest_w;
IPA_DMADDR = (uint32_t)dest_buf;
IPA_DLOFF = dest_stride - dest_w;
IPA_IMS = (dest_w << 16) | (dest_h << 0);
IPA_CTL |= ((uint32_t)1 << 0); //Start IPA
}
else {
IPA_CTL |= ((uint32_t)1 << 2); //Stop IPA
IPA_CTL &= ~((uint32_t)0x3 << 16);
IPA_CTL |= ((uint32_t)0x2 << 16);
IPA_BPCTL = LV_IPA_COLOR_FORMAT;
IPA_BMADDR = (uint32_t)dest_buf;
IPA_BLOFF = dest_stride - dest_w;
IPA_FPCTL = (uint32_t)LV_IPA_COLOR_FORMAT
/*alpha mode 2, replace with foreground * alpha value*/
| (2 << 16)
/*alpha value*/
| (opa << 24);
IPA_FMADDR = (uint32_t)src_buf;
IPA_FLOFF = src_stride - dest_w;
IPA_DMADDR = (uint32_t)dest_buf;
IPA_DLOFF = dest_stride - dest_w;
IPA_IMS = (dest_w << 16) | (dest_h << 0);
IPA_CTL |= ((uint32_t)1 << 0); //Start IPA
}
}
4.2.3 lv_gpu_gd32_ipa_wait_cb
这个函数的作用是等待渲染结束。
void lv_gpu_gd32_ipa_wait_cb(lv_draw_ctx_t * draw_ctx)
{
lv_disp_t * disp = _lv_refr_get_disp_refreshing();
if(disp->driver && disp->driver->wait_cb) {
while(IPA_CTL & 0x01) {
disp->driver->wait_cb(disp->driver);
}
}
else {
while(IPA_CTL & 0x01);
}
lv_draw_sw_wait_for_finish(draw_ctx);
}
4.3 tick
LVGL 需要一个系统滴答来了解动画和其他任务(例如输入设备读取)所用的时间,所以需要在一个定时器中调用lv_tick_inc。
lv_tick_inc(TIMER_MS);
定时调用这个函数即可,实现方式可以随意,裸奔的方式可以在system tick中调用。
5. 初始化
在使用其他LVGL的API前必须调用lv_init(),然后调用lv_port_disp_init即可。
lv_init();
lv_port_disp_init();
这时候屏幕应该是没有显示(如果把pTFTBuf 的数据全部改成0xF800,即红色,可以看到屏幕为红色,LVGL并没有起作用)。
在非OS应用中,需要在主循环中添加lv_task_handler();而在OS应用中,应该是在一个任务循环中添加这个函数的调用。
这是屏幕会显示白屏,添加一段测试程序(在lv_port_disp_init()后添加即可)。
lv_obj_t *rect = lv_obj_create(lv_scr_act());
lv_obj_set_size(rect, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect, LV_ALIGN_CENTER, 0, 0);
即画一个矩形。
模拟器显示效果: