ESP32S3R8+LVGL9+ST77916+CST816S

前言

最近购入了一个主控是 ESP32S3R8,屏幕是 1.8 寸圆屏,分辨率 360x360 的小玩具,屏幕驱动芯片是 CT77916,触摸是 CST816。由于没有提供源码,所以想用 ESP-IDF+LVGL 驱动起来玩一下。

实现

工程创建

ESP-IDF 开发环境搭建我就不赘述了,感兴趣可以看看我之前用 ESP32-C3 搭建的流程,ESP32-C3 开发环境搭建

首先,创建一个基础工程,这里我选用了 sample_project ,操作如下,

保存好工程的位置后,先不着急编译,导入需要的组件,组件仓库可以访问 ESP Component Registry

搜索 LVGL

复制对应的命令,

其他的组件我就不一一列出来了,具体需要的组件如下:

1
2
3
4
idf.py add-dependency "lvgl/lvgl^9.2.2"
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"

随后在 vscode 中,打开 ESP-IDF Terminal,输入上述指令,将组件添加到工程里,

设置对应的芯片型号,

点击编译,等待编译完成,完成后会在工程目录增加对应的 managed_components, 里面包含了我们添加进来的组件。

添加屏幕和触摸

工程创建好后,接下来就要为 LVGL 添加对应的显示器和输入设备。我们先从显示开始,

这是提供给我的原理图,可以看到屏幕是用 QSPI 驱动的,并且屏幕复位引脚和背光引脚都引出来了,

那么我们查看组件提供的 README,它提供了 SPIQSPI 两种初始化流程,而我们显示屏的接口是 QSPI 的,所以我们直接复制 QSPI 的代码,进行修改,

为了图方便,我把所有逻辑都放在 main.c,大家不要学我,LCD 初始化代码如下,

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
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 * 80 * sizeof(uint16_t));
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);
}

LCD 背光代码如下:

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
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();
}

接下来就是触摸,触摸是 IIC 驱动的,对应的 IO 也在上边的图里有列出,并且同样的,在组件 README.md 中提供里相应的初始化代码,这里我不多赘述,代码如下:

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
static esp_lcd_touch_handle_t tp;

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);
}

LVGL

屏幕和触摸都初始化完毕后,接下来就是接入 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
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 * 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);

/* 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);
}

完整代码

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
#include <stdio.h>

#include "lvgl.h"
#include "lv_examples.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"

#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,
.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);

/* 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();

lvgl_port_lock(0);
lv_example_anim_3();
lvgl_port_unlock();
}

效果

编译完成后烧录效果如下: