#include #include #include #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_err.h" #include "esp_log.h" #include "esp_timer.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_rgb.h" #include "driver/i2c_master.h" #include "lvgl.h" #define LCD_HRES 800 #define LCD_VRES 480 #define LCD_PIXEL_SIZE 2 // in ms #define LVGL_TICK_PERIOD 2 #define LVGL_TASK_PRIORITY 2 #define LVGL_TASK_STACK_SIZE (4 * 1024) static const char *TAG = "valconomy"; i2c_master_bus_handle_t i2c_bus_handle; // LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it static _lock_t lvgl_api_lock; extern void example_lvgl_demo_ui(lv_display_t *disp); static bool example_on_vsync(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx) { return false; } static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp); // pass the draw buffer to the driver esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map); lv_disp_flush_ready(disp); } static void example_increase_lvgl_tick(void *arg) { /* Tell LVGL how many milliseconds has elapsed */ lv_tick_inc(LVGL_TICK_PERIOD); } static void example_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 task watch dog timeout, set the minimal delay to 10ms if (time_till_next_ms < 10) { time_till_next_ms = 10; } usleep(1000 * time_till_next_ms); } } static esp_err_t i2c_master_init(void) { i2c_master_bus_config_t i2c_mst_config = { .clk_source = I2C_CLK_SRC_DEFAULT, .i2c_port = I2C_NUM_0, .scl_io_num = 9, .sda_io_num = 8, .glitch_ignore_cnt = 7, .flags.enable_internal_pullup = true, }; return i2c_new_master_bus(&i2c_mst_config, &i2c_bus_handle); } static void setup_lcd() { ESP_LOGI(TAG, "Init I2C"); ESP_ERROR_CHECK(i2c_master_init()); i2c_device_config_t exio_dev_cfg = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = 0x24, // CH422G config register .scl_speed_hz = 100000, }; i2c_master_dev_handle_t exio_cfg_handle, exio_o_handle; ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_handle, &exio_dev_cfg, &exio_cfg_handle)); exio_dev_cfg.device_address = 0x38; ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_handle, &exio_dev_cfg, &exio_o_handle)); uint8_t i2c_b; i2c_b = 0x01; // Output enable ESP_ERROR_CHECK(i2c_master_transmit(exio_cfg_handle, &i2c_b, 1, -1)); i2c_b = 0x00; // Reset everything ESP_ERROR_CHECK(i2c_master_transmit(exio_o_handle, &i2c_b, 1, -1)); usleep(10 * 1000); i2c_b = 0x08; // Pull LCD out of reset ESP_ERROR_CHECK(i2c_master_transmit(exio_o_handle, &i2c_b, 1, -1)); usleep(100 * 1000); ESP_LOGI(TAG, "Install RGB LCD panel driver"); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_rgb_panel_config_t panel_config = { .data_width = 16, // RGB565 .psram_trans_align = 64, .num_fbs = 2, .clk_src = LCD_CLK_SRC_DEFAULT, .disp_gpio_num = -1, // Only accessible via GPIO expander .pclk_gpio_num = 7, .vsync_gpio_num = 3, .hsync_gpio_num = 46, .de_gpio_num = 5, .data_gpio_nums = { 14, // B3 38, // B4 18, // B5 17, // B6 10, // B7 39, // G2 0, // G3 45, // G4 48, // G5 47, // G6 21, // G7 1, // R3 2, // R4 42, // R5 41, // R6 40, // R7 }, .timings = { .pclk_hz = 18 * 1000 * 1000, // 18000000 / (hs+hbp+hfp+hres) / (vs+vbp+hfp+vres) = ~42Hz .h_res = LCD_HRES, .v_res = LCD_VRES, .hsync_back_porch = 8, .hsync_front_porch = 8, .hsync_pulse_width = 4, .vsync_back_porch = 16, .vsync_front_porch = 16, .vsync_pulse_width = 4, .flags = { .pclk_active_neg = true, }, }, .flags.fb_in_psram = true, // allocate frame buffer in PSRAM }; ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); ESP_LOGI(TAG, "Initialize RGB LCD panel"); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_LOGI(TAG, "Turn on LCD backlight"); i2c_b = 0x0e; // LCD out of reset, backlight on ESP_ERROR_CHECK(i2c_master_transmit(exio_o_handle, &i2c_b, 1, -1)); ESP_LOGI(TAG, "Initialize LVGL library"); lv_init(); // create a lvgl display lv_display_t *display = lv_display_create(LCD_HRES, LCD_VRES); // associate the rgb panel handle to the display lv_display_set_user_data(display, panel_handle); // set color depth lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565); // create draw buffers void *buf1 = NULL; void *buf2 = NULL; ESP_LOGI(TAG, "Use frame buffers as LVGL draw buffers"); ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2)); // set LVGL draw buffers and direct mode lv_display_set_buffers(display, buf1, buf2, LCD_HRES * LCD_VRES * LCD_PIXEL_SIZE, LV_DISPLAY_RENDER_MODE_DIRECT); // set the callback which can copy the rendered image to an area of the display lv_display_set_flush_cb(display, example_lvgl_flush_cb); ESP_LOGI(TAG, "Register event callbacks"); esp_lcd_rgb_panel_event_callbacks_t cbs = { .on_vsync = example_on_vsync, }; ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display)); 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 = &example_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 * 1000)); ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate(example_lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL); ESP_LOGI(TAG, "Display LVGL UI"); // Lock the mutex due to the LVGL APIs are not thread-safe _lock_acquire(&lvgl_api_lock); example_lvgl_demo_ui(display); _lock_release(&lvgl_api_lock); } void app_main(void) { setup_lcd(); }