所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nixgnauhcuy’s blog!
如需转载,请标明出处!
前言
最近购入了一个主控是 ESP32S3R8
,屏幕是 1.8 寸圆屏,分辨率 360x360
的小玩具,屏幕驱动芯片是 CT77916
,触摸是 CST816
。由于没有提供源码,所以想用 ESP-IDF+LVGL
驱动起来玩一下。
实现
工程创建
ESP-IDF 开发环境搭建我就不赘述了,感兴趣可以看看我之前用 ESP32-C3
搭建的流程,ESP32-C3 开发环境搭建。
首先,创建一个基础工程,这里我选用了 sample_project
,操作如下,
保存好工程的位置后,先不着急编译,导入需要的组件,组件仓库可以访问 ESP Component Registry 。
搜索 LVGL
,
复制对应的命令,
其他的组件我就不一一列出来了,具体需要的组件如下:
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
,它提供了 SPI
和 QSPI
两种初始化流程,而我们显示屏的接口是 QSPI 的,所以我们直接复制 QSPI 的代码,进行修改,
为了图方便,我把所有逻辑都放在 main.c
,大家不要学我,LCD 初始化代码如下,
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 背光代码如下:
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
中提供里相应的初始化代码,这里我不多赘述,代码如下:
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,代码如下:
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);
}
完整代码
#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();
}
效果
编译完成后烧录效果如下: