发现一个新的 vector graphic 的库,用 C 写的,效果丰富,接口简单,而且是 MIT License,所以想试一试。因为它支持 framebuffer,所以,在 linux 上先走一个。
项目主页:https://littlevgl.com/
1. 文件准备
项目组织还不是很好,所以需要手动配置项目,需要的文件包括:
- lvgl 主项目
- lv_driver 目前支持的驱动。基本上如果使用渲染缓存的话,只要考虑怎么把渲染缓存里的东西搬移到显示缓存中即可。即最简实现 disp_init 和 disp_flush。
- lv_example 示例代码。本实验尝试了 demo 和 benchmark,记录的是 benchmark 的调试。
另外,作者还提供了一个基于 SDL 的模拟器,如果不是特别熟悉 LInux 的话,从 SDL 开始会是个比较好的选择。作者 github 上可以找到。
2. 配置
我使用的环境是 vmware+mint19。
项目需要建一个新的目录,将上面准备的三个文件夹拷贝到项目目录下,也可以用软连接链过来,随意。
在项目目录下,需要添加下面几个文件:
1)lv_conf.h 这个文件从 lvgl 目录下的 lv_config_temple.h 拷贝而来。我这里修改了分辨率为 800x400,color_depth = 24
2)lv_drv_conf.h 这个文件从 lv_drv_conf_temple.h 拷贝而来。将 USE_FBDEV(frame_buffer) 和 USE_EVDEV(/dev/input/event) 修改为 1.
3) lv_ex_conf.h 这个文件从 lv_ex_conf_temple.h 拷贝而来。将 BENCHMARK 改为1 就好了。
4)mian.c 最终版本如下。从官网的例子修改而来,benchmark 的 demo,然后使用 /dev/input/event2 作为鼠标输入,还给鼠标加了个图标。
#include "lvgl/lvgl.h" #include "lv_drivers/display/fbdev.h" #include "lv_drivers/indev/evdev.h" #include "lv_examples/lv_apps/benchmark/benchmark.h" #include <unistd.h> int main(void) { lv_init(); fbdev_init(); lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.disp_flush = fbdev_flush; lv_disp_drv_register(&disp_drv); evdev_init(); lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read = evdev_read; lv_indev_drv_register(&indev_drv); lv_indev_t *mouse = lv_indev_next(NULL); lv_obj_t *cursor = lv_label_create(lv_scr_act(), NULL); lv_label_set_recolor(cursor, true); lv_label_set_text(cursor, "#ff0000 .cursor"); lv_indev_set_cursor(mouse, cursor); benchmark_create(); while(1) { lv_tick_inc(5); lv_task_handler(); usleep(5000); } return 0; }
5)Makefile 用的下面这个,从哪里找来的,找不到出处了,自己稍微做了修改
# # 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)
3. 做的修改
原作者的代码可能是测试环境不一样,我第一次没跑出来,经过调试,最终做了下面修改后得到想要的结果。
1)第一处是 fbdev 的驱动,修改了 /lv_driver/display/fbdev.c。第一次编译后,显示一团浆糊,仔细看可以辨认是填图的时候错位了,借鉴了后面附1 的代码,修改了 flush 函数如下。
if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { uint32_t *fbp32 = (uint32_t*)fbp; uint32_t x; uint32_t y; int stride = finfo.line_length / 4; for(y = act_y1; y <= act_y2; y++) { for(x = act_x1; x <= act_x2; x++) { location = (x+vinfo.xoffset) + (y+vinfo.yoffset) * stride; fbp32[location] = color_p->full; color_p++; } color_p += x2 - act_x2; } }
重新计算了 buffer 中一行的长度,作者原来直接是把 vinfo.xref 拿来用了,我这里是 800。上面代码算出来的 stride 是 1176,但是能正常显示。
2)针对 event 设备的修改。修改了文件 lv_drivers/indev/evdev.c。这里的问题是,驱动里面的 x 范围只有 0 - LV_HOR_RES。大概是作者的理解中,鼠标的坐标范围是和分辨率一致的。但是,我用 附录2 的代码以及 hexdump 看到的,这个范围是 0-65535。所以,这个坐标需要归一化。我添加了两个变量。
#if USE_FBDEV extern int fb_x_max; extern int fb_y_max; #endif
这两个全局变量,需要先在 fbdev.c 中声明过,他们取下面的值:
// Get variable screen information if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) { perror("Error reading variable information"); return; } printf("%dx%d, %dbpp ", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); printf("%d ", finfo.line_length); fb_x_max = vinfo.xres; fb_y_max = vinfo.yres;
因此,main 函数中,初始化 event 设备,要在初始化 fbdev 之后,要不然,这两个值取不到。然后,在 evdev_init 中 将这两个值转化为鼠标的分辨率。
void evdev_init(void) { evdev_fd = open(EVDEV_NAME, O_RDWR|O_NOCTTY|O_NDELAY); if (evdev_fd == -1) { perror("unable open evdev interface:"); return; } fcntl(evdev_fd, F_SETFL, O_ASYNC|O_NONBLOCK); evdev_root_x = 0; evdev_root_y = 0; evdev_button = LV_INDEV_STATE_REL; #if USE_FBDEV //global var: double evdev_x_resolution, evdev_y_resolution evdev_x_resolution = (float)fb_x_max / 65535.0; evdev_y_resolution = (float)fb_y_max / 65535.0; #else evdev_x_resolution = 1; evdev_y_resolution = 1; #endif }
然后,读取鼠标坐标的时候,进行归一化:
while(read(evdev_fd, &in, sizeof(struct input_event)) > 0) { if (in.type == EV_REL) { if (in.code == REL_X) evdev_root_x += in.value; else if (in.code == REL_Y) evdev_root_y += in.value; } else if (in.type == EV_ABS) { if (in.code == ABS_X) evdev_root_x = in.value * evdev_x_resolution; else if (in.code == ABS_Y) evdev_root_y = in.value * evdev_y_resolution; } else if (in.type == EV_KEY) { if (in.code == BTN_MOUSE || in.code == BTN_TOUCH) { if (in.value == 0) evdev_button = LV_INDEV_STATE_REL; else if (in.value == 1) evdev_button = LV_INDEV_STATE_PR; } //printf("BTN ClICKED(%d, %d) ", evdev_root_x, evdev_root_y); } }
这样,/dev/intpu/event 中读到的坐标就可以被 lvgl 识别了。
4. 运行
需要关闭图形系统,来运行这个程序。首先按 ctr+alt+f1 登录到 tty,然后使用 sudo service ligthdm stop 来关闭系统的图形系统。
然后,就可以 sudo .demo 来查看效果了。
想回到图形系统,直接 sudo service lightdm start 即可。
附1. framebuffer 测试程序
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <linux/kd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/time.h> #include <string.h> #include <errno.h> struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; char *frameBuffer = 0; //打印fb驱动中fix结构信息,注:在fb驱动加载后,fix结构不可被修改。 void printFixedInfo () { printf ("Fixed screen info: " " id: %s " " smem_start: 0x%lx " " smem_len: %d " " type: %d " " type_aux: %d " " visual: %d " " xpanstep: %d " " ypanstep: %d " " ywrapstep: %d " " line_length: %d " " mmio_start: 0x%lx " " mmio_len: %d " " accel: %d " " ", finfo.id, finfo.smem_start, finfo.smem_len, finfo.type, finfo.type_aux, finfo.visual, finfo.xpanstep, finfo.ypanstep, finfo.ywrapstep, finfo.line_length, finfo.mmio_start, finfo.mmio_len, finfo.accel); } //打印fb驱动中var结构信息,注:fb驱动加载后,var结构可根据实际需要被重置 void printVariableInfo () { printf ("Variable screen info: " " xres: %d " " yres: %d " " xres_virtual: %d " " yres_virtual: %d " " yoffset: %d " " xoffset: %d " " bits_per_pixel: %d " " grayscale: %d " " red: offset: %2d, length: %2d, msb_right: %2d " " green: offset: %2d, length: %2d, msb_right: %2d " " blue: offset: %2d, length: %2d, msb_right: %2d " " transp: offset: %2d, length: %2d, msb_right: %2d " " nonstd: %d " " activate: %d " " height: %d " " %d " " accel_flags: 0x%x " " pixclock: %d " " left_margin: %d " " right_margin: %d " " upper_margin: %d " " lower_margin: %d " " hsync_len: %d " " vsync_len: %d " " sync: %d " " vmode: %d " " ", vinfo.xres, vinfo.yres, vinfo.xres_virtual, vinfo.yres_virtual, vinfo.xoffset, vinfo.yoffset, vinfo.bits_per_pixel, vinfo.grayscale, vinfo.red.offset, vinfo.red.length, vinfo.red.msb_right, vinfo.green.offset, vinfo.green.length, vinfo.green.msb_right, vinfo.blue.offset, vinfo.blue.length, vinfo.blue.msb_right, vinfo.transp.offset, vinfo.transp.length, vinfo.transp.msb_right, vinfo.nonstd, vinfo.activate, vinfo.height, vinfo.width, vinfo.accel_flags, vinfo.pixclock, vinfo.left_margin, vinfo.right_margin, vinfo.upper_margin, vinfo.lower_margin, vinfo.hsync_len, vinfo.vsync_len, vinfo.sync, vinfo.vmode); } //画大小为width*height的同色矩阵,8alpha+8reds+8greens+8blues void drawRect_rgb32 (int x0, int y0, int width, int height, int color) { const int bytesPerPixel = 4; const int stride = finfo.line_length / bytesPerPixel; int *dest = (int *) (frameBuffer) + (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y; for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { dest[x] = color; } dest += stride; } } //画大小为width*height的同色矩阵,5reds+6greens+5blues void drawRect_rgb16 (int x0, int y0, int width, int height, int color) { const int bytesPerPixel = 2; const int stride = finfo.line_length / bytesPerPixel; const int red = (color & 0xff0000) >> (16 + 3); const int green = (color & 0xff00) >> (8 + 2); const int blue = (color & 0xff) >> 3; const short color16 = blue | (green << 5) | (red << (5 + 6)); short *dest = (short *) (frameBuffer) + (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y; for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { dest[x] = color16; } dest += stride; } } //画大小为width*height的同色矩阵,5reds+5greens+5blues void drawRect_rgb15 (int x0, int y0, int width, int height, int color) { const int bytesPerPixel = 2; const int stride = finfo.line_length / bytesPerPixel; const int red = (color & 0xff0000) >> (16 + 3); const int green = (color & 0xff00) >> (8 + 3); const int blue = (color & 0xff) >> 3; const short color15 = blue | (green << 5) | (red << (5 + 5)) | 0x8000; short *dest = (short *) (frameBuffer) + (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y; for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { dest[x] = color15; } dest += stride; } } void drawRect (int x0, int y0, int width, int height, int color) { switch (vinfo.bits_per_pixel) { case 32: drawRect_rgb32 (x0, y0, width, height, color); break; case 16: drawRect_rgb16 (x0, y0, width, height, color); break; case 15: drawRect_rgb15 (x0, y0, width, height, color); break; default: printf ("Warning: drawRect() not implemented for color depth %i ", vinfo.bits_per_pixel); break; } } #define PERFORMANCE_RUN_COUNT 5 void performSpeedTest (void *fb, int fbSize) { int i, j, run; struct timeval startTime, endTime; unsigned long long results[PERFORMANCE_RUN_COUNT]; unsigned long long average; unsigned int *testImage; unsigned int randData[17] = { 0x3A428472, 0x724B84D3, 0x26B898AB, 0x7D980E3C, 0x5345A084, 0x6779B66B, 0x791EE4B4, 0x6E8EE3CC, 0x63AF504A, 0x18A21B33, 0x0E26EB73, 0x022F708E, 0x1740F3B0, 0x7E2C699D, 0x0E8A570B, 0x5F2C22FB, 0x6A742130 }; printf ("Frame Buffer Performance test... "); for (run = 0; run < PERFORMANCE_RUN_COUNT; ++run) { /* Generate test image with random(ish) data: */ testImage = (unsigned int *) malloc (fbSize); j = run; for (i = 0; i < (int) (fbSize / sizeof (int)); ++i) { testImage[i] = randData[j]; j++; if (j >= 17) j = 0; } gettimeofday (&startTime, NULL); memcpy (fb, testImage, fbSize); gettimeofday (&endTime, NULL); long secsDiff = endTime.tv_sec - startTime.tv_sec; results[run] = secsDiff * 1000000 + (endTime.tv_usec - startTime.tv_usec); free (testImage); } average = 0; for (i = 0; i < PERFORMANCE_RUN_COUNT; ++i) average += results[i]; average = average / PERFORMANCE_RUN_COUNT; printf (" Average: %llu usecs ", average); printf (" Band %.03f MByte/Sec ", (fbSize / 1048576.0) / ((double) average / 1000000.0)); printf (" Max. FPS: %.03f fps ", 1000000.0 / (double) average); /* Clear the framebuffer back to black again: */ memset (fb, 0, fbSize); } int main (int argc, char **argv) { const char *devfile = "/dev/fb0"; long int screensize = 0; int fbFd = 0; /* Open the file for reading and writing */ fbFd = open (devfile, O_RDWR); if (fbFd == -1) { perror ("Error: cannot open framebuffer device"); exit (1); } //获取finfo信息并显示 if (ioctl (fbFd, FBIOGET_FSCREENINFO, &finfo) == -1) { perror ("Error reading fixed information"); exit (2); } printFixedInfo (); //获取vinfo信息并显示 if (ioctl (fbFd, FBIOGET_VSCREENINFO, &vinfo) == -1) { perror ("Error reading variable information"); exit (3); } printVariableInfo (); /* Figure out the size of the screen in bytes */ screensize = finfo.smem_len; /* Map the device to memory */ frameBuffer = (char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbFd, 0); if (frameBuffer == MAP_FAILED) { perror ("Error: Failed to map framebuffer device to memory"); exit (4); } //测试virt fb的性能 performSpeedTest (frameBuffer, screensize); printf ("Will draw 3 rectangles on the screen, " "they should be colored red, green and blue (in that order). "); drawRect (vinfo.xres / 8, vinfo.yres / 8, vinfo.xres / 4, vinfo.yres / 4, 0xffff0000); drawRect (vinfo.xres * 3 / 8, vinfo.yres * 3 / 8, vinfo.xres / 4, vinfo.yres / 4, 0xff00ff00); drawRect (vinfo.xres * 5 / 8, vinfo.yres * 5 / 8, vinfo.xres / 4, vinfo.yres / 4, 0xff0000ff); sleep (5); printf (" Done. "); munmap (frameBuffer, screensize); //解除内存映射,与mmap对应 close (fbFd); return 0; }
附2 event 的测试程序
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <linux/input.h> #define DEV_NAME "/dev/input/event2" #define DBG_PRINTF printf //#define DBG_PRINTF(...) struct input_event input_mouse; int main(int argc, char **argv) { int fd,retval; fd_set readfds; fd = open(DEV_NAME, O_RDONLY); if (fd < 0) { printf("can't open %s ",DEV_NAME); return -1; } while(1) { FD_ZERO( &readfds ); FD_SET( fd, &readfds ); retval = select( fd+1, &readfds, NULL, NULL, NULL); if(retval==0) { printf( "Time out! " ); } if(FD_ISSET(fd,&readfds)) { read(fd, &input_mouse,sizeof(struct input_event)); // printf("mouse.type = %d, mouse.code = %d ", input_mouse.type, input_mouse.code); switch(input_mouse.type) { case EV_KEY: {/* have key is press */ switch(input_mouse.code) { case BTN_LEFT: { if(input_mouse.value==1) DBG_PRINTF("the left is press! "); } break; case BTN_RIGHT: { if(input_mouse.value==1) DBG_PRINTF("the right is press! "); } break; case BTN_MIDDLE: { if(input_mouse.value==1) DBG_PRINTF("the middle is press! "); } break; } } break; case EV_REL: case EV_ABS: { switch(input_mouse.code) { case REL_X: { /* if(input_mouse.value>0) DBG_PRINTF("X slip is right! "); else if(input_mouse.value<0) DBG_PRINTF("X slip is left! "); */ printf("POS( %d, ", input_mouse.value); } break; case REL_Y: {/* if(input_mouse.value<0) DBG_PRINTF("Y slip is up! "); else if(input_mouse.value>0) DBG_PRINTF("Y slip is down! "); */ printf("%d ) ", input_mouse.value); } break; } } break; } } } close(fd); return 0; }