【EASY EAI Nano开源套件试用体验】6. LVGL软件的移植与测试
大信(QQ:8125036)
电子发烧友和灵眸科技推出EASY EAI Nano
开发板的试用。该开发板采用的是Rockchip 的RV1126处理器,该处理器拥有四核32位Cortex®-A7架构,集成2.0 TOPs AI算力的NPU。能够广泛适用物联网边缘计算以及智能应用,如人脸闸机、车载录像、安防监控等方面。
EASY EAI Nano所带的SDK带有QT图形界面,QT是一个应用非常广泛的界面库。开发板上的人脸识别门禁应用就是使用QT做为图形界面。在移植一个我的一款QT应用时发现,该开发板虽然有QT,但没有QTSVG等库,也就是并没有支持完全,导致移植失败。而在QT上开发,矢量图形是常用的资源,能够解决不同屏幕尺寸的适配问题。
因而转向尝试在EASYEAI Nano上移植LVGL 用户图形库,在EASY EAI Nano上测试LVGL图形应用。
一、LVGL简介
LVGL(Light and Versa
tileGraphics Library,轻巧而多功能的图形库)是一个免费的开放源代码图形库LVGL的作者是来自匈牙利的Gabor Kiss-Vamosikisvegabor,LVGL用C语言编写,以实现最大的兼容性(与C ++兼容),模拟器可在没有嵌入式硬件的PC上启动嵌入式GUI设计,同时LVGL作为一个图形库,它自带着接近三十多种小工具可以供开发者使用。这些强大的构建块按钮搭配上带有非常丝滑的动画以及可以做到平滑滚动的高级图形,同时兼具着不高的配置要求以及开源属性,显著的优势使得LVGL蔚然成风,成为广大开发者在选择GUI时的第一选择。
二、LVGL主要特性
强大的UI组件,如按钮,图表,列表,滑块,图像等。
高级图形动画,抗锯齿,不透明度,平滑滚动
各种输入设备,如触摸板、鼠标、键盘、编码器等
多语言支持与UTF-8编码
多显示器支持,即使用更多的TFT,单色显示器同时
完全可定制的图形元素与css类样式
硬件独立与任何微控制器或显示器使用
可扩展,使用少量内存(64kb Flash, 16kb RAM)
支持操作系统、外部内存和GPU,但不是必需的
单帧缓冲操作,甚至与高级图形效果
用C编写的最大兼容性(c++兼容)
模拟器在没有嵌入式硬件的PC上开始嵌入式GUI设计
绑定到MicroPython
教程,例子,快速GUI设计的主题
文档可以在线和PDF格式获取
MIT许可下的免费和开源
三.运行硬件要求
配置要求
基本上,每个能够驱动显示器的现代控制器都适合运行 LVGL。最低要求是:
16、32 或64 位微控制器或处理器
建议使用 16MHz 时钟速度
闪存/ROM:> 64 kB 用于非常重要的组件 (> 建议使用 180 kB)
RAM:
静态 RAM 使用量:0~2 kB,取决于使用的功能和对象类型
堆: >2kB (> 建议使用 8 kB)
动态数据(堆):> 2 KB (> 如果使用多个对象,建议使用 16 kB). 在 lv_conf.h 文件中配置 LV_MEM_SIZE 生效。
显示缓冲区:>“水平分辨率”像素(推荐> 10 × 10ד水平分辨率”)
MCU 或外部显示控制器中的一个帧缓冲器
C99 或更新的编译器
四、移植适配预编译
LVGL 是由三个工程组成的,分别是核心库,驱动库,演示应用程序库。
1.从github上拉取最新lvgl 核心库源码
2.从github上拉取最新驱动库源码
3.从github上拉取演示例子源码
2.查看工程代码结构
3.重新整理工程目录
重新整理工程目录,把三个工程都放到src子目录下,形成一个新的完整的工程
4. 编写项目编译配置脚本
编写总工程的CMake脚本,内容如下:
5.编写各个子模块编译配置脚本
进入src目录,编写项目cmakefile文件,内容如下
lvgl子模块的cmake脚本
lv_drivers子模块的cmake脚本
lv_demos子模块的cmake脚本
6. 初始化软件配置文件
把 lvgl/lv_conf_template.h模板复制到上层目录,并且改名lv_conf.h
把 lv_drivers/lv_drv_conf_template.h模板复制到上层目录,并且改名lv_drv_conf.h
把lv_demos/ lv_ex_conf_template.h 模板复制到上层目录,并且改名 lv_ex_conf.h
这两个文件是LVGL移植的头文件,所有与移植有关的参数都在这两个文件里定义。
7.修改模型的头文件里的配置
修改lv_conf.h里如下的配置,主要是与屏幕有关,屏幕大小,屏幕DPI,图像内存分配有关的参数
修改lv_drv_conf.h文件,配置屏幕显示使用的设备和触摸屏输入使用的设备参数:
修改lv_ex_conf.h文件,配置打开哪个演示程序,为防止冲突,一次最好打开一个,变化设置设置轮番测试。
8.添加一些外部的代码
添加 main.cpp ,用于启动一个LVGL例子,可以从lvgl/example里复制一个例程出来。
- #include "lvgl.h"
- #include
- #include "lv_port_disp.h"
- #include "lv_port_indev.h"
- #include "lv_demo_widgets.h"
- #include "lv_demo_music.h"
- #include "lv_demos/lv_examples.h"
- void lvgl_first_demo_start(void);
- int main(void)
- {
- lv_init(); //lvgl 系统初始化
- lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面
- lv_port_indev_init(); //lvgl 输入接口初始化,放在 lv_init()的后面
- //lvgl_first_demo_start(); //打开这个注释就是一个画button的简单demo 注意注释掉下面的lv_demo_widgets
- lv_demo_widgets();
- //lv_demo_music();
- //lv_demo_benchmark();
- printf("start demo n");
- while(1)
- {
- lv_task_handler();
- }
- }
- void tim_lv_tick()
- {
- lv_tick_inc(1);//lvgl 的 1ms 心跳
- }
- static void btn_event_cb(lv_obj_t * btn, lv_event_t event)
- {
- if(event == LV_EVENT_CLICKED) {
- static uint8_t cnt = 0;
- cnt++;
- /*Get the first child of the button which is the label and change its text*/
- lv_obj_t * label = lv_obj_get_child(btn, NULL);
- lv_label_set_text_fmt(label, "Button: %d", cnt);
- }
-
- }
- void lvgl_first_demo_start(void)
- {
- lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL); /*Add a button the current screen*/
- lv_obj_set_pos(btn, 10, 10); /*Set its position*/
- lv_obj_set_size(btn, 120, 50); /*Set its size*/
- lv_obj_set_event_cb(btn, btn_event_cb); /*Assign a callback to the button*/
- lv_obj_t * label = lv_label_create(btn, NULL); /*Add a label to the button*/
- lv_label_set_text(label, "Button"); /*Set the labels text*/
- lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
- lv_label_set_text(label1, "Hello world!");
- lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
- lv_obj_align(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);
- }
添加系统时钟的实现,LVGL需要调用。添加sys_tick.c, sys_tick.h
- #include "sys_tick.h"
- #include
- uint32_t custom_tick_get(void)
- {
- static uint64_t start_ms = 0;
- if(start_ms == 0) {
- struct timeval tv_start;
- gettimeofday(&tv_start, NULL);
- start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
- }
- struct timeval tv_now;
- gettimeofday(&tv_now, NULL);
- uint64_t now_ms;
- now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
- uint32_t time_ms = now_ms - start_ms;
- return time_ms;
- }
- #ifndef __SYS_TICK__H
- #define __SYS_TICK__H
- #include
- #include
- #include
- extern uint32_t custom_tick_get(void);
- #endif
9.开始进行编译
做完上面的修改和代码添加后,可以进行初步的编译,进入工程根目录,进行cmake配置编译:
mkdirbuild
cdbuild
cmake..
cmake脚本执行完毕后,会产生makefile文件,然后就可以进行编译,执行
make–j4
经过检查,目标文件已经编译成功。
五、适配层代码开发
把上面编译出的目标文件传动到板子里运行时,发现屏幕是黑的,什么都看不到,但是程序没有报出错或者退出。说明LVGL已经基本的运行起来了,只是显示的代码没有生效。
经过分析代码后发现,LVGL为了适配多种硬件,因此它把显示的驱动代码放在lv_drivers/lv_port_disp.c 里,需要移植者根据硬件来完成这个适配。
经过研究,只需要做小的开发修改,即可完成适配的代码,主要修改为在输出化时,调用drm的初始化,并且规划好双缓冲的屏显方式,并且分配好显存空间,分别修改 lv_port_disp_init、disp_init、disp_flush这三个函数即可。
修改后的代码如下:
- /**
- * [url=home.php?mod=space&uid=1455510]@file[/url] lv_port_disp_templ.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 "display/drm.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_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
- static void gpu_fill(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);
- #endif
- /**********************
- * STATIC VARIABLES
- **********************/
- /**********************
- * MACROS
- **********************/
- /**********************
- * GLOBAL FUNCTIONS
- **********************/
- void lv_port_disp_init(void)
- {
- /*-------------------------
- * Initialize your display
- * -----------------------*/
- disp_init();
- /*-----------------------------
- * Create a buffer for drawing
- *----------------------------*/
- /* LVGL requires a buffer where it internally draws the widgets.
- * Later this buffer will passed your display drivers `flush_cb` to copy its content to your display.
- * The buffer has to be greater than 1 display row
- *
- * There are three buffering configurations:
- * 1. Create ONE buffer with some rows:
- * LVGL will draw the display's content here and writes it to your display
- *
- * 2. Create TWO buffer with some rows:
- * LVGL will draw the display's content to a buffer and writes it your display.
- * You should use DMA to write the buffer's content to the display.
- * It will enable LVGL to draw the next part of the screen to the other buffer while
- * the data is being sent form the first buffer. It makes rendering and flushing parallel.
- *
- * 3. Create TWO screen-sized buffer:
- * Similar to 2) but the buffer have to be screen sized. When LVGL is ready it will give the
- * whole frame to display. This way you only need to change the frame buffer's address instead of
- * copying the pixels.
- * */
- #if 0
- /* Example for 1) */
- static lv_disp_buf_t draw_buf_dsc_1;
- static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
- lv_disp_buf_init(&draw_buf_dsc_1, draw_buf_1, NULL, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/
- /* Example for 2) */
- static lv_disp_buf_t draw_buf_dsc_2;
- static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
- static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * 10]; /*An other buffer for 10 rows*/
- lv_disp_buf_init(&draw_buf_dsc_2, draw_buf_2_1, draw_buf_2_2, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/
- #endif
- /* Example for 3) */
- static lv_disp_buf_t draw_buf_dsc_3;
- static lv_color_t draw_buf_3_1[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*A screen sized buffer*/
- static lv_color_t draw_buf_3_2[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*An other screen sized buffer*/
- lv_disp_buf_init(&draw_buf_dsc_3, draw_buf_3_1, draw_buf_3_2, LV_HOR_RES_MAX * LV_VER_RES_MAX); /*Initialize the display buffer*/
- /*-----------------------------------
- * Register the display in LVGL
- *----------------------------------*/
- 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*/
- lv_coord_t w=0,h=0;
- uint32_t d=0;
- drm_get_sizes(&w,&h,&d);
- //LV_LOG_INFO("drm_get_sizes %d x %d , dpi %d n", w, h, d );
- /*Set the resolution of the display*/
- disp_drv.hor_res = 1280;
- disp_drv.ver_res = 720;
-
- /*Used to copy the buffer's content to the display*/
- disp_drv.flush_cb = disp_flush;
- /*Set a display buffer*/
- disp_drv.buffer = &draw_buf_dsc_3;
- #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_cb = gpu_blend;
- /*Fill a memory array with a color*/
- disp_drv.gpu_fill_cb = gpu_fill;
- #endif
- /*Finally register the driver*/
- lv_disp_t *disp=lv_disp_drv_register(&disp_drv);
- lv_disp_set_default(disp);
- // lv_disp_set_rotation(disp, LV_DISP_ROT_90);
- }
- /**********************
- * STATIC FUNCTIONS
- **********************/
- /* Initialize your display and the required peripherals. */
- static void disp_init(void)
- {
- LV_LOG_INFO("disp_init....");
- /*You code here*/
- drm_init();
- }
- /* 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)
- {
- /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
- #if 0
- int32_t x;
- int32_t y;
- 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)*/
- // color_p++;
- }
- }
- #endif
- //lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p);
- drm_flush(disp_drv , area , color_p);
- /* IMPORTANT!!!
- * Inform the graphics library that you are ready with the flushing*/
- // LV_LOG_INFO("....disp_flush....");
- 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);
- }
- LV_LOG_INFO("....gpu_blend....");
- }
- /* 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(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*/
- int32_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;
- #endif
同样修改触摸屏的适配代码lv_port_indev.c,主要修改
touchpad_init,touchpad_read,完成触摸屏事件转换为图形系统的坐标,实现代码如下:
- /**
- * @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.h"
- #include "../lv_drv_conf.h" //添加依赖的头文件
- #include "../tslib.h" //添加tslib库头文件
- #include
- #include
- #include
- #include
- #include
- #include
- #include //如果你用了epoll 或者select可添加此头文件
- #include
- #include
- #include
- #include //如果你是自己处理去抖动那直接读取触摸屏事件可以添加此头文件
- #include
- #include "indev/evdev.h"
- /*********************
- * DEFINES
- *********************/
- /**********************
- * TYPEDEFS
- **********************/
- /**********************
- * STATIC PROTOTYPES
- **********************/
- static void touchpad_init(void);
- static bool 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 void mouse_init(void);
- static bool mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
- static bool mouse_is_pressed(void);
- static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y);
- static void keypad_init(void);
- static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
- static uint32_t keypad_get_key(void);
- static void encoder_init(void);
- static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
- //static void encoder_handler(void);
- static void button_init(void);
- static bool button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
- static int8_t button_get_pressed_id(void);
- static bool button_is_pressed(uint8_t id);
- /**********************
- * STATIC VARIABLES
- **********************/
- lv_indev_t * indev_touchpad;
- lv_indev_t * indev_mouse;
- lv_indev_t * indev_keypad;
- lv_indev_t * indev_encoder;
- lv_indev_t * indev_button;
- static int32_t encoder_diff;
- static lv_indev_state_t encoder_state;
- /**********************
- * MACROS
- **********************/
- /**********************
- * GLOBAL FUNCTIONS
- **********************/
- 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
- */
- lv_indev_drv_t indev_drv;
- /*------------------
- * Touchpad
- * -----------------*/
- /*Initialize your touchpad if you have*/
- touchpad_init();
- /*Register a touchpad input device*/
- 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);
- /*------------------
- * Mouse
- * -----------------*/
- /*Initialize your touchpad if you have*/
- mouse_init();
- /*Register a mouse input device*/
- lv_indev_drv_init(&indev_drv);
- indev_drv.type = LV_INDEV_TYPE_POINTER;
- indev_drv.read_cb = mouse_read;
- indev_mouse = lv_indev_drv_register(&indev_drv);
- /*Set cursor. For simplicity set a HOME symbol now.*/
- lv_obj_t * mouse_cursor = lv_img_create(lv_disp_get_scr_act(NULL), NULL);
- lv_img_set_src(mouse_cursor, (const void *)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*/
- lv_indev_drv_init(&indev_drv);
- indev_drv.type = LV_INDEV_TYPE_KEYPAD;
- indev_drv.read_cb = keypad_read;
- indev_keypad = lv_indev_drv_register(&indev_drv);
- /* 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*/
- lv_indev_drv_init(&indev_drv);
- indev_drv.type = LV_INDEV_TYPE_ENCODER;
- indev_drv.read_cb = encoder_read;
- indev_encoder = lv_indev_drv_register(&indev_drv);
- /* 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*/
- lv_indev_drv_init(&indev_drv);
- indev_drv.type = LV_INDEV_TYPE_BUTTON;
- indev_drv.read_cb = button_read;
- indev_button = lv_indev_drv_register(&indev_drv);
- /*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);
- }
- /**********************
- * STATIC FUNCTIONS
- **********************/
- /*------------------
- * Touchpad
- * -----------------*/
- /*Initialize your touchpad*/
- static void touchpad_init(void)
- {
- /*Your code comes here*/
- evdev_init();
- }
- /* 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 lv_coord_t last_a = 0;
- static lv_coord_t last_b = 0;
-
- static lv_coord_t last_x = 0;
- static lv_coord_t last_y = 0;
- //static lv_coord_t tmp;
- /*Save the pressed coordinates and the state*/
- if(touchpad_is_pressed()) {
- printf("is_pressed !n");
- touchpad_get_xy(&last_x, &last_y);
- data->state = LV_INDEV_STATE_PR;
- } else {
- data->state = LV_INDEV_STATE_REL;
- }
- /*Set the last pressed coordinates*/
- data->point.x = last_x;
- data->point.y = last_y;
-
- evdev_read(indev_drv,data);
-
- if(data->point.x != last_a || data->point.y != last_b)
- {
- last_a = data->point.x; last_b = data->point.y;
- //printf("evdev x,y=%d,%dn", data->point.x, data->point.y);
- }
-
- // tmp = data->point.x;
- //data->point.x = data->point.y ;
- //data->point.y = 719-tmp;
- /*Return `false` because we are not buffering and no more data to read*/
- return false;
- }
- /*Return true is the touchpad is pressed*/
- static bool touchpad_is_pressed(void)
- {
- /*Your code comes here*/
- return false;
- }
- /*Get the x and y coordinates if the touchpad is pressed*/
- static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
- {
- /*Your code comes here*/
- (*x) = 0;
- (*y) = 0;
- }
- /*------------------
- * Mouse
- * -----------------*/
- /* Initialize your mouse */
- static void mouse_init(void)
- {
- /*Your code comes here*/
- }
- /* Will be called by the library to read the mouse */
- static bool mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
- {
- /*Get the current x and y coordinates*/
- mouse_get_xy(&data->point.x, &data->point.y);
- /*Get whether the mouse button is pressed or released*/
- if(mouse_is_pressed()) {
- data->state = LV_INDEV_STATE_PR;
- } else {
- data->state = LV_INDEV_STATE_REL;
- }
- /*Return `false` because we are not buffering and no more data to read*/
- return false;
- }
- /*Return true is the mouse button is pressed*/
- static bool mouse_is_pressed(void)
- {
- /*Your code comes here*/
- return false;
- }
- /*Get the x and y coordinates if the mouse is pressed*/
- static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y)
- {
- /*Your code comes here*/
- (*x) = 0;
- (*y) = 0;
- }
- /*------------------
- * Keypad
- * -----------------*/
- /* Initialize your keypad */
- static void keypad_init(void)
- {
- /*Your code comes here*/
- }
- /* Will be called by the library to read the mouse */
- static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
- {
- static uint32_t last_key = 0;
- /*Get the current x and y coordinates*/
- mouse_get_xy(&data->point.x, &data->point.y);
- /*Get whether the a key is pressed and save the pressed key*/
- uint32_t act_key = keypad_get_key();
- if(act_key != 0) {
- data->state = LV_INDEV_STATE_PR;
- /*Translate the keys to LVGL control characters according to your key definitions*/
- switch(act_key) {
- case 1:
- act_key = LV_KEY_NEXT;
- break;
- case 2:
- act_key = LV_KEY_PREV;
- break;
- case 3:
- act_key = LV_KEY_LEFT;
- break;
- case 4:
- act_key = LV_KEY_RIGHT;
- break;
- case 5:
- act_key = LV_KEY_ENTER;
- break;
- }
- last_key = act_key;
- } else {
- data->state = LV_INDEV_STATE_REL;
- }
- data->key = last_key;
- /*Return `false` because we are not buffering and no more data to read*/
- return false;
- }
- /*Get the currently being pressed key. 0 if no key is pressed*/
- static uint32_t keypad_get_key(void)
- {
- /*Your code comes here*/
- return 0;
- }
- /*------------------
- * Encoder
- * -----------------*/
- /* Initialize your keypad */
- static void encoder_init(void)
- {
- /*Your code comes here*/
- }
- /* Will be called by the library to read the encoder */
- static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
- {
- data->enc_diff = encoder_diff;
- data->state = encoder_state;
- /*Return `false` because we are not buffering and no more data to read*/
- return false;
- }
- /*Call this function in an interrupt to process encoder events (turn, press)*/
- //static void encoder_handler(void)
- //{
- /*Your code comes here*/
- // encoder_diff += 0;
- // encoder_state = LV_INDEV_STATE_REL;
- //}
- /*------------------
- * Button
- * -----------------*/
- /* Initialize your buttons */
- static void button_init(void)
- {
- /*Your code comes here*/
- }
- /* Will be called by the library to read the button */
- static bool button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
- {
- static uint8_t last_btn = 0;
- /*Get the pressed button's ID*/
- int8_t btn_act = button_get_pressed_id();
- if(btn_act >= 0) {
- data->state = LV_INDEV_STATE_PR;
- last_btn = btn_act;
- } else {
- data->state = LV_INDEV_STATE_REL;
- }
- /*Save the last pressed button's ID*/
- data->btn_id = last_btn;
- /*Return `false` because we are not buffering and no more data to read*/
- return false;
- }
- /*Get ID (0, 1, 2 ..) of the pressed button*/
- static int8_t button_get_pressed_id(void)
- {
- uint8_t i;
- /*Check to buttons see which is being pressed (assume there are 2 buttons)*/
- for(i = 0; i < 2; i++) {
- /*Return the pressed button's ID*/
- if(button_is_pressed(i)) {
- return i;
- }
- }
- /*No button pressed*/
- return -1;
- }
- /*Test if `id` button is pressed or not*/
- static bool button_is_pressed(uint8_t id)
- {
- /*Your code comes here*/
- return false;
- }
- #else /* Enable this file at the top */
- /* This dummy typedef exists purely to silence -Wpedantic. */
- typedef int keep_pedantic_happy;
- #endif
把修改完的代码合并到工程,然后再执行编译,可以得到完成适配的开发板上的可执行文件。
六、板上测试运行把文件传到目标板上运行,即可看道LVGL的界面了,这是一个lv_demo_widgets 的演示,界面显示效果如下:
同样可以修改demo的定义,跑起一个lv_demo_benchmark来全面测试该硬件运行LVGL的效果,后面附带一个视频,即为运行该GUI的效果,可见实测能跑到 22fps,运行非常流程,响应也很灵敏。界面非常漂亮。
七、屏幕旋转显示
在开始的测试中,显示是竖屏的,因为这个开发板系统底层显示驱动模型是 720X1280的显示。跟一些LVGL演示的界面不和谐。
因此需要把显示进行旋转,开始尝试使用LVGLdisp自带的旋转函数 lv_disp_set_rotation(disp, LV_DISP_ROT_90);发现界面虽然旋转了90度,是横向显示,但是屏幕输出仍然是纵向,造成界面只显示坐厕,屏幕下方是黑的。显然这个旋转是界面的方向,不是显示的方向。
显示的旋转应需要在 DRM的驱动里去修改屏幕的输出的方向,经过一番研究,没找找到DRM直接修改显示方向的方法,因时间有限,于是使用了一个简单粗暴的方法,即在刷屏时,把刷屏的数据进行重新调整顺序,达到“旋转”的目标,代码实现在drm.c里,如下:
同样在显示旋转后,触摸屏的坐标也需要“旋转”,除了打开上面配置的交换x,y坐标后,测试时发现,虽然x,y坐标交换了,但是y坐标的方向错了,应该是左上角为原点,向下为正,但实际是左下角为原点,向上为正了,导致屏幕上所有控件无法或得正确的触摸消息。
修改方法很简单,修改lv_drivers/indev/evdev.c,把y坐标掉个头。代码如下:
坐标修改下面的代码是对坐标加以保护,防止触摸到边界时,出现负的坐标,而导致系统异常。经过上面修改后,再在板上测试,所有显示和触摸都正确了,界面也显示了横屏的效果,但是因为DRM旋转的方法比较低效,导致刷新帧率明显降低,由GUI的30fps,降低到20fps左右。
同样,对屏幕上的UI显示的大小,可以通过修改lv_conf.h里的DIP来调整,计算方法很简单。
比如这个屏是6英寸,对720x1280这个分辨率是约为1468像素,即每英寸 1468/6 = 245, 即可得标准的DIP ,设置为245,显示最佳。一下是几个界面拍照图:
八、LVGL移植总结
经过在EASY-EAI-Nano(RV1126)开发板成功移植LVGL图形库,进一步了解该开发板的性能,同时也了解LVGL软件的一些特点,工程代码结构简洁,结构合理,容易理解。各个地方的注释也非常丰富,模块结构划分清晰,移植适配起来非常方便。
同时也测试得LVGl在该开发板上运行非常流畅,由于是纯C实现的界面,板上执行效率非常高。并且具有良好的界面的适配性,不同尺寸的屏幕的LVGL图形应用非常方便的移植。而且LVGL界面提供大量可直接使用的组件,这些组件也很美观。之后就可以方便把大量LVGL的UI应用移植到该平台上了。