#include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.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 "esp_lcd_co5300.h" #include "ui.h" #include "lv_examples.h" #define LCD_HOST (SPI2_HOST) #define PIN_NUM_LCD_CS (GPIO_NUM_12) #define PIN_NUM_LCD_PCLK (GPIO_NUM_10) #define PIN_NUM_LCD_DATA0 (GPIO_NUM_13) #define PIN_NUM_LCD_DATA1 (GPIO_NUM_11) #define PIN_NUM_LCD_DATA2 (GPIO_NUM_14) #define PIN_NUM_LCD_DATA3 (GPIO_NUM_9) #define PIN_NUM_LCD_RST (GPIO_NUM_8) #define PIN_NUM_BK_LIGHT (GPIO_NUM_17) #define LCD_H_RES 471 #define LCD_V_RES 466 #define LCD_BIT_PER_PIXEL 16 #define LCD_BK_LIGHT_ON_LEVEL 1 #define LVGL_DRAW_BUF_LINES 20 // number of display lines in each draw buffer #define LVGL_TICK_PERIOD_MS 2 #define LVGL_TASK_MAX_DELAY_MS 500 #define LVGL_TASK_MIN_DELAY_MS 1000 / CONFIG_FREERTOS_HZ #define LVGL_TASK_STACK_SIZE (4 * 1024) #define LVGL_TASK_PRIORITY 2 static const char *TAG = "main"; // Custom initalizations according to vendor's example - doesn't seem to make a difference static const co5300_lcd_init_cmd_t lcd_init_cmds[] = { {0xFE, (uint8_t []){0x00}, 0, 0}, {0xC4, (uint8_t []){0x80}, 1, 0}, {0x3A, (uint8_t []){0x55}, 1, 0}, {0x35, (uint8_t []){0x00}, 0, 10}, {0x53, (uint8_t []){0x20}, 1, 10}, {0x51, (uint8_t []){0xFF}, 1, 10}, {0x63, (uint8_t []){0xFF}, 1, 10}, //{0x2A, (uint8_t []){0x00,0x06,0x01,0xD7}, 4, 0}, {0x2A, (uint8_t []){0x00,0x06,0x01,0xDD}, 4, 0}, {0x2B, (uint8_t []){0x00,0x00,0x01,0xD1}, 4, 0}, {0x11, (uint8_t []){0x00}, 0, 60}, {0x29, (uint8_t []){0x00}, 0, 0}, }; // LVGL library = not thread-safe, so we will call LVGL APIs from different tasks and use a mutex to protect it static _lock_t lvgl_api_lock; static esp_lcd_panel_handle_t panel_handle = NULL; 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_display_t *disp = (lv_display_t *)user_ctx; lv_disp_flush_ready(disp); return false; // was true ? } void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map) { esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map); // flush_ready is called from the notify callback } static void increase_lvgl_tick(void *arg) { lv_tick_inc(2); } void lvgl_display_init(void) { gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << PIN_NUM_BK_LIGHT }; ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); gpio_set_level(PIN_NUM_BK_LIGHT, LCD_BK_LIGHT_ON_LEVEL); ESP_LOGI(TAG, "Backlight turned on"); lv_display_t * disp = lv_display_create(LCD_H_RES, LCD_V_RES); lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565); lv_display_set_flush_cb(disp, lvgl_flush_cb); // Double buffer size_t buf_size = (LCD_H_RES * LCD_V_RES / 10) * 2; // 2 bytes per pixel for RGB565 uint8_t *buf1 = heap_caps_aligned_alloc(64, buf_size, MALLOC_CAP_DMA); uint8_t *buf2 = heap_caps_aligned_alloc(64, buf_size, MALLOC_CAP_DMA); assert(buf1); assert(buf2); lv_display_set_buffers(disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL); ESP_LOGI(TAG, "Initialize QSPI bus"); const spi_bus_config_t buscfg = CO5300_PANEL_BUS_QSPI_CONFIG( PIN_NUM_LCD_PCLK, PIN_NUM_LCD_DATA0, PIN_NUM_LCD_DATA1, PIN_NUM_LCD_DATA2, PIN_NUM_LCD_DATA3, LCD_H_RES * 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 = CO5300_PANEL_IO_QSPI_CONFIG(PIN_NUM_LCD_CS, notify_lvgl_flush_ready, (void *)disp); ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle)); ESP_LOGI(TAG, "Install CO5300 panel driver"); //esp_lcd_panel_handle_t panel_handle = NULL; Declared globally at the beginning co5300_vendor_config_t vendor_config = { .init_cmds = lcd_init_cmds, .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(co5300_lcd_init_cmd_t), }; const esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = PIN_NUM_LCD_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` .bits_per_pixel = LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18/24) .vendor_config = (void *)&vendor_config, }; ESP_ERROR_CHECK(esp_lcd_new_panel_co5300(io_handle, &panel_config, &panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); 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 = &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, LVGL_TICK_PERIOD_MS * 1000)); } static void lvgl_port_task(void *arg) { ESP_LOGI(TAG, "Starting LVGL task"); uint32_t time_till_next_ms = 0; while (1) { _lock_acquire(&lvgl_api_lock); time_till_next_ms = lv_timer_handler(); _lock_release(&lvgl_api_lock); // in case of triggering a task watch dog time out time_till_next_ms = MAX(time_till_next_ms, LVGL_TASK_MIN_DELAY_MS); // in case of lvgl display not ready yet time_till_next_ms = MIN(time_till_next_ms, LVGL_TASK_MAX_DELAY_MS); usleep(1000 * time_till_next_ms); } } void app_main(void) { lv_init(); lvgl_display_init(); ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL); ESP_LOGI(TAG, "Lock & Create Spinner"); // Lock the mutex due to the LVGL APIs are not thread-safe _lock_acquire(&lvgl_api_lock); /*Create a spinner*/ lv_obj_t * spinner = lv_spinner_create(lv_screen_active()); lv_obj_set_size(spinner, 300, 300); lv_obj_center(spinner); lv_spinner_set_anim_params(spinner, 10000, 200); _lock_release(&lvgl_api_lock); }