From 21b38e8b9707dc8bdf47e04711e72c812946b82a Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Fri, 13 Sep 2024 14:14:49 +0800 Subject: [PATCH] feat(i2c_oled): adapt i2c_oled example to LVGL v9 This commit simplified the example and removed esp_lvgl_port component. And adapted example to LVGL v9. Closes https://github.com/espressif/esp-idf/issues/14179 --- .../lcd/i2c_oled/main/i2c_oled_example_main.c | 138 +++++++++++++++--- .../lcd/i2c_oled/main/idf_component.yml | 3 +- .../lcd/i2c_oled/main/lvgl_demo_ui.c | 10 +- .../lcd/i2c_oled/sdkconfig.defaults | 5 +- 4 files changed, 123 insertions(+), 33 deletions(-) diff --git a/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c b/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c index 1276da8aef..098c62d66c 100644 --- a/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c +++ b/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c @@ -5,14 +5,16 @@ */ #include +#include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "esp_timer.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" #include "esp_err.h" #include "esp_log.h" #include "driver/i2c_master.h" -#include "esp_lvgl_port.h" #include "lvgl.h" #if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 @@ -46,8 +48,81 @@ static const char *TAG = "example"; #define EXAMPLE_LCD_CMD_BITS 8 #define EXAMPLE_LCD_PARAM_BITS 8 +#define EXAMPLE_LVGL_TICK_PERIOD_MS 5 +#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024) +#define EXAMPLE_LVGL_TASK_PRIORITY 2 +#define EXAMPLE_LVGL_PALETTE_SIZE 8 + +// To use LV_COLOR_FORMAT_I1, we need an extra buffer to hold the converted data +static uint8_t oled_buffer[EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES / 8]; +// 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_disp_t *disp); +static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t io_panel, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + lv_display_t *disp = (lv_display_t *)user_ctx; + lv_display_flush_ready(disp); + 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); + + // This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. Skip the palette here + // More information about the monochrome, please refer to https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays + px_map += EXAMPLE_LVGL_PALETTE_SIZE; + + uint16_t hor_res = lv_display_get_physical_horizontal_resolution(disp); + int x1 = area->x1; + int x2 = area->x2; + int y1 = area->y1; + int y2 = area->y2; + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + /* The order of bits is MSB first + MSB LSB + bits 7 6 5 4 3 2 1 0 + pixels 0 1 2 3 4 5 6 7 + Left Right + */ + bool chroma_color = (px_map[(hor_res >> 3) * y + (x >> 3)] & 1 << (7 - x % 8)); + + /* Write to the buffer as required for the display. + * It writes only 1-bit for monochrome displays mapped vertically.*/ + uint8_t *buf = oled_buffer + hor_res * (y >> 3) + (x); + if (chroma_color) { + (*buf) &= ~(1 << (y % 8)); + } else { + (*buf) |= (1 << (y % 8)); + } + } + } + // pass the draw buffer to the driver + esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2 + 1, y2 + 1, oled_buffer); +} + +static void example_increase_lvgl_tick(void *arg) +{ + /* Tell LVGL how many milliseconds has elapsed */ + lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS); +} + +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); + usleep(1000 * time_till_next_ms); + } +} + void app_main(void) { ESP_LOGI(TAG, "Initialize I2C bus"); @@ -107,33 +182,48 @@ void app_main(void) #endif ESP_LOGI(TAG, "Initialize LVGL"); - const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&lvgl_cfg); + lv_init(); + // create a lvgl display + lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES); + // associate the i2c panel handle to the display + lv_display_set_user_data(display, panel_handle); + // create draw buffer + void *buf = NULL; + ESP_LOGI(TAG, "Allocate separate LVGL draw buffers"); + // LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. + size_t draw_buffer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES / 8 + EXAMPLE_LVGL_PALETTE_SIZE; + buf = heap_caps_calloc(1, draw_buffer_sz, MALLOC_CAP_INTERNAL); + assert(buf); - const lvgl_port_display_cfg_t disp_cfg = { - .io_handle = io_handle, - .panel_handle = panel_handle, - .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES, - .double_buffer = true, - .hres = EXAMPLE_LCD_H_RES, - .vres = EXAMPLE_LCD_V_RES, - .monochrome = true, - .rotation = { - .swap_xy = false, - .mirror_x = false, - .mirror_y = false, - } + // LVGL9 suooprt new monochromatic format. + lv_display_set_color_format(display, LV_COLOR_FORMAT_I1); + // initialize LVGL draw buffers + lv_display_set_buffers(display, buf, NULL, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_FULL); + // 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 io panel event callback for LVGL flush ready notification"); + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = example_notify_lvgl_flush_ready, }; - lv_disp_t *disp = lvgl_port_add_disp(&disp_cfg); + /* Register done callback */ + esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display); - /* Rotation of the screen */ - lv_disp_set_rotation(disp, LV_DISP_ROT_NONE); + ESP_LOGI(TAG, "Use esp_timer as LVGL tick timer"); + 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, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000)); + + ESP_LOGI(TAG, "Create LVGL task"); + xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL); ESP_LOGI(TAG, "Display LVGL Scroll Text"); // Lock the mutex due to the LVGL APIs are not thread-safe - if (lvgl_port_lock(0)) { - example_lvgl_demo_ui(disp); - // Release the mutex - lvgl_port_unlock(); - } + _lock_acquire(&lvgl_api_lock); + example_lvgl_demo_ui(display); + _lock_release(&lvgl_api_lock); } diff --git a/examples/peripherals/lcd/i2c_oled/main/idf_component.yml b/examples/peripherals/lcd/i2c_oled/main/idf_component.yml index e7c49c66f6..b8408dba26 100644 --- a/examples/peripherals/lcd/i2c_oled/main/idf_component.yml +++ b/examples/peripherals/lcd/i2c_oled/main/idf_component.yml @@ -1,4 +1,3 @@ dependencies: - lvgl/lvgl: "8.3.0" + lvgl/lvgl: "9.2.0" esp_lcd_sh1107: "^1" - esp_lvgl_port: "^1" diff --git a/examples/peripherals/lcd/i2c_oled/main/lvgl_demo_ui.c b/examples/peripherals/lcd/i2c_oled/main/lvgl_demo_ui.c index 251ef5bc97..743711ea52 100644 --- a/examples/peripherals/lcd/i2c_oled/main/lvgl_demo_ui.c +++ b/examples/peripherals/lcd/i2c_oled/main/lvgl_demo_ui.c @@ -1,18 +1,18 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include "lvgl.h" -void example_lvgl_demo_ui(lv_disp_t *disp) +void example_lvgl_demo_ui(lv_display_t *disp) { - lv_obj_t *scr = lv_disp_get_scr_act(disp); + lv_obj_t *scr = lv_display_get_screen_active(disp); lv_obj_t *label = lv_label_create(scr); lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); /* Circular scroll */ lv_label_set_text(label, "Hello Espressif, Hello LVGL."); - /* Size of the screen (if you use rotation 90 or 270, please set disp->driver->ver_res) */ - lv_obj_set_width(label, disp->driver->hor_res); + /* Size of the screen (if you use rotation 90 or 270, please use lv_display_get_vertical_resolution) */ + lv_obj_set_width(label, lv_display_get_horizontal_resolution(disp)); lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0); } diff --git a/examples/peripherals/lcd/i2c_oled/sdkconfig.defaults b/examples/peripherals/lcd/i2c_oled/sdkconfig.defaults index d92efacc71..dd302d9c63 100644 --- a/examples/peripherals/lcd/i2c_oled/sdkconfig.defaults +++ b/examples/peripherals/lcd/i2c_oled/sdkconfig.defaults @@ -1,5 +1,6 @@ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # -CONFIG_LV_USE_USER_DATA=y -CONFIG_LV_COLOR_DEPTH_1=y +CONFIG_LV_CONF_SKIP=y +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y