UEFI开发学习8 – LVGL GUI库的移植
前言
目前UEFI上是没有自带任何可用的GUI 库的,这导致efi app大多也只能以命令行或简约的界面存在了。所以移植一款可在UEFI上用的GUI库是我一直想做的事。最近项目不忙了,想起这件事,便开始着手了。
UEFI目前支持的是C语言,所以要找的当然是用C语言写的GUI库比较好,当然c++的也可以,罗冰大佬移植的便是C++的GUI库,详见->UEFI开发探索47 – UEFI上移植GUILite。于是在网上找了一下,了解到一款为嵌入式设计的GUI库,叫LVGL,用纯C语言开发,无需任何依赖,有了这些特点便值得动手一试了。最终还是成功了,看下效果图:
LVGL地址:
Github地址:https://github.com/lvgl/lvgl
在线文档:https://docs.lvgl.io/latest/en/html/index.html
本移植过程主要分为3个步骤,分别为:
- 编译
- 运行DEMO
- 键盘输入接口移植
1、编译
1.1 在AppPkg/Applications建立一个文件夹Lvgl以及一个inf文件:Lvgl.inf,然后将下载源码中的src目录、lv_conf_template.h、lvgl.h放入该文件夹中。
1.2 将src目录中的.c文件全部写到inf文件中,由于文件比较多,可打开每个子目录下的mk文件,里面有该目录下的c文件名,将其复制到inf的[Sources]下,通过列操作(notepad++或vscode按住shift+alt再拖动鼠标光标)删除前面的不需要的信息,并加上所需的目录。
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参数去掉才可编译通过。
到这里便可以编译成功了。
2、运行DEMO
编译成功后该怎么使用这个GUI 库呢?官方有详细的介绍如何建立一个工程-> Quick overview、Set-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/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。
Sakura
Ay123@Sakura
象牙塔
bin@象牙塔
象牙塔@bin
bin@象牙塔
象牙塔@bin
bin@象牙塔
hurryboylqs
hurryboylqs
bin@hurryboylqs
白菜
bin@白菜
白菜@bin
bin@白菜
白菜
bin@白菜
Jim
skyline
bin@skyline
go
bin@go
一点点
bin@一点点
Obins
tcyfs