1、前言

在之前的一篇文章LVGL在VSCode中安装模拟器,已经对LVGL进行了较为详细的介绍,本文将着重讲解如何移植适配LVGL,让这款图形化GUI库在STM32或其它类型的嵌入式MCU设备上运行起来。

LVGL在VScode中安装模拟器运行配置笔记教程_vscode lvgl-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_49337111/article/details/136536375?spm=1001.2014.3001.5502

本文所使用的目标硬件设备为STM32F407VET6,LVGL版本为8.3。

提醒:LVGL移植时除了LCD液晶显示触摸屏的引脚初始化和硬件平台有关外,只有一个LVGL的心跳会和硬件平台及对应的库有关。因此本文的移植教程,适用于各类嵌入式MCU平台,标准库及HAL库等。

2、LVGL源码下载及加入工程

2.1 下载LVGL源码

进入LVGL的仓库,选择如图所示的v8.3分支下载。

项目目录预览 - lvgl - GitCodeicon-default.png?t=N7T8https://gitcode.com/lvgl/lvgl/tree/release%2Fv8.3?init=initRepo

2.2 LVGL工程结构完善

①、解压下载的LVGL库压缩包,复制如下图所示被选中的文件,(demosexamplessrclv_conf_template.hlvgl.h),其余的其它的文件可以根据需要进行选择是否去除。

将选中的文件复制到项目工程文件目录下的LVGL文件夹下,(没有则手动创建LVGL文件夹)。

②、如下图所示为移植LVGL工程时,所需要的文件。

③、将examples目录下的,porting文件夹复制到LVGL目录下,examples下其它文件可以直接删除。

④、修改文件名

为了标准化,规范化,将如下图所示,将porting文件夹中的所有文件文件名去除 template,并在其对应的.c或.h文件中,将include包含了这些文件的代码进行修改。

将如下图所示的lv_conf_template.h的文件名修改为lv_conf.h,并将各文件中,include包含的文件名进行修改。

3、LVGL移植到MCU工程中

3.1、在工程中新建文件夹

LVGL_SRC             //存放LVGL的源码文件

LVGL_PORTING    //存放LVGL的移植文件

LVGL_APP             //存放LVGL的用户APP文件

LVGL_DEMO         //存放LVGL的Demo文件

3.2、将LVGL库文件加入到工程

添加LVGL源文件,将LVGL/src目录下的core、draw、font、hal、misc、widgets、extra文件夹下的所有文件全部添加进LVGL_SRC组中,如果文件夹内还有子文件夹,则需依次打开,将全部的.c源文件加入到工程中。

将LVGL/porting目录下的全部源文件加入到LVGL_PORTING组中

添加LVGL头文件

3.3、勾选C99模式和取消微库

3.4、编译整个工程

将全部的LVGL源文件和头文件正常加入到工程中,编译整个工程时,应该是没有错误的。如果存在报错,请检查未加入LVGL库时,工程编译是否存在错误。是否存在文件及头文件路径未加入工程。

4、LVGL显示屏移植适配

在完成上述操作后,就可以开始对显示屏进行移植适配操作。

4.1、修改lv_port_disp.c文件

打开lv_port_disp.c文件,并开启如图所示的宏定义

注释掉lv_port_disp.c文件中的这一部分内容,后面有需要再添加、修改

    //注释掉lv_port_disp.c文件中的这一部分内容,后面有需要再添加、修改
    /* Example for 2) */
    static lv_disp_draw_buf_t draw_buf_dsc_2;
    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        /*A buffer for 10 rows*/
    static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                        /*An other buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    /* Example for 3) also set disp_drv.full_refresh = 1 below*/
    static lv_disp_draw_buf_t draw_buf_dsc_3;
    static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*A screen sized buffer*/
    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*Another screen sized buffer*/
    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
                          MY_DISP_VER_RES * LV_VER_RES_MAX);   /*Initialize the display buffer*/

找到lv_pory_disp.c文件中的disp_flush 函数,注释掉如下图中框选中的内容,添加自己屏幕对应的像素填充函数,或者根据屏幕实际情况进行修改

//在指定区域内填充指定颜色块
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{  
    u16 height,width;
    u16 i,j;
    width=ex-sx+1;             //得到填充的宽度
    height=ey-sy+1;            //高度
    for(i=0;i<height;i++)
    {
        setCursor(sx,sy+i);           //设置光标位置      
        LCD->LCD_REG = 0x2C;        //开始写入GRAM
        for(j=0;j<width;j++)
        {    
            LCD->LCD_RAM=color[i*width+j];    //写入数据 
        }
    }          
}

4.2、修改lv_port_disp.h文件

打开lv_port_disp.h文件,并开启如下图所示的宏定义

#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
将图中的红框内的代码全部删除,换成
#include "lvgl.h"'

4.3、修改lv_conf.h文件

打开lv_conf.h文件,开启宏定义并加入屏幕分辨率信息

//设置屏幕的分辨率---需根据屏幕的实际情况进行修改
//注意、特别提醒:屏幕有横屏显示和竖屏显示之分,请仔细检查屏幕到底是横屏显示,还是竖屏显示,否则后面可能会出现显示界面不完整,或者显示有问题。
//如果遇到显示不全、有问题,可以尝试,将分辨率数据调换一下
#define MY_DISP_HOR_RES  240
#define MY_DISP_VER_RES  320

4.4、添加LVGL心跳

创建一个单片机1ms定时器,并在定时器的中断处理函数中加入LVGL心跳。创建和单片机型号和对应的库有关,请根据实际的目标平台进行修改处理,下面以STM32F4的标准库为例:

①、timer.c
#include "Timer.h"

/**
  * @brief  定时器2配置(1ms触发一次定时器中断)
  * @param  NONE
  * @retval NONE
  */
void TIM2_Init(void)
{       
    /** 使能相关时钟 **/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);          // 使能定时器时钟 
    
    /** 配置TIM2中断优先级 **/
    NVIC_InitTypeDef NVIC_InitStructure;                          // 声明优先级配置结构体
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;               // 中断通道来源        
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;      // 抢占优先级      
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;              // 子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;               // 使能通道
    NVIC_Init(&NVIC_InitStructure);                               // 配置写入寄存器
    
    /** 配置所用TIM的时基 **/
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;               // 声明时基配置结构体
    TIM_TimeBaseStructure.TIM_Prescaler = 84-1;                  // 分频; 把接口时钟分频后给计数器使用, 即多少个接口脉冲,才产生一次计数器脉冲; 简单理解:计算每一计数器脉冲的时长;
    TIM_TimeBaseStructure.TIM_Period = 1000-1;                    // ARR, 自动重载值; 多少个计数器脉冲作为一周期; 注意:TIM2和5是32位的,其它TIM是16位的;
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;         // 采样时钟分频
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;     // 计数方式    
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);               // 初始化定时器,把配置写入寄存器
    
    //t = (arr+1) * (psc+1) / Freq
    //t = (84-1+1) * (1000-1+1) / 84 000 000
    //t = 0.001
s
    /** 配置中断 **/
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);                         // 清除定时器更新中断标志位
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);                      // 开启定时器更新中断
    
    /** 使能定时器 **/
    
    TIM_Cmd(TIM2, ENABLE);                                          // 使能定时器            
}

// 中断服务函数
void  TIM2_IRQHandler (void)
{
    if ( TIM_GetITStatus( TIM2, TIM_IT_Update) != RESET ) 
    {    

        lv_tick_inc(1);          //定时器中加入LVGL心跳
        TIM_ClearITPendingBit(TIM2 , TIM_IT_Update);           
    }             
}
②、timer.h
#ifndef __TIMER_H
#define __TIMER_H

#include "stm32f4xx.h"

#include "lvgl.h"


void TIM2_Init(void);
void  TIM2_IRQHandler (void);


#endif

4.5、测试LVGL的显示功能

如下所示为测试LVGL移植功能的main.c,根据实际情况进行修改

#include "stm32f4xx.h"
#include "led.h"
#include "USART.h"
#include "Timer.h"
#include "LCD_ILI9341.h"
#include "XPT2046.h"

#include "lvgl.h"
#include "lv_port_disp.h"

/**
 * LVGL专业点灯Demo
 */
void lv_example_led_1(void)
{
    /*Create a LED and switch it OFF*/
    lv_obj_t * led1  = lv_led_create(lv_scr_act());
    lv_obj_align(led1, LV_ALIGN_CENTER, -80, 0);
    lv_led_off(led1);

    /*Copy the previous LED and set a brightness*/
    lv_obj_t * led2  = lv_led_create(lv_scr_act());
    lv_obj_align(led2, LV_ALIGN_CENTER, 0, 0);
    lv_led_set_brightness(led2, 150);
    lv_led_set_color(led2, lv_palette_main(LV_PALETTE_RED));

    /*Copy the previous LED and switch it ON*/
    lv_obj_t * led3  = lv_led_create(lv_scr_act());
    lv_obj_align(led3, LV_ALIGN_CENTER, 80, 0);
    lv_led_on(led3);
}

int main(void)                                         
{    
    //其它外设初始化代码             
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      // 设置系统中断优先级分组2
    USART1_Init(115200);                                 // USART1 初始化,用于与串口软件通信; TX-PA9、RX-PA10, 115200-8-N-1,中断发送、接收
    TIM2_Init();                                               
    Led_Init();                                          // LED 初始化
    LED_RED_ON;                                            
    LED_BLUE_ON;
    W25Q128_Init ();                                     // 外部Flash 初始化,其内部存储有汉字字模数据
    //显示屏、触摸屏初始化代码                       
    LCD_Init();                                          // LCD 初始化;
    XPT2046_Init(xLCD.width , xLCD.height, xLCD.dir);    // 触摸 初始化
    XPT2046_Cmd(ENABLE);                                 // 触摸使能:打开
    //LVGL初始化
    lv_init();
    lv_port_disp_init();
    //LVGL测试Demo                                        
    lv_example_led_1();
        
    while (1)                                            // while函数死循环,不能让main函数运行结束,否则会产生硬件错误
    {                                                  
        lv_task_handler();                               //LVGL事物处理,必须加到循环中
    }
}

LVGL移植正常,显示效果。

4.6、屏幕异常显示说明

程序编译下载到开发板后,如果出现有数据显示,但显示不正常,需要着重检查以下2个方面:

①、检查屏幕的显示方向,是横屏显示还是竖屏显示,可以和店家或厂家沟通咨询;

②、LVGL调用的屏幕数据刷新部分是否正常,即disp_flush函数的内容。

博主此前就是没有弄清楚显示屏的横屏、竖屏,导致程序烧录后,出现如下所示的显示不全。

如果可以查看到显示屏驱动源码,并且有设置屏幕显示方向的接口,那么也可以直接修改屏幕的显示方向。

注意:修改了屏幕的显示方向时,如果有用到触摸屏,也要一同修改触摸屏的方向!!!

5、LVGL触摸屏移植适配

5.1、修改lv_port_indev.h文件

打开lv_port_indev.h文件,开启该文件的宏定义,并包含触摸屏相关的头文件

5.2、修改lv_port_indev.c文件

①、打开lv_port_indev.c文件

如下图所示,开启该文件的宏定义,并修改包含的头文件为最新的文件名

②、修改lv_port_indev_init函数

因为本次移植,仅有一个触摸屏作为输入设备,因此直接屏蔽掉了Mouse、Keypad、Encoder和Button这些输入设备的配置代码。

③、修改LVGL触摸屏触摸和获取触摸坐标函数

打开LCD触摸屏板级驱动文件,添加触摸标志位,并查找获取LCD触摸屏坐标的方式。不同的触摸屏的驱动程序编写方式不同,可以咨询厂家或板级驱动文件开发者,如何获取触摸屏的坐标位置信息等。

通过查看触摸屏的驱动程序确定了触摸屏获取触摸坐标的接口

打开LVGL的输入设备文件lv_port_indev.c,根据实际情况修改touchpad_is_pressed()和touchpad_get_xy()函数。

5.3、修改定时器中断处理函数

打开上面移植LCD触摸屏时的定时器处理函数,在定时器的中断处理函数中,添加触摸屏的触摸扫描检测函数。

这个部分的操作,根据实际情况进行处理,有的触摸屏有单独的中断函数进行触控扫描操作

5.4、LVGL触摸测试

①、添加LVGL触摸Demo到工程

完成如下所示的操作后,还需要将该头文件包含到路径中

②、修改lv_cong.h

打开lv_conf.h文件,找到LV_USE_DEMO_KEYPAD_AND_ENCODER这个宏定义,并将其值改为1,以开启该宏定义

③、触摸测试

包含相应的触摸屏头文件及在代码中添加触摸屏初始化及demo调用的函数

#include "stm32f4xx.h"
#include "led.h"
#include "USART.h"
#include "Timer.h"
#include "LCD_ILI9341.h"
#include "XPT2046.h"

#include "lvgl.h"
#include "lv_conf.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

#include "lv_demo_keypad_encoder.h"

/**
 * LVGL专业点灯Demo
 */
void lv_example_led_1(void)
{
    /*Create a LED and switch it OFF*/
    lv_obj_t * led1  = lv_led_create(lv_scr_act());
    lv_obj_align(led1, LV_ALIGN_CENTER, -80, 0);
    lv_led_off(led1);

    /*Copy the previous LED and set a brightness*/
    lv_obj_t * led2  = lv_led_create(lv_scr_act());
    lv_obj_align(led2, LV_ALIGN_CENTER, 0, 0);
    lv_led_set_brightness(led2, 150);
    lv_led_set_color(led2, lv_palette_main(LV_PALETTE_RED));

    /*Copy the previous LED and switch it ON*/
    lv_obj_t * led3  = lv_led_create(lv_scr_act());
    lv_obj_align(led3, LV_ALIGN_CENTER, 80, 0);
    lv_led_on(led3);
}

int main(void)                                         
{           
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      // 设置系统中断优先级分组2
    USART1_Init(115200);                                 // USART1 初始化,用于与串口软件通信; TX-PA9、RX-PA10, 115200-8-N-1,中断发送、接收
    TIM2_Init();                                               
    Led_Init();                                          // LED 初始化
    LED_RED_ON;                                            
    LED_BLUE_ON;
    W25Q128_Init ();                                     // 外部Flash 初始化,其内部存储有汉字字模数据
                                                
    LCD_Init();                                          // LCD 初始化;
    XPT2046_Init(xLCD.width , xLCD.height, xLCD.dir);    // 触摸 初始化
    XPT2046_Cmd(ENABLE);                                 // 触摸使能:打开
    LCD_Fill (0, 0, 240, 320, BLACK);                    // 整个背景填充白色
    LCD_String(15, 8, " LVGL移植测试 ", 24, BLACK, WHITE); // 本函数,可显示中英文字符串; 函数内自动选择font.h中的ASCII字模、Flash中的汉字字模; Flash中已存在放较为完成的汉字字模数据,四字字号,12号、16号、24号、32号,约6M大小
    
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();
    
    //lv_example_led_1();
    lv_demo_keypad_encoder();
    
    while (1)                                            // while函数死循环,不能让main函数运行结束,否则会产生硬件错误
    {                                                  
        lv_task_handler();                //LVGL事物处理
    }
}

最后提醒:如果移植过程中,出现内存大小报错,需要检查使用的MDK工程是否已经激活了;

如果烧录下载后,程序运行不起来,可以尝试修改启动文件中的,堆空间、栈空间的大小,检查芯片是否满足官方的移植性能要求。

本次移植Demo的程序源码已上传至资源下载区:【免费】LVGL移植到STM32MCU平台通用程序源码资源-CSDN文库icon-default.png?t=N7T8https://download.csdn.net/download/weixin_49337111/89346775?spm=1001.2014.3001.5503

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐