LittleVGL最新已经更新到V7,网上大多数移植教程的版本比较老,很多特性没有,界面也不够酷炫。
原子最近更新的 LittleVGL 教程则是基于V6版本的,基本上搬过来全是报错,无法参考。新旧版本一致还是有很大区别的,这里介绍下最新版本的移植要点,针对嵌入式linux的framebuffer(dev/fb0)移植。
当然最最新的版本是V7.4.0,源码可以在github下载https://github.com/lvgl/lvgl。
关于lvgl的官网及介绍,在https://lvgl.io,Online demo:https://lvgl.io/demos,Docs:https://docs.lvgl.io
移植比较简单,主要区别是几个接口跟老版本的不一样了。不过最终都是实现disp_flush显示驱动接口即可。
接口原型新版本的是这样的:
void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
移植说明:
新建个工程文件夹,我这取名叫test,
然后在test文件夹下新建个lvgl文件夹,把下载到的源码中的src文件夹整个拷贝出来放进去。
把lv_conf_template.h拷贝出来,到工程的文件夹下,重命名为lv_conf.h.
编辑下,根据实际情况改下相关的配置:
#define LV_HOR_RES_MAX (480) #define LV_VER_RES_MAX (272) #define LV_COLOR_DEPTH 16 #define LV_USE_GPU 0 /* 1: Enable file system (might be required for images */ #define LV_USE_FILESYSTEM 1
。。。
在工程的文件夹下建个lv_drivers文件夹,用于实现的驱动文件。
/** * @file fbdev.c * */ /********************* * INCLUDES *********************/ #include "fbdev.h" #if USE_FBDEV #include <stdlib.h> #include <unistd.h> #include <stddef.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/mman.h> #include <sys/ioctl.h> /********************* * DEFINES *********************/ #ifndef FBDEV_PATH #define FBDEV_PATH "/dev/fb0" #endif /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ /********************** * STATIC VARIABLES **********************/ static struct fb_var_screeninfo vinfo; static struct fb_fix_screeninfo finfo; static char *fbp = 0; static long int screensize = 0; static int fbfd = 0; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void fbdev_init(void) { // Open the file for reading and writing fbfd = open(FBDEV_PATH, O_RDWR); if (fbfd == -1) { perror("Error: cannot open framebuffer device"); return; } printf("The framebuffer device was opened successfully.\n"); // Get fixed screen information if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) { perror("Error reading fixed information"); return; } // Get variable screen information if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) { perror("Error reading variable information"); return; } printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); // Figure out the size of the screen in bytes screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; // Map the device to memory fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); if ((int)fbp == -1) { perror("Error: failed to map framebuffer device to memory"); return; } printf("The framebuffer device was mapped to memory successfully.\n"); } /** * Flush a buffer to the marked area * @param x1 left coordinate * @param y1 top coordinate * @param x2 right coordinate * @param y2 bottom coordinate * @param color_p an array of colors */ void fbdev_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) { if(fbp == NULL) return; /*Return if the area is out the screen*/ if(x2 < 0) return; if(y2 < 0) return; if(x1 > vinfo.xres - 1) return; if(y1 > vinfo.yres - 1) return; /*Truncate the area to the screen*/ int32_t act_x1 = x1 < 0 ? 0 : x1; int32_t act_y1 = y1 < 0 ? 0 : y1; int32_t act_x2 = x2 > vinfo.xres - 1 ? vinfo.xres - 1 : x2; int32_t act_y2 = y2 > vinfo.yres - 1 ? vinfo.yres - 1 : y2; long int location = 0; /*32 or 24 bit per pixel*/ if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { uint32_t *fbp32 = (uint32_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp32[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } /*16 bit per pixel*/ else if(vinfo.bits_per_pixel == 16) { uint16_t *fbp16 = (uint16_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp16[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } /*8 bit per pixel*/ else if(vinfo.bits_per_pixel == 8) { uint8_t *fbp8 = (uint8_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp8[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } else { /*Not supported bit per pixel*/ } //May be some direct update command is required //ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect)); //lv_flush_ready(); /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ } /* 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. */ void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ fbdev_flush(area->x1,area->y1,area->x2,area->y2,color_p); lv_disp_flush_ready(disp_drv); } /** * Fill out the marked area with a color * @param x1 left coordinate * @param y1 top coordinate * @param x2 right coordinate * @param y2 bottom coordinate * @param color fill color */ void fbdev_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color) { if(fbp == NULL) return; /*Return if the area is out the screen*/ if(x2 < 0) return; if(y2 < 0) return; if(x1 > vinfo.xres - 1) return; if(y1 > vinfo.yres - 1) return; /*Truncate the area to the screen*/ int32_t act_x1 = x1 < 0 ? 0 : x1; int32_t act_y1 = y1 < 0 ? 0 : y1; int32_t act_x2 = x2 > vinfo.xres - 1 ? vinfo.xres - 1 : x2; int32_t act_y2 = y2 > vinfo.yres - 1 ? vinfo.yres - 1 : y2; uint32_t x; uint32_t y; long int location = 0; /*32 or 24 bit per pixel*/ if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { uint32_t *fbp32 = (uint32_t*)fbp; for(x = act_x1; x <= act_x2; x++) { for(y = act_y1; y <= act_y2; y++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp32[location] = color.full; } } } else if(vinfo.bits_per_pixel == 16) { uint16_t *fbp16 = (uint16_t*)fbp; for(x = act_x1; x <= act_x2; x++) { for(y = act_y1; y <= act_y2; y++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp16[location] = color.full; } } } else if(vinfo.bits_per_pixel == 8) { uint8_t *fbp8 = (uint8_t*)fbp; for(x = act_x1; x <= act_x2; x++) { for(y = act_y1; y <= act_y2; y++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp8[location] = color.full; } } } else { /*Not supported bit per pixel*/ } //May be some direct update command is required //ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect)); } /** * Put a color map to the marked area * @param x1 left coordinate * @param y1 top coordinate * @param x2 right coordinate * @param y2 bottom coordinate * @param color_p an array of colors */ void fbdev_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) { if(fbp == NULL) return; /*Return if the area is out the screen*/ if(x2 < 0) return; if(y2 < 0) return; if(x1 > vinfo.xres - 1) return; if(y1 > vinfo.yres - 1) return; /*Truncate the area to the screen*/ int32_t act_x1 = x1 < 0 ? 0 : x1; int32_t act_y1 = y1 < 0 ? 0 : y1; int32_t act_x2 = x2 > vinfo.xres - 1 ? vinfo.xres - 1 : x2; int32_t act_y2 = y2 > vinfo.yres - 1 ? vinfo.yres - 1 : y2; long int location = 0; /*32 or 24 bit per pixel*/ if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { uint32_t *fbp32 = (uint32_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp32[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } /*16 bit per pixel*/ else if(vinfo.bits_per_pixel == 16) { uint16_t *fbp16 = (uint16_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp16[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } /*8 bit per pixel*/ else if(vinfo.bits_per_pixel == 8) { uint8_t *fbp8 = (uint8_t*)fbp; uint32_t x; uint32_t y; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * vinfo.xres; fbp8[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } } else { /*Not supported bit per pixel*/ } //May be some direct update command is required //ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect)); } /********************** * STATIC FUNCTIONS **********************/ #endif
附上一个小测试demo:
#include "lvgl.h" #include "lv_drivers/display/fbdev.h" #include <unistd.h> //#include "lv_examples/lv_apps/demo/demo.h" static lv_disp_buf_t disp_buf_2; static lv_color_t buf2_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/ static lv_color_t buf2_2[LV_HOR_RES_MAX * 10]; /*An other buffer for 10 rows*/ //extern void demo_create(void); int main(void) { /*LittlevGL init*/ lv_init(); /*Linux frame buffer device init*/ fbdev_init(); /*Add a display the LittlevGL sing the frame buffer driver*/ lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.flush_cb = disp_flush; /*Set a display buffer*/ lv_disp_buf_init(&disp_buf_2, buf2_1, buf2_2, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/ disp_drv.buffer = &disp_buf_2; //disp_drv.disp_flush = fbdev_flush; /*It flushes the internal graphical buffer to the frame buffer*/ lv_disp_drv_register(&disp_drv); /*Create a "Hello world!" label*/ lv_obj_t * label = lv_label_create(lv_scr_act(), NULL); lv_label_set_text(label, "hello worldaaa!v7.4"); lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0); /*Handle LitlevGL tasks (tickless mode)*/ //demo_create(); while(1) { lv_tick_inc(5); lv_task_handler(); usleep(5000); } return 0; }
如何运行?直接make即可生成可执行文件。
附makefile文件:
######################################## #makefile ######################################## #**************************************************************************** # Cross complie path #**************************************************************************** CHAIN_ROOT= /home/yang/crosstool/ctools/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf- #CROSS_COMPILE = CC := $(CROSS_COMPILE)gcc CXX := $(CROSS_COMPILE)g++ AS := $(CROSS_COMPILE)as AR := $(CROSS_COMPILE)ar LD := $(CROSS_COMPILE)ld RANLIB := $(CROSS_COMPILE)ranlib OBJDUMP:= $(CROSS_COMPILE)objdump OBJCOPY:= $(CROSS_COMPILE)objcopy STRIP := $(CROSS_COMPILE)strip #编译主程序 BINARY := littlevgl OBJ_DIR := ./ INCS := -I ./ -I./lvgl/src/ CFLAGS= -Wall -g -std=c99 -fno-common -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fno-var-tracking #**************************************************************************** # Source files #**************************************************************************** SRC_C=$(shell find . -name "*.c") OBJ_C=$(patsubst %.c, %.o, $(SRC_C)) SRCS := $(SRC_C) $(SRC_C) OBJS := $(OBJ_C) LDSCRIPT= -lasan LDFLAGS= -Llibs #LDSCRIPT= -lNC_FileSys #LDFLAGS= -Llib #SRC = $(wildcard *.c) #DIR = $(notdir $(SRC)) #OBJS = $(patsubst %.c,$(OBJ_DIR)%.o,$(DIR)) #OBJS= main.o myutils.o inirw.o cmdpboc.o cputest.o bustcp.o ansrec.o m1cmd.o m1api.o m1test.o upcash.o myother.o getsys.o #CFLAGS=-std=c99 #@echo Building lib... #$(call make_subdir) .PHONY: clean all: prebuild $(BINARY) prebuild: @echo Building app... $(BINARY) : $(OBJS) @echo Generating ... $(CC) -o $(BINARY) $(OBJS) $(LDFLAGS) $(LDSCRIPT) @echo OK! $(OBJ_DIR)%.o : %.c $(CC) -c $(CFLAGS) $(INCS) $< -o $@ clean: rm -f $(OBJ_DIR)*.o find . -name "*.[od]" |xargs rm @
# # Makefile # CC = gcc CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized CFLAGS += -O3 -g3 -I./ #LDFLAGS += -lSDL2 -lm BIN = demo VPATH = MAINSRC = main.c #LIBRARIES include ./lvgl/lv_core/lv_core.mk include ./lvgl/lv_hal/lv_hal.mk include ./lvgl/lv_objx/lv_objx.mk include ./lvgl/lv_misc/lv_fonts/lv_fonts.mk include ./lvgl/lv_misc/lv_misc.mk include ./lvgl/lv_themes/lv_themes.mk include ./lvgl/lv_draw/lv_draw.mk #DRIVERS include ./lv_drivers/display/display.mk include ./lv_drivers/indev/indev.mk #EXAMPLE include ./lv_examples/lv_apps/benchmark/benchmark.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: clean default %.o: %.c @$(CC) $(CFLAGS) -c $< -o $@ @echo "CC $<" default: $(AOBJS) $(COBJS) $(MAINOBJ) $(CC) -o $(BIN) $(MAINOBJ) $(AOBJS) $(COBJS) $(LDFLAGS) clean: rm -f $(BIN) $(AOBJS) $(COBJS) $(MAINOBJ)