2.1. LVGL移植

本文档仅介绍LVGL移植到D211平台的流程和步骤,没有涉及到基于LVGL应用的开发

2.1.1. LVGL简介

../../_images/home_banner.jpg

LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式 GUI 所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用

2.1.1.1. 主要特性

  • 丰富且强大的模块化图形组件:按钮、图标、列表、互动条、图片等

  • 先进的图形界面:动画、抗锯齿、透明度、平滑滚动等效果

  • 支持不同的输入设备包括键盘,鼠标,触摸屏,编码器等

  • UTF-8编码支持多语言

  • 多显示器支持,可以同时使用多个TFT或单色显示

  • 可以通过类 CSS的方式来设计、布局图形界面

  • 不限制芯片类型、硬件,可在各种微控制器或显示器上使用LVGL

  • 配置可裁剪(最低资源占用:64 kB Flash,16 kB RAM)

  • 支持操作系统、外部存储和GPU,但都不是硬性要求

  • 即使单缓冲区(frame buffer)也能实现高级图形效果

  • 不需要嵌入式硬件环境在PC模拟器就可以调试GUI

  • 支持 Micropython 编程

  • 有用于快速GUI设计的教程、示例、主题

  • 详尽的文档以及 API 参考手册,可线上查阅或可下载为 PDF 格式

  • 在 MIT 许可下免费和开源

2.1.1.2. 配置要求

  • 16、32或64位微控制器或处理器

  • 最低 16 MHz 时钟频率

  • Flash/ROM: >64 kB(建议 180 kB)

  • RAM: 8 kB(建议 24 kB)

  • 显示缓冲区: >水平分辨率像素(建议为1/10屏幕大小)

  • 支持C99编程

  • 具备基本的C或C++知识

2.1.2. LVGL移植

软件模块的移植一般包括下载代码、平台配置、应用开发、编译等几个步骤,本章节也从这几个方面进行描述

2.1.2.1. 获取代码

一个LVGL应用项目工程的代码分为两部分,一部分为LVGL图形库代码lvgl目录,一部分为驱动支持部分代码lv_driver目录。由于LVGL的图形库非常轻量级,所以这部分是以源码的形式集成到应用项目中。两部分都可以从GitHub获取最新的源码。

注解

本文档中移植示例中使用的lvgl版本和lv_driver版本均为v8.2版本

创建一个名称为lvgl的工程目录,把所从GitHub上所获取的两部分代码放入工程目录,结构如下:

lvgl
|-- lv_drivers                     --> 驱动支持总目录
|   |-- CMakeLists.txt
|   |-- display
|   |-- docs
|   |-- gtkdrv
|   |-- indev
|   |-- library.json
|   |-- LICENSE
|   |-- lv_drivers.mk
|   |-- lv_drv_conf_template.h     --> 驱动配置模板
|   |-- README.md
|   |-- sdl
|   |-- wayland
|   |-- win32drv
|   |-- win_drv.c
|   `-- win_drv.h
`-- lvgl                           --> 图形库总目录
    |-- CMakeLists.txt
    |-- component.mk
    |-- demos                      --> 应用示例
    |-- docs
    |-- env_support
    |-- examples
    |-- idf_component.yml
    |-- Kconfig
    |-- library.json
    |-- library.properties
    |-- LICENCE.txt
    |-- lv_conf_template.h         --> 图形库配置模板
    |-- lvgl.h
    |-- lvgl.mk
    |-- README.md
    |-- README_zh.md
    |-- SConscript
    |-- scripts
    |-- src                        --> 源码目录
    `-- tests

2.1.2.2. 配置代码

  • lvgl配置

基于模板创建配置文件:
cp lvgl/lv_conf_template.h ./lv_conf.h

修改lv_conf.h如下:
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -12,7 +12,7 @@
  */

 /* clang-format off */
-#if 0 /*Set it to "1" to enable content*/
+#if 1 /*Set it to "1" to enable content*/               --> 使能配置文件内容

 #ifndef LV_CONF_H
 #define LV_CONF_H
@@ -85,10 +85,10 @@

 /*Use a custom tick source that tells the elapsed time in milliseconds.
  *It removes the need to manually update the tick with `lv_tick_inc()`)*/
-#define LV_TICK_CUSTOM 0
+#define LV_TICK_CUSTOM 1                                --> 使能客户custom_tick_get函数
 #if LV_TICK_CUSTOM
-    #define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/
-    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
+    #define LV_TICK_CUSTOM_INCLUDE <stdint.h>         /*Header for the system time function*/
+    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get())    /*Expression evaluating to current system time in ms*/
 #endif   /*LV_TICK_CUSTOM*/

 /*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
@@ -705,7 +705,7 @@
  ====================*/

 /*Show some widget. It might be required to increase `LV_MEM_SIZE` */
-#define LV_USE_DEMO_WIDGETS 0
+#define LV_USE_DEMO_WIDGETS 1                           --> 打开widgets示例demo
 #if LV_USE_DEMO_WIDGETS
 #define LV_DEMO_WIDGETS_SLIDESHOW 0
 #endif
  • lv_drivers配置

基于模板创建配置文件:
cp lv_drivers/lv_drv_conf_template.h ./lv_drv_conf.h

修改lv_drv_conf.h如下:
--- a/lv_drv_conf.h
+++ b/lv_drv_conf.h
@@ -8,7 +8,7 @@
  */

 /* clang-format off */
-#if 0 /*Set it to "1" to enable the content*/
+#if 1 /*Set it to "1" to enable the content*/           --> 使能配置文件内容

 #ifndef LV_DRV_CONF_H
 #define LV_DRV_CONF_H
@@ -316,7 +316,7 @@
  *  Linux frame buffer device (/dev/fbx)
  *-----------------------------------------*/
 #ifndef USE_FBDEV
-#  define USE_FBDEV           0
+#  define USE_FBDEV           1                         --> 使用/dev/fb设备节点
 #endif

 #if USE_FBDEV
@@ -439,7 +439,7 @@
  * Mouse or touchpad as evdev interface (for Linux based systems)
  *------------------------------------------------*/
 #ifndef USE_EVDEV
-#  define USE_EVDEV           0
+#  define USE_EVDEV           1                         --> 使用/dev/fb设备节点
 #endif

 #ifndef USE_BSD_EVDEV

2.1.2.3. 编写应用

应用程序主题main.c如下:

#include "lvgl/lvgl.h"
#include "lvgl/demos/lv_demos.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define DISP_BUF_SIZE (128 * 1024)

int main(void)
{
   /*LittlevGL init*/
   lv_init();

   /*Linux frame buffer device init*/
   fbdev_init();

   /*A small buffer for LittlevGL to draw the screen's content*/
   static lv_color_t buf[DISP_BUF_SIZE];

   /*Initialize a descriptor for the buffer*/
   static lv_disp_draw_buf_t disp_buf;
   lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);

   /*Initialize and register a display driver*/
   static lv_disp_drv_t disp_drv;
   lv_disp_drv_init(&disp_drv);
   disp_drv.draw_buf   = &disp_buf;
   disp_drv.flush_cb   = fbdev_flush;
   disp_drv.hor_res    = 1024;
   disp_drv.ver_res    = 600;
   lv_disp_drv_register(&disp_drv);

   evdev_init();
   static lv_indev_drv_t indev_drv_1;
   lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/
   indev_drv_1.type = LV_INDEV_TYPE_POINTER;

   /*This function will be called periodically (by the library) to get the mouse position and state*/
   indev_drv_1.read_cb = evdev_read;
   lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv_1);

   /*Create a Demo*/
   lv_demo_widgets();

   /*Handle LitlevGL tasks (tickless mode)*/
   while(1) {
      lv_timer_handler();
      usleep(5000);
   }

   return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
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;
}

注解

本章节所展示的应用基于lvgl自带的demo程序:lv_port_linux_frame_buffer,可从GitHub获取:https://github.com/lvgl/lv_port_linux_frame_buffer

2.1.2.4. 编译运行

  • 编译脚本Makefile内容如下:

CC = arm-linux-gnueabihf-gcc
LVGL_DIR_NAME ?= lvgl
LVGL_DIR ?= ${shell pwd}
CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wstack-usage=2048 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
LDFLAGS ?= -lm
BIN = demo


#Collect the files to compile
MAINSRC = ./main.c

include $(LVGL_DIR)/lvgl/lvgl.mk
include $(LVGL_DIR)/lv_drivers/lv_drivers.mk

OBJEXT ?= .o

AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))

MAINOBJ = $(MAINSRC:.c=$(OBJEXT))

SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
OBJS = $(AOBJS) $(COBJS)

## MAINOBJ -> OBJFILES

all: default

%.o: %.c
      @$(CC)  $(CFLAGS) -std=c99 -c $< -o $@
      @echo "CC $<"

default: $(AOBJS) $(COBJS) $(MAINOBJ)
      $(CC) -o $(BIN) $(MAINOBJ) $(AOBJS) $(COBJS) $(LDFLAGS)

clean:
      rm -f $(BIN) $(AOBJS) $(COBJS) $(MAINOBJ)
  • 在应用工程目录下运行makem命令后, 生成可执行程序demo文件,把二进制文件demo通过adb推送到开发板,运行:./demo,程序运行效果如下:

../../_images/demo.jpg