正点原子手把手教你学littleVGL–01:littleVGL 移植


目录

一、littleVGL移植

1. littleVGL 介绍

2. littleVGL 移植

2.1 步骤 1,准备素材

2.2 步骤 2,修改 Keil 工程名

2.3 步骤 3,导入 littleVGL 库到 Keil 中

2.4 步骤 4,修改 lv_conf.h 和 lv_ex_conf.h 配置文件

2.5 步骤 5,添加定时器,为 littleVGL 提供心跳节拍

2.6 步骤 6,移植底层显示驱动

2.7 步骤 7,移植底层触摸驱动

2.8 步骤 8,移植官方的演示例程


一、littleVGL移植

1. littleVGL 介绍

littleVGL一个小型开源嵌入式GUI库,具有界面精美,消耗资源小,可移植度高,响应式布局等特点,全库采用纯c语言开发

littleVGL的官方网址

littleVGL的github网址

littleVGL的在线文档网址

littleVGL 的主要特性如下:

? 具有非常丰富的内置控件,像 buttons, charts, lists, sliders, images 等

? 高级图形效果:动画,反锯齿,透明度,平滑滚动

? 支持多种输入设备,像 touchpad, mouse, keyboard, encoder 等

? 支持多语言的 UTF-8 编码

? 支持多个和多种显示设备,例如同步显示在多个彩色屏或单色屏上

? 完全自定制的图形元素

? 硬件独立于任何微控制器或显示器

? 可以缩小到最小内存 (64 kB Flash, 16 kB RAM)

? 支持操作系统、外部储存和 GPU(非必须)

? 仅仅单个帧缓冲设备就可以呈现高级视觉特效

? 使用 C 编写以获得最大兼容性(兼容 C++)

? 支持 PC 模拟器

? 为加速 GUI 设计,提供教程,案例和主题,支持响应式布局

? 提供了在线和离线文档

? 基于自由和开源的 MIT 协议

littleVGL 的要求如下:

? 16、32 或 64 位的单片机(微控制器)或处理器

? 微处理器的主频最好高于 16MHZ

? Flash/ROM:如果只用 littleVGL 核心组件的话,则至少需要 64kB 的容量,如果想完整使用的

话,最好保证 180kB 以上的容量

? RAM:

o 静态 RAM: 大约 8 到 16 kB,这取决于你所用的组件功能和 objects 控件对象类型

o 栈: 至少为 2Kb,一般推荐值为 4kB

o 动态数据(堆): 至少 4kB,如果你用到了多个或多种控件的话,那么最好设置为 16kB 以上,这个是可以通过 lv_conf.h 配置文件中的 LV_MEM_SIZE 宏来定义的

o 显示缓冲区: 至少要比”水平分辨率像素”要大,一般推介值为 10 倍的”水平分辨率像 素”,取个例子,假如我们屏幕的水平分辨率为480个像素,采用16位的颜色深度进行显 示,即一个像素占 2 个字节,那么推介的显示缓冲区大小为 10*480*2=9600 个字节

? C99 或更新的编译器,如果是用 keil 开发的话,一定得勾选”c99”模式,否则编译会报错的

? 基本的 c(或者 c++)语言知识,如:指针,结构体,回调函数

2. littleVGL 移植

2.1 步骤 1,准备素材

2.2 步骤 2,修改 Keil 工程名

2.3 步骤 3,导入 littleVGL 库到 Keil

在 template 项目根目录下新建 GUI 和 GUI_APP 俩个子目录,即和 USER 目录是同级别的

GUI 目录是用来存放跟 littleVGL 库相关的所有文件的,而 GUI_APP 是用来放我们自己的 GUI 应用代码的

把 lv_pc_simulator.zip 压缩包里面的 lv_examples.zip 和 lvgl.zip 俩个子压缩包直接拷贝到 template/GUI 目录下

改lv_conf.h和lv_ex_conf.h配置文件

把 template/GUI/lvgl/lv_conf_template.h 和 template/GUI/lv_examples/lv_ex_conf_templ.h 俩 个配置模板文件统统拷贝到 template/GUI 目录下,然后对这个 2 文件分别重命名为 lv_conf.h和 lv_ex_conf.h

要在 template/GUI 目录下新建一 lvgl_driver子目录,这个目录是用来放底层显示驱动和触摸驱动文件的

2.4 步骤 4,修改 lv_conf.h lv_ex_conf.h 配置文件

2.5 步骤 5,添加定时器,littleVGL 提供心跳节拍

采用定时器 3,设置其每隔 1ms 进入中断,为 littleVGL 提供 1ms 的心跳节拍为littleVGL提供心跳节拍

1
2
3
4
5
6
7
8
9
#include "lvgl.h"
//中断服务函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
        lv_tick_inc(1);//心跳
    }
}

--diag_suppress=68 --diag_suppress=111 --diag_suppress=550

2.6 步骤 6,移植底层显示驱动

lv_port_disp.h 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * @file lv_port_disp.h
 *
 */

 /*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/
#if 1

#ifndef LV_PORT_DISP_H
#define LV_PORT_DISP_H

#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
#include "lvgl/lvgl.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/
void lv_port_disp_init(void);

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

#endif /*LV_PORT_DISP_H*/

#endif /*Disable/Enable content*/

lv_port_disp.c 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
 * @file lv_port_disp.c
 *
 */

 /*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include "lcd.h"
/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
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);
#if LV_USE_GPU
static void gpu_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_color_t * dest, uint32_t length, lv_color_t color);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    /* Example for 1) */
    static lv_disp_buf_t disp_buf_1;
    static lv_color_t buf1_1[LV_HOR_RES_MAX * 10];                      /*A buffer for 10 rows*/
    lv_disp_buf_init(&disp_buf_1, buf1_1, NULL, LV_HOR_RES_MAX * 10);   /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LittlevGL
     *----------------------------------*/

    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 = lcddev.width;
    disp_drv.ver_res = lcddev.height;
       

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

    /*Set a display buffer*/
    disp_drv.buffer = &disp_buf_1;

#if LV_USE_GPU
    /*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/

    /*Blend two color array using opacity*/
    disp_drv.gpu_blend = gpu_blend;

    /*Fill a memory array with a color*/
    disp_drv.gpu_fill = gpu_fill;
#endif

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

/**********************
 *   STATIC FUNCTIONS
 **********************/

/* Initialize your display and the required peripherals. */
static void disp_init(void)
{
    /*You code here*/
}

/* 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)
{
        LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}


/*OPTIONAL: GPU INTERFACE*/
#if LV_USE_GPU

/* If your MCU has hardware accelerator (GPU) then you can use it to blend to memories using opacity
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa)
{
    /*It's an example code which should be done by your GPU*/
    uint32_t i;
    for(i = 0; i < length; i++) {
        dest[i] = lv_color_mix(dest[i], src[i], opa);
    }
}

/* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
                    const lv_area_t * fill_area, lv_color_t color);
{
    /*It's an example code which should be done by your GPU*/
    uint32_t x, y;
    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/

    for(y = fill_area->y1; y < fill_area->y2; y++) {
        for(x = fill_area->x1; x < fill_area->x2; x++) {
            dest_buf[x] = color;
        }
        dest_buf+=dest_width;    /*Go to the next line*/
    }
}

#endif  /*LV_USE_GPU*/

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

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

2.7 步骤 7,移植底层触摸驱动

lv_port_indev.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * @file lv_port_indev.h
 *
 */

 /*Copy this file as "lv_port_indev.h" and set this value to "1" to enable content*/
#if 1

#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H

#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
#include "lvgl/lvgl.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/
void lv_port_indev_init(void);

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

#endif /*LV_PORT_INDEV_TEMPL_H*/

#endif /*Disable/Enable content*/

lv_port_indev.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * @file lv_port_indev.c
 *
 */

 /*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev.h"
#include "touch.h"
/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

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



/**********************
 *  STATIC VARIABLES
 **********************/



/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_indev_init(void)
{
    lv_indev_drv_t indev_drv;

    /*------------------
     * Touchpad
     * -----------------*/

    /*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 FUNCTIONS
 **********************/

/* Will be called by the library to read the touchpad */
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;
        if(tp_dev.sta&TP_PRES_DOWN)//触摸按下了
        {
            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` because we are not buffering and no more data to read*/
    return false;
}





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

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

2.8 步骤 8,移植官方的演示例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "lcd.h"
#include "ltdc.h"
#include "sdram.h"
#include "touch.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_apps\demo\demo.h"
#include "lv_tests\lv_test_theme\lv_test_theme_1.h"
#include "lv_tests\lv_test_theme\lv_test_theme_2.h"

//注:对于littleVGL库,最好不要采用-O的优化,保持为-O0不优化就可以了

#define TEST_NUM        1   //1,2,3分别对应3个例程

int main(void)
{
    Cache_Enable();                 //打开L1-Cache
    HAL_Init();                                 //初始化HAL库
    Stm32_Clock_Init(432,25,2,9);   //设置时钟,216Mhz
    delay_init(216);                //延时初始化
    uart_init(115200);                  //串口初始化
    LED_Init();                     //初始化LED
    KEY_Init();                     //初始化按键
    SDRAM_Init();                   //初始化SDRAM
    TIM3_Init(999,107);                         //定时器初始化(1ms中断),用于给lvgl提供1ms的心跳节拍
    LCD_Init();                     //LCD初始化
    if(lcdltdc.pwidth!=0)      
        LCD_Display_Dir(1);                     //如果是RGB屏的话,则强制设置为横屏显示
    tp_dev.init();                              //触摸屏初始化
 
    lv_init();                                          //lvgl系统初始化
    lv_port_disp_init();                       
    lv_port_indev_init();                      
   

    #if(TEST_NUM==1)
        demo_create();         
    #elif(TEST_NUM==2)
        lv_test_theme_1(lv_theme_night_init(210, NULL));
    #else
        lv_test_theme_2();
    #endif
   
    while(1)
    {
        tp_dev.scan(0);//触摸扫描
        lv_task_handler();//lvgl的事务处理
    }
}