Skip to content

Commit

Permalink
fix(LVGL port): Fixed monochromatic screen in LVGL9 + example
Browse files Browse the repository at this point in the history
  • Loading branch information
espzav committed Nov 27, 2024
1 parent ebd7e9d commit ba375fb
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 28 deletions.
8 changes: 8 additions & 0 deletions components/esp_lvgl_port/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 8 additions & 2 deletions components/esp_lvgl_port/examples/i2c_oled/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -52,24 +52,25 @@ 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
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, // According to SSD1306 datasheet
#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306
.dc_bit_offset = 6, // According to SSD1306 datasheet
#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107
Expand All @@ -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,
Expand All @@ -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

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
68 changes: 56 additions & 12 deletions components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)!");
}

Expand Down Expand Up @@ -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!");
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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)) {
Expand Down

0 comments on commit ba375fb

Please sign in to comment.