【LittlevGL】移植

“LittlevGL is a free and open-source graphics library providing everything you need to create embedded GUI with easy-to-use graphical elements, beautiful visual effects and low memory footprint.” -- https://littlevgl.com/#

LittlevGL的移植工作可以說是異常簡單,開發人員的工作做得很到位。具體的移植步驟和簡易教程可以參考github上的LittlevGL工程的說明文檔:https://github.com/littlevgl/lvgl。下面簡單說明LittlevGL的移植步驟(參考官方文檔):

1、下載(克隆)LittlevGL的源碼到自己的工程目錄下的lvgl目錄。

2、將 lvgl/lv_conf_template.h 文件複製一份爲 lv_conf.h 文件與 lvgl 目錄並列存放,該文件用於配置LittlevGL,至少需要設置其中的LV_HOR_RES_MAX(水平分辨率)LV_VER_RES_MAX(垂直分辨率)LV_COLOR_DEPTH(像素深度)三個參數

3、在工程中包含 lvgl/lvgl.h ,該頭文件中包含了使用LittlevGL所需要的函數定義。

4、每過 x 毫秒調用 lv_tick_inc(x) 函數一次(1 ≤ x ≤ 10),這個函數是LittlevGL運行所需的時鐘源。如果定義LV_TICK_CUSTOM爲1的話,就無須在應用程序中主動調用 lv_tick_inc(x) 函數,而是需要定義一個獲取當前系統已運行時間的函數(例如uint32_t custom_tick_get(void);)並使用宏定義LV_TICK_CUSTOM_SYS_TIME_EXPR表示該函數,這個函數會在調用 lv_task_handler() 函數的時候自動調用並獲取當前時間戳。

5、調用 lv_init() 函數初始化LittlevGL。

6、創建一個顯示緩存區,該緩存區用於給LittlevGL進行繪製

static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];                     /*Declare a buffer for 10 lines*/
lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);    /*Initialize the display buffer*/

7、創建並註冊一個顯示器驅動,這個驅動被註冊到LittlevGL之後,LittlevGL就可以向該顯示器輸出數據:

lv_disp_drv_t disp_drv;               /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv);          /*Basic initialization*/
disp_drv.flush_cb = my_disp_flush;    /*Set your driver function*/
disp_drv.buffer = &disp_buf;          /*Assign the buffer to the display*/
lv_disp_drv_register(&disp_drv);      /*Finally register the driver*/

其中my_disp_flush函數是用於向顯示器中寫入數據的底層函數,在LittlevGL中,關於顯示器的接口函數就這麼一個,我們將這個函數實現得儘可能的高效率:

void my_disp_flush(lv_disp_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    int32_t x, y;
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            set_pixel(x, y, *color_p);  /* Put a pixel to the display.*/
            color_p++;
        }
    }

    lv_disp_flush_ready(disp);         /* Indicate you are ready with the flushing*/
}

8、創建並註冊輸入設備(鼠標、鍵盤、觸屏等),例如創建並註冊一個觸摸板的驅動:

lv_indev_drv_init(&indev_drv);             /*Descriptor of a input device driver*/
indev_drv.type = LV_INDEV_TYPE_POINTER;    /*Touch pad is a pointer-like device*/
indev_drv.read_cb = my_touchpad_read;      /*Set your driver function*/
lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/

其中my_touchpad_read則是觸摸板的底層操作函數,例如:

bool my_touchpad_read(lv_indev_t * indev, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    /*Save the state and save the pressed coordinate*/
    data->state = touchpad_is_pressed() ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; 
    if(data->state == LV_INDEV_STATE_PR) touchpad_get_xy(&last_x, &last_y);
   
    /*Set the coordinates (if released use the last pressed coordinates)*/
    data->point.x = last_x;
    data->point.y = last_y;

    return false; /*Return `false` because we are not buffering and no more data to read*/
}

9、週期性調用 lv_task_handler() 函數,週期間隔爲若干毫秒,在定時器中斷或者一個線程中調用。這個函數會在適當的時候重繪屏幕以及讀取輸入設備的數據。

現在手頭沒有現成的可以一直LittlevGL得板子,但是LittlevGL官方給出的移植例程中有基於Linux系統下的FrameBuffer的移植例程:https://blog.littlevgl.com/2018-01-03/linux_fb。我們正好可以使用Ubuntu虛擬機對這個例程進行測試,從github上下載這個例程的源碼:https://blog.littlevgl.com/2018-01-03/linux_fb,注意其中lv_drivers、lv_examples和lvgl是單獨的git項目,需要單獨下載。獲取到完整的源碼之後,在Linux下進行編譯,直接執行make命令即可,得到一個 demo 可執行文件。先不要急着執行,先在Ubuntu下切換到字符終端模式(Ctrl + Alt + F1 and service lightdm stop),然後執行demo文件:

執行效果直接出來了,就是這麼簡單。但是這個執行的效果和官網上的有一些區別,我們的demo執行結果中Write欄目中的鍵盤沒有顯示,下面查看demo工程的源碼進行進一步的學習。

既然是基於Linux下的FrameBuffer移植測試的,我們需要先搞定Linux的FrameBuffer的驅動,下面是一個典型的Linux下FrameBuffer操作例程:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

int main()
{
    int fbfd = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;
    char *fbp = 0;
    int x = 0, y = 0;
    long int location = 0;

    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd == -1) {
        perror("Error: cannot open framebuffer device");
        exit(1);
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
        perror("Error reading fixed information");
        exit(2);
    }

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("Error reading variable information");
        exit(3);
    }

    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");
        exit(4);
    }
    printf("The framebuffer device was mapped to memory successfully.\n");

    x = 0; y = 0;       // Where we are going to put the pixel

    // Figure out where in memory to put the pixel
    for (y = 0; y < vinfo.yres; y++)
        for (x = 0; x < vinfo.xres; x++) {

            location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
                       (y+vinfo.yoffset) * finfo.line_length;

			*((unsigned int*)(fbp + location)) = 0xE81123;
        }
    for (y = 0; y < vinfo.yres / 2; y++)
        for (x = 0; x < vinfo.xres / 2; x++) {

            location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
                       (y+vinfo.yoffset) * finfo.line_length;

				*((unsigned int*)(fbp + location)) = 0x746283;
        }
    munmap(fbp, screensize);
    close(fbfd);
    return 0;
}

該程序的執行效果如下:

紅色填充滿了全部的顯示區域,灰色填充了1/2的寬度和1/2的高度的區域。現在我們擁有一個基於FrameBuffer的顯示器驅動了。下面可以看看demo例程的源碼了。main文件內容如下:

#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_examples/lv_apps/demo/demo.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define DISP_BUF_SIZE (80*LV_HOR_RES_MAX)

int main(void)
{
    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init();

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer*/
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);

    /*Initialize and register a display driver*/
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.buffer = &disp_buf;
    disp_drv.flush_cb = fbdev_flush;
    lv_disp_drv_register(&disp_drv);

    /*Create a Demo*/
    demo_create();

    /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_task_handler();
        usleep(5000);
    }

    return 0;
}


/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}

該demo高度貼合前面說明的LittlevGL的移植步驟。其中 fbdev_init() 函數用於初始化系統的FrameBuffer顯示驅動,和我們的FrameBuffer測試程序是相似的,然後還添加了一個 fbdev_flush() 函數,這個函數就是顯示器驅動的底層繪製函數:

/**
 * Flush a buffer to the marked area
 * @param drv pointer to driver where this function belongs
 * @param area an area where to copy `color_p`
 * @param color_p an array of pixel to copy to the `area` part of the screen
 */
void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
{
    if(fbp == NULL ||
            area->x2 < 0 ||
            area->y2 < 0 ||
            area->x1 > (int32_t)vinfo.xres - 1 ||
            area->y1 > (int32_t)vinfo.yres - 1) {
        lv_disp_flush_ready(drv);
        return;
    }

    /*Truncate the area to the screen*/
    int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
    int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
    int32_t act_x2 = area->x2 > (int32_t)vinfo.xres - 1 ? (int32_t)vinfo.xres - 1 : area->x2;
    int32_t act_y2 = area->y2 > (int32_t)vinfo.yres - 1 ? (int32_t)vinfo.yres - 1 : area->y2;


    lv_coord_t w = lv_area_get_width(area);
    long int location = 0;
    long int byte_location = 0;
    unsigned char bit_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;
        int32_t y;
        for(y = act_y1; y <= act_y2; y++) {
            location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 4;
            memcpy(&fbp32[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 4);
            color_p += w;
        }
    }
    /*16 bit per pixel*/
    else if(vinfo.bits_per_pixel == 16) {
        uint16_t * fbp16 = (uint16_t *)fbp;
        int32_t y;
        for(y = act_y1; y <= act_y2; y++) {
            location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 2;
            memcpy(&fbp16[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 2);
            color_p += w;
        }
    }
    /*8 bit per pixel*/
    else if(vinfo.bits_per_pixel == 8) {
        uint8_t * fbp8 = (uint8_t *)fbp;
        int32_t y;
        for(y = act_y1; y <= act_y2; y++) {
            location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length;
            memcpy(&fbp8[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1));
            color_p += w;
        }
    }
    /*1 bit per pixel*/
    else if(vinfo.bits_per_pixel == 1) {
        uint8_t * fbp8 = (uint8_t *)fbp;
        int32_t x;
        int32_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;
                byte_location = location / 8; /* find the byte we need to change */
                bit_location = location % 8; /* inside the byte found, find the bit we need to change */
                fbp8[byte_location] &= ~(((uint8_t)(1)) << bit_location);
                fbp8[byte_location] |= ((uint8_t)(color_p->full)) << bit_location;
                color_p++;
            }

            color_p += area->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_disp_flush_ready(drv);
}

註冊好顯示器驅動之後調用 demo_create() 函數並週期性地執行 lv_task_handler() 函數。然後我們注意到有定義一個 custom_tick_get(void) 函數,但是主程序中沒有調用該函數,細心點會注意到這個函數其實就是前面說過的,如果不使用 lv_tick_inc(x) 函數的話,需要定義一個獲取當前程序已運行時間的函數,並以宏LV_TICK_CUSTOM_SYS_TIME_EXPR來表示該函數。到這裏demo程序的框架就已經搞定了,具體的LittlevGL的使用也就是 demo_create() 函數中的實現後面再繼續學習。

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章