ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《LVGL开发指南 V1.0(G4电机开发板专用)》第二章 LVGL无操作系统移植

《LVGL开发指南 V1.0(G4电机开发板专用)》第二章 LVGL无操作系统移植

原创 嵌入式/内核开发 作者:正点原子 时间:2026-05-13 11:52:49 0 删除 编辑

第二章 LVGL无操作系统移植


       本章我们主要介绍LVGL的无操作系统移植(裸机),让大家了解如何将LVGL移植到DMG474电机开发板。在移植之前,先声明一点,本书仅适用于正点原子DMG474电机开发板。

       本章节将分为以下几个小节:

       2.1 移植准备工作

       2.2 向工程添加文件

       2.3 修改工程文件

       2.4 移植官方例程

       2.5下载验证

 

        2.1 移植准备工作


       1. 准备基础工程

       在移植LVGL之前,用户必须在裸机实验中选择一个例程作为移植的基础工程,值得注意的是,最终的基础工程中必须包含LCD显示驱动以及基本定时器驱动。这里建议大家选择《TFTLCD(1.3寸)实验》作为基础工程,并准备好《基本定时器中断实验》例程,有了这些之后,我们接下来需要将基本定时器相关的驱动文件夹复制到《TFTLCD(1.3寸)实验》中,如图2.1.1所示:


图2.1.1 添加基本定时器驱动


       2. 准备LVGL源码

       LVGL官方的GitHub仓库(https://github.com/lvgl/lvgl/)中可以下载源码,除此之外,我们也可以在开发板的光盘资料中获取,路径: A盘 →5,软件资料 →9,LVGL学习资料 →lvgl-maser.zip。解压lvgl-maser.zip压缩包后,即可得到如下图所示的文件和文件夹:


图2.1.2 LVGL源码目录


       3. 把上图中的lv_conf_template.h文件改名为lv_conf.h。

       4. 打开lv_conf.h文件,修改条件编译指令。

       修改前:

#if 0 /*Set it to "1" to enable content*/

       修改后:

#if 1 /* 把#if 0 修改成#if 1 */

       5. 精简LVGL源码

       除了examples文件夹、src文件夹、lv_conf_template.h和lvgl.h文件,其他的文件和文件夹均与移植无关,我们可以将它们删除,这样即可得到LVGL的精简源码,如下图所示(保留demos文件夹):


图2.1.3 LVGL移植简洁源码


       由上图可知, demos文件夹没有被删除,该文件夹中存放的是LVGL官方的演示例程,后续我们将移植其中的一些示例。

       接下来我们打开上图中的examples文件夹,仅保留其中的porting文件夹,其他的文件和文件夹皆可删除,删减后如下图所示:


图2.1.4 仅保留porting文件夹


        2.2 向工程添加文件


       1. 新建LVGL相关文件夹

       首先我们把《TFTLCD(1.3寸)实验》重命名为“LVGL例程1 无操作系统移植”,然后在该工程的Middlewares目录下新建LVGL文件夹,并在该文件夹下新建GUI文件夹和GUI_APP文件夹,最后在GUI文件夹下新建lvgl文件夹。具体的文件夹结构如下图所示:


图2.2.1 Middlewares目录下文件夹结构


       这里说明一点,我们的工程之所以采用这样的文件夹结构,主要是为了兼容LVGL源码中包含头文件的格式。对于初学者,这里建议按照此结构新建文件夹,否则有可能会遇到很多关于头文件的报错。


       2. 复制LVGL源码到工程中

       把精简后的LVGL源码(图2.1.3中的文件和文件夹)复制到《LVGL例程1 无操作系统移植》的Middlewares/LVGL/GUI/lvgl路径下,如下图所示:


图2.2.2 移植LVGL源码


       3. 添加工程分组、LVGL源文件

       打开《LVGL例程1 无操作系统移植》工程,点击 图标,添加如下图所示的分组:


图2.2.3 添加工程分组


       打开《LVGL例程1 无操作系统移植》工程下的src文件夹(所在路径:Middlewares/LVGL/GUI/lvgl/src),我们会发现它的子文件夹名称与上图的分组名称相似,如下图所示:


图2.2.4 src文件夹下的子文件夹


       结合图2.2.3和图2.2.4,我们在对应的分组中添加文件,具体如下所示:

       (1) 往Middlewares/lvgl/src/core分组中添加core文件夹下的全部.c文件,如下图所示:


图2.2.5 Middlewares/lvgl/src/core组添加的文件


       (2) 往Middlewares/lvgl/src/draw组中添加draw文件夹下除nxp_pxp、nxp_vglite、sdl和stm32_dma2d文件夹之外的全部.c文件,如下图所示:


图2.2.6 Middlewares/lvgl/src/draw组添加的文件


       (3)往Middlewares/lvgl/src/extra组中添加extra文件夹下除了lib文件夹之外的全部.c文件,如下图所示:


图2.2.7 Middlewares/lvgl/src/extra组添加的文件


       注意:Middlewares/lvgl/src/extra/lib文件夹下的文件没有添加到该分组,这些文件是第三方解码库相关的,我们目前还不用。

       (4)往 Middlewares/lvgl/src/font组中添加font文件夹下的全部.c文件,如下图所示:


图2.2.8 Middlewares/lvgl/src/font组添加的文件


       (5) 往Middlewares/lvgl/src/gpu组中添加draw/stm32_dma2d和draw/sdl文件夹下的全部.c文件,如下图所示:


图2.2.9 Middlewares/lvgl/src/gpu组添加的文件


       (6) 往Middlewares/lvgl/src/hal组中添加hal文件夹下的全部.c文件,如下图所示:


图2.2.10 Middlewares/lvgl/src/hal组添加的文件


       (7) 往Middlewares/lvgl/src/misc组中添加misc文件夹下的全部.c文件,如下图所示:


图2.2.11 Middlewares/lvgl/src/misc组添加的文件


       (8)往 Middlewares/lvgl/src/widgets组中添加widgets文件夹下的全部.c文件,如下图所示:


图2.2.12 Middlewares/lvgl/src/widgets组添加的文件


       (9) Middlewares/lvgl/examples/porting组添加Middlewares/LVGL/GUI/lvgl/examples/porting目录下的lv_port_disp_template.c和lv_port_indev_template.c文件,如下图所示:


图2.2.13 Middlewares/lvgl/examples/porting组添加的文件


       4. 添加头文件路径

       移植LVGL只需要添加关键的头文件路径即可,因为lvgl.h文件已经为我们省去了很多包含头文件的操作,添加的头文件路径如下图所示:


图2.2.14 添加头文件路径


       5. 添加定时器驱动

       往Drivers/BSP分组中添加基本定时器相关驱动,如下图所示:


图2.2.15 添加基本定时器驱动


       6. 修改工程目标名称

       将工程的目标名修改为“LVGL”或根据读者的实际场景进行修改,修改后如下图所示:


图 2.2.16 修改工程目标名称


       7. 屏蔽MDK的警告

       移植至此,如果我们编译代码,则会出现很多警告,这些警告都是LVGL源码所带来的,大家如果想屏幕这些警告,可以采用以下方法(非必须,慎用):点击 图标,选中C/C++选项卡,在Misc Controls框中填入以下内容:--diag_suppress=68 --diag_suppress=111 --diag_suppress=188 --diag_suppress=223 --diag_suppress=546 --diag_suppress=1295。


        2.3 修改工程文件


       1. 为LVGL提供时基

       打开Drivers/BSP分组中的btim.c文件,声明LVGL的头文件,如下源码所示:

#include "lvgl.h"

       在该文件下的HAL_TIM_PeriodElapsedCallback函数中添加以下源码:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&g_timx_handle))
    {
        lv_tick_inc(1);   /* lvgl的1ms心跳 */
    }
}

       上述源码中,定时器中断回调函数调用了LVGL的lv_tick_inc函数,该函数可以让LVGL内部的时基参数加1(入口参数为1的情况下)。

       接下来我们打开main.c文件,在main函数中,对基本定时器进行初始化,并设置中断周期为1ms。初始化配置如下:btim_timx_int_init(10 - 1, 17000 - 1);


       2. 配置显示屏、按键输入驱动

       打开Middlewares/lvgl/examples/porting分组中的lv_port_disp_template.c/h(显示屏相关)和lv_port_indev_template.c/h(按键输入相关)文件,将这4个文件中的条件编译指令#if  0都修改成#if 1,如下源码所示:

       修改前:

#if 0  /* lv_port_disp_template.c/h和lv_port_indev_template.c/h */

       修改后:

#if 1  /* 把#if 0 修改成#if 1 */

       ① 修改lv_port_disp_template.c文件

       该文件用于配置显示屏,它可以将用户的底层显示驱动与LVGL的显示驱动衔接起来。我们打开官方提供的lv_port_disp_template.c文件,包含LCD驱动头文件,然后初始化LCD以及配置打点函数,修改后的源码如下:

#include "lv_port_disp_template.h"
#include "../../lvgl.h"
#include "./BSP/LCD/lcd.h"
 
#define MY_DISP_HOR_RES (240)   /* 屏幕宽度 */
#define MY_DISP_VER_RES (240)   /* 屏幕高度 */
 
/* 显示设备初始化函数 */
static void disp_init(void);
 
/* 显示设备刷新函数 */
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, 
lv_color_t *color_p);
 
/**
 * @brief       初始化并注册显示设备
 * @param       无
 * @retval      无
 */
void lv_port_disp_init(void)
{
    /* 第一步:初始化显示设备 */
    disp_init();
 
    /* 创建一个绘图缓冲区 */
static lv_disp_draw_buf_t draw_buf_dsc_1;
 
    /* 设置缓冲区的大小为20行屏幕的大小 */
static lv_color_t buf_1[MY_DISP_HOR_RES * 20]; 
 
/* 第二步:初始化显示缓冲区 */
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 20); 
 
    /* 第三步:在 LVGL 中注册显示设备 */
    static lv_disp_drv_t disp_drv;                  /* 显示设备的描述符 */
    lv_disp_drv_init(&disp_drv);                    /* 初始化为默认值 */
 
    /* 第四步:设置显示设备的分辨率 */
    disp_drv.hor_res = 240;
    disp_drv.ver_res = 240;
 
    /* 第五步:用来将缓冲区的内容复制到显示设备 */
    disp_drv.flush_cb = disp_flush;
 
    /* 第六步:设置显示缓冲区 */
    disp_drv.draw_buf = &draw_buf_dsc_1;
 
    /* 第七步:注册显示设备 */
    lv_disp_drv_register(&disp_drv);
}
 
/**
 * @brief       初始化显示设备和必要的外围设备
 * @param       无
 * @retval      无
 */
static void disp_init(void)
{
    lcd_init();         /* 初始化LCD */
}
 
/**
 * @brief       将内部缓冲区的内容刷新到显示屏上的特定区域
 * @param       disp_drv    : 显示设备
 *   @arg       area        : 要刷新的区域,包含了填充矩形的对角坐标
 *   @arg       color_p     : 颜色数组
 * @retval      无
 */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area,
 lv_color_t * color_p)
{
    /* 在指定区域内填充指定颜色块 */
    lcd_color_fill(area->x1, area->y1, area->x2, area->y2,(uint16_t*)color_p);
    /* 通知图形库,已经刷新完毕了 */
    lv_disp_flush_ready(disp_drv);
}
 
#else
 
typedef int keep_pedantic_happy;
 
#endif

       由上述源码可知,配置LVGL显示屏驱动的步骤可分为以下7步:

       (1) 调用函数disp_init初始化LCD驱动。

       (2) 调用函数lv_disp_draw_buf_init初始化缓冲区(最低标准:显示屏的宽度分配率*20)。

       (3) 调用函数lv_disp_drv_init初始化显示驱动。

       (4) 设置显示的高度与宽度。

       (5) 注册显示驱动回调(打点函数)。

       (6) 设置显示驱动的绘画缓冲区。

       (7) 调用lv_disp_drv_register函数注册显示驱动到LVGL列表中。

       在整个配置的过程中,实际上我们只需要提供两个函数:LCD初始化函数和LCD填充函数。

       ② 修改lv_port_indev_template.c文件

       该文件用于配置输入设备,例如:触摸屏、鼠标、键盘、编码器、按键等,它可以将用户的底层输入设备驱动与LVGL的输入驱动衔接起来。这里我们使用键盘(多个按键)作为输入设备,具体的配置源码如下所示:

#include "lv_port_indev_template.h"
#include "../../lvgl.h"
#include "./BSP/KEY/key.h"
 
/* 键盘 */
static void keypad_init(void);
static void keypad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static uint32_t keypad_get_key(void);
 
lv_indev_t *indev_keypad;
 
/**
 * @brief       初始化并注册输入设备
 * @param       无
 * @retval      无
 */
void lv_port_indev_init(void)
{
    static lv_indev_drv_t indev_drv;
 
/* 第一步:初始化按键*/
    keypad_init();
 
    /* 第二步:初始化输入设备 */
lv_indev_drv_init(&indev_drv);
 
/* 第三步:设置设备类型 */
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
 
/* 第四步:设置按键回调 */
indev_drv.read_cb = keypad_read;
 
/* 第五步:注册输入设备 */
    indev_keypad = lv_indev_drv_register(&indev_drv);
}
 
/**
 * @brief       初始化键盘
 * @param       无
 * @retval      无
 */
static void keypad_init(void)
{
    key_init();
}
 
uint32_t g_last_key = 0;
 
/**
 * @brief       图形库的键盘读取回调函数
 * @param       indev_drv : 键盘设备
 *   @arg       data      : 输入设备数据结构体
 * @retval      无
 */
static void keypad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    /* 获取按键是否被按下,并保存键值 */
    uint32_t act_key = keypad_get_key();
 
    if(act_key != 0) 
    {
        data->state = LV_INDEV_STATE_PR;
 
        /* 将键值转换成 LVGL 的控制字符 */
        switch(act_key) 
        {
            case KEY0_PRES:
                act_key = LV_KEY_NEXT;
            break;
            
            case KEY1_PRES:
                act_key = LV_KEY_PREV;
            break;
            
            case KEY2_PRES:
                act_key = LV_KEY_ENTER;
            break;
        }
        g_last_key = act_key;
    } 
    else 
    {
        data->state = LV_INDEV_STATE_REL;
        g_last_key = 0;
    }
    data->key = g_last_key;
}
 
/**
 * @brief       获取当前正在按下的按键
 * @param       无
 * @retval      0 : 按键没有被按下
 */
static uint32_t keypad_get_key(void)
{
    return key_scan(0);
}
 
#else
 
typedef int keep_pedantic_happy;
 
#endif

       由上述源码可知,配置LVGL的按键驱动分为以下5个步骤:

       (1) 调用keypad_init函数初始化按键设备。

       (2) 调用函数lv_indev_drv_init初始化输入设备。

       (3) 设置设备的类型。

       (4) 设置按键回调函数,该函数用于获取按键的键值(需要根据实际需求转换为LVGL内部键值,这样才能使部件成功识别键值)。

       (5) 调用函数lv_indev_drv_register注册输入设备。


       3. 编写测试代码

       在编写测试代码之前,我们必须先开启C99模式,否则编译代码会出现很多报错,开启C99模式的方法如下图所示:


图2.3.3 开启C99模式


       开启C99模式之后,接下来就可以在main.c中编写测试代码,检测LVGL是否移植成功,具体源码如下:

#include "./BSP/TIMER/btim.h"
#include "lvgl.h"
#include "lv_port_indev_template.h"
#include "lv_port_disp_template.h"
 
int main(void)
{
    /* 系统其他硬件初始化信息,这里忽略 */
    btim_timx_int_init(10 - 1, 17000 - 1);   /* 定时器周期为1ms */
    lv_init();                    /* lvgl系统初始化 */
    lv_port_disp_init();          /* lvgl显示接口初始化,放在lv_init()的后面 */
    lv_port_indev_init();         /* lvgl输入接口初始化,放在lv_init()的后面 */
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label,"Hello Alientek!!!");
    lv_obj_center(label);
    while(1)
    {
        lv_timer_handler();    /* LVGL管理函数相当于RTOS触发任务调度函数 */
        delay_ms(5);
    }
}

       值得注意的是,上述源码中,基本定时器的初始化函数需要正确配置,这一个细节非常的关键!如果移植成功,即可看到测试代码的运行效果,如下图所示:


图2.3.4 显示”Hello Alientek”字体


        2.4 移植官方例程

       在精简LVGL源码的时候,我们保留了demos文件夹,该文件夹中存放的就是LVGL的官方示例。由于demos文件夹中存在多个官方例程,不同的例程对硬件的要求有所不同,这里以官方的压力测试实验为例,为大家介绍官方示例的移植流程,具体步骤如下:

       (1) 把demos文件夹复制到Middlewares/LVGL/GUI_APP路径下,如下图所示:


图2.4.1 复制官方例程


       (2) 添加文件路径


图2.4.2 添加文件路径


       (3) 打开lv_conf.h文件,找到LV_USE_DEMO_STRESS宏定义并设置为1,开启该实验。

       (4) 创建Middlewares/LVGL/GUI_APP分组,往其中添加demos/stress路径下的全部.c文件,如下图所示:


图2.4.3 官方例程文件


       (5) 编译工程,如果出现如图2.4.4所示的错误,这说明某些字体没有定义。


图2.4.4 字体没有定义


       如果大家遇到此报错,可以打开lv_conf.h文件,找到相应字体的宏定义,并将其设置为1即可,如下图所示:


图2.4.5 定义字体


       定义相应的字体之后,再一次编译工程,如果配置的内存足够,此时就不会出现报错了。接下来即可在main函数中调用lv_demo_stress函数,运行压力测试示例,具体源码如下:

#include " lv_demo_stress.h"
int main(void)
{
    /* 各种初始化/时钟/驱动等等,省略 */
    btim_timx_int_init(10 - 1, 17000 - 1);    /* 初始化定时器  */
    lv_init();                            /* lvgl系统初始化 */
    lv_port_disp_init();      /* lvgl显示接口初始化,放在lv_init()的后面 */
    lv_port_indev_init();     /* lvgl输入接口初始化,放在lv_init()的后面 */
    lv_demo_stress();
 
    while(1)
    {
        lv_timer_handler();   /* LVGL管理 */
    delay_ms(5);
}
}


        2.5下载验证

       编译工程并下载到开发板中。值得注意的是,这个官方例程仅支持800*480分辨率的屏幕,我们使用的1.3寸屏幕分辨率为240*240,,无法正常展现全部界面,但是不影响压力测试。压力测试例程在1.3寸屏幕中运行的界面如下图所示:


图2.5.1 压力测试界面


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3117729/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    763865