UEFI开发学习8 – LVGL GUI库的移植

前言

目前UEFI上是没有自带任何可用的GUI 库的,这导致efi app大多也只能以命令行或简约的界面存在了。所以移植一款可在UEFI上用的GUI库是我一直想做的事。最近项目不忙了,想起这件事,便开始着手了。

UEFI目前支持的是C语言,所以要找的当然是用C语言写的GUI库比较好,当然c++的也可以,罗冰大佬移植的便是C++的GUI库,详见->UEFI开发探索47 – UEFI上移植GUILite。于是在网上找了一下,了解到一款为嵌入式设计的GUI库,叫LVGL,用纯C语言开发,无需任何依赖,有了这些特点便值得动手一试了。最终还是成功了,看下效果图:20200805141040.jpg

LVGL地址:

Github地址:https://github.com/lvgl/lvgl

在线文档:https://docs.lvgl.io/latest/en/html/index.html

论坛:http://forum.lvgl.io

本移植过程主要分为3个步骤,分别为:
  1. 编译
  2. 运行DEMO
  3. 键盘输入接口移植

1、编译

1.1 在AppPkg/Applications建立一个文件夹Lvgl以及一个inf文件:Lvgl.inf,然后将下载源码中的src目录、lv_conf_template.h、lvgl.h放入该文件夹中。20200805143555.jpg

1.2 将src目录中的.c文件全部写到inf文件中,由于文件比较多,可打开每个子目录下的mk文件,里面有该目录下的c文件名,将其复制到inf的[Sources]下,通过列操作(notepad++或vscode按住shift+alt再拖动鼠标光标)删除前面的不需要的信息,并加上所需的目录。

20200805145841.png

1.3 将lvgl.inf加入AppPkg.dsc中,再将lv_conf_template.h重命名为lv_conf.h,然后打开该文件,将开头的#if 0改为#if 1启用该头文件。接着添加lvgl.c(inf文件也要记得加入该文件),加入一个空main主函数,如下:

#include  <Library/ShellCEntryLib.h>

int main(int argc, char **argv)
{
    return 0;
}

再编译AppPkg.dsc,发现报错:

h:\udk2018\apppkg\applications\lvgl\src\lv_core\../lv_misc/lv_color.h(616): error C2220: 警告被视为错误 - 没有生成“object”文件
h:\udk2018\apppkg\applications\lvgl\src\lv_core\../lv_misc/lv_color.h(616): warning C4204: 使用了非标准扩展: 非常量聚合初始值设定项
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(349): warning C4244: “函数”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(350): warning C4244: “函数”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(360): warning C4244: “=”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(361): warning C4244: “=”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(362): warning C4244: “=”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
h:\udk2018\apppkg\applications\lvgl\src\lv_draw\lv_img_buf.h(363): warning C4244: “=”: 从“__int32_t”转换到“lv_coord_t”,可能丢失数据
NMAKE : fatal error U1077: “"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Vc\bin\x86_amd64\cl.exe"”: 返回代码“0x2”

这是因为采用C99的语法,VS会警告,使用#pragma warning(disable:警告号)可将其禁用,可在lv_conf.h开头加入,主要有如下几个:

#pragma warning(disable:4018) 
#pragma warning(disable:4090) 
#pragma warning(disable:4028) 
#pragma warning(disable:4204) 
#pragma warning(disable:4228) 
#pragma warning(disable:4244)

如果是在Linux下用GCC编译,则需将UDK2018/Conf/tools_def.txt中DEFINE GCC44_ALL_CC_FLAGS后的-Werror参数去掉才可编译通过。

到这里便可以编译成功了。

20200805153806.jpg

2、运行DEMO

编译成功后该怎么使用这个GUI 库呢?官方有详细的介绍如何建立一个工程-> Quick overviewSet-up a project

1) 首先调用lv_init()初始化lv库

2) 创建一个显示的缓冲区

3) 初始化显示驱动

4) 初始化输入设备

这里先直接使用官方的源码,由于没有触摸输入设备,就不加那一部分了,代码如下:

#include  <Library/ShellCEntryLib.h>
#include  "lvgl.h"

void my_disp_flush(lv_disp_drv_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*/
}

int main(int argc, char **argv)
{
    lv_init();

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

    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*/

    while (1) {
        lv_tick_inc(5);
        lv_task_handler();
    }

    return 0;
}

编译运行之后发现什么也没有,不过这是正常的,因为上面的代码中还没有移植UEFI显示部分的,my_disp_flush这个callback是需要我们自己去实现的,具体要做的就是如何将需要显示的缓冲数据绘制在显示器上,我在这一篇中已经有介绍了-> UEFI开发学习3 -高级GUI编程,即使用EFI_GRAPHICS_OUTPUT_PROTOCOL.Blt()将接收到的数据显示即可,修改后代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/GraphicsOutput.h>
#include  <Library/UefiBootServicesTableLib.h>
#include  "lvgl.h"

void my_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    EFI_GRAPHICS_OUTPUT_PROTOCOL       *GraphicsOutput;
    EFI_STATUS  Status;
    UINTN Width, Height;

    Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
        return;
    }

    if (area->x2 - area->x1 >= 0) {
        Width = area->x2 - area->x1;
    }
    else {
        Width = area->x1 - area->x2;
    }

    if (area->y2 - area->y1 >= 0) {
        Height = area->y2 - area->y1;
    }
    else {
        Height = area->y1 - area->y2;
    }

    Width  ++;
    Height ++;

    Status = GraphicsOutput->Blt (
        GraphicsOutput,
        (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) &color_p->ch ,
        EfiBltBufferToVideo,
        0,
        0,
        area->x1,
        area->y1,
        Width,
        Height,
        0
    );

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

int main(int argc, char **argv)
{
    lv_init();

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

    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*/

    while (1) {
        lv_tick_inc(5);
        lv_task_handler();
    }

    return 0;
}

运行:

能看到这个效果说明已经移植成功了,下面需要再配置一下lv_conf.h这个文件,主要有几个地方:

1) LV_HOR_RES_MAX 和 LV_VER_RES_MAX用于设置分辨率大小,暂且设为800x600

2) LV_COLOR_DEPTH需设置为32

3) LV_COLOR_SCREEN_TRANSP是否启动透明效果

4) LV_MEM_CUSTOM自定义内存分配函数,这个是需要打开的,然后再设置下面三个宏:

#  define LV_MEM_CUSTOM_INCLUDE <Library/MemoryAllocationLib.h>   /*Header for the dynamic memory function*/ 
#  define LV_MEM_CUSTOM_ALLOC   AllocatePool       /*Wrapper to malloc*/ 
#  define LV_MEM_CUSTOM_FREE    FreePool         /*Wrapper to free*/

5) LV_USE_LOG 这个需要打印debug信息的时候可以打开

配置完这些,再在主函数加入一个message box,打印一段话,再看实验结果。代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/GraphicsOutput.h>
#include  <Library/UefiBootServicesTableLib.h>
#include  "lvgl.h"

void my_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    EFI_GRAPHICS_OUTPUT_PROTOCOL       *GraphicsOutput;
    EFI_STATUS  Status;
    UINTN Width, Height;

    Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
        return;
    }

    if (area->x2 - area->x1 >= 0) {
        Width = area->x2 - area->x1;
    }
    else {
        Width = area->x1 - area->x2;
    }

    if (area->y2 - area->y1 >= 0) {
        Height = area->y2 - area->y1;
    }
    else {
        Height = area->y1 - area->y2;
    }

    Width  ++;
    Height ++;

    Status = GraphicsOutput->Blt (
        GraphicsOutput,
        (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) &color_p->ch ,
        EfiBltBufferToVideo,
        0,
        0,
        area->x1,
        area->y1,
        Width,
        Height,
        0
    );

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

int main(int argc, char **argv)
{
    lv_init();

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

    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*/

    lv_obj_t * m = lv_msgbox_create(lv_scr_act(), NULL);
    lv_msgbox_set_text(m, "LVGL for UEFI - https://ay123.net");

    while (1) {
        lv_tick_inc(5);
        lv_task_handler();
    }

    return 0;
}

做到这一步,显示部分已经没问题了,接下来就是输入设备的移植了。

3、输入设备移植

LVGL支持的输入设备包括键盘鼠标和触摸,这里只写写如何移植键盘设备。

移植之前需要先下载一个LVGL的driver和一个example包,地址:lv_drivers , lv_examples

下载后解压,将lv_drivers下的indev目录复制到lvgl/src目录,lv_examples下的src目录复制到lvgl下。

1)  将lv_ex_conf_templ.h命名为lv_ex_conf.h、打开文件开头的#if 0、以及LV_EX_KEYBOARD、LV_USE_DEMO_KEYPAD_AND_ENCODER两个宏

2)  将src/indev/keyboard.c和src/lv_demo_keypad_encoder/lv_demo_keypad_encoder.c加入inf文件

3)  lvgl.c调用example中的lv_demo_keypad_encoder函数,lvgl.c文件如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/GraphicsOutput.h>
#include  <Library/UefiBootServicesTableLib.h>
#include  "lvgl.h"
#include  "lv_examples.h"

void my_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    EFI_GRAPHICS_OUTPUT_PROTOCOL       *GraphicsOutput;
    EFI_STATUS  Status;
    UINTN Width, Height;

    Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
        return;
    }

    if (area->x2 - area->x1 >= 0) {
        Width = area->x2 - area->x1;
    }
    else {
        Width = area->x1 - area->x2;
    }

    if (area->y2 - area->y1 >= 0) {
        Height = area->y2 - area->y1;
    }
    else {
        Height = area->y1 - area->y2;
    }

    Width  ++;
    Height ++;

    Status = GraphicsOutput->Blt (
        GraphicsOutput,
        (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) &color_p->ch ,
        EfiBltBufferToVideo,
        0,
        0,
        area->x1,
        area->y1,
        Width,
        Height,
        0
    );

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

int main(int argc, char **argv)
{
    lv_init();

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

    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*/

    //lv_obj_t * m = lv_msgbox_create(lv_scr_act(), NULL);
    //lv_msgbox_set_text(m, "LVGL for UEFI - https://ay123.net");

    lv_demo_keypad_encoder();

    while (1) {
        lv_tick_inc(5);
        lv_task_handler();
        gBS->Stall(1000 * 5);
    }

    return 0;
}

4)  keyboard.h中包含了SDL2/SDL.h,这个是需要额外下载的库,我整理了一下只需几个头文件即可,点击下载

然后编译,有些头文件会提示找不到,稍微改下就可以了,这里不详述。结果如下:

5)  确定编译通过后就可以移植键盘输入设备了。主要是修改keyboard.c中的keyboard_read和keycode_to_ascii函数,把UEFI中读取的字符数据传递给它即可,相关获取键盘输入可查看->UEFI开发学习5 - 获取键盘输入,最终两个修改的程序如下:

bool keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    EFI_STATUS Status;
    EFI_INPUT_KEY Key;
    Status =  gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
    if (Status == EFI_SUCCESS)
    {
        data->key = keycode_to_ascii(Key);
        data->state = LV_INDEV_STATE_PR;
        //Print (L"Key:%d %d", Key.ScanCode, Key.UnicodeChar);
        return false;
    }
    data->state = LV_INDEV_STATE_REL;

    return false;
}

static uint32_t keycode_to_ascii(EFI_INPUT_KEY sdl_key)
{
    /*Remap some key to LV_KEY_... to manage groups*/
    switch(sdl_key.ScanCode) {
        case SCAN_UP:
            return LV_KEY_UP;

        case SCAN_DOWN:
            return LV_KEY_DOWN;

        case SCAN_RIGHT:
            return LV_KEY_RIGHT;

        case SCAN_LEFT:
            return LV_KEY_LEFT;

        case SCAN_HOME:
            return LV_KEY_HOME;

        case SCAN_END:
            return LV_KEY_END;

        case SCAN_DELETE:
            return LV_KEY_DEL;

        case SCAN_PAGE_UP:
            return LV_KEY_PREV;

        case SCAN_PAGE_DOWN:
            return LV_KEY_NEXT;

        case SCAN_ESC:
            return LV_KEY_ESC;

        case SCAN_NULL:
            switch(sdl_key.UnicodeChar) {
                case 13:
                    return LV_KEY_ENTER;

                default:
                    return sdl_key.UnicodeChar;
            }
        default:
            return sdl_key.UnicodeChar;
    }
}

看一下最终的成果:

 

版权声明:
作者:bin
链接:https://ay123.net/mystudy/845/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>