調試 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(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\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 == -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) \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 即可。

 

附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:\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 = 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\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[17] = {
        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 = 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\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, 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,\n"
            "they should be colored red, green and blue (in that order).\n");
    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.\n");

    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\n",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!\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==1)
                            DBG_PRINTF("the left is press!\n");
                    }
                    break;
                    case BTN_RIGHT:
                    {
                        if(input_mouse.value==1)
                            DBG_PRINTF("the right is press!\n");    
                    }
                    break;
                    case BTN_MIDDLE:
                    {
                        if(input_mouse.value==1)
                            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 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM