ESP32GUI入门-基于ESP-IDF官方组件移植LVGL(SPD2010带触摸)

说明:

  1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
  2. 这篇文章主要记录SPD2010(带触摸)驱动的LVGL移植,仅作为记录,并非教程。

修订历史:

文档名称 版本 作者 时间 备注
基于ESP-IDF官方组件移植LVGL(SPD2010(带触摸)) v1.0.0 DuRuofu 2024-03-08 首次建立

基于ESP-IDF官方组件移植LVGL(SPD2010(带触摸))

一、介绍

1.1 QSPI协议

SPI协议其实是包括:Standard SPI、Dual SPI和Queued SPI三种协议接口,分别对应3-wire, 4-wire, 6-wire。

  1. 通常我们说的SPI就是Standard SPI,有4根信号线,分别为CLK、CS、MOSI和MISO。数据线工作在全双工
  2. Dual SPI,它只是针对SPI Flash而言,不是针对所有SPI外设。对于SPI Flash,全双工并不常用,因此扩展了mosi和miso的用法,让它们工作在半双工,用以加倍数据传输。也就是对于Dual SPI Flash,可以发送一个命令字节进入dual mode,这样mosi变成SIO0(serial io 0),mosi变成SIO1(serial io 1),这样一个时钟周期内就能传输2个bit数据,加倍了数据传输。
  3. 类似的,还可以扩展,与也是针对SPI Flash,Qual SPI Flash增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit而QSPI就是Queued SPI的简写。

1.2 硬件介绍:

屏幕使用华夏彩光电 1.46寸412*412高清圆屏智能穿戴家电旋钮QSPI、MIPI接口触摸屏

参数:

接口定义:

主控使用ESP32-S3,开发板为合宙ESP32S3开发板

二、移植过程

2.1 新建工程并添加依赖

新建工程,添加依赖的组件库,分别是:lvgl, esp_lcd_spd2010esp_lcd_touch_spd2010

1
2
3
idf.py add-dependency "lvgl/lvgl^8.3.11"
idf.py add-dependency "espressif/esp_lcd_spd2010^1.0.1"
idf.py add-dependency "espressif/esp_lcd_touch_spd2010^0.0.1"

2.2 硬件接线

接线对应关系如图所示:

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

// 引脚配置
// ESP Board SPD1020 Panel (QSPI)
// ┌──────────────────────┐ ┌────────────────────┐
// │ GND ├─────────────►│ GND │
// │ │ │ │
// │ 3V3 ├─────────────►│ VCC │
// │ │ │ │
// │ CS ├─────────────►│ CS │
// │ │ │ │
// │ SCK ├─────────────►│ CLK │
// │ │ │ │
// │ D3 ├─────────────►│ IO3 │
// │ │ │ │
// │ D2 ├─────────────►│ IO2 │
// │ │ │ │
// │ D1 ├─────────────►│ IO1 │
// │ │ │ │
// │ D0 ├─────────────►│ IO0 │
// │ │ │ │
// │ RST ├─────────────►│ RSTN │
// │ │ │ │
// │ (SCL) ├─────────────►│ TP_SCL │
// │ │ │ │
// │ (SDA) ├─────────────►│ TP_SDA │
// │ │ │ │
// │ (TP_INT) ├─────────────►│ TP_INT │
// │ │ │ │
// │ (3V3) ├─────────────►│ TP_RST │
// │ │ │ │
// └──────────────────────┘ └────────────────────┘

实际引脚配置见下节:

2.3 配置屏幕

导入头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_spd2010.h"
#include "esp_lcd_touch_spd2010.h"

选择显示SPI和触摸I2C的控制器

1
2
3
// LCD和触摸控制器配置
#define LCD_HOST SPI2_HOST
#define TOUCH_HOST I2C_NUM_0

定义分辨率

1
2
3
// LCD分辨率
#define EXAMPLE_LCD_H_RES 400
#define EXAMPLE_LCD_V_RES 400

定义引脚接线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  0
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_9)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_10)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_11)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_12)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_13)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_14)
#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_BK_LIGHT (GPIO_NUM_3)

// 触摸引脚配置
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_18)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_8)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_7)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_17)

配置背光(main)

1
2
3
4
5
6
7
8
9
10
11
12
// 背光引脚配置
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

// 打开背光
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);

配置SPI总线(main)

按照屏幕驱动库里的SPI配置函数,配置SPI总线:

1
2
3
   ESP_LOGI(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = SPD2010_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 * EXAMPLE_LCD_V_RES * LCD_BIT_PER_PIXEL / 8);
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));

创建屏幕IO句柄(main)

屏幕IO句柄用于给屏幕发送指令和数据。

1
2
3
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS,example_notify_lvgl_flush_ready,&disp_drv);

notify_lvgl_flush_ready是一个回调函数,用于颜色传输完成被调用。

1
2
3
4
5
6
7
// 回调函数:颜色传输完成 (通知LVGL刷新)
static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}

我们在这个回调函数里通知LVGL刷新

disp_drv 用于给回调函数传参:

1
static lv_disp_drv_t disp_drv;      // contains callback functions

将LCD连接至SPI(main)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spd2010_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

esp_lcd_panel_handle_t panel_handle = NULL;
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,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};

配置屏幕驱动(main):

1
2
3
4
5
6
7
8
ESP_LOGI(TAG, "Install SPD2010 panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(io_handle, &panel_config, &panel_handle));


ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

到这里屏幕就应该点亮了。

2.4 配置触摸

初始I2C总线(main)

1
2
3
4
5
6
7
8
9
10
11
ESP_LOGI(TAG, "Initialize I2C bus");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400 * 1000,
};
ESP_ERROR_CHECK(i2c_param_config(TOUCH_HOST, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(TOUCH_HOST, i2c_conf.mode, 0, 0, 0));

这里的上拉不一定有效,若调试阶段发现I2C无法通信,可以尝试外加上拉电阻。

创建触摸IO句柄(main)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_SPD2010_CONFIG();

const 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_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.interrupt_callback = example_touch_isr_cb,
};

interrupt_callback是触屏回调函数,如下:

1
2
3
4
5
6
7
8
static void example_touch_isr_cb(esp_lcd_touch_handle_t tp)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(touch_mux, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}

配置触摸驱动

1
2
3
4
5
6
 // Attach the TOUCH to the I2C bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_HOST, &tp_io_config, &tp_io_handle));

// 初始化触摸驱动
ESP_LOGI(TAG, "Initialize touch controller SPD2010");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_spd2010(tp_io_handle, &tp_cfg, &tp));

2.5 配置LVGL

初始化LVGL:
1
2
3
4
5
6
7
8
9
10
// 初始化LVGL
ESP_LOGI(TAG, "Initialize LVGL");
lv_init();
// 申请内存 两个buf
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1); // 检查内存是否申请成功
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2);
// 初始化LVGL显示缓冲区
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 50);
注册LVGL显示驱动:
1
2
3
4
5
6
7
8
9
// 初始化LVGL显示驱动
ESP_LOGI(TAG, "Initialize LVGL display driver");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = EXAMPLE_LCD_H_RES; // 设置屏幕水平分辨率
disp_drv.ver_res = EXAMPLE_LCD_V_RES; // 设置屏幕垂直分辨率
disp_drv.flush_cb = lvgl_flush_cb; // 设置刷新回调函数
disp_drv.draw_buf = &disp_buf; // 设置显示缓冲区
disp_drv.user_data = panel_handle; // 设置用户数据
lv_disp_t *disp = lv_disp_drv_register(&disp_drv); // 注册显示驱动
1
2
3
4
5
6
7
8
9
10
11
// 回调函数:刷新屏幕
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
/* Copy a buffer's content to a specific area of the display */
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}
注册LVGL触摸驱动:
1
2
3
4
5
6
7
8
static lv_indev_drv_t indev_drv;    // Input device driver (Touch)
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = disp;
indev_drv.read_cb = example_lvgl_touch_cb;
indev_drv.user_data = tp;

lv_indev_drv_register(&indev_drv);

example_lvgl_touch_cb为触屏回调,如下:

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

// 触屏信号量
static SemaphoreHandle_t touch_mux = NULL;
// lvgl触摸回调函数
static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)drv->user_data;
assert(tp);

uint16_t tp_x;
uint16_t tp_y;
uint8_t tp_cnt = 0;
/* Read data from touch controller into memory */
if (xSemaphoreTake(touch_mux, 0) == pdTRUE) {
esp_lcd_touch_read_data(tp);
}

/* Read data from touch controller */
bool tp_pressed = esp_lcd_touch_get_coordinates(tp, &tp_x, &tp_y, NULL, &tp_cnt, 1);
if (tp_pressed && tp_cnt > 0) {
data->point.x = tp_x;
data->point.y = tp_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGI(TAG, "Touch position: %d,%d", tp_x, tp_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
创建LVGL定时器及任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建定时器
ESP_LOGI(TAG, "Install LVGL tick timer");
/* Tick interface for LVGL (using esp_timer to generate 2ms periodic event) */
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

// 创建LVGL任务
ESP_LOGI(TAG, "Create LVGL task");
lvgl_mux = xSemaphoreCreateMutex();
assert(lvgl_mux);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
1
2
3
4
5
6
#define EXAMPLE_LVGL_TICK_PERIOD_MS     2               
// LVGL任务参数
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (5 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2
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
static bool example_lvgl_lock(int timeout_ms)
{
assert(lvgl_mux && "bsp_display_start must be called first");

const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}

static void example_lvgl_unlock(void)
{
assert(lvgl_mux && "bsp_display_start must be called first");
xSemaphoreGive(lvgl_mux);
}

// LVGL任务
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
ESP_LOGI(TAG, "Display LVGL UI");

//test_ui();
lv_demo_widgets();

uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
while (1) {
ESP_LOGI(TAG, "LVGL task");
/* Lock the mutex due to the LVGL APIs are not thread-safe */
if (example_lvgl_lock(-1)) {
task_delay_ms = lv_timer_handler();
/* Release the mutex */
example_lvgl_unlock();
}
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}

三、最终代码

代码仓库:https://github.com/DuRuofu/ESP32_Learning/tree/master/09.gui/lvgl_transplant_spd2010

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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_spd2010.h"
#include "esp_lcd_touch_spd2010.h"


static const char *TAG = "main";
static SemaphoreHandle_t lvgl_mux = NULL;

// LCD和触摸控制器配置
#define LCD_HOST SPI2_HOST
#define TOUCH_HOST I2C_NUM_0


// 颜色深度
#define LCD_BIT_PER_PIXEL (16)

// 引脚配置
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 0
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_9)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_10)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_11)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_12)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_13)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_14)
#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_BK_LIGHT (GPIO_NUM_3)

// 触摸引脚配置
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_18)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_8)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_7)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_17)

esp_lcd_touch_handle_t tp = NULL;


// LCD分辨率
#define EXAMPLE_LCD_H_RES 412
#define EXAMPLE_LCD_V_RES 412

// LVGLtask配置
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}


static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;

// copy a buffer's content to a specific area of the display
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

/* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */
static void example_lvgl_update_cb(lv_disp_drv_t *drv)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;

switch (drv->rotated) {
case LV_DISP_ROT_NONE:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, true, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
case LV_DISP_ROT_90:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
case LV_DISP_ROT_180:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, false, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);

break;
case LV_DISP_ROT_270:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, false, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
}
}

void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area)
{
uint16_t x1 = area->x1;
uint16_t x2 = area->x2;
// round the start of coordinate down to the nearest 4M number
area->x1 = (x1 >> 2) << 2;
// round the end of coordinate up to the nearest 4N+3 number
area->x2 = ((x2 >> 2) << 2) + 3;
}


// 触屏信号量
static SemaphoreHandle_t touch_mux = NULL;
// lvgl触摸回调函数
static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)drv->user_data;
assert(tp);

uint16_t tp_x;
uint16_t tp_y;
uint8_t tp_cnt = 0;
/* Read data from touch controller into memory */
if (xSemaphoreTake(touch_mux, 0) == pdTRUE) {
esp_lcd_touch_read_data(tp);
}

/* Read data from touch controller */
bool tp_pressed = esp_lcd_touch_get_coordinates(tp, &tp_x, &tp_y, NULL, &tp_cnt, 1);
if (tp_pressed && tp_cnt > 0) {
data->point.x = tp_x;
data->point.y = tp_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGI(TAG, "Touch position: %d,%d", tp_x, tp_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}

static void example_touch_isr_cb(esp_lcd_touch_handle_t tp)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(touch_mux, &xHigherPriorityTaskWoken);

if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}


// LVGL任务
static void example_increase_lvgl_tick(void *arg)
{
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

static bool example_lvgl_lock(int timeout_ms)
{
assert(lvgl_mux && "bsp_display_start must be called first");

const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}

static void example_lvgl_unlock(void)
{
assert(lvgl_mux && "bsp_display_start must be called first");
xSemaphoreGive(lvgl_mux);
}


// LVGL任务
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
while (1) {
ESP_LOGI(TAG, "LVGL task");
// Lock the mutex due to the LVGL APIs are not thread-safe
if (example_lvgl_lock(-1)) {
task_delay_ms = lv_timer_handler();
// Release the mutex
example_lvgl_unlock();
}
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}

void app_main(void)
{
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions

// 背光引脚配置
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

ESP_LOGI(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = SPD2010_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 * EXAMPLE_LCD_V_RES * LCD_BIT_PER_PIXEL / 8);

ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));

ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS,
example_notify_lvgl_flush_ready,
&disp_drv);
spd2010_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};

// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

esp_lcd_panel_handle_t panel_handle = NULL;
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,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
ESP_LOGI(TAG, "Install SPD2010 panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(io_handle, &panel_config, &panel_handle));


ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

// 初始化I2C总线
touch_mux = xSemaphoreCreateBinary();
assert(touch_mux);

ESP_LOGI(TAG, "Initialize I2C bus");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400 * 1000,
};
ESP_ERROR_CHECK(i2c_param_config(TOUCH_HOST, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(TOUCH_HOST, i2c_conf.mode, 0, 0, 0));

// 触屏驱动IO句柄
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_SPD2010_CONFIG();

const 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_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.interrupt_callback = example_touch_isr_cb,
};

// Attach the TOUCH to the I2C bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_HOST, &tp_io_config, &tp_io_handle));

// 初始化触摸驱动
ESP_LOGI(TAG, "Initialize touch controller SPD2010");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_spd2010(tp_io_handle, &tp_cfg, &tp));

// 打开背光
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);

// 初始化LVGL
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
// alloc draw buffers used by LVGL
// it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 10 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 10 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2);
// initialize LVGL draw buffers
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 10);

ESP_LOGI(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = EXAMPLE_LCD_H_RES;
disp_drv.ver_res = EXAMPLE_LCD_V_RES;
disp_drv.flush_cb = example_lvgl_flush_cb;
disp_drv.rounder_cb = example_lvgl_rounder_cb;
disp_drv.drv_update_cb = example_lvgl_update_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = panel_handle;
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);

ESP_LOGI(TAG, "Install LVGL tick timer");
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

static lv_indev_drv_t indev_drv; // Input device driver (Touch)
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = disp;
indev_drv.read_cb = example_lvgl_touch_cb;
indev_drv.user_data = tp;

lv_indev_drv_register(&indev_drv);


lvgl_mux = xSemaphoreCreateMutex();
assert(lvgl_mux);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);

ESP_LOGI(TAG, "Display LVGL demos");
// Lock the mutex due to the LVGL APIs are not thread-safe
if (example_lvgl_lock(-1)) {
lv_demo_widgets(); /* A widgets example */
//lv_demo_music(); /* A modern, smartphone-like music player demo. */
// lv_demo_stress(); /* A stress test for LVGL. */
// lv_demo_benchmark(); /* A demo to measure the performance of LVGL or to compare different settings. */

// Release the mutex
example_lvgl_unlock();
}
}


效果展示:

参考链接

  1. https://www.bilibili.com/video/BV1Yc411y7bb/?spm_id_from=333.880.my_history.page.click

ESP32GUI入门-基于ESP-IDF官方组件移植LVGL(SPD2010带触摸)
https://www.duruofu.xyz/posts/12894/
作者
DuRuofu
发布于
2024年6月1日
更新于
2025年1月10日
许可协议