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