From 8bb2402bcc97d2186e91b8a200338e630bcce55c Mon Sep 17 00:00:00 2001 From: Vilem Zavodny Date: Thu, 12 Sep 2024 15:56:30 +0200 Subject: [PATCH] fix(LVGL port): Fixed monochromatic screen in LVGL9 + example --- components/esp_lvgl_port/CHANGELOG.md | 8 +++ .../esp_lvgl_port/examples/i2c_oled/README.md | 10 ++- .../i2c_oled/main/i2c_oled_example_main.c | 37 ++++++---- .../examples/i2c_oled/main/lvgl_demo_ui.c | 4 ++ components/esp_lvgl_port/idf_component.yml | 2 +- .../src/lvgl9/esp_lvgl_port_disp.c | 68 +++++++++++++++---- 6 files changed, 101 insertions(+), 28 deletions(-) diff --git a/components/esp_lvgl_port/CHANGELOG.md b/components/esp_lvgl_port/CHANGELOG.md index bad1d820..37c0812b 100644 --- a/components/esp_lvgl_port/CHANGELOG.md +++ b/components/esp_lvgl_port/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.4.3 + +### Fixes +- Fixed I2C example for using with LVGL9 + +### Features +- Support for LV_COLOR_FORMAT_I1 for monochromatic screen + ## 2.4.2 ### Fixes diff --git a/components/esp_lvgl_port/examples/i2c_oled/README.md b/components/esp_lvgl_port/examples/i2c_oled/README.md index ed1d7f31..fe7c09c0 100644 --- a/components/esp_lvgl_port/examples/i2c_oled/README.md +++ b/components/esp_lvgl_port/examples/i2c_oled/README.md @@ -5,15 +5,21 @@ [esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports I2C interfaced OLED LCD, whose color depth is usually 1bpp. -This example shows how to make use of the SSD1306 panel driver from `esp_lcd` component to facilitate the porting of LVGL library. In the end, example will display a scrolling text on the OLED screen. +This example shows how to make use of the SSD1306 panel driver from `esp_lcd` component to facilitate the porting of LVGL library. In the end, example will display a scrolling text on the OLED screen. ## LVGL Version -This example is using the **LVGL8** version. For use it with LVGL9 version, please remove this line from [idf_component.yml](main/idf_component.yml) file: +This example is using the **LVGL8** version. For use it with LVGL9 version, please delete file [sdkconfig.defaults](sdkconfig.defaults) and change version to `"^9"` on this line in [idf_component.yml](main/idf_component.yml) file: ``` lvgl/lvgl: "^8" ``` +NOTE: When you are changing LVGL versions, please remove these files and folders before new build: +- build/ +- managed_components/ +- dependencies.lock +- sdkconfig + ## How to use the example ### Hardware Required diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c b/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c index 9347e887..435b3251 100644 --- a/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c +++ b/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c @@ -10,7 +10,7 @@ #include "esp_timer.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" -#include "driver/i2c.h" +#include "driver/i2c_master.h" #include "esp_err.h" #include "esp_log.h" #include "lvgl.h" @@ -52,21 +52,22 @@ extern void example_lvgl_demo_ui(lv_disp_t *disp); void app_main(void) { ESP_LOGI(TAG, "Initialize I2C bus"); - i2c_config_t i2c_conf = { - .mode = I2C_MODE_MASTER, + i2c_master_bus_handle_t i2c_bus = NULL; + i2c_master_bus_config_t bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .i2c_port = I2C_HOST, .sda_io_num = EXAMPLE_PIN_NUM_SDA, .scl_io_num = EXAMPLE_PIN_NUM_SCL, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = EXAMPLE_LCD_PIXEL_CLOCK_HZ, + .flags.enable_internal_pullup = true, }; - ESP_ERROR_CHECK(i2c_param_config(I2C_HOST, &i2c_conf)); - ESP_ERROR_CHECK(i2c_driver_install(I2C_HOST, I2C_MODE_MASTER, 0, 0, 0)); + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus)); ESP_LOGI(TAG, "Install panel IO"); esp_lcd_panel_io_handle_t io_handle = NULL; esp_lcd_panel_io_i2c_config_t io_config = { .dev_addr = EXAMPLE_I2C_HW_ADDR, + .scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, .control_phase_bytes = 1, // According to SSD1306 datasheet .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet .lcd_param_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet @@ -80,9 +81,8 @@ void app_main(void) } #endif }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(I2C_HOST, &io_config, &io_handle)); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle)); - ESP_LOGI(TAG, "Install SSD1306 panel driver"); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_panel_dev_config_t panel_config = { .bits_per_pixel = 1, @@ -98,8 +98,10 @@ void app_main(void) }; panel_config.vendor_config = &ssd1306_config; #endif + ESP_LOGI(TAG, "Install SSD1306 panel driver"); ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); #elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + ESP_LOGI(TAG, "Install SH1107 panel driver"); ESP_ERROR_CHECK(esp_lcd_new_panel_sh1107(io_handle, &panel_config, &panel_handle)); #endif @@ -123,20 +125,29 @@ void app_main(void) .hres = EXAMPLE_LCD_H_RES, .vres = EXAMPLE_LCD_V_RES, .monochrome = true, +#if LVGL_VERSION_MAJOR >= 9 + .color_format = LV_COLOR_FORMAT_RGB565, +#endif .rotation = { .swap_xy = false, .mirror_x = false, .mirror_y = false, + }, + .flags = { +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = false, +#endif + .sw_rotate = false, } }; lv_disp_t *disp = lvgl_port_add_disp(&disp_cfg); - /* Rotation of the screen */ - lv_disp_set_rotation(disp, LV_DISP_ROT_NONE); - ESP_LOGI(TAG, "Display LVGL Scroll Text"); // Lock the mutex due to the LVGL APIs are not thread-safe if (lvgl_port_lock(0)) { + /* Rotation of the screen */ + lv_disp_set_rotation(disp, LV_DISPLAY_ROTATION_0); + example_lvgl_demo_ui(disp); // Release the mutex lvgl_port_unlock(); diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c b/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c index fa60a8b9..df53d272 100644 --- a/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c +++ b/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c @@ -13,6 +13,10 @@ void example_lvgl_demo_ui(lv_disp_t *disp) 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) */ +#if LVGL_VERSION_MAJOR >= 9 + lv_obj_set_width(label, lv_display_get_physical_horizontal_resolution(disp)); +#else lv_obj_set_width(label, disp->driver->hor_res); +#endif lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0); } diff --git a/components/esp_lvgl_port/idf_component.yml b/components/esp_lvgl_port/idf_component.yml index e5d019fb..fb7e7619 100644 --- a/components/esp_lvgl_port/idf_component.yml +++ b/components/esp_lvgl_port/idf_component.yml @@ -1,4 +1,4 @@ -version: "2.4.2" +version: "2.4.3" description: ESP LVGL port url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port dependencies: diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index 46bcd97c..72eb8b93 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -45,6 +45,7 @@ typedef struct { esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */ lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ lv_color_t *draw_buffs[3]; /* Display draw buffers */ + uint8_t *oled_buffer; lv_display_t *disp_drv; /* LVGL display driver */ lv_display_rotation_t current_rotation; SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ @@ -207,6 +208,10 @@ esp_err_t lvgl_port_remove_disp(lv_display_t *disp) free(disp_ctx->draw_buffs[2]); } + if (disp_ctx->oled_buffer) { + free(disp_ctx->oled_buffer); + } + if (disp_ctx->trans_sem) { vSemaphoreDelete(disp_ctx->trans_sem); } @@ -243,16 +248,16 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp buffer_size = disp_cfg->buffer_size; /* Check supported display color formats */ - ESP_RETURN_ON_FALSE(disp_cfg->color_format == 0 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB565 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB888 || disp_cfg->color_format == LV_COLOR_FORMAT_XRGB8888 || disp_cfg->color_format == LV_COLOR_FORMAT_ARGB8888, NULL, TAG, "Not supported display color format!"); + ESP_RETURN_ON_FALSE(disp_cfg->color_format == 0 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB565 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB888 || disp_cfg->color_format == LV_COLOR_FORMAT_XRGB8888 || disp_cfg->color_format == LV_COLOR_FORMAT_ARGB8888 || disp_cfg->color_format == LV_COLOR_FORMAT_I1, NULL, TAG, "Not supported display color format!"); lv_color_format_t display_color_format = (disp_cfg->color_format != 0 ? disp_cfg->color_format : LV_COLOR_FORMAT_RGB565); if (disp_cfg->flags.swap_bytes) { - /* Swap bytes can be used only in RGB656 color format */ + /* Swap bytes can be used only in RGB565 color format */ ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, "Swap bytes can be used only in display color format RGB565!"); } if (disp_cfg->flags.buff_dma) { - /* DMA buffer can be used only in RGB656 color format */ + /* DMA buffer can be used only in RGB565 color format */ ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, "DMA buffer can be used only in display color format RGB565 (not alligned copy)!"); } @@ -315,13 +320,31 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp disp = lv_display_create(disp_cfg->hres, disp_cfg->vres); + /* Set display color format */ + lv_display_set_color_format(disp, display_color_format); + /* Monochrome display settings */ if (disp_cfg->monochrome) { +#if CONFIG_LV_COLOR_DEPTH_1 +#error please disable LV_COLOR_DEPTH_1 for using monochromatic screen +#endif + + /* Monochrome can be used only in RGB565 color format */ + ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565 || display_color_format == LV_COLOR_FORMAT_I1, NULL, TAG, "Monochrome can be used only in display color format RGB565 or I1!"); + /* When using monochromatic display, there must be used full bufer! */ ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!"); disp_ctx->flags.monochrome = 1; lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL); + + if (display_color_format == LV_COLOR_FORMAT_I1) { + /* OLED monochrome buffer */ + // To use LV_COLOR_FORMAT_I1, we need an extra buffer to hold the converted data + disp_ctx->oled_buffer = heap_caps_malloc(buffer_size, buff_caps); + ESP_GOTO_ON_FALSE(disp_ctx->oled_buffer, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (OLED buffer) allocation!"); + } + } else if (disp_cfg->flags.direct_mode) { /* When using direct_mode, there must be used full bufer! */ ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Direct mode must using full buffer!"); @@ -338,7 +361,6 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL); } - lv_display_set_color_format(disp, display_color_format); lv_display_set_flush_cb(disp, lvgl_port_flush_callback); lv_display_add_event_cb(disp, lvgl_port_disp_size_update_callback, LV_EVENT_RESOLUTION_CHANGED, disp_ctx); lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_INVALIDATE_AREA, disp_ctx); @@ -365,6 +387,9 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp if (disp_ctx->draw_buffs[2]) { free(disp_ctx->draw_buffs[2]); } + if (disp_ctx->oled_buffer) { + free(disp_ctx->oled_buffer); + } if (disp_ctx) { free(disp_ctx); } @@ -430,10 +455,13 @@ static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t pane #endif #endif -static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t *color_map) +static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t **color_map) { - uint8_t *buf = color_map; - lv_color_t *color = (lv_color_t *)color_map; + assert(color_map); + assert(*color_map); + uint8_t *src = *color_map; + lv_color16_t *color = (lv_color16_t *)*color_map; + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(display); uint16_t hor_res = lv_display_get_physical_horizontal_resolution(display); uint16_t ver_res = lv_display_get_physical_vertical_resolution(display); uint16_t res = hor_res; @@ -444,10 +472,24 @@ static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area int y1 = area->y1; int y2 = area->y2; + lv_color_format_t color_format = lv_display_get_color_format(display); + if (color_format == LV_COLOR_FORMAT_I1) { + // 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 + src += 8; + /*Use oled_buffer as output */ + *color_map = disp_ctx->oled_buffer; + } + int out_x, out_y; for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { - bool chroma_color = (color[hor_res * y + x].blue > 16); + bool chroma_color = 0; + if (color_format == LV_COLOR_FORMAT_I1) { + chroma_color = (src[(hor_res >> 3) * y + (x >> 3)] & 1 << (7 - x % 8)); + } else { + chroma_color = (color[hor_res * y + x].blue > 16); + } if (swap_xy) { out_x = y; @@ -461,14 +503,16 @@ static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area /* Write to the buffer as required for the display. * It writes only 1-bit for monochrome displays mapped vertically.*/ - buf = color_map + res * (out_y >> 3) + (out_x); + uint8_t *outbuf = NULL; + outbuf = *color_map + res * (out_y >> 3) + (out_x); if (chroma_color) { - (*buf) &= ~(1 << (out_y % 8)); + (*outbuf) &= ~(1 << (out_y % 8)); } else { - (*buf) |= (1 << (out_y % 8)); + (*outbuf) |= (1 << (out_y % 8)); } } } + } void lvgl_port_rotate_area(lv_display_t *disp, lv_area_t *area) @@ -552,7 +596,7 @@ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, u /* Transfer data in buffer for monochromatic screen */ if (disp_ctx->flags.monochrome) { - _lvgl_port_transform_monochrome(drv, area, color_map); + _lvgl_port_transform_monochrome(drv, area, &color_map); } if ((disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) && (disp_ctx->flags.direct_mode || disp_ctx->flags.full_refresh)) {