当前位置: 首页 > news >正文

linux移植lvgl

引言

        lvgl是一个专为嵌入式系统设计的开源图形库,用C语言编写,其具有轻量、高效、跨平台等特点。我们在做嵌入式相关可视化工作时经常会用到lvgl,其中我们还会像做QT项目一样,自己创建组件库,那么我们理解lvgl的原理就显得非常重要了。这里提一点,lvgl主要面向触屏,所以对鼠标这类的不是很友好,只有一些基础功能,像鼠标悬停、右键等等是没有的,不过问题不大,可以自己在lvgl的设备库中自己添加 。大家如果可以的话好好理解lvgl的代码,后面会讲一些lvgl的代码,话不多说,先搭建好环境。

一、构建lvgl项目框架

        首先,我们在ubuntu下创建一个项目文件夹,这里我就以lvgl_demo为例,在lvgl_demo项目文件夹中创建build、ui、lib、resouce,这里解释一下:

        build中还会创建obj和bin两个文件夹,分别存放的是gcc生成的中间文件和可执行bin文件,不过一般我们在开发项目中会先使用SDL进行模拟,然后再用所对应开发板的编译器编译,为了方便中间文件的管理,这里又创建了一个ubuntu文件夹,ubuntu中创建obj

        ui存放开发的页面

        lib存放外部库文件,比如curl库、iniparser库文件等

        resouce中存放的资源文件,如图片、音视频、字体等

mkdir -p /build/bin /build/ubuntu/obj ui resouce lib

        这些都是根据项目需求进行添加,下图是项目树结构

        现在我们需要拉取git仓库的核心代码和快速构建好的框架,这里我选择的都是8.3版本的,主要拉取的有lv_drives驱动库(8.3版本)、lvgl核心库(8.3版本)、lv_port_linux(8.3版本)、lv_port_pc_eclipse(8.3版本),这里使用--branch <version number> <repository_url> --depth 1去拉取指定版本,使用下面命令拉去代码:

git clone --branch release/v8.3 https://github.com/lvgl/lv_drivers.git --depth 1
git clone --branch release/v8.3 https://github.com/lvgl/lvgl.git --depth 1
git clone --branch release/v8.3 https://github.com/lvgl/lv_port_linux.git --depth 1
git clone --branch release/v8.3 https://github.com/lvgl/lv_port_pc_eclipse.git --depth 1

二、配置

        别看我们拉去这么多,其实核心的是lvgl_drivers、lvgl,话不多说,开整。

        将lv_port_linux下的main.c、lv_conf.h、lv_drv_conf.h、mouse_cursor_icon.c复制到项目文件中,删除lv_port_linux

cp lv_port_linux/main.c lv_port_linux/lv_conf.h lv_port_linux/lv_drv_conf.h lv_port_linux/mouse_cursor_icon.c ./ 
rm -rf lv_port_linux

       将lv_port_pc_eclipse的makefile、CMakeLists.txt复制到项目中,并暂时复制下来该文件夹中的main.c,后续根据该文件夹中的main.c为lv_port_linux的main.c增加SDL模拟器代码,最后删除lv_port_pc_eclipse

cp lv_port_pc_eclipse/main.c ./main_temp.c
cp lv_port_pc_eclipse/Makefile lv_port_pc_eclipse/CMakeLists.txt ./
rm -rf lv_port_pc_eclipse/

        下图是此时项目文件夹中的结构:

注:这里给大家说一下应用lv_port_pc_eclipse的makefile是因为其更简介且功能和lv_port_linux的makefile功能一样,并且还新增了一个-Wshift-negative-value选项进行检测负数的位移操作。有兴趣的可以看看,lv_port_linux的makefile有好多重复项,比较累赘;虽然lv_port_pc_eclipse的makefile也有重复项,但是很少,可以手动删掉。

        下面修改main.c文件的内容,建议使用vscode打开,开发比较方便,不会可以看看之前发的vscode链接linux,这里不在啰嗦。

        我们再连接好linux后,按照下面打开项目:

到这里我们打开成功了,然后修改main.c

我们创建一个lv_hal_init进行一些功能的初始化,main中直接调用,简介名明了

lv_hal_init的实现看如图,顺便给大家中文解释了:

      

  这就是核心代码了,可以看出并不难理解,就是根据设备修改了不同的刷新回调函数,其实还有添加点东西,不过后续再说,到这里就可以删除main_temp.c文件了

        下面修改lv_conf.h文件,基本不用怎么修改。首先LV_MEM_CUSTOM 设置为1,采用自己管理分配内存,LV_USE_LOG 设置为1,方便找到错误处,后面就没什要改的了。

        修改lv_frv_conf.h文件,USE_SDL设置为1,可以使用SDL模拟,设置SDL_HOR_RES 为1280,SDL_VER_RES为720,添加一个宏#define FBDEV_DISPLAY_POWER_ON    1,后面的暂时不需要设置,用到时再说。

然后修改一下心跳函数,也就是custom_tick_get函数,应为git给出的方法比较老旧,新的方法也就是clock_gettime不受系统时间调整的影响,比如NTP同步,手动修改时间,而老的方法也就是gettimeofday返回的是系统实时时间,受系统时间被调整的影响,可能导致错误。除此之外clock_gettime提供的时间更精确,提更纳秒级,而gettimeofday只有微妙级。

需要注意的是需要加入#define _POSIX_C_SOURCE 199309L这样的宏,因为clock_gettime POSIX的标准定义函数,而非ANSI C的标准,需要显示启动,将这宏加入main.c的顶上即可,下面给出main.c的完整代码:

#define _POSIX_C_SOURCE 199309L#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include "main.h"#ifdef SIMULATOR_FROM_BUILD
#include "lv_drivers/sdl/sdl.h"
#else
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#endif#include "lvgl/lvgl.h"
#include "lvgl/demos/lv_demos.h"#define DISP_BUF_SIZE (1280 * 720 / 8)   //缓冲区大小
#define DEVICE_HOR_RES  1280             //显示设备水平分辨率(宽度)
#define DEVICE_VER_RES  720             //显示设备垂直分辨率(高度)lv_disp_drv_t disp_drv;                  //配置和控制显示设备的核心结构体
lv_disp_t *disp;                         //管理显示设备的核心结构体
lv_indev_t *mouse_indev;                 //输入设备的核心结构体static int lv_hal_init(void);
uint32_t custom_tick_get(void);int main(void)
{lv_hal_init();/*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)
{struct timespec ts;uint64_t current_ms;/* timespec have two member variable* tv_sec  : seconds* tv_nsec : nanoseconds*/clock_gettime(CLOCK_MONOTONIC, &ts);current_ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000 / 1000;   //calcilate millisecondsstatic uint64_t start_ms = 0;if (start_ms == 0) {start_ms = current_ms;}/* milliseconds level difference */uint32_t time_ms = current_ms - start_ms;return time_ms;
}static int lv_hal_init()
{/* lvgl的核心代码,主要是lvgl的核心库的初始化,比如内存、、文件系统、心跳等 */lv_init();/* 在开发中为了方便调试,直接再编译时进行选择,有这个宏就是*  对sdl模拟编译,不是则是开发版编译*/
#ifdef SIMULATOR_FROM_BUILD/* SDL模拟起的核心初始化 */sdl_init();
#else/* 这两个函数主要是用于再开发板上显示,利用的就是linux的*  frame buffer,一般是fb0,也有的是fb1,主要看开发板*  另一个函数则是输入设备的初始化*  通俗易懂的是一个是输入设备初始化,一个是显示设备初始化*/fbdev_init();evdev_init();
#endif/* 屏幕刷新缓冲区的大小,这里建议不要低于屏幕的1/10,这里采用了1/8*  在文件的开头定义了*/static lv_color_t buf[DISP_BUF_SIZE];/* 初始化显示缓冲区 */static lv_disp_draw_buf_t disp_buf;lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);/* 初始化并注册显示设备 *  这里需要注意了,应为SDL和开发板的显示回调事件不一样*/lv_disp_drv_init(&disp_drv);disp_drv.draw_buf   = &disp_buf;
#if SIMULATOR_FROM_BUILDdisp_drv.flush_cb   = sdl_display_flush;    //使用SDL的显示回调事件
#elsedisp_drv.flush_cb   = fbdev_flush;
#endifdisp_drv.hor_res    = DEVICE_HOR_RES;disp_drv.ver_res    = DEVICE_VER_RES;disp = lv_disp_drv_register(&disp_drv);/* 初始化并注册输入设备*  LV_INDEV_TYPE_POINTER这个就是输入设备的类型,他表示触摸或者鼠标*  这里也得根据设备选择不同的回调函数*/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*/
#if SIMULATOR_FROM_BUILDindev_drv_1.read_cb = sdl_mouse_read;
#elseindev_drv_1.read_cb = evdev_read;
#endifmouse_indev = lv_indev_drv_register(&indev_drv_1);/* 这里是创建了一个鼠标*/LV_IMG_DECLARE(mouse_cursor_icon)lv_obj_t * cursor_obj = lv_img_create(lv_scr_act()); /*Create an image object for the cursor */lv_img_set_src(cursor_obj, &mouse_cursor_icon);           /*Set the image source*/lv_indev_set_cursor(mouse_indev, cursor_obj);             /*Connect the image  object to the driver*/return 0;
}

这个函数会在lv_conf.h用到,所用在创建一个mian.h,并在此文件中声明一下这个函数,最后在    lv_conf.h中加入main.h头文件,main.h内容:

#ifndef __MAIN_H_
#define __MAIN_H_uint32_t custom_tick_get(void);#endif

到此我们基本成功搭建好了,最后修改一下Makefile,这里直接展示修改后的代码,里面有我的注释:

#
# Makefile
##生成可执行文件以及中间文件的目录
LVGL_DIR		=	.
LVGL_BUILD_DIR	=	$(LVGL_DIR)/build
LVGL_BIN_DIR	=	$(LVGL_BUILD_DIR)/bin#根据编译类型来确定编译器,如gcc aarch64-ca53-linux-gnu-gcc 等等,这里一这两种为例
ifeq ($(SIMULATOR_FROM_BUILD),y)
CC 				= 	gcc
STRIP			=	strip
BUILD_OBJ_DIR	= 	$(LVGL_BUILD_DIR)/ubuntu/obj
else ifeq ($(FB1_FROM_BUILD),y)
CC				=	aarch64-ca53-linux-gnu-gcc
STRIP			=	aarch64-ca53-linux-gnu-strip
BUILD_OBJ_DIR	=	$(LVGL_BUILD_DIR)/ca53/obj
else
CC 				= 	gcc
STRIP			=	strip
BUILD_OBJ_DIR	= 	$(LVGL_BUILD_DIR)/ubuntu/obj
endif#要编译文件的名字
LVGL_DIR_NAME			?=	lvgl
LVGL_DRIVERS_DIR_NAME	?=	lv_drivers#警告选项
WARNINGS ?= -Wall -Wextra \-Wshadow -Wundef -Wmaybe-uninitialized -Wmissing-prototypes -Wno-discarded-qualifiers \-Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing \-Wno-error=cpp -Wuninitialized -Wno-unused-parameter -Wno-missing-field-initializers    \-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 \-Wshift-negative-value -Wstack-usage=2048 -Wtype-limits -Wsizeof-pointer-memaccess -Wno-unused-variable#编译设置 配置编译优化等级、头文件搜索路径、警告选项、C 语言标准、启用 GNU 扩展功能、快速数学优化
CFLAGS 	?= -O3 -I$(LVGL_DIR)/ $(WARNINGS) -std=c99 -D_GNU_SOURCE -O3 -ffast-math#根据测试还是发行版本进一步配置
ifeq ($(BUILD_MODE),release)	#发行版
BIN		= 	$(release_name)
#禁用调试符号 将全局变量放入独立段 将函数放入独立段	 启用链接时优化(LTO)
CFLAGS	+=	-g0 -fdata-sections -ffunction-sections -flto=jobserver
#移除未使用的段		剥离所有符号表	进一步减小二进制大小
CFLAGS	+=	-Wl,--gc-sections -s
else
BIN		=	$(debug_name)
CFLAGS 	+= -g
endif#加入外部库配置
CFLAGS	+=	-I./lib#根据具体平台进一步配置
ifeq ($(SIMULATOR_FROM_BUILD),y)
#加入宏SIMULATOR_FROM_BUILD		忽略 const 等限定符被丢弃的警告
CFLAGS	+=	-DSIMULATOR_FROM_BUILD -Wno-discarded-qualifiers
#链接SDL2库
LDFLAGS +=	-lSDL2
else ifeq ($(FB1_FROM_BUILD),y)
#跟上面意图一样
CFLAGS 	+= -DFB0_FROM_BUILD -Wno-discarded-qualifiers
#可能板子上的linux系统低,需要加上-rt链接库
LDFLAGS += -lrt
endif#Collect the files to compile
MAINSRC = $(LVGL_DIR)/main.c#包括所有参与编译的c文件
include $(LVGL_DIR)/$(LVGL_DIR_NAME)/lvgl.mk
include $(LVGL_DIR)/$(LVGL_DRIVERS_DIR_NAME)/lv_drivers.mkCSRCS += $(LVGL_DIR)/mouse_cursor_icon.c #目标文件扩展名
OBJEXT ?= .o#转换汇编文件为目标文件
AOBJS = $(ASRCS:.S=$(OBJEXT))
#转换 C 文件为目标文件
COBJS = $(CSRCS:.c=$(OBJEXT))
# 转换主程序文件为目标文件
MAINOBJ = $(MAINSRC:.c=$(OBJEXT))# 合并所有源文件和目标文件列表
SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
OBJS = $(AOBJS) $(COBJS) $(MAINOBJ)#移除 OBJS 中所有文件名前的 ./(当前目录前缀)  将 BUILD_OBJ_DIR/ 添加到每个目标文件名前。
TARGET=$(addprefix $(BUILD_OBJ_DIR)/,$(patsubst ./%, %, $(OBJS)))## MAINOBJ -> OBJFILESall: default#统一管理生成的中间文件 
$(BUILD_OBJ_DIR)/%.o: %.c@mkdir -p $(dir $@) @$(CC)  $(CFLAGS) -c $< -o $@@echo "CC $<"default: $(TARGET)@mkdir -p $(dir $(LVGL_BIN_DIR)/)@$(CC) $(TARGET) -o $(LVGL_BIN_DIR)/$(BIN) $(LDFLAGS)
ifeq ($(BUILD_MODE), release)@$(STRIP) --strip-all $(LVGL_BIN_DIR)/$(BIN)
endifclean: rm -rf $(BUILD_OBJ_DIR) $(LVGL_BIN_DIR)/*

结合我的注释慢慢看,要看懂看会,能够自己编写,最后编写个build.sh来统一编译,首先创建文件 touch build.sh,然后chmod +x ./build.sh 赋予可执行能力,具体脚本内容如下:

#! /bin/bashexport release_name=""
export debug_name=""
export SIMULATOR_FROM_BUILD=n
export FB1_FROM_BUILD=n
export BUILD_MODE="debug"#没有参数,默认配置
if [ "$#" -eq 0 ];thenSIMULATOR_FROM_BUILD=ydebug_name="ubuntu_sdl_debug"
fi#根据参数选择
case $1 insimulator)SIMULATOR_FROM_BUILD=y;;fb1)FB1_FROM_BUILD=y;;*)echo "param does not exist"echo "param can select: simulator fb1"exit 1;;;
esac#有第二参数据根据第二个参数选择
if [ "$#" -eq 2 ];thencase $2 inrelease)BUILD_MODE="release"make clean;;clean)make clean;;*)echo "param does not exist"echo "param can select: release clean";;esac
fi#根据版本选择最终可执行文件名称
if [ "$BUILD_MODE" == "release" ];thenrelease_name="ubuntu_sdl_release"
elsedebug_name="ubuntu_sdl_debug"
fi#获取cpu数量
cpu_count=$(grep -c ^processor /proc/cpuinfo)#全力编译
make -j$cpu_count#判断编译成功还是失败 成功返回0
if [ "$?" -ne 0 ];thenecho "******build fail******"exit 1
fi

        恭喜您们成功到此,接下里就是测试了

三、测试功能

       下面再编译前先安装SDL2库,如果没有安装必要的编译工具,如gcc、g++、make等就先安装这些,然后执行下面指令安装SDL2库。

sudo apt-get install -y libsdl2-2.0 libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-gfx-dev

        然后就可以输入下面命令进行编译,然后再ubuntu中运行。

./build.sh simulator
./build/bin/ubuntu_sdl_debug 

可以看出完美运行

结束语

        很高心能看到最后,下一期抽时间将一下内部实现原理,并设计一个键盘文本框的小组件,实现登录页面。

相关文章:

  • 带您了解工业级网络变压器的浪涌等级测试有哪些条件?
  • Mistral AI 开源最新 Small 模型——Devstral-Small-2505
  • CATIA高效工作指南——常规配置篇(三)
  • 【Nature子刊聚焦:超构表面多维调控与AI驱动的设计革命 ——2024-2025年超构表面领域突破性进展速览 】
  • Day 29 训练
  • 免费在线AI聊天工具
  • 数据同步自动化——如何用Python打造高效工具?
  • 数学建模MathAI智能体-2025电工杯A题实战
  • Linux性能监控:工具与最佳实践
  • 双重攻击锁定饮料巨头,黑客组织宣称窃取可口可乐海量数据
  • JavaWeb面试题 (一)
  • Java 8 Lambda 表达式使用说明与案例
  • Java 集合框架核心知识点全解析:从入门到高频面试题(含 JDK 源码剖析)
  • synchronized 实现原理
  • 双流芯谷元宇宙产业园,引领元宇宙产业新潮流
  • 快捷回复预设文本工具
  • TCP 三次握手,第一次握手报文丢失会发生什么?
  • 黑马点评-实现安全秒杀优惠券(使并发一人一单,防止并发超卖)
  • 易境通专线散拼系统:全方位支持多种专线物流业务!
  • 中宏立达与天空卫士达成战略合作
  • 融安有那几个网站做的比较好的/sem账户托管
  • 福州培训网站建设/企业网站系统
  • 东莞南城做网站/网络营销的实现方式包括
  • 天津做网站开发的/北京seo培训
  • 电子商务网站建设与管理感想/营销推广活动策划书模板
  • 网站带后台模板/运营推广计划怎么写