LittleVGL最新已經更新到V7,網上大多數移植教程的版本比較老,不少特性沒有,界面也不夠酷炫。linux
原子最近更新的 LittleVGL 教程則是基於V6版本的,基本上搬過來全是報錯,沒法參考。新舊版本一致仍是有很大區別的,這裏介紹下最新版本的移植要點,針對嵌入式linux的framebuffer(dev/fb0)移植。git
固然最最新的版本是V7.4.0,源碼能夠在github下載https://github.com/lvgl/lvgl。github
關於lvgl的官網及介紹,在https://lvgl.io,Online demo:https://lvgl.io/demos,Docs:https://docs.lvgl.ioshell
移植比較簡單,主要區別是幾個接口跟老版本的不同了。不過最終都是實現disp_flush顯示驅動接口便可。api
接口原型新版本的是這樣的:app
void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)less
移植說明:tcp
新建個工程文件夾,我這取名叫test,測試
而後在test文件夾下新建個lvgl文件夾,把下載到的源碼中的src文件夾整個拷貝出來放進去。ui
把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 @