ESP32S3R8+Gui Guider 运行 LVGL

前言

NXP 提供了基于 LVGL 图形库快速开发用户界面的开发工具 Gui Guider,可以通过拖放编辑小部件、动画修改样式的方式创建 GUI,并且自动生成代码,在上位机仿真或移植到 MCU 里,实现界面的快速开发。

本文讲述了使用 GUI Guider 生成界面并应用在 ESP32S3R8,目前 GUI Guider 只支持到 LVGL8,所以本文会以 ESP-IDF + LVGL8 的形式说明。

环境如下:

1
2
3
4
5
6
MCU: ESP32S3R8
开发框架: ESP-IDF v5.3.1
LVGL: V8.3.10
系统: Ubuntu(windows 一样,两种环境都验证过)
LCD: CT77916
TP: CST816

工程的创建、LCD、TP 等这些驱动的设置可以参考上一篇文章:ESP32S3R8+LVGL9+ST77916+CST816S | nixgnauhcuy

需要说明的是,因为上一篇用的 LVGL9,这一篇更改为 LVGL8,所以略微会有不同,不过基本相似,更改如下:

1
2
3
4
idf.py add-dependency "lvgl/lvgl^8.3.10"
idf.py add-dependency "espressif/esp_lvgl_port^2.4.3"
idf.py add-dependency "espressif/esp_lcd_st77916^1.0.0"
idf.py add-dependency "espressif/esp_lcd_touch_cst816s^1.0.3~1"

组件由 "lvgl/lvgl^9.2.2" 更改为 "lvgl/lvgl^8.3.10",其他的相同的。

代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Add LCD screen */  
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t),
.double_buffer = 0,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.color_format = LV_COLOR_FORMAT_RGB565,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.swap_bytes = true,
.buff_dma = true,
}
};
lvgl_disp = lvgl_port_add_disp(&disp_cfg);

需修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ESP_LOGD(TAG, "Add LCD screen");  
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t),
.double_buffer = 0,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = true,
}
};
lvgl_disp = lvgl_port_add_disp(&disp_cfg);

主要删除了 .color_format.swap_bytes,这些在 LVGL8 是没有的,会报错,其他基本一致,可以直接食用。

由于删除了 .swap_bytes,所以需要在 sdkconfig 中设置 CONFIG_LV_COLOR_16_SWAP=1

接下来进入正文。

Gui Guider

安装

进入 GUI Guider | NXP 半导体,点击下载,会要求登录账号及同意协议这些,

根据系统选择对应的版本,windows 下载完直接运行安装即可,Linux 下载后会得到 Gui-Guider-Setup-1.8.1-GA.deb

运行对应指令安装即可,

1
sudo apt install ./Gui-Guider-Setup-1.8.1-GA.deb

安装完成后运行,会要求登录,多半还有一些数据收集的调查啥的,可以直接跳过,右上角可以更换语言和配色,这里我修改为中文和深色,效果如图,

工程创建

接下来,我们创建一个项目,选择使用的 LVGL 版本,这里我的是 v8.3.10

选择 Simulator,并下一步,

进入选择模版,这里我选择 EmptyUI,还有其他的模板可以参考其他选项,

进入项目配置信息设置,操作如图:

接下来会进入工程界面,

移植

这里我添加了一张图片,图片是 360*360 的,用来当背景,步骤如下,

添加完成后,可以先仿真看看,有两种方式,一种是 C 一种是 MicroPython,我选择了 MicroPython,效果如下:

确认没问题后,点击左边的图标进行代码生成,同样有两种方式,这里就要选择生成 C 代码了,

完成后,会生成代码,在工程的路径可以找到生成的代码,

在 ESP32S3 项目工程里创建 components,在 components 目录下创建 lvgl_guider 文件夹,名称可以自定义,主要方便识别即可,将 Gui Guider 生成的 customgenerated 两个文件夹拷贝到 lvgl_guider,并且在 lvgl_guider 创建 CMakeLists.txt 文件,最终如下:

CMakeLists.txt 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
if(ESP_PLATFORM)

file(GLOB_RECURSE SOURCES *.c ./generated/images/*.c ./generated/guider_fonts/*.c ./custom/*.c)
set(INCLUDE_DIRS ./generated ./generated/guider_fonts ./generated/guider_customer_fonts ./generated/guider_fonts ./custom)

idf_component_register(SRCS ${SOURCES}
INCLUDE_DIRS ${INCLUDE_DIRS}
REQUIRES lvgl)
idf_build_set_property(COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND)
else()
message(FATAL_ERROR "ESP_PLATFORM is not defined. Try reinstalling ESP-IDF.")
endif()

主要是将 lvgl_guider 组件的内容包含进工程里,
注意,idf_build_set_property (COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND) 是必须的,否则编译后,会提示找不到 lvgl/lvgl.h

随后在需要引用 LVGL 显示的地方增加对应代码,这里我还是按之前文章里的,所有的代码都放在了 main.c,大家根据需要自己调整工程结构即可。

添加头文件和变量,

1
2
#include "gui_guider.h"
lv_ui guider_ui;

在驱动和 LVGL 初始化完成后,调用显示:

1
setup_ui(&guider_ui);

具体代码如下:

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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <stdio.h>

#include "lvgl.h"

#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_st77916.h"
#include "esp_lcd_touch_cst816s.h"
#include "esp_lvgl_port.h"

#include "driver/ledc.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"

#include "gui_guider.h"

lv_ui guider_ui;

#define TAG "MAIN"
#define EXAMPLE_LCD_HOST (SPI2_HOST)
#define EXAMPLE_PIN_NUM_LCD_PCLK (9)
#define EXAMPLE_PIN_NUM_LCD_CS (10)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (11)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (12)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (13)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (14)
#define EXAMPLE_PIN_NUM_LCD_RST (47)
#define EXAMPLE_PIN_NUM_LCD_BL (15)

#define EXAMPLE_TP_PORT (I2C_NUM_0)
#define EXAMPLE_PIN_NUM_TP_SDA (7)
#define EXAMPLE_PIN_NUM_TP_SCL (8)
#define EXAMPLE_PIN_NUM_TP_RST (40)
#define EXAMPLE_PIN_NUM_TP_INT (41)

#define EXAMPLE_LCD_H_RES (360)
#define EXAMPLE_LCD_V_RES (360)
#define EXAMPLE_LCD_BIT_PER_PIXEL (16)

static esp_lcd_panel_io_handle_t io_handle = NULL;
static esp_lcd_touch_handle_t tp_handle;
static esp_lcd_panel_handle_t panel_handle = NULL;

static lv_disp_t *lvgl_disp = NULL;
static lv_indev_t *lvgl_touch_indev = NULL;

void bsp_lcd_tp_init(void)
{
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_PIN_NUM_TP_SDA,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_io_num = EXAMPLE_PIN_NUM_TP_SCL,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
.master.clk_speed = 400000
};
ESP_ERROR_CHECK(i2c_param_config(EXAMPLE_TP_PORT, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(EXAMPLE_TP_PORT, i2c_conf.mode, 0, 0, 0));

esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
esp_lcd_new_panel_io_i2c(EXAMPLE_TP_PORT, &io_config, &io_handle);

esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TP_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TP_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.interrupt_callback = NULL,
};

esp_lcd_touch_new_i2c_cst816s(io_handle, &tp_cfg, &tp_handle);
}

void bsp_lcd_bl_set(int brightness_percent)
{
if (brightness_percent > 100) {
brightness_percent = 100;
}
if (brightness_percent < 0) {
brightness_percent = 0;
}

ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}

void bsp_lcd_bl_off(void)
{
bsp_lcd_bl_set(0);
}

void bsp_lcd_bl_on(void)
{
bsp_lcd_bl_set(100);
}

void bsp_lcd_bl_init(void)
{
const ledc_channel_config_t LCD_backlight_channel = {
.gpio_num = EXAMPLE_PIN_NUM_LCD_BL,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0
};

const ledc_timer_config_t LCD_backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};

ledc_timer_config(&LCD_backlight_timer);
ledc_channel_config(&LCD_backlight_channel);

bsp_lcd_bl_on();
}

void bsp_lcd_init(void)
{
ESP_LOGI(TAG, "Initialize QSPI bus");
const spi_bus_config_t bus_config = ST77916_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK,
EXAMPLE_PIN_NUM_LCD_DATA0,
EXAMPLE_PIN_NUM_LCD_DATA1,
EXAMPLE_PIN_NUM_LCD_DATA2,
EXAMPLE_PIN_NUM_LCD_DATA3,
EXAMPLE_LCD_H_RES * 72 * 2);
ESP_ERROR_CHECK(spi_bus_initialize(EXAMPLE_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));

ESP_LOGI(TAG, "Install panel IO");

const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS, NULL, NULL);
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_HOST, &io_config, &io_handle));

ESP_LOGI(TAG, "Install ST77916 panel driver");

st77916_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
.bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18)
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(io_handle, &panel_config, &panel_handle));

esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_disp_on_off(panel_handle, true);
}

void app_lvgl(void)
{
const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_port_init(&lvgl_cfg);

/* Add LCD screen */
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = EXAMPLE_LCD_H_RES * 72,
.double_buffer = 0,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = true,
}
};
lvgl_disp = lvgl_port_add_disp(&disp_cfg);

/* Add touch input (for selected screen) */
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lvgl_disp,
.handle = tp_handle,
};
lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg);

}

void app_main(void)
{
bsp_lcd_tp_init();
bsp_lcd_init();
bsp_lcd_bl_init();

app_lvgl();
setup_ui(&guider_ui);
}

效果

最终效果如下: