STM32F1与STM32CubeIDE快速入门-LVGL8.2移植(基于FreeRTOS+FSMC+ILI9325)

LVGL8.2移植(基于FreeRTOS+FSMC+ILI9325)

文章目录

本文将详细介绍如何将LVGL移植到STM32F1上。

1、LVGL介绍

LVGL 是用于显示器和触摸屏的轻量级嵌入式库,提供构建功能齐全的嵌入式 GUI。

LVGL具有如下特性:

  • 占用储存小:最小应用只占用64KB Flash和8KB RAM
  • 组件丰富:LVGL默认提供30多种小部件(Widget),同时支持自定义
  • 支持硬件平台广泛:NXP LPC、iMX、STM32、PIC、Arduino、ESP32、Raspberry等等
  • 支持Micropython
  • 支持各类显示屏:驱动单色、OLED、TFT 显示器、监视器或任何其他显示器
  • 开源,纯C语言

LVGL的最新版本为8.2.0,是本次实例移植的版本。下载地址为:https://github.com/lvgl/lvgl/archive/refs/tags/v8.2.0.zip

2、LVGL移植

LVGL移植主要包含两个方面:

  • 显示屏移植:各类显示屏驱动适配
  • 输入移植:包含鼠标、按键、触摸屏等

2.1 LVGL配置

第一步,更改LVGL配置文件:

将LVGL源码lvgl_conf_template.h更名为lvgl_conf.h,并将该文件的第15行代码更改为:

1
2
#if 1 /\*Set it to "1" to enable content\*/

该步骤用于启用LVGL相关配置内容。

第二步,配置显示屏颜色,请根据TFT支持的颜色模式更改,本次使用的ILI9325驱动器TFT,支持16位颜色模式,配置如下:

1
2
3
/\*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)\*/
#define LV\_COLOR\_DEPTH 16

请注意,如是使用SPI方式驱动,还需要更改配置如下:

1
2
3
/\*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)\*/
#define LV\_COLOR\_16\_SWAP 1

第三步,配置LVGL显存。LVGL显示最小需要2KB,本次使用的MCU为STM32F103VE,SRAM为64KB,考虑到CMSIS-RTOS V2还需要内存分配、应用程序需要内存分配,在这里配置如下:

1
2
3
/\*Size of the memory available for `lv\_mem\_alloc()` in bytes (>= 2kB)\*/
#define LV\_MEM\_SIZE (16ul \* 1024ul) /\*[bytes]\*/

我们为LVGL分配了16KB的内存空间。

第四步,配置字体。字体的配置需要根据应用程序需要选择,在这里的配置如下:

1
2
3
4
5
#define LV\_FONT\_MONTSERRAT\_14 1
#define LV\_FONT\_MONTSERRAT\_16 1
#define LV\_FONT\_MONTSERRAT\_18 1
#define LV\_FONT\_MONTSERRAT\_20 1

第五步,调试日志配置。这步是可选的。如果开启调试日志,应用程序的ROM占用空间将增加。配置如下:

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
#define LV\_USE\_LOG 1
#if LV\_USE\_LOG

/\*How important log should be added:
\*LV\_LOG\_LEVEL\_TRACE A lot of logs to give detailed information
\*LV\_LOG\_LEVEL\_INFO Log important events
\*LV\_LOG\_LEVEL\_WARN Log if something unwanted happened but didn't cause a problem
\*LV\_LOG\_LEVEL\_ERROR Only critical issue, when the system may fail
\*LV\_LOG\_LEVEL\_USER Only logs added by the user
\*LV\_LOG\_LEVEL\_NONE Do not log anything\*/
#define LV\_LOG\_LEVEL LV\_LOG\_LEVEL\_USER

/\*1: Print the log with 'printf';
\*0: User need to register a callback with `lv\_log\_register\_print\_cb()`\*/
#define LV\_LOG\_PRINTF 1

/\*Enable/disable LV\_LOG\_TRACE in modules that produces a huge number of logs\*/
#define LV\_LOG\_TRACE\_MEM 1
#define LV\_LOG\_TRACE\_TIMER 1
#define LV\_LOG\_TRACE\_INDEV 1
#define LV\_LOG\_TRACE\_DISP\_REFR 1
#define LV\_LOG\_TRACE\_EVENT 1
#define LV\_LOG\_TRACE\_OBJ\_CREATE 1
#define LV\_LOG\_TRACE\_LAYOUT 1
#define LV\_LOG\_TRACE\_ANIM 1

#endif /\*LV\_USE\_LOG\*/

第六步,关闭GPU硬件加速功能

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
/\*-------------
\* GPU
\*-----------\*/

/\*Use STM32's DMA2D (aka Chrom Art) GPU\*/
#define LV\_USE\_GPU\_STM32\_DMA2D 0
#if LV\_USE\_GPU\_STM32\_DMA2D
/\*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h"\*/
#define LV\_GPU\_DMA2D\_CMSIS\_INCLUDE
#endif

/\*Use NXP's PXP GPU iMX RTxxx platforms\*/
#define LV\_USE\_GPU\_NXP\_PXP 0
#if LV\_USE\_GPU\_NXP\_PXP
/\*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv\_gpu\_nxp\_pxp\_osa.c)
\* and call lv\_gpu\_nxp\_pxp\_init() automatically during lv\_init(). Note that symbol SDK\_OS\_FREE\_RTOS
\* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
\*0: lv\_gpu\_nxp\_pxp\_init() has to be called manually before lv\_init()
\*/
#define LV\_USE\_GPU\_NXP\_PXP\_AUTO\_INIT 0
#endif

/\*Use NXP's VG-Lite GPU iMX RTxxx platforms\*/
#define LV\_USE\_GPU\_NXP\_VG\_LITE 0

/\*Use SDL renderer API\*/
#define LV\_USE\_GPU\_SDL 0
#if LV\_USE\_GPU\_SDL
#define LV\_GPU\_SDL\_INCLUDE\_PATH <SDL2/SDL.h>
/\*Texture cache size, 8MB by default\*/
#define LV\_GPU\_SDL\_LRU\_SIZE (1024 \* 1024 \* 8)
/\*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib\*/
#define LV\_GPU\_SDL\_CUSTOM\_BLEND\_MODE (SDL\_VERSION\_ATLEAST(2, 0, 6))
#endif

第七步,配置主题模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/\*A simple, impressive and very complete theme\*/
#define LV\_USE\_THEME\_DEFAULT 1
#if LV\_USE\_THEME\_DEFAULT

/\*0: Light mode; 1: Dark mode\*/
#define LV\_THEME\_DEFAULT\_DARK 1

/\*1: Enable grow on press\*/
#define LV\_THEME\_DEFAULT\_GROW 0

/\*Default transition time in [ms]\*/
#define LV\_THEME\_DEFAULT\_TRANSITION\_TIME 80
#endif /\*LV\_USE\_THEME\_DEFAULT\*/

本次设置深色模式。

最后,其他部分按默认配置即可。

2.2 LVGL源码配置

LVGL的源码配置非简单,由于STM32F1不支持DMA_2D硬件加速,因此只需要删除其他硬件平台代码或将其屏蔽即可。GPU硬件加速代码储存位置为:

  • src/draw/sw:软件模拟绘图,该目录代码保留
  • src/draw/nxp_pxp:删除或屏蔽
  • src/draw/nxp_vglite:删除或屏蔽
  • src/draw/sdl:在PC中使用LVGL模拟器时用到,在这里删除或屏蔽
  • src/draw/stm32_dma2d:本次使用的MCU不支持该功能,在这里删除或屏蔽

在这里插入图片描述

2.3、LVGL显示屏驱动适配

本实例使用的显示屏为2.4寸的ILI9325驱动器的TFT显示,并带有XPT2046电阻触摸屏。

TFT显示屏使用FSCM方式驱动及其相关配置、TFT底层驱动在前面的文章中已经做了详细的介绍:

显示屏在LVGL中适配的步骤如下:

  • LVGL显示刷新回调函数实现
  • LVGL显示设备配置及注册

新建一个目录lvgl_port并添加disp_driver.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
/\*
\* disp\_driver.h
\*
\* Created on: 2022年7月5日
\* Author: jenson
\*/
#ifndef \_\_DISP\_DRIVER\_H\_\_
#define \_\_DISP\_DRIVER\_H\_\_

#ifdef LV\_LVGL\_H\_INCLUDE\_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#ifdef \_\_cplusplus
extern "C" {
#endif

/\* Display flush callback \*/
void lv\_port\_disp\_init(void);

#if 0
/\* Display rounder callback, used with monochrome dispays \*/
void disp\_driver\_rounder(lv\_disp\_drv\_t \* disp_drv, lv\_area\_t \* area);

/\* Display set\_px callback, used with monochrome dispays \*/
void disp\_driver\_set\_px(lv\_disp\_drv\_t \* disp_drv, uint8\_t \* buf, lv\_coord\_t buf_w, lv\_coord\_t x, lv\_coord\_t y,
lv\_color\_t color, lv\_opa\_t opa);
#endif

#ifdef \_\_cplusplus
} /\* extern "C" \*/
#endif

#endif // \_\_DISP\_DRIVER\_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
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
/\*
\* disp\_driver.c
\*
\* Created on: 2022年7月5日
\* Author: jenson
\*/

#include "disp\_driver.h"
#include "lcd/ili9325\_fsmc.h"
#ifdef LV\_LVGL\_H\_INCLUDE\_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#define LV\_HOR\_RES\_MAX (XSIZE\_PHYS) // 屏幕分辨率宽度
#define LV\_VER\_RES\_MAX (YSIZE\_PHYS) // 屏幕分辨率高度

void disp\_driver\_init(void) {
// TFT的初始化已经在TFT底层驱动初始化时完成,不需要在这里再次初始化
}

/\*显示屏刷新回调函数 \*/
static void disp\_driver\_flush(lv\_disp\_drv\_t \*drv, const lv\_area\_t \*area,
lv\_color\_t \*color_map) {
int32\_t x;
int32\_t y;
for (y = area->y1; y <= area->y2; y++) {
for (x = area->x1; x <= area->x2; x++) {
/\*Put a pixel to the display. For example:\*/
/\*put\_px(x, y, \*color\_p)\*/
// 绘制像素
LCD\_Pixel(x, y, \*((uint16\_t\*) color_map));

color_map++;
}
}
// 注意:这里按像素绘制,效率比较低。
// 可以按块绘制像素提高效率,但是需要实现TFT的块绘制底层实现。
// LCD\_Rect\_Fill(area->x1, area->y1, area->x2, area->y2,
// \*((uint16\_t\*) color\_map));

// 刷新显示
lv\_disp\_flush\_ready(drv);
}

void lv\_port\_disp\_init(void) {
// TFT硬件初始化
disp\_driver\_init();

// LVGL绘图缓存
static lv\_disp\_draw\_buf\_t disp_buf;
static lv\_color\_t buf1_1[LV_HOR_RES_MAX \* 16 + 1]; // 缓存16行
// static lv\_color\_t buf2\_1[LV\_HOR\_RES\_MAX \* 8 + 1]; 由于内存大小限制,不使用双缓冲绘制
// 初始化LVGL绘图缓存
lv\_disp\_draw\_buf\_init(&disp_buf, buf1_1, NULL, LV_HOR_RES_MAX \* 16 + 1);

// LVGL显示设备
static lv\_disp\_drv\_t disp_drv;
// 初始化LVGL显示设备
lv\_disp\_drv\_init(&disp_drv);
// 设置显示刷新回调函数
disp_drv.flush_cb = disp_driver_flush;

disp_drv.hor_res = LCD\_GetLcdPixelWidth(); // 获取屏幕宽度
disp_drv.ver_res = LCD\_GetLcdPixelHeight(); // 获取屏幕高度
disp_drv.rotated = LV_DISP_ROT_NONE; // 屏幕旋转方向(必须与屏幕初始化时一致)
disp_drv.antialiasing = true; //反锯齿
disp_drv.screen_transp = false;

disp_drv.draw_buf = &disp_buf;

// 注册驱动
lv\_disp\_drv\_register(&disp_drv);

printf("lv\_port\_disp\_init\r\n");
}



2.4 LVGL输入设备驱动适配

LVGL的输入设备驱动适配与显示设备步骤类似,主要由如下步骤组成:

  • LVGL输入设备坐标读取回调函数实现
  • LVGL输入设备配置及注册

在前面的文章中,对XPT2046的驱动做了详细的介绍,请参考:

lvgl_port中添加touch_driver.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
/\*
\* touch\_driver.h
\*
\* Created on: 2022年7月5日
\* Author: jenson
\*/

#ifndef \_\_TOUCH\_DRIVER\_H\_\_
#define \_\_TOUCH\_DRIVER\_H\_\_

#ifdef LV\_LVGL\_H\_INCLUDE\_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#ifdef \_\_cplusplus
extern "C" {
#endif

void touch\_driver\_init(void);


#ifdef \_\_cplusplus
} /\* extern "C" \*/
#endif

#endif // \_\_TOUCH\_DRIVER\_H\_\_


lvgl_port中添加touch_driver.c文件,并实现LVGL输入设备适配代码:

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
/\*
\* touch\_driver.c
\*
\* Created on: 2022年7月5日
\* Author: jenson
\*/

#include "touch\_driver.h"
#include "xpt2046/xpt2046\_touch.h"
#include <stdbool.h>
#include <stdio.h>
#include "main.h"
#include "spi.h"

static xpt2046\_t xpt2046;

// XPT2046初始化
void touch\_init(void) {
xpt2046.spi = &hspi1;
xpt2046.cs_port = T_CS_GPIO_Port;
xpt2046.cs_pin = T_CS_Pin;
xpt2046.irq_port = T_PEN_GPIO_Port;
xpt2046.irq_pin = T_PEN_Pin;

xpt2046\_init(&xpt2046);
}

void touch\_driver\_read(lv\_indev\_drv\_t \*drv, lv\_indev\_data\_t \*data);

void touch\_driver\_init(void) {
touch\_init();
static lv\_indev\_drv\_t indev_drv;
lv\_indev\_drv\_init(&indev_drv);

indev_drv.read_cb = touch_driver_read;
indev_drv.type = LV_INDEV_TYPE_POINTER;

lv\_indev\_drv\_register(&indev_drv);

printf("touch driver inited\r\n");
}

void touch\_driver\_read(lv\_indev\_drv\_t \*drv, lv\_indev\_data\_t \*data) {
bool valid = false;

if (xpt2046\_pressed(&xpt2046)) {
if (xpt2046\_read\_xy(&xpt2046)) {
valid = true;
data->point.x = xpt2046.x;
data->point.y = xpt2046.y;
data->state =
valid == false ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
// printf("touch x = %d,y = %d\r\n", xpt2046.x, xpt2046.y);
} else {
printf("get coord failed\r\n");
}
}
}



3、FreeRTOS配置

在前面的系列文章中,利用STM32CubeIDE如何配置、使用CMSIS-RTOS V2,做了详细的介绍,请参考:

  1. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2配置(基于FreeRTOS)
  2. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-线程管理
  3. STM32F1与STM32CubeIDE编程实例-延时
  4. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-定时器管理
  5. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-互斥(Mutex)管理
  6. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-信号量(Semaphore)
  7. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-消息队列
  8. STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-事件标志(Event Flags)

在这里只针对LVGL如何结合FreeRTOS使用。

在STM32CubeIDE生成的freertos.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
/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#include "lcd/ili9325\_fsmc.h"
#include "lvgl.h"
#include "xpt2046/xpt2046\_touch.h"
#include "lvgl\_port/disp\_driver.h"
#include "lvgl\_port/touch\_driver.h"
#include <stdio.h>
#include "ui\_main.h"
/\* USER CODE END Includes \*/


void StartDefaultTask(void \*argument) {
/\* USER CODE BEGIN StartDefaultTask \*/
printf("lvgl render task started\r\n");
/\* Infinite loop \*/
for (;;) {
lv\_task\_handler();
osDelay(5);

}
/\* USER CODE END StartDefaultTask \*/
}

在默认任务中调用lv_task_handler函数

另外,LVGL的内部定时器每1毫秒计数一次。由于FreeRTOS的基时钟使用TIM1,如下所示:

在这里插入图片描述

因此,LVGL的内部时钟计时器只需要在TIM1的中断回调函数中调用即可。在stm32f1xx_it.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
/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#ifdef LV\_LVGL\_H\_INCLUDE\_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
#include "xpt2046/xpt2046\_touch.h"
/\* USER CODE END Includes \*/

/\*\*
\* @brief This function handles TIM1 update interrupt.
\*/
void TIM1\_UP\_IRQHandler(void)
{
/\* USER CODE BEGIN TIM1\_UP\_IRQn 0 \*/

/\* USER CODE END TIM1\_UP\_IRQn 0 \*/
HAL\_TIM\_IRQHandler(&htim1);
/\* USER CODE BEGIN TIM1\_UP\_IRQn 1 \*/
lv\_tick\_inc(1);
/\* USER CODE END TIM1\_UP\_IRQn 1 \*/
}

4、主程序测试实现

main.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
/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#include "lcd/ili9325\_fsmc.h"
#include "lcd/fonts.h"
#include "xpt2046/xpt2046\_touch.h"
#include <stdio.h>
#include <stdbool.h>

#ifdef LV\_LVGL\_H\_INCLUDE\_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#include "lvgl\_port/disp\_driver.h"
#include "lvgl\_port/touch\_driver.h"

/\* USER CODE END Includes \*/

/\* Private user code ---------------------------------------------------------\*/
/\* USER CODE BEGIN 0 \*/

void lv\_helloworld(void) {
lv\_obj\_t \*obj = lv\_obj\_create(lv\_scr\_act());
lv\_obj\_set\_size(obj, LV\_PCT(20), LV\_PCT(10));
lv\_obj\_align(obj, LV_ALIGN_CENTER, 0, 0);

lv\_obj\_t \*label = lv\_label\_create(obj);
lv\_label\_set\_text(label, "Hello, LVGL!");
lv\_obj\_align(label, LV_ALIGN_CENTER, 0, 0);
}

/\* USER CODE END 0 \*/

int main(void) {
/\* USER CODE BEGIN 1 \*/

/\* USER CODE END 1 \*/

/\* MCU Configuration--------------------------------------------------------\*/

/\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/
HAL\_Init();

/\* USER CODE BEGIN Init \*/

/\* USER CODE END Init \*/

/\* Configure the system clock \*/
SystemClock\_Config();

/\* USER CODE BEGIN SysInit \*/

/\* USER CODE END SysInit \*/

/\* Initialize all configured peripherals \*/
MX\_GPIO\_Init();
MX\_USART1\_UART\_Init();
MX\_FSMC\_Init();
MX\_SPI1\_Init();
/\* USER CODE BEGIN 2 \*/

LCD\_Init();
LCD\_Rect\_Fill(0, 0, LCD\_GetLcdPixelWidth(), LCD\_GetLcdPixelHeight(), WHITE);

lv\_init();
printf("lv\_inited\r\n");
lv\_port\_disp\_init();
touch\_driver\_init();

lv\_helloworld();

/\* USER CODE END 2 \*/

/\* Init scheduler \*/
osKernelInitialize(); /\* Call init function for freertos objects (in freertos.c) \*/
MX\_FREERTOS\_Init();

/\* Start scheduler \*/
osKernelStart();

/\* We should never get here as control is now taken by the scheduler \*/
/\* Infinite loop \*/
/\* USER CODE BEGIN WHILE \*/
while (1) {
/\* USER CODE END WHILE \*/

/\* USER CODE BEGIN 3 \*/

}
/\* USER CODE END 3 \*/
}

文章来源: https://iotsmart.blog.csdn.net/article/details/128638941