1.准备素材 首先我们需要从 https://littlevgl.com/download/lv_pc_simulator.zip 链接上下载到lv_pc_simulator.zip压缩包。此处默认读者有一定的STM32开发基础,已经建好带有触摸屏驱动的工程。
2.导入littleVGL库到Keil中 在项目根目录下新建GUI和GUI_APP俩个子目录,即和USER目录是同级别的,GUI目录是用来存放跟littleVGL库相关的所有文件的,而 GUI_APP是用来放我们自己的GUI 应用代码的,因为现在才刚开始移植,还来不及自己写 GUI 应用,所以 GUI_APP 目录里面先留空,我们的重点是来介绍 GUI 目录。 接着把 lv_pc_simulator.zip 压缩包里面的 lv_examples.zip 和 lvgl.zip 俩个子压缩包直接拷贝到 GUI 目录下,拷贝完成之后,接着分别对lv_examples.zip 和 lvgl.zip 俩个子压缩包在当前目录下进行解压缩操作,解压缩完成后,可以把 lv_examples.zip 和 lvgl.zip 都删除了,接着把 GUI/lvgl/lv_conf_template.h 和 GUI/lv_examples/lv_ex_conf_templ.h 俩个配置模板文件统统拷贝到 GUI 目录下,然后对这个 2 文件分别重命名为 lv_conf.h和 lv_ex_conf.h,接着还要在 GUI 目录下新建一个 lvgl_driver子目录,这个目录是用来放底层显示驱动和触摸驱动文件的。接下来,我们要在分组管理面板中添加GUI和GUI_APP两个分组,在GUI分组中,把 template\GUI\lvgl\src\lv_core template\GUI\lvgl\src\lv_draw template\GUI\lvgl\src\lv_font template\GUI\lvgl\src\lv_hal template\GUI\lvgl\src\lv_misc template\GUI\lvgl\src\lv_objx template\GUI\lvgl\src\lv_themes 目录下的所有.c文件全部添加到GUI分组中,最后如图所示: 接着我们需要做2个比较重要的小操作,加大项目的栈空间到 2KB 和使能 C99 编译器功能,打开 Core 分组下的.s 启动文件,修改 Stack_Size 的值到 2KB(0x00000800)3.修改 lv_conf.h 和 lv_ex_conf.h 配置文件 打开lv_conf.h文件,第一个#if 后面的0 改为1,使整个文件生效,接着修改LV_HOR_RES_MAX 和 LV_VER_RES_MAX 宏的值,这个是告诉 littleVGL 你所用的液晶屏分辨率是多少,请根据自己手头液晶屏的实际分辨率大小相应设置。 接着我们修改 LV_COLOR_DEPTH 颜色深度,最常见的设置就是 1 或者 16 了,1 是用于单色屏,而 16 是用于彩色屏,这里我们设置成 16 即可。 再接着我们来设置 LV_DPI 的值,默认值为 100,我们把他设置到 60,这个宏是用来调节界面缩放比例的,此值越大,控件分布的就越散,控件自身的间隔也会变大。 再接着修改 LV_MEM_SIZE 的大小,这个就是控制 littleVGL 中所谓的动态数据堆的大小,是用来给控件的创建动态分配空间的,我们这里设置为 16KB 的大小。 再接着修改 LV_USE_GPU 的值,默认值是 1,我们把它设置为 0,即不使能 GPU 功能。 再接着修改LV_USE_FILESYSTEM 的值,其默认值为1,我们把他设置为0,即不使能文件系统的功能。 至此 lv_conf.h 文件修改就完成了,接下来我们要修改 lv_ex_conf.h 文件,这个文件就简单很多了,而且这个文件也不是很重要,只有当我们要演示官方自带的例程时,才会用到。若需演示官方例程,把 LV_EX_KEYBOARD, LV_EX_MOUSEWHEEL, LV_USE_TESTS, LV_USE_TUTORIALS, LV_USE_BENCHMARK, LV_USE_DEMO, LV_USE_TERMINAL 等宏的值全部设置为 1,其他宏保持默认即可。 4.添加定时器,为 littleVGL 提供心跳节拍 这里我打算采用定时器 3,设置其每隔 1ms 进入中断,为 littleVGL 提供 1ms 的心跳节拍,当然你也可以采用其他的定时器,原理都是一样的。
//定时器3中断服务程序 void TIM3_IRQHandler(void) { if(TIM3->SR&TIM_IT_Update)//溢出中断 { lv_tick_inc(1);//lvgl的1ms心跳 } TIM3->SR = (uint16_t)~TIM_IT_Update; }然后在 main 函数中加入定时器的 TIM3_Int_Init(arr, psc)初始化代码,必须保证中断间隔为1ms。
5.移植底层显示驱动 littleVGL 官方给我们提供了显示驱动,输入驱动,文件系统驱动的模板文件,存放在template\GUI\lvgl\porting 目录下,这里我们不需要文件系统,所以只需要把 lv_port_disp_template.c, lv_port_disp_template.h, lv_port_indev_template.c,lv_port_indev_template.h 四个文件拷贝到 template\GUI\lvgl_driver 目录下面,并分别重命名为 lv_port_disp.c, lv_port_disp.h, lv_port_indev.c, lv_port_indev.h,前面 2 个文件是跟显示驱动相关的,后面俩个文件是跟触摸驱动相关的,我们先只需要修改lv_port_disp.c, lv_port_disp.h 俩个文件的内容就可以了,通过看注释,发现并不难移植,我这里直接给出正确的移植代码。
lv_port_disp.h 文件内容如下:
/** * @file lv_port_disp.h * */ /*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/ #if 1 #ifndef LV_PORT_DISP_H #define LV_PORT_DISP_H #ifdef __cplusplus extern "C" { #endif /********************* * INCLUDES *********************/ #include "lvgl/lvgl.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * GLOBAL PROTOTYPES **********************/ /********************** * MACROS **********************/ void lv_port_disp_init(void); #ifdef __cplusplus } /* extern "C" */ #endif #endif /*LV_PORT_DISP_H*/ #endif /*Disable/Enable content*/lv_port_disp.c 文件内容如下:
/** * @file lv_port_disp.c * */ /*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/ #if 1 /********************* * INCLUDES *********************/ #include "lv_port_disp.h" #include "lcd.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static void disp_init(void); static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); #if LV_USE_GPU static void gpu_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa); static void gpu_fill(lv_color_t * dest, uint32_t length, lv_color_t color); #endif /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_port_disp_init(void) { /*------------------------- * Initialize your display * -----------------------*/ disp_init(); /*----------------------------- * Create a buffer for drawing *----------------------------*/ /* Example for 1) */ static lv_disp_buf_t disp_buf_1; static lv_color_t buf1_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/ lv_disp_buf_init(&disp_buf_1, buf1_1, NULL, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/ /*----------------------------------- * Register the display in LittlevGL *----------------------------------*/ 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 = lcddev.width; disp_drv.ver_res = lcddev.height; /*Used to copy the buffer's content to the display*/ disp_drv.flush_cb = disp_flush; /*Set a display buffer*/ disp_drv.buffer = &disp_buf_1; #if LV_USE_GPU /*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/ /*Blend two color array using opacity*/ disp_drv.gpu_blend = gpu_blend; /*Fill a memory array with a color*/ disp_drv.gpu_fill = gpu_fill; #endif /*Finally register the driver*/ lv_disp_drv_register(&disp_drv); } /********************** * STATIC FUNCTIONS **********************/ /* Initialize your display and the required peripherals. */ static void disp_init(void) { /*You code here*/ } /* 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) { LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p); /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv); } /*OPTIONAL: GPU INTERFACE*/ #if LV_USE_GPU /* If your MCU has hardware accelerator (GPU) then you can use it to blend to memories using opacity * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/ static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa) { /*It's an example code which should be done by your GPU*/ uint32_t i; for(i = 0; i < length; i++) { dest[i] = lv_color_mix(dest[i], src[i], opa); } } /* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/ static void gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width, const lv_area_t * fill_area, lv_color_t color); { /*It's an example code which should be done by your GPU*/ uint32_t x, y; dest_buf += dest_width * fill_area->y1; /*Go to the first line*/ for(y = fill_area->y1; y < fill_area->y2; y++) { for(x = fill_area->x1; x < fill_area->x2; x++) { dest_buf[x] = color; } dest_buf+=dest_width; /*Go to the next line*/ } } #endif /*LV_USE_GPU*/ #else /* Enable this file at the top */ /* This dummy typedef exists purely to silence -Wpedantic. */ typedef int keep_pedantic_happy; #endif6.移植底层触摸驱动 littleVGL 是支持很多种输入设备的,像 Touchpad, Mouse, Keypad, Encoder, Button 等统统支持,而通常情况下,我们用的最多的就是触摸屏了,他属于 Touchpad 类,通过打开template\GUI\lvgl_driver 目录下的 lv_port_indev.c 和 lv_port_indev.h,文件,不难发现只需要实现 lv_port_indev_init 和touchpad_read 俩个 API 接口就行了,其他的统统可以删除,下面我给出正确的移植代码: lv_port_indev.c 文件的内容如下:
/** * @file lv_port_indev.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.h" #include "touch.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_port_indev_init(void) { lv_indev_drv_t indev_drv; /*------------------ * Touchpad * -----------------*/ /*Register a touchpad input device*/ lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = touchpad_read; lv_indev_drv_register(&indev_drv); } /********************** * STATIC FUNCTIONS **********************/ /* Will be called by the library to read the touchpad */ static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { static uint16_t last_x = 0; static uint16_t last_y = 0; if(tp_dev.sta&TP_PRES_DOWN)//触摸按下了 { last_x = tp_dev.x[0]; last_y = tp_dev.y[0]; data->point.x = last_x; data->point.y = last_y; data->state = LV_INDEV_STATE_PR; }else{ data->point.x = last_x; data->point.y = last_y; data->state = LV_INDEV_STATE_REL; } /*Return `false` because we are not buffering and no more data to read*/ return false; } #else /* Enable this file at the top */ /* This dummy typedef exists purely to silence -Wpedantic. */ typedef int keep_pedantic_happy; #endif到此为止,移植基本完成,大家可以自行将官方例程添加至GUI_APP下自行查看效果。