调试 lvgl 的一个例子

发现一个新的 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() {
lv_tick_inc();
lv_task_handler();
usleep();
} return ;
}

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 ==  || vinfo.bits_per_pixel == ) {
uint32_t *fbp32 = (uint32_t*)fbp;
uint32_t x;
uint32_t y; int stride = finfo.line_length / ; 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) == -) {
perror("Error reading variable information");
return;
} printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("%d \n", 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 == -) {
perror("unable open evdev interface:");
return;
} fcntl(evdev_fd, F_SETFL, O_ASYNC|O_NONBLOCK); evdev_root_x = ;
evdev_root_y = ;
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 = ;
evdev_y_resolution = ;
#endif }

然后,读取鼠标坐标的时候,进行归一化:

    while(read(evdev_fd, &in, sizeof(struct input_event)) > ) {
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 == )
evdev_button = LV_INDEV_STATE_REL;
else if (in.value == )
evdev_button = LV_INDEV_STATE_PR;
}
//printf("BTN ClICKED(%d, %d) \n", 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 即可。

调试 lvgl 的一个例子

附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 = ; //打印fb驱动中fix结构信息,注:在fb驱动加载后,fix结构不可被修改。
void
printFixedInfo ()
{
printf ("Fixed screen info:\n"
"\tid: %s\n"
"\tsmem_start: 0x%lx\n"
"\tsmem_len: %d\n"
"\ttype: %d\n"
"\ttype_aux: %d\n"
"\tvisual: %d\n"
"\txpanstep: %d\n"
"\typanstep: %d\n"
"\tywrapstep: %d\n"
"\tline_length: %d\n"
"\tmmio_start: 0x%lx\n"
"\tmmio_len: %d\n"
"\taccel: %d\n"
"\n",
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:\n"
"\txres: %d\n"
"\tyres: %d\n"
"\txres_virtual: %d\n"
"\tyres_virtual: %d\n"
"\tyoffset: %d\n"
"\txoffset: %d\n"
"\tbits_per_pixel: %d\n"
"\tgrayscale: %d\n"
"\tred: offset: %2d, length: %2d, msb_right: %2d\n"
"\tgreen: offset: %2d, length: %2d, msb_right: %2d\n"
"\tblue: offset: %2d, length: %2d, msb_right: %2d\n"
"\ttransp: offset: %2d, length: %2d, msb_right: %2d\n"
"\tnonstd: %d\n"
"\tactivate: %d\n"
"\theight: %d\n"
"\twidth: %d\n"
"\taccel_flags: 0x%x\n"
"\tpixclock: %d\n"
"\tleft_margin: %d\n"
"\tright_margin: %d\n"
"\tupper_margin: %d\n"
"\tlower_margin: %d\n"
"\thsync_len: %d\n"
"\tvsync_len: %d\n"
"\tsync: %d\n"
"\tvmode: %d\n"
"\n",
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 = ;
const int stride = finfo.line_length / bytesPerPixel; int *dest = (int *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y;
for (y = ; y < height; ++y)
{
for (x = ; 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 = ;
const int stride = finfo.line_length / bytesPerPixel;
const int red = (color & 0xff0000) >> ( + );
const int green = (color & 0xff00) >> ( + );
const int blue = (color & 0xff) >> ;
const short color16 = blue | (green << ) | (red << ( + )); short *dest = (short *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y;
for (y = ; y < height; ++y)
{
for (x = ; 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 = ;
const int stride = finfo.line_length / bytesPerPixel;
const int red = (color & 0xff0000) >> ( + );
const int green = (color & 0xff00) >> ( + );
const int blue = (color & 0xff) >> ;
const short color15 = blue | (green << ) | (red << ( + )) | 0x8000; short *dest = (short *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset); int x, y;
for (y = ; y < height; ++y)
{
for (x = ; 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 :
drawRect_rgb32 (x0, y0, width, height, color);
break;
case :
drawRect_rgb16 (x0, y0, width, height, color);
break;
case :
drawRect_rgb15 (x0, y0, width, height, color);
break;
default:
printf ("Warning: drawRect() not implemented for color depth %i\n",
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[] = {
0x3A428472, 0x724B84D3, 0x26B898AB, 0x7D980E3C, 0x5345A084,
0x6779B66B, 0x791EE4B4, 0x6E8EE3CC, 0x63AF504A, 0x18A21B33,
0x0E26EB73, 0x022F708E, 0x1740F3B0, 0x7E2C699D, 0x0E8A570B,
0x5F2C22FB, 0x6A742130
}; printf ("Frame Buffer Performance test...\n");
for (run = ; run < PERFORMANCE_RUN_COUNT; ++run)
{
/* Generate test image with random(ish) data: */
testImage = (unsigned int *) malloc (fbSize);
j = run;
for (i = ; i < (int) (fbSize / sizeof (int)); ++i)
{
testImage[i] = randData[j];
j++;
if (j >= )
j = ;
} gettimeofday (&startTime, NULL);
memcpy (fb, testImage, fbSize);
gettimeofday (&endTime, NULL); long secsDiff = endTime.tv_sec - startTime.tv_sec;
results[run] =
secsDiff * + (endTime.tv_usec - startTime.tv_usec); free (testImage);
} average = ;
for (i = ; i < PERFORMANCE_RUN_COUNT; ++i)
average += results[i];
average = average / PERFORMANCE_RUN_COUNT; printf (" Average: %llu usecs\n", average);
printf (" Bandwidth: %.03f MByte/Sec\n",
(fbSize / 1048576.0) / ((double) average / 1000000.0));
printf (" Max. FPS: %.03f fps\n\n",
1000000.0 / (double) average); /* Clear the framebuffer back to black again: */
memset (fb, , fbSize);
} int
main (int argc, char **argv)
{
const char *devfile = "/dev/fb0";
long int screensize = ;
int fbFd = ; /* Open the file for reading and writing */
fbFd = open (devfile, O_RDWR);
if (fbFd == -)
{
perror ("Error: cannot open framebuffer device");
exit ();
} //获取finfo信息并显示
if (ioctl (fbFd, FBIOGET_FSCREENINFO, &finfo) == -)
{
perror ("Error reading fixed information");
exit ();
}
printFixedInfo ();
//获取vinfo信息并显示
if (ioctl (fbFd, FBIOGET_VSCREENINFO, &vinfo) == -)
{
perror ("Error reading variable information");
exit ();
}
printVariableInfo (); /* Figure out the size of the screen in bytes */
screensize = finfo.smem_len; /* Map the device to memory */
frameBuffer =
(char *) mmap (, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,
fbFd, );
if (frameBuffer == MAP_FAILED)
{
perror ("Error: Failed to map framebuffer device to memory");
exit ();
} //测试virt fb的性能
performSpeedTest (frameBuffer, screensize); printf ("Will draw 3 rectangles on the screen,\n"
"they should be colored red, green and blue (in that order).\n");
drawRect (vinfo.xres / , vinfo.yres / ,
vinfo.xres / , vinfo.yres / , 0xffff0000);
drawRect (vinfo.xres * / , vinfo.yres * / ,
vinfo.xres / , vinfo.yres / , 0xff00ff00);
drawRect (vinfo.xres * / , vinfo.yres * / ,
vinfo.xres / , vinfo.yres / , 0xff0000ff); sleep ();
printf (" Done.\n"); munmap (frameBuffer, screensize); //解除内存映射,与mmap对应 close (fbFd);
return ;
}

附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 < )
{
printf("can't open %s\n",DEV_NAME);
return -;
} while()
{
FD_ZERO( &readfds );
FD_SET( fd, &readfds );
retval = select( fd+, &readfds, NULL, NULL, NULL);
if(retval==)
{
printf( "Time out!\n" );
}
if(FD_ISSET(fd,&readfds))
{
read(fd, &input_mouse,sizeof(struct input_event));
// printf("mouse.type = %d, mouse.code = %d\n", 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==)
DBG_PRINTF("the left is press!\n");
}
break;
case BTN_RIGHT:
{
if(input_mouse.value==)
DBG_PRINTF("the right is press!\n");
}
break;
case BTN_MIDDLE:
{
if(input_mouse.value==)
DBG_PRINTF("the middle is press!\n");
}
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!\n");
else if(input_mouse.value<0)
DBG_PRINTF("X slip is left!\n");
*/
printf("POS( %d, ", input_mouse.value);
}
break;
case REL_Y:
{/*
if(input_mouse.value<0)
DBG_PRINTF("Y slip is up!\n");
else if(input_mouse.value>0)
DBG_PRINTF("Y slip is down!\n");
*/
printf("%d ) \n", input_mouse.value);
}
break;
}
}
break;
}
}
}
close(fd);
return ;
}
上一篇:python| 2021款 m1pro mbp python开发配置


下一篇:前端新人学习笔记-------html/css/js基础知识点(二)