/* * LVGL bring-up for ESP32-2432S028R ("Cheap Yellow Display") * ESP-IDF 5.2+ / LVGL 9 / esp_lvgl_port 2.x * * main/idf_component.yml: * dependencies: * idf: ">=5.2" * espressif/esp_lvgl_port: "^2" * espressif/esp_lcd_ili9341: "^1" * atanisoft/esp_lcd_touch_xpt2046: "^1" * * main/CMakeLists.txt: * idf_component_register(SRCS "testing.c" * PRIV_REQUIRES esp_driver_gpio esp_driver_spi esp_lcd * INCLUDE_DIRS ".") */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "driver/spi_master.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_vendor.h" #include "esp_lcd_touch_xpt2046.h" #include "esp_lvgl_port.h" #include "esp_err.h" #include "esp_log.h" static const char *TAG = "cyd"; /* ---- Display: ILI9341 on SPI2 (HSPI) ---- */ #define LCD_HOST SPI2_HOST #define PIN_LCD_SCLK 14 #define PIN_LCD_MOSI 13 #define PIN_LCD_MISO 12 #define PIN_LCD_CS 15 #define PIN_LCD_DC 2 #define PIN_LCD_RST -1 /* tied high on this board */ #define PIN_LCD_BL 21 /* active high */ /* ---- Touch: XPT2046 on SPI3 (VSPI) ---- */ #define TOUCH_HOST SPI3_HOST #define PIN_TP_SCLK 25 #define PIN_TP_MOSI 32 #define PIN_TP_MISO 39 /* input-only pin, fine for MISO */ #define PIN_TP_CS 33 #define PIN_TP_IRQ 36 /* input-only pin */ #define LCD_H_RES 240 #define LCD_V_RES 320 #define LCD_CMD_BITS 8 #define LCD_PARAM_BITS 8 #define LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000) /* XPT2046 is slow — datasheet max is ~2.5 MHz. Going faster gives * garbage coordinates that look like random taps. */ #define TOUCH_CLOCK_HZ (2 * 1000 * 1000) /* Partial draw buffer. No PSRAM on this board, so keep it modest: * 240 * 40 * 2 bytes = 19.2 KB per buffer, doubled = 38.4 KB. */ #define LVGL_BUF_LINES 40 static esp_lcd_panel_handle_t s_panel = NULL; static esp_lcd_panel_io_handle_t s_panel_io = NULL; static esp_lcd_touch_handle_t s_touch = NULL; static lv_display_t *s_disp = NULL; static lv_indev_t *s_indev = NULL; static void backlight_init(void) { const gpio_config_t cfg = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << PIN_LCD_BL, }; ESP_ERROR_CHECK(gpio_config(&cfg)); gpio_set_level(PIN_LCD_BL, 0); /* off until the panel is ready */ } static void display_init(void) { ESP_LOGI(TAG, "init LCD SPI bus"); const spi_bus_config_t bus = { .sclk_io_num = PIN_LCD_SCLK, .mosi_io_num = PIN_LCD_MOSI, .miso_io_num = PIN_LCD_MISO, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = LCD_H_RES * LVGL_BUF_LINES * sizeof(uint16_t), }; ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &bus, SPI_DMA_CH_AUTO)); const esp_lcd_panel_io_spi_config_t io_cfg = { .cs_gpio_num = PIN_LCD_CS, .dc_gpio_num = PIN_LCD_DC, .spi_mode = 0, .pclk_hz = LCD_PIXEL_CLOCK_HZ, .trans_queue_depth = 10, .lcd_cmd_bits = LCD_CMD_BITS, .lcd_param_bits = LCD_PARAM_BITS, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi( (esp_lcd_spi_bus_handle_t)LCD_HOST, &io_cfg, &s_panel_io)); const esp_lcd_panel_dev_config_t panel_cfg = { .reset_gpio_num = PIN_LCD_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, /* CYD is BGR */ .bits_per_pixel = 16, }; ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(s_panel_io, &panel_cfg, &s_panel)); ESP_ERROR_CHECK(esp_lcd_panel_reset(s_panel)); ESP_ERROR_CHECK(esp_lcd_panel_init(s_panel)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(s_panel, false)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(s_panel, true)); } static void touch_init(void) { ESP_LOGI(TAG, "init touch SPI bus"); const spi_bus_config_t bus = { .sclk_io_num = PIN_TP_SCLK, .mosi_io_num = PIN_TP_MOSI, .miso_io_num = PIN_TP_MISO, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 0, }; ESP_ERROR_CHECK(spi_bus_initialize(TOUCH_HOST, &bus, SPI_DMA_DISABLED)); esp_lcd_panel_io_handle_t tp_io = NULL; esp_lcd_panel_io_spi_config_t tp_io_cfg = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(PIN_TP_CS); tp_io_cfg.pclk_hz = TOUCH_CLOCK_HZ; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi( (esp_lcd_spi_bus_handle_t)TOUCH_HOST, &tp_io_cfg, &tp_io)); const esp_lcd_touch_config_t tp_cfg = { .x_max = LCD_H_RES, .y_max = LCD_V_RES, .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = GPIO_NUM_NC, .flags = { .swap_xy = 0, .mirror_x = 1, .mirror_y = 0, }, }; ESP_ERROR_CHECK(esp_lcd_touch_new_spi_xpt2046(tp_io, &tp_cfg, &s_touch)); } static void lvgl_init(void) { const lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); ESP_ERROR_CHECK(lvgl_port_init(&port_cfg)); const lvgl_port_display_cfg_t disp_cfg = { .io_handle = s_panel_io, .panel_handle = s_panel, .buffer_size = LCD_H_RES * LVGL_BUF_LINES, .double_buffer = true, .hres = LCD_H_RES, .vres = LCD_V_RES, .monochrome = false, .rotation = { .swap_xy = false, .mirror_x = false, .mirror_y = false, }, .flags = { .buff_dma = true, }, }; s_disp = lvgl_port_add_disp(&disp_cfg); const lvgl_port_touch_cfg_t touch_cfg = { .disp = s_disp, .handle = s_touch, }; s_indev = lvgl_port_add_touch(&touch_cfg); } static void build_ui(void) { /* Any LVGL call from outside the port task must be locked. */ if (!lvgl_port_lock(0)) { ESP_LOGE(TAG, "could not take LVGL lock"); return; } lv_obj_t *scr = lv_screen_active(); lv_obj_t *label = lv_label_create(scr); lv_label_set_text(label, "Reflow Plate"); lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 20); lv_obj_t *arc = lv_arc_create(scr); lv_obj_set_size(arc, 180, 180); lv_arc_set_range(arc, 0, 260); lv_arc_set_value(arc, 25); lv_obj_center(arc); lv_obj_t *temp = lv_label_create(scr); lv_label_set_text(temp, "25 \xC2\xB0""C"); lv_obj_center(temp); lvgl_port_unlock(); } void app_main(void) { backlight_init(); display_init(); touch_init(); lvgl_init(); build_ui(); gpio_set_level(PIN_LCD_BL, 1); /* backlight on, no white flash */ /* The LVGL port runs its own task and handles lv_timer_handler(). * Your control loop lives here, or better, in its own task. */ while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } }