/* * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "driver/i2c_master.h" #include "driver/spi_master.h" #include "esp_check.h" #include "esp_err.h" #include "esp_heap_caps.h" #include "esp_lcd_io_i2c.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_vendor.h" #include "esp_lcd_touch.h" #include "esp_lcd_touch_gt911.h" #include "esp_log.h" #include "esp_timer.h" #include "lvgl.h" #include "demos/lv_demos.h" static const char *TAG = "lvgl_touch"; #define LCD_SPI_HOST SPI2_HOST #define EXAMPLE_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000) #define EXAMPLE_LCD_CMD_BITS 8 #define EXAMPLE_LCD_PARAM_BITS 8 #define EXAMPLE_LVGL_TICK_PERIOD_MS 2 #define EXAMPLE_LVGL_BUFFER_LINES 80 #define EXAMPLE_TOUCH_I2C_FREQ_HZ (100 * 1000) #define EXAMPLE_TOUCH_LOG_PERIOD_MS 120 #define EXAMPLE_TOUCH_PROBE_TIMEOUT_MS 30 // Current adapter wiring: // LCD: CLK->IO6, MOSI/SDA->IO7, RES->IO15, DC->IO16, CS->IO5, BLK->IO17. // MISO is not required for LCD write-only testing. // GT911 touch: T_SCL->IO4, T_SDA->IO18, T_INT->IO8, T_RES->IO9. #define EXAMPLE_PIN_NUM_SCLK 6 #define EXAMPLE_PIN_NUM_MOSI 7 #define EXAMPLE_PIN_NUM_MISO GPIO_NUM_NC #define EXAMPLE_PIN_NUM_LCD_CS 5 #define EXAMPLE_PIN_NUM_LCD_DC 16 #define EXAMPLE_PIN_NUM_LCD_RST 15 #define EXAMPLE_PIN_NUM_BK_LIGHT 17 #define EXAMPLE_PIN_NUM_TOUCH_SCL 4 #define EXAMPLE_PIN_NUM_TOUCH_SDA 18 #define EXAMPLE_PIN_NUM_TOUCH_IRQ 8 #define EXAMPLE_PIN_NUM_TOUCH_RST 9 #define EXAMPLE_LCD_H_RES CONFIG_EXAMPLE_LCD_ST7735_H_RES #define EXAMPLE_LCD_V_RES CONFIG_EXAMPLE_LCD_ST7735_V_RES #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1 #define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL static esp_lcd_panel_handle_t s_panel; static esp_lcd_touch_handle_t s_touch; static i2c_master_bus_handle_t s_i2c_bus; static uint8_t *s_image_buffer; extern const uint8_t image_rgb565_start[] asm("_binary_2_rgb565_bin_start"); extern const uint8_t image_rgb565_end[] asm("_binary_2_rgb565_bin_end"); static lv_obj_t *s_coord_label; static lv_obj_t *s_status_label; #if !LV_USE_DEMO_WIDGETS static lv_obj_t *s_button_label; static lv_obj_t *s_slider_label; static int s_button_count; #endif 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) { (void)panel_io; (void)edata; lv_display_flush_ready((lv_display_t *)user_ctx); return false; } static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { esp_err_t ret = esp_lcd_panel_draw_bitmap(s_panel, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map); if (ret != ESP_OK) { ESP_LOGE(TAG, "LCD flush failed: %s", esp_err_to_name(ret)); lv_display_flush_ready(disp); } } static void lvgl_touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { (void)indev; esp_lcd_touch_point_data_t point[CONFIG_ESP_LCD_TOUCH_MAX_POINTS]; uint8_t point_num = 0; static bool was_pressed = false; static int64_t last_log_ms = 0; static uint8_t last_point_num = 0; if (!s_touch) { data->state = LV_INDEV_STATE_RELEASED; return; } esp_err_t ret = esp_lcd_touch_read_data(s_touch); if (ret == ESP_OK) { ret = esp_lcd_touch_get_data(s_touch, point, &point_num, CONFIG_ESP_LCD_TOUCH_MAX_POINTS); } if (ret == ESP_OK && point_num > 0) { data->state = LV_INDEV_STATE_PRESSED; data->point.x = point[0].x; data->point.y = point[0].y; int64_t now_ms = esp_timer_get_time() / 1000; if (!was_pressed || point_num != last_point_num || now_ms - last_log_ms >= EXAMPLE_TOUCH_LOG_PERIOD_MS) { ESP_LOGI(TAG, "gt911 points=%u p0=(%u,%u,%u)", point_num, point[0].x, point[0].y, point[0].strength); for (uint8_t i = 1; i < point_num; i++) { ESP_LOGI(TAG, " p%u=(%u,%u,%u)", i, point[i].x, point[i].y, point[i].strength); } if (s_coord_label) { lv_label_set_text_fmt(s_coord_label, "x:%u y:%u z:%u", point[0].x, point[0].y, point[0].strength); } if (s_status_label) { lv_label_set_text(s_status_label, "PRESSED"); } last_log_ms = now_ms; } was_pressed = true; last_point_num = point_num; } else { data->state = LV_INDEV_STATE_RELEASED; if (ret != ESP_OK) { ESP_LOGW(TAG, "touch read failed: %s", esp_err_to_name(ret)); } else if (was_pressed) { ESP_LOGI(TAG, "released"); if (s_status_label) { lv_label_set_text(s_status_label, "RELEASED"); } } was_pressed = false; last_point_num = 0; } } static void lvgl_tick_cb(void *arg) { (void)arg; lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS); } static void lvgl_task(void *arg) { (void)arg; while (1) { uint32_t delay_ms = lv_timer_handler(); if (delay_ms < 2) { delay_ms = 2; } else if (delay_ms > 8) { delay_ms = 8; } vTaskDelay(pdMS_TO_TICKS(delay_ms)); } } #if !LV_USE_DEMO_WIDGETS static void button_event_cb(lv_event_t *e) { if (lv_event_get_code(e) == LV_EVENT_CLICKED) { s_button_count++; ESP_LOGI(TAG, "LVGL button clicked: %d", s_button_count); lv_label_set_text_fmt(s_button_label, "Button: %d", s_button_count); } } static void slider_event_cb(lv_event_t *e) { lv_obj_t *slider = lv_event_get_target(e); int value = (int)lv_slider_get_value(slider); lv_label_set_text_fmt(s_slider_label, "Slider: %d", value); ESP_LOGI(TAG, "LVGL slider value: %d", value); } static void create_touch_ui(void) { lv_obj_t *screen = lv_screen_active(); lv_obj_set_style_bg_color(screen, lv_color_hex(0x101820), 0); lv_obj_t *title = lv_label_create(screen); lv_label_set_text(title, "ST7789 + GT911 Touch"); lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12); s_status_label = lv_label_create(screen); lv_label_set_text(s_status_label, "RELEASED"); lv_obj_set_style_text_color(s_status_label, lv_color_hex(0x64D2FF), 0); lv_obj_align(s_status_label, LV_ALIGN_TOP_MID, 0, 42); s_coord_label = lv_label_create(screen); lv_label_set_text(s_coord_label, "x:-- y:-- z:--"); lv_obj_set_style_text_color(s_coord_label, lv_color_hex(0xE9F1F7), 0); lv_obj_align(s_coord_label, LV_ALIGN_TOP_MID, 0, 70); lv_obj_t *button = lv_button_create(screen); lv_obj_set_size(button, 160, 52); lv_obj_align(button, LV_ALIGN_CENTER, 0, -20); lv_obj_add_event_cb(button, button_event_cb, LV_EVENT_CLICKED, NULL); s_button_label = lv_label_create(button); lv_label_set_text(s_button_label, "Button: 0"); lv_obj_center(s_button_label); lv_obj_t *slider = lv_slider_create(screen); lv_obj_set_width(slider, 190); lv_slider_set_range(slider, 0, 100); lv_slider_set_value(slider, 50, LV_ANIM_OFF); lv_obj_align(slider, LV_ALIGN_CENTER, 0, 70); lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); s_slider_label = lv_label_create(screen); lv_label_set_text(s_slider_label, "Slider: 50"); lv_obj_set_style_text_color(s_slider_label, lv_color_hex(0xE9F1F7), 0); lv_obj_align_to(s_slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 12); } #endif static esp_err_t init_spi_bus(void) { ESP_LOGI(TAG, "Initialize SPI bus"); spi_bus_config_t buscfg = { .sclk_io_num = EXAMPLE_PIN_NUM_SCLK, .mosi_io_num = EXAMPLE_PIN_NUM_MOSI, .miso_io_num = EXAMPLE_PIN_NUM_MISO, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUFFER_LINES * sizeof(lv_color_t), }; return spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO); } static void verify_lcd_reset_released(void) { // The ST7789 driver configures RST as output-only. Enable the input path so // gpio_get_level() reports the physical voltage instead of a disabled input. esp_err_t ret = gpio_set_direction(EXAMPLE_PIN_NUM_LCD_RST, GPIO_MODE_INPUT_OUTPUT); if (ret != ESP_OK) { ESP_LOGE(TAG, "LCD RES IO%d input enable failed: %s", EXAMPLE_PIN_NUM_LCD_RST, esp_err_to_name(ret)); return; } ret = gpio_set_level(EXAMPLE_PIN_NUM_LCD_RST, 1); if (ret != ESP_OK) { ESP_LOGE(TAG, "LCD RES IO%d release failed: %s", EXAMPLE_PIN_NUM_LCD_RST, esp_err_to_name(ret)); return; } vTaskDelay(pdMS_TO_TICKS(2)); int level = gpio_get_level(EXAMPLE_PIN_NUM_LCD_RST); ESP_LOGI(TAG, "LCD RES IO%d readback after reset: %d", EXAMPLE_PIN_NUM_LCD_RST, level); if (level != 1) { ESP_LOGE(TAG, "LCD RES is held low externally; check the adapter, FPC, and RES wiring"); } } static esp_err_t init_st7789_panel(esp_lcd_panel_io_handle_t *ret_io) { ESP_LOGI(TAG, "Initialize ST7789 panel"); gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT, }; ESP_RETURN_ON_ERROR(gpio_config(&bk_gpio_config), TAG, "configure backlight GPIO failed"); gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL); esp_lcd_panel_io_spi_config_t io_config = { .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC, .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS, .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, .spi_mode = 0, .trans_queue_depth = 10, }; ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, ret_io), TAG, "install panel IO failed"); esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, .data_endian = LCD_RGB_DATA_ENDIAN_BIG, .bits_per_pixel = 16, }; ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(*ret_io, &panel_config, &s_panel), TAG, "install ST7789 panel failed"); ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "reset panel failed"); verify_lcd_reset_released(); ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "init panel failed"); ESP_RETURN_ON_ERROR(esp_lcd_panel_set_gap(s_panel, CONFIG_EXAMPLE_LCD_ST7735_X_GAP, CONFIG_EXAMPLE_LCD_ST7735_Y_GAP), TAG, "set panel gap failed"); ESP_RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel, true), TAG, "turn panel on failed"); gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL); return ESP_OK; } static esp_err_t init_display_buffer(void) { size_t expected_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t); size_t image_size = image_rgb565_end - image_rgb565_start; ESP_RETURN_ON_FALSE(image_size == expected_size, ESP_ERR_INVALID_SIZE, TAG, "embedded image size is %u bytes, expected %u", (unsigned int)image_size, (unsigned int)expected_size); s_image_buffer = heap_caps_malloc(expected_size, MALLOC_CAP_DMA); ESP_RETURN_ON_FALSE(s_image_buffer, ESP_ERR_NO_MEM, TAG, "no DMA memory for image frame"); return ESP_OK; } static void prepare_standard_color_bars(void) { // The DCT module's ST7789 interface requires each RGB565 pixel MSB first. static const uint8_t colors[][sizeof(uint16_t)] = { {0xF8, 0x00}, // red: 0xF800 {0x07, 0xE0}, // green: 0x07E0 {0x00, 0x1F}, // blue: 0x001F {0x00, 0x00}, // black: 0x0000 }; for (int y = 0; y < EXAMPLE_LCD_V_RES; y++) { for (int x = 0; x < EXAMPLE_LCD_H_RES; x++) { size_t offset = (y * EXAMPLE_LCD_H_RES + x) * sizeof(uint16_t); memcpy(&s_image_buffer[offset], colors[x * 4 / EXAMPLE_LCD_H_RES], sizeof(uint16_t)); } } } static void prepare_standard_image(void) { size_t expected_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t); // 2_rgb565.bin is stored little-endian for the ESP32, but the panel // interface must receive the high byte of every RGB565 pixel first. for (size_t offset = 0; offset < expected_size; offset += sizeof(uint16_t)) { s_image_buffer[offset] = image_rgb565_start[offset + 1]; s_image_buffer[offset + 1] = image_rgb565_start[offset]; } } static esp_err_t draw_display_buffer(const char *description) { ESP_LOGI(TAG, "Draw %s, RGB565 MSB first (first pixel: %02x %02x)", description, s_image_buffer[0], s_image_buffer[1]); ESP_RETURN_ON_ERROR(esp_lcd_panel_draw_bitmap(s_panel, 0, 0, EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, s_image_buffer), TAG, "queue display frame failed"); vTaskDelay(pdMS_TO_TICKS(200)); return ESP_OK; } static esp_err_t display_standard_diagnostic(void) { prepare_standard_color_bars(); ESP_RETURN_ON_ERROR(draw_display_buffer("four color bars"), TAG, "draw color bars failed"); vTaskDelay(pdMS_TO_TICKS(2000)); prepare_standard_image(); return draw_display_buffer("embedded 2.jpg" ); } static esp_err_t init_i2c_bus(void) { ESP_LOGI(TAG, "Initialize GT911 I2C bus"); i2c_master_bus_config_t i2c_bus_config = { .clk_source = I2C_CLK_SRC_DEFAULT, .i2c_port = I2C_NUM_0, .scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL, .sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA, .glitch_ignore_cnt = 7, .flags = { .enable_internal_pullup = true, }, }; ESP_RETURN_ON_ERROR(i2c_new_master_bus(&i2c_bus_config, &s_i2c_bus), TAG, "create GT911 I2C bus failed"); ESP_LOGI(TAG, "GT911 I2C bus initialized"); return ESP_OK; } static esp_err_t release_touch_reset_before_i2c(void) { gpio_config_t rst_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_RST, }; ESP_LOGI(TAG, "Release GT911 reset on IO%d before I2C bus init", EXAMPLE_PIN_NUM_TOUCH_RST); esp_err_t ret = gpio_config(&rst_gpio_config); if (ret != ESP_OK) { ESP_LOGE(TAG, "GPIO%d configuration failed: %s (0x%x)", EXAMPLE_PIN_NUM_TOUCH_RST, esp_err_to_name(ret), ret); return ret; } ret = gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 1); if (ret != ESP_OK) { ESP_LOGE(TAG, "GPIO%d release failed: %s (0x%x)", EXAMPLE_PIN_NUM_TOUCH_RST, esp_err_to_name(ret), ret); return ret; } vTaskDelay(pdMS_TO_TICKS(120)); return ESP_OK; } static bool scan_i2c_bus_handle(i2c_master_bus_handle_t bus, const char *label, int scl, int sda) { bool found = false; ESP_LOGI(TAG, "Scan I2C devices on %s: SCL=IO%d SDA=IO%d", label, scl, sda); for (uint8_t addr = 0x08; addr < 0x78; addr++) { esp_err_t ret = i2c_master_probe(bus, addr, 10); if (ret == ESP_OK) { ESP_LOGI(TAG, "I2C device found on %s at 0x%02x", label, addr); found = true; } } if (!found) { ESP_LOGW(TAG, "No I2C device found on %s: SCL=IO%d SDA=IO%d", label, scl, sda); } return found; } static bool scan_i2c_bus(void) { bool found = scan_i2c_bus_handle(s_i2c_bus, "configured GT911 bus", EXAMPLE_PIN_NUM_TOUCH_SCL, EXAMPLE_PIN_NUM_TOUCH_SDA); if (!found) { ESP_LOGW(TAG, "Check T_SCL/T_SDA wiring, touch power, common GND, and I2C pull-ups"); } return found; } static esp_err_t select_gt911_address(uint8_t dev_addr) { uint32_t int_level = (dev_addr == ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP) ? 1 : 0; gpio_config_t rst_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_RST, }; gpio_config_t int_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pull_up_en = 1, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_IRQ, }; ESP_LOGI(TAG, "Select GT911 address 0x%02x with RST=IO%d INT=IO%d", dev_addr, EXAMPLE_PIN_NUM_TOUCH_RST, EXAMPLE_PIN_NUM_TOUCH_IRQ); ESP_RETURN_ON_ERROR(gpio_config(&rst_gpio_config), TAG, "configure GT911 reset GPIO failed"); ESP_RETURN_ON_ERROR(gpio_config(&int_gpio_config), TAG, "configure GT911 INT GPIO failed"); ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 0), TAG, "drive GT911 reset low failed"); ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_IRQ, 0), TAG, "drive GT911 INT low failed"); vTaskDelay(pdMS_TO_TICKS(10)); ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_IRQ, int_level), TAG, "set GT911 address level failed"); vTaskDelay(pdMS_TO_TICKS(2)); ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 1), TAG, "release GT911 reset failed"); vTaskDelay(pdMS_TO_TICKS(80)); return ESP_OK; } static esp_err_t try_init_gt911_touch(uint8_t dev_addr) { ESP_LOGI(TAG, "Initialize GT911 touch at I2C address 0x%02x", dev_addr); ESP_RETURN_ON_ERROR(select_gt911_address(dev_addr), TAG, "select GT911 address failed"); esp_err_t ret = i2c_master_probe(s_i2c_bus, dev_addr, EXAMPLE_TOUCH_PROBE_TIMEOUT_MS); if (ret != ESP_OK) { ESP_LOGW(TAG, "GT911 address 0x%02x did not ACK: %s", dev_addr, esp_err_to_name(ret)); return ESP_ERR_NOT_FOUND; } esp_lcd_panel_io_handle_t tp_io_handle = NULL; esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); tp_io_config.dev_addr = dev_addr; tp_io_config.scl_speed_hz = EXAMPLE_TOUCH_I2C_FREQ_HZ; ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(s_i2c_bus, &tp_io_config, &tp_io_handle), TAG, "install GT911 panel IO failed"); static esp_lcd_touch_io_gt911_config_t gt911_config; gt911_config.dev_addr = dev_addr; esp_lcd_touch_config_t tp_cfg = { .x_max = EXAMPLE_LCD_H_RES, .y_max = EXAMPLE_LCD_V_RES, .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_IRQ, .levels = { .reset = 0, .interrupt = 0, }, .flags = { .swap_xy = 0, .mirror_x = 0, .mirror_y = 0, }, .driver_data = >911_config, }; ret = esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &s_touch); if (ret != ESP_OK) { s_touch = NULL; esp_lcd_panel_io_del(tp_io_handle); } return ret; } static esp_err_t init_gt911_touch(void) { esp_err_t ret = try_init_gt911_touch(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS); if (ret == ESP_OK) { return ESP_OK; } ESP_LOGW(TAG, "GT911 address 0x%02x failed: %s, try 0x%02x", ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, esp_err_to_name(ret), ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP); return try_init_gt911_touch(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP); } static void init_touch_optional(void) { esp_err_t ret = release_touch_reset_before_i2c(); if (ret == ESP_OK) { ret = init_i2c_bus(); } if (ret == ESP_OK) { ret = init_gt911_touch(); } if (ret == ESP_OK) { ESP_LOGI(TAG, "GT911 touch initialized"); return; } ESP_LOGE(TAG, "GT911 is unavailable: %s. Continue with LCD only", esp_err_to_name(ret)); s_touch = NULL; if (s_i2c_bus != NULL) { scan_i2c_bus(); } } static esp_err_t init_lvgl(esp_lcd_panel_io_handle_t panel_io) { ESP_LOGI(TAG, "Initialize LVGL"); lv_init(); size_t buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUFFER_LINES * sizeof(lv_color_t); void *draw_buf1 = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA); void *draw_buf2 = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA); ESP_RETURN_ON_FALSE(draw_buf1 && draw_buf2, ESP_ERR_NO_MEM, TAG, "no memory for LVGL draw buffers"); lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES); lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565); lv_display_set_flush_cb(display, lvgl_flush_cb); lv_display_set_buffers(display, draw_buf1, draw_buf2, buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL); esp_lcd_panel_io_callbacks_t cbs = { .on_color_trans_done = notify_lvgl_flush_ready, }; ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, display), TAG, "register panel IO callback failed"); if (s_touch) { lv_indev_t *indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, lvgl_touch_read_cb); } else { ESP_LOGW(TAG, "LVGL touch input is disabled because GT911 was not initialized"); } const esp_timer_create_args_t tick_timer_args = { .callback = lvgl_tick_cb, .name = "lvgl_tick", }; esp_timer_handle_t tick_timer = NULL; ESP_RETURN_ON_ERROR(esp_timer_create(&tick_timer_args, &tick_timer), TAG, "create LVGL tick failed"); ESP_RETURN_ON_ERROR(esp_timer_start_periodic(tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000), TAG, "start LVGL tick failed"); #if LV_USE_DEMO_WIDGETS lv_demo_widgets(); #else create_touch_ui(); #endif xTaskCreate(lvgl_task, "lvgl", 8192, NULL, 5, NULL); return ESP_OK; } void app_main(void) { esp_lcd_panel_io_handle_t panel_io = NULL; ESP_ERROR_CHECK(init_spi_bus()); ESP_ERROR_CHECK(init_st7789_panel(&panel_io)); ESP_ERROR_CHECK(init_display_buffer()); ESP_ERROR_CHECK(display_standard_diagnostic()); }