打印
[应用相关]

移植LVGL到STM32

[复制链接]
950|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
coshi|  楼主 | 2021-6-8 13:35 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、前言
littlevgl是一个小型开源嵌入式 GUI 库(简称LVGL),界面精美,消耗资源小,可移植度高,支持响应式布局,全库采用纯 c 语言开发,移植上手简单。
littleVGL 的主要特性如下:
• 具有非常丰富的内置控件,像 buttons, charts, lists, sliders, images 等
• 高级图形效果:动画,反锯齿,透明度,平滑滚动
• 支持多种输入设备,像 touchpad, mouse, keyboard, encoder 等
• 支持多语言的 UTF-8 编码
• 支持多个和多种显示设备,例如同步显示在多个彩色屏或单色屏上
• 完全自定制的图形元素
• 硬件独立于任何微控制器或显示器
• 可以缩小到最小内存 (64 kB Flash, 16 kB RAM)
• 支持操作系统、外部储存和 GPU(非必须)
• 仅仅单个帧缓冲设备就可以呈现高级视觉特效
• 使用 C 编写以获得最大兼容性(兼容 C++)
• 支持 PC 模拟器
• 为加速 GUI 设计,提供教程,案例和主题,支持响应式布局
• 提供了在线和离线文档
• 基于自由和开源的 MIT 协议
效果图以及更多详细的说明请见官网


使用特权

评论回复
沙发
coshi|  楼主 | 2021-6-8 13:36 | 只看该作者
二、移植前的准备工作
开发环境:
Keil MDK5 v5.28
ARM Compiler 6.0版本或以上
关于编译器,老版本的5.x编译器也能编译,但是新的6版本的编译器在编译速度与效率上有很大的提升,并且解决了一些莫名其妙的bug,强烈建议升级到6版本的编译器。笔者使用的版本是v6.14.1。

硬件:
STM32开发板
屏幕一块(单色点阵屏或者是LCD彩屏都可以)
笔者使用的开发板是正点原子的战舰V3开发板,芯片是STM32F103ZET6,72M主频+512k flash+64k ram,这样的性能足够跑LVGL了,当然,你的芯片性能越高肯定越好。哦,板上还外挂了一颗1M字节的SRAM,这对提升性能很有帮助。如果你用的不是这款芯片或者这块开发板,那也没关系,笔者会详细指出移植过程中不同的芯片移植需要注意的地方。


使用特权

评论回复
板凳
coshi|  楼主 | 2021-6-8 13:37 | 只看该作者
获取源码:

源码有很多的发行版,不同的发行版对应着不同的LVGL版本,注意不同的版本之间是不同的,比如版本V6与版本V7之间就变化很大,具体的版本变化信息可以看它的release note,这里我以v7.1.0版本为例来移植,并且今后的学习都会使用这个版本。

打开下载好的文件夹

我们重点关注src/目录和lv_conf_template.h, lvgl.h 这几个文件,其它的暂时不用管。


使用特权

评论回复
地板
coshi|  楼主 | 2021-6-8 13:37 | 只看该作者
准备模板工程:

为你的开发板准备好一个模板工程
要求:这个模板工程至少能够驱动一块屏幕(做GUI必须得有屏幕呀= =)
如果你的屏幕带有触摸功能,那你的工程还需要有触摸屏的驱动,其实触摸屏驱动需要但非必须,没有触摸功能的话你也可以做一些展示的界面。事实上LVGL也支持键盘,鼠标,触控板等外设,因为在LVGL核心那里,它会将这些全部抽象为输入设备,它只需要从输入设备那里取得数据,并不关心你所用的是何种设备。
当然,触摸屏已经用得相当广泛,笔者使用的屏幕同样是正点原子家开发板配套的屏幕,型号:4.3英寸MCU屏,800*480分辨率,带电容触摸功能,其接口为16位并口,驱动IC:NT35510,触摸控制IC:GT9147,触摸控制接口为IIC。
因此我需要提前准备好并且测试好这些驱动,对于屏幕控制器,除了初始化API(就是函数),还需要提供填充LCD的API,像这样:


LCD_Color_Fill(x1,y1,x2,y2,(u16*)color_p);


(x1,y1), (x2,y2)为俩坐标点,color_p为显存指针,该API要实现的功能为:将显存区域内的像素数据点,依次填充到以(x1,y1)为起点, (x2,y2)为重点的对角矩形区域内。
注意,这个API是LVGL渲染屏幕的唯一API,哈哈哈简单吧。同样LVGL内部也不关心使用的何种屏幕,他们之间仅通过一个填充色块儿的API耦合,这种高内聚、低耦合的模块儿化设计思想是非常值得我们学习借鉴的。


使用特权

评论回复
5
coshi|  楼主 | 2021-6-8 13:39 | 只看该作者
三、开始移植

①修改MDK工程(默认你已经会使用keil了)
用keil mdk打开我们的模板工程,添加4个Groups:GUI_core, GUI_drv, GUI_demo, GUI_app,这些组用于添加我们的c代码到工程中,不同的组名意味着会存放不同功能的c代码。(注意:工程中的组与实际文件目录并不相同)

GUI_core:GUI核心文件层
GUI_drv:GUI驱动设备层,包括显示设备与输入设备
GUI_demo:存放官方demo
GUI_app:存放我们自己写的GUI应用
修改后的工程结构:



使用特权

评论回复
6
coshi|  楼主 | 2021-6-8 13:39 | 只看该作者
②添加源文件
在项目的根路径下新建GUI目录,以及GUI_app目录,然后将准备工作第三步下载到的源码直接解压到GUI/目录下,然后把解压出来的文件夹改名为lvgl;在GUI/目录下新建lvgl_driver目录,然后:

//从GUI\lvgl\examples\porting\目录拷贝:
lv_port_disp_template.c
lv_port_disp_template.h
lv_port_indev_template.c
lv_port_indev_template.h
// 到GUI/lvgl_driver/目录下并分别改名为:
lv_port_disp.c
lv_port_disp.h
lv_port_indev.c
lv_port_indev.h

//添加:
dma.c
dma.h
//这两个文件时我编写的使用DMA进行加速的代码,如果你不使用DMA请忽略


然后把GUI/lvgl/lv_conf_template.h拷贝到GUI/目录下并改名为lv_conf.h。
修改后的目录结构:




使用特权

评论回复
7
coshi|  楼主 | 2021-6-8 13:40 | 只看该作者
③添加源文件到工程中
在GUI_core组中添加以下文件夹中所有的.c文件:

GUI/lvgl/src/lv_core
GUI/lvgl/src/lv_draw
GUI/lvgl/src/lv_font
GUI/lvgl/src/lv_hal
GUI/lvgl/src/lv_misc
GUI/lvgl/src/lv_themes
GUI/lvgl/src/lv_widgets
//注意不要添加 GUI/lvgl/src/lv_gpu中的文件,除非你用到了相关功能



在GUI_drv组中添加以下.c文件:

GUI/lvgl_driver/dma.c
GUI/lvgl_driver/lv_port_disp.c
GUI/lvgl_driver/lv_port_indev.c


使用特权

评论回复
8
coshi|  楼主 | 2021-6-8 13:41 | 只看该作者
④添加头文件路径


使用特权

评论回复
9
coshi|  楼主 | 2021-6-8 13:42 | 只看该作者
⑤修改配置文件
打开GUI/lv_conf.h
首先第十行设置为#if 1 使能整个配置文件
找到以下几个宏定义并修改

#define LV_HOR_RES_MAX          (480)//定义屏幕的最大水平分辨率
#define LV_VER_RES_MAX          (800)//定义屏幕的最大垂直分辨率
//修改为你的屏幕分辨率即可

/* Color depth:
* - 1:  1 byte per pixel
* - 8:  RGB332
* - 16: RGB565
* - 32: ARGB8888
*/
#define LV_COLOR_DEPTH     16
//定义颜色深度,如果是单色屏的话就改为1

#  define LV_MEM_SIZE    (30U * 1024U)
//给LVGL分配内存的大小,至少需要2k



这个文件很重要,控制着整个LVGL的功能,但我们暂时就先修改这几个参数,其它参数暂时保持默认即可。等到我们深入了解了LVGL或者需要用到其它特性时再去研究修改也无妨。
然后我们需要修改GUI/lvgl_driver/目录下的lv_port_disp.c, lv_port_disp.h,lv_port_indev.c, lv_port_indev.h这4个文件,这里直接给处修改好之后的文件。


使用特权

评论回复
10
coshi|  楼主 | 2021-6-8 13:43 | 只看该作者
lv_port_disp.c



#if 1
#include "lv_port_disp.h"
#include "LCD.h"
#include "dma.h"

#define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX)
//分配到外部 1MB sram 的最起始处
static lv_color_t *color_buf;

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

void lv_port_disp_init(void)
{
    static lv_disp_buf_t disp_buf;
    color_buf = (lv_color_t *)0X68000000;//显示缓冲区初始化
    lv_disp_buf_init(&disp_buf, color_buf, NULL,COLOR_BUF_SIZE);
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);

    //注册显示驱动回调
    disp_drv.flush_cb = disp_flush;

    //注册显示缓冲区
    disp_drv.buffer = &disp_buf;

    //注册显示驱动到 lvgl 中
    lv_disp_drv_register(&disp_drv);
}

//把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器
//在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()
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,(u16*)color_p);//TODO
    DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
    lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
}

#else /* Enable this file at the top */
/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif


使用特权

评论回复
11
coshi|  楼主 | 2021-6-8 13:44 | 只看该作者
lv_port_disp.h



#if 1
#ifndef LV_PORT_DISP_H
#define LV_PORT_DISP_H

#ifdef __cplusplus
extern "C" {
#endif

#include "lvgl/lvgl.h"

void lv_port_disp_init(void);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /*LV_PORT_DISP_TEMPL_H*/
#endif /*Disable/Enable content*/


使用特权

评论回复
12
coshi|  楼主 | 2021-6-8 13:45 | 只看该作者
lv_port_indev.c



#if 1
#include "lv_port_indev.h"
#include "Touch.h"

static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

void lv_port_indev_init(void)
{
    lv_indev_drv_t indev_drv;

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    lv_indev_drv_register(&indev_drv);
}

static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
                static uint16_t last_x = 0;
                static uint16_t last_y = 0;
                Tp_dev.scan();
                if(Tp_dev.sta&0x80)//触摸按下了
                {
                        last_x = Tp_dev.X[0];
                        last_y = Tp_dev.Y[0];//竖屏
                       
                        data->point.x = last_x;
                        data->point.y = last_y;
                        data->state = LV_INDEV_STATE_PR;
                }else{
                        data->point.x = last_x;
                        data->point.y = last_y;
                        data->state = LV_INDEV_STATE_REL;
                }
    return false;
}

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif


使用特权

评论回复
13
coshi|  楼主 | 2021-6-8 13:46 | 只看该作者
lv_port_indev.h


#if 1
#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H

#ifdef __cplusplus
extern "C" {
#endif

#include "lvgl/lvgl.h"

void lv_port_indev_init(void);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /*LV_PORT_INDEV_TEMPL_H*/
#endif /*Disable/Enable content*/


使用特权

评论回复
14
coshi|  楼主 | 2021-6-8 13:46 | 只看该作者
⑥完善main函数

至此,移植工作基本完成,接下来只需要完善main函数,初始化LVGL,便可放心玩耍。
修改main.c如下:


#include "include_config.h"
#include "sys.h"
#include "delay.h"
#include "dma.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

void tim_lv_tick(void);
void lvgl_first_demo_start(void);
void setup()
{
        JTAG_Set(SWD_ENABLE);   //关闭JTAG,只用SWD
        delay_init(72);
        led_init();
        myuart_init(USART1,1152000,PA(10),PA(9));
        Timer_InitDef TIM_2_conf=
        {
                .TIMx = TIM2,
                .ms   = 1,
                .NVIC_Priority = 0,
                .event_handler = tim_lv_tick,
        };
        timer_init(&TIM_2_conf);
       
        LCD_init(BKOR); //默认竖屏
        if(Tp_dev.init())printf("Tp_dev failed!\r\n");
        else printf("Tp_dev success!\r\n");
       
        FSMC_SRAM_Init();
        DMA_fsmc_Init();
}

int main(void)
{                       
        setup();
        printf("set up success!\r\n");

        lv_init(); //lvgl 系统初始化
        lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面
        lv_port_indev_init(); //lvgl 输入接口初始化,放在 lv_init()的后面


        lvgl_first_demo_start();
        while(1)
        {
                lv_task_handler();
        }
}

void tim_lv_tick()
{
        lv_tick_inc(1);//lvgl 的 1ms 心跳
}


static void btn_event_cb(lv_obj_t * btn, lv_event_t event)
{
    if(event == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;

        /*Get the first child of the button which is the label and change its text*/
        lv_obj_t * label = lv_obj_get_child(btn, NULL);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}
void lvgl_first_demo_start(void)
{
    lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);     /*Add a button the current screen*/
    lv_obj_set_pos(btn, 10, 10);                            /*Set its position*/
    lv_obj_set_size(btn, 120, 50);                          /*Set its size*/
    lv_obj_set_event_cb(btn, btn_event_cb);                 /*Assign a callback to the button*/

    lv_obj_t * label = lv_label_create(btn, NULL);          /*Add a label to the button*/
    lv_label_set_text(label, "Button");                     /*Set the labels text*/


        lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
        lv_label_set_text(label1, "Hello world!");
        lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
        lv_obj_align(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);
}


使用特权

评论回复
15
coshi|  楼主 | 2021-6-8 13:48 | 只看该作者
⑦编译并运行
设置编译器版本

注意:这一步应该在移植之前就应该执行,模板工程也使用V6编译器编译通过,免得移植后再编译各种报错。这一步非强制的。

设置编译参数
C99:LVGL要求 C99 或更新的编译器,否则编译是会报错的
勾选Execute-only Code, 会自动去除未使用到的代码函数,大大减小了编译后的固件大小,建议勾选,其它默认即可。

编译
烧写运行
效果图:


使用特权

评论回复
16
coshi|  楼主 | 2021-6-8 13:49 | 只看该作者
四、总结

到这里移植工作已经结束,接下来我会介绍一下移植需要修改的那几个重点文件,假如你要移植到其它平台,请看这里。

lv_port_disp.c

//定义显存的大小
//我使用了刚好一整块屏幕的显存
//如果你的内存没有那么大,那么定义10行的显存也是可以的   LV_HOR_RES_MAX*10
//LV_HOR_RES_MAX,LV_VER_RES_MAX定义于lv_conf.h
#define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX)

static lv_color_t *color_buf;   //定义显存指针


//lv_port_disp_init函数
//显存初始化,将显存指针指向我指定的内存啊区域
//0X68000000是我板子上外挂的SRAM启始地址
//显存需要根据你自己的板子合理分配
color_buf = (lv_color_t *)0X68000000;


//把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器
//在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()
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,(u16*)color_p);//TODO
    DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
    lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
}
//DMA_Fill_Color或者LCD_Color_Fill函数需要根据你自己的平台实现


使用特权

评论回复
17
coshi|  楼主 | 2021-6-8 13:50 | 只看该作者
lv_port_indev.c


static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
                static uint16_t last_x = 0;
                static uint16_t last_y = 0;
                Tp_dev.scan();
                if(Tp_dev.sta&0x80)//触摸按下了
                {
                        last_x = Tp_dev.X[0];
                        last_y = Tp_dev.Y[0];//竖屏
                       
                        data->point.x = last_x;
                        data->point.y = last_y;
                        data->state = LV_INDEV_STATE_PR;
                }else{
                        data->point.x = last_x;
                        data->point.y = last_y;
                        data->state = LV_INDEV_STATE_REL;
                }
    return false;
}
//Tp_dev.scan() 是触摸屏驱动层的函数
//其作用是扫描整块儿屏幕,得到触摸状态写入Tp_dev.sta中,如果有触摸点按下,得到其坐标并存入Tp_dev.X和Tp_dev.Y数组中
//因此这个函数需要根据你自己的板子和屏幕提供


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

96

主题

3309

帖子

4

粉丝