如何将lvgl粗略的移植到stm32f429上?

电子说

1.3w人已加入

描述

一、

上周将lvgl粗略的移植到stm32f429上,界面刷新问题没有好好处理,看着非常非常卡顿,今天初步处理了这个问题,效果还算可以了,后边应该是可以更进一步优化。我们先在裸机上移植,以后移植到OS上。移植的视频效果在结尾处。

二、移植步骤

1、提前准备一个移植好LCD、触摸驱动的工程,并修改工程名。

2、获取lvgl图形库源码。

3、将源码添加到提前准备好的工程。

4、修改配置lv_conf.h文件。

5、调用lvgl的初始化程序 lv_init()。

6、在lvgl中注册显示和输入设备驱动程序。

7、在中断服务函数中调用lv_tick_inc(x)以告知lvgl经过的时间。

8、每隔几毫秒调用lv_timer_handle()来处理lvgl相关的任务。

三、提前准备一个移植好LCD、触摸驱动的工程,并修改工程名

我的板子是用的野火的,所以我直接找一个野火的例程进行修改,就用这个触摸屏的例程。

裸机

根据个人喜好更改文件夹名以及工程名,工程名需要是英文,这些我在这里不多说了,自己修改就好,当然也可以不修改。

修改工程的时候建议将Browse information勾上,勾上以后函数跳转很方便,不过勾上以后第一次编译程序会很慢,这个也看个人需求。

裸机

四、获取lvgl源码

新建文件夹使用git获取源码。

裸机

获取成功后可以看到一个lvgl的文件夹,这就是源码了。

裸机

不熟悉git的结尾处我也会提供一份源码。

五、将源码添加到提前准备好的工程,修改配置lv_conf.h文件

到目前我们准备好lvgl的源码以及准备移植的工程。

裸机

1、将lvgl文件夹直接放到lvgl_porting中,然后在新建一个lvgl_app文件夹,后边自己的lvgl应用程序可以放这里。

裸机

2、进入lvgl这个文件里可以看到以下文件

裸机

3、进入src文件夹后,按照下图文件夹名字在keil工程中添加目录。

裸机

裸机

4、然后除了以下目录下文件不添加,其他c文件全部添加,注意有的目录下还有文件夹,文件夹里的c文件也要添加,不要漏了。

裸机

裸机

裸机

gpu暂时也不需要添加。

5、将lv_conf_template.h复制到上一层目录,并修改lv_conf_template.h为lv_conf.h,这个是一定要修改的,官方介绍的也是这样,工程里也是使用的lv_conf.h的头文件。

裸机

6、打开改名后的lv_conf.h,使能文件。同时可以看到这里配置的色彩深度是16位的,对应RGB565格式,一会儿我们LCD屏也是配置RGB565使用。其他配置暂时先不用改。

裸机

7、将lvgl->examples->porting中选中的文件复制到与lv_conf.h文件同一目录下,并把template字眼去掉。分别为显示设备的驱动接口和输入设备的接口,我们一会儿完善。

裸机

裸机

8、在keil工程新建两个目录lvgl_porting,lvgl_demo。lvgl_porting放如下三个文件。lvgl_demo一会儿放例程。

裸机

9、添加所有目录下的头文件

裸机

10、然后开始编译一下工程,如果你C文件添加正确,以及头文件目录配置正确,这个时候已经可以编译通过了。如果这里编译报错可以参考我结尾处附上的工程文件进行参考。

裸机

六、按照LVGL的运行需求修改工程

Basically, every modern controller which is able to drive a display is suitable to run LVGL. The minimal requirements are:

  • 16, 32 or 64 bit microcontroller or processor
  • 16 MHz clock speed is recommended

  • Flash/ROM: > 64 kB for the very essential components (> 180 kB is recommended)
  • RAM:
    • Static RAM usage: ~2 kB depending on the used features and object types
    • Stack: > 2kB (> 8 kB is recommended)
    • Dynamic data (heap): > 4 KB (> 32 kB is recommended if using several objects). Set by LV_MEM_SIZE in lv_conf.h .
    • Display buffer: > "Horizontal resolution" pixels (> 10 × "Horizontal resolution" is recommended)
    • One frame buffer in the MCU or in an external display controller
  • C99 or newer compiler
  • Basic C (or C++) knowledge: pointers, structs, callbacks.

1、设置堆栈大小

裸机

2、配置C99模式

裸机

七、在lvgl中注册显示驱动程序

到目前为止我们基本没修改任何代码,只是使能了一些文件的宏,同时最繁琐的事情我们已经做完了。接下来完善注册显示接口驱动以及设备输入接口驱动。

显示驱动程序

  • 定义一个或者两个绘制缓冲区,用来渲染屏幕内容
  • 注册显示驱动
  • 补充刷新界面的回调函数

定义缓冲区流程

通过lv_disp_draw_buf_t 变量初始化,如下所示:

/* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/


    /* 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_disp_draw_buf_t 需要是静态的、全局的或动态分配的,而不是超出范围时销毁的局部变量。

如上所示,有三个示例。绘制缓冲区可以比屏幕小,如果刷新整个界面,需要刷新几次就好了,如果只有小区域发生变化(如只按下按钮),则只刷新该区域。官方建议缓冲区的大小至少为屏幕宽度的1/10。

如果使用 一个缓冲区 ,LVGL 将屏幕内容绘制到该绘制缓冲区中并将其发送到显示器。 这样 LVGL 需要等到缓冲区的内容发送到显示器,然后再在其中绘制新内容。

如果使用 两个缓冲区 ,LVGL 可以绘制到一个缓冲区中,而另一个缓冲区的内容被发送到后台显示。应使用 DMA 或其他硬件将数据传输到显示器,让 MCU 同时绘制。这样,显示的渲染和刷新变得并行。

注册显示驱动流程

其实打开lv_port_disp.c文件中描述的比较清楚如下代码。

/*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/


    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/


    /*Set up the functions to access to your display*/


    /*Set the resolution of the display*/
    disp_drv.hor_res = 480;
    disp_drv.ver_res = 320;


    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;


    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;


    /*Required for Example 3)*/
    //disp_drv.full_refresh = 1


    /* Fill a memory array with a color if you have GPU.
     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
     * But if you have a different GPU you can use with this callback.*/
    //disp_drv.gpu_fill_cb = gpu_fill;


    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);

注意,lv_disp_drv_t 也需要是静态的、全局的或动态分配的,而不是超出范围时销毁的局部变量。

  • 配置屏幕的分辨率
  • 指定回调函数
  • 指向缓冲区
  • 注册显示驱动

实际操作

1、将lv_port_disp.c和lv_port_disp.h文件宏都打开,并修改.c文件中相应头文件名字,将template去掉。

裸机

2、找到lv_port_disp_init()初始化函数,我们使用第一种方法,并修改屏幕宽度,我的屏幕是800*480的,所以我改为800。

裸机

如果你有外扩sram或者sdram这里可以配置与屏幕一样大。

我这里有sdram我就放到sdram中了。

裸机

3、修改分辨率

裸机

回调函数disp_flush完善

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
  • *area中有绘制区域的起始xy地址和结束xy地址
  • *color_p,存放着从起始xy地址到结束xy地址的每一个像素颜色

知道了这两个我们使用到参数后,我们需要编写一个刷新函数LCD_FillRect_color()的。因为我们使用的这个工程已经配置好了DMA2D,我们直接用DMA2D传输,函数实现如下。

void LCD_FillRect_Color(int16_t x1, int16_t y1, int16_t x2, int16_t y2, lv_color_t *color)
{
    uint32_t dst_addr = 0;//需要更新显存中内容的起始地址

    dst_addr = Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + 2*(800*y1 + x1);//计算需要更新的目标地址 800是屏幕宽度,2是每个像素占2个字节

    /* DMA2D Config */
    DMA2D- >CR      = 0x00000000UL; //配置DMA2D的传输模式为 内存到内存
    DMA2D- >FGMAR   = (uint32_t)color; //拷贝的源地址,绘制缓存区
    DMA2D- >OMAR    = (uint32_t)dst_addr; //拷贝的目标地址,即显存位置
    DMA2D- >FGOR    = 0; //源地址的偏移地址,它被添加到每一行的末尾以确定下一行的起始地址
    DMA2D- >OOR     = (800 - (x2 - x1 +1)); //目标地址的偏移地址,它被添加到每一行的末尾以确定下一行的起始地址
    DMA2D- >FGPFCCR = LTDC_PIXEL_FORMAT_RGB565;  //设置颜色格式;
    DMA2D- >NLR     = (uint32_t)((x2 - x1 + 1) < < 16) | (uint16_t)(y2 - y1 + 1);// 设置拷贝数据的长度和宽度

    /* Start */
    DMA2D- >CR |= DMA2D_CR_START;

    while(DMA2D- >CR & DMA2D_CR_START) {}   
}

将我们准备好的绘制函数LCD_FillRect_Color添加进来。

/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
    LCD_FillRect_Color(area- >x1, area- >y1, area- >x2, area- >y2, (lv_color_t *)color_p);
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

回调函数完事儿。

显示屏移植已经完成了,可以添加一个demo先点亮屏幕。

添加demo并修改main()函数

在main.c文件中添加头文件

#include "./lvgl.h"
#include "../lv_port_disp.h"
#include "lv_demo_widgets.h"

修改显示屏配置

裸机

调用如下初始化函数

裸机

添加demo文件,路径:lvgldemoswidgets,并添加头文件路径。

裸机

进入lv_demo_widgets.c文件中,使能宏

裸机

将此处均设置为1

裸机

这个时候编译不报错是正确的。

八、在中断服务函数中调用lv_tick_inc(x)以告知lvgl经过的时间

裸机

需要添加头文件

#include "../lvgl/src/hal/lv_hal_tick.h"

九、每隔几毫秒调用lv_timer_handle()来处理lvgl相关的任务

裸机

操作到现在,可以编译下载测试了。

十、在lvgl中注册输入设备驱动程序

输入设备可以是触摸板、鼠标、键盘、编码器、按键,我们使用触摸板。

打开lv_port_indev.c文件,只留下触摸板相关程序,其他程序全部删除。

裸机

我们只需要把touchpad_read回调函数补充完整即可。

主要是提供当前的按压状态,释放还是压下,以及按下时的坐标即可,这里自己添加就好。

裸机

然后将lv_port_indev_init()添加到main()函数中即可。

到此已经移植完成了,剩下就是优化刷新程序以及lvgl控件的应用了。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分