Update LVGL (master 9.2+) and related libraries (#130)

- LVGL 9.2+ (master commit)
- esp_lvgl_port (master with my PR hotfix changes)
- espressif/esp_lcd_touch_gt911 1.1.1~2
This commit is contained in:
Ken Van Hoeylandt
2024-12-16 23:30:57 +01:00
committed by GitHub
parent e4b8519f67
commit 80b69b7f45
90 changed files with 7328 additions and 469 deletions
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
@@ -13,16 +14,21 @@
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "lvgl.h"
static const char *TAG = "LVGL";
#define ESP_LVGL_PORT_TASK_MUX_DELAY_MS 10000
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct lvgl_port_ctx_s {
TaskHandle_t lvgl_task;
SemaphoreHandle_t lvgl_mux;
SemaphoreHandle_t task_mux;
esp_timer_handle_t tick_timer;
bool running;
int task_max_sleep_ms;
@@ -63,14 +69,18 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
if (lvgl_port_ctx.task_max_sleep_ms == 0) {
lvgl_port_ctx.task_max_sleep_ms = 500;
}
/* LVGL semaphore */
lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!");
/* Task semaphore */
lvgl_port_ctx.task_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.task_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL task sem fail!");
BaseType_t res;
if (cfg->task_affinity < 0) {
res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL);
res = xTaskCreate(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task);
} else {
res = xTaskCreatePinnedToCore(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL, cfg->task_affinity);
res = xTaskCreatePinnedToCore(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task, cfg->task_affinity);
}
ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!");
@@ -118,10 +128,17 @@ esp_err_t lvgl_port_deinit(void)
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
} else {
lvgl_port_task_deinit();
}
/* Wait for stop task */
if (xSemaphoreTake(lvgl_port_ctx.task_mux, pdMS_TO_TICKS(ESP_LVGL_PORT_TASK_MUX_DELAY_MS)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to stop LVGL task");
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Stopped LVGL task");
lvgl_port_task_deinit();
return ESP_OK;
}
@@ -139,6 +156,26 @@ void lvgl_port_unlock(void)
xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux);
}
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param)
{
ESP_LOGE(TAG, "Task wake is not supported, when used LVGL8!");
return ESP_ERR_NOT_SUPPORTED;
}
IRAM_ATTR bool lvgl_port_task_notify(uint32_t value)
{
BaseType_t need_yield = pdFALSE;
// Notify LVGL task
if (xPortInIsrContext() == pdTRUE) {
xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield);
} else {
xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction);
}
return (need_yield == pdTRUE);
}
/*******************************************************************************
* Private functions
*******************************************************************************/
@@ -147,6 +184,13 @@ static void lvgl_port_task(void *arg)
{
uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
/* Take the task semaphore */
if (xSemaphoreTake(lvgl_port_ctx.task_mux, 0) != pdTRUE) {
ESP_LOGE(TAG, "Failed to take LVGL task sem");
lvgl_port_task_deinit();
vTaskDelete( NULL );
}
ESP_LOGI(TAG, "Starting LVGL task");
lvgl_port_ctx.running = true;
while (lvgl_port_ctx.running) {
@@ -154,15 +198,16 @@ static void lvgl_port_task(void *arg)
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
}
if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) {
if (task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) {
task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
} else if (task_delay_ms < 1) {
task_delay_ms = 1;
} else if (task_delay_ms < 5) {
task_delay_ms = 5;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
lvgl_port_task_deinit();
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_mux);
/* Close task */
vTaskDelete( NULL );
@@ -173,6 +218,9 @@ static void lvgl_port_task_deinit(void)
if (lvgl_port_ctx.lvgl_mux) {
vSemaphoreDelete(lvgl_port_ctx.lvgl_mux);
}
if (lvgl_port_ctx.task_mux) {
vSemaphoreDelete(lvgl_port_ctx.task_mux);
}
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
/* Deinitialize LVGL */
@@ -4,13 +4,27 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "esp_idf_version.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_lcd_panel_rgb.h"
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
#include "esp_lcd_mipi_dsi.h"
#endif
#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0))
#define LVGL_PORT_HANDLE_FLUSH_READY 0
@@ -25,8 +39,10 @@ static const char *TAG = "LVGL";
*******************************************************************************/
typedef struct {
lvgl_port_disp_type_t disp_type; /* Display type */
esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */
esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */
esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */
lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */
lv_disp_drv_t disp_drv; /* LVGL display driver */
lv_color_t *trans_buf; /* Buffer send to driver */
@@ -37,9 +53,17 @@ typedef struct {
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg);
static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(lv_disp_t *disp);
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
#endif
#endif
static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map);
static void lvgl_port_update_callback(lv_disp_drv_t *drv);
@@ -51,111 +75,90 @@ static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf,
lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)
{
esp_err_t ret = ESP_OK;
lv_disp_t *disp = NULL;
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
lv_color_t *buf3 = NULL;
SemaphoreHandle_t trans_sem = NULL;
assert(disp_cfg != NULL);
assert(disp_cfg->io_handle != NULL);
assert(disp_cfg->panel_handle != NULL);
assert(disp_cfg->buffer_size > 0);
assert(disp_cfg->hres > 0);
assert(disp_cfg->vres > 0);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
/* Display context */
lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t));
ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!");
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->trans_size = disp_cfg->trans_size;
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER;
uint32_t buff_caps = MALLOC_CAP_DEFAULT;
if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram && (0 == disp_cfg->trans_size)) {
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!");
} else if (disp_cfg->flags.buff_dma) {
buff_caps = MALLOC_CAP_DMA;
} else if (disp_cfg->flags.buff_spiram) {
buff_caps = MALLOC_CAP_SPIRAM;
}
if (disp_cfg->trans_size) {
buf3 = heap_caps_malloc(disp_cfg->trans_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(buf3, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for buffer(transport) allocation!");
disp_ctx->trans_buf = buf3;
trans_sem = xSemaphoreCreateCounting(1, 0);
ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore");
disp_ctx->trans_sem = trans_sem;
}
/* alloc draw buffers used by LVGL */
/* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */
buf1 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!");
if (disp_cfg->double_buffer) {
buf2 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!");
}
lv_disp_draw_buf_t *disp_buf = malloc(sizeof(lv_disp_draw_buf_t));
ESP_GOTO_ON_FALSE(disp_buf, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL display buffer allocation!");
/* initialize LVGL draw buffers */
lv_disp_draw_buf_init(disp_buf, buf1, buf2, disp_cfg->buffer_size);
ESP_LOGD(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_ctx->disp_drv);
disp_ctx->disp_drv.hor_res = disp_cfg->hres;
disp_ctx->disp_drv.ver_res = disp_cfg->vres;
disp_ctx->disp_drv.flush_cb = lvgl_port_flush_callback;
disp_ctx->disp_drv.draw_buf = disp_buf;
disp_ctx->disp_drv.user_data = disp_ctx;
disp_ctx->disp_drv.sw_rotate = disp_cfg->flags.sw_rotate;
if (disp_ctx->disp_drv.sw_rotate == false) {
disp_ctx->disp_drv.drv_update_cb = lvgl_port_update_callback;
}
assert(disp_ctx->io_handle != NULL);
#if LVGL_PORT_HANDLE_FLUSH_READY
/* Register done callback */
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_ready_callback,
};
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_io_ready_callback,
};
/* Register done callback */
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv);
#endif
/* Monochrome display settings */
if (disp_cfg->monochrome) {
/* When using monochromatic display, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == disp_cfg->buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!");
disp_ctx->disp_drv.full_refresh = 1;
disp_ctx->disp_drv.set_px_cb = lvgl_port_pix_monochrome_callback;
}
disp = lv_disp_drv_register(&disp_ctx->disp_drv);
return disp;
}
err:
if (ret != ESP_OK) {
if (buf1) {
free(buf1);
lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_dsi_cfg_t *dsi_cfg)
{
assert(dsi_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = dsi_cfg->flags.avoid_tearing,
};
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI;
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
if (dsi_cfg->flags.avoid_tearing) {
cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback;
} else {
cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
}
if (buf2) {
free(buf2);
}
if (buf3) {
free(buf3);
}
if (trans_sem) {
vSemaphoreDelete(trans_sem);
}
if (disp_ctx) {
free(disp_ctx);
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv);
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!");
#endif
}
return disp;
}
lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_rgb_cfg_t *rgb_cfg)
{
assert(rgb_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = rgb_cfg->flags.avoid_tearing,
};
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB;
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
/* Register done callback */
const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = {
.on_vsync = lvgl_port_flush_rgb_vsync_ready_callback,
};
const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
.on_bounce_frame_finish = lvgl_port_flush_rgb_vsync_ready_callback,
#endif
};
if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, &disp_ctx->disp_drv));
} else {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, &disp_ctx->disp_drv));
}
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!");
#endif
}
return disp;
@@ -167,6 +170,9 @@ esp_err_t lvgl_port_remove_disp(lv_disp_t *disp)
lv_disp_drv_t *disp_drv = disp->driver;
assert(disp_drv);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data;
if (disp_ctx->trans_sem) {
vSemaphoreDelete(disp_ctx->trans_sem);
}
lv_disp_remove(disp);
@@ -201,8 +207,154 @@ void lvgl_port_flush_ready(lv_disp_t *disp)
* Private functions
*******************************************************************************/
static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(lv_disp_t *disp)
{
assert(disp);
lv_disp_drv_t *disp_drv = disp->driver;
assert(disp_drv);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data;
return disp_ctx;
}
static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg)
{
esp_err_t ret = ESP_OK;
lv_disp_t *disp = NULL;
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
lv_color_t *buf3 = NULL;
uint32_t buffer_size = 0;
SemaphoreHandle_t trans_sem = NULL;
assert(disp_cfg != NULL);
assert(disp_cfg->panel_handle != NULL);
assert(disp_cfg->buffer_size > 0);
assert(disp_cfg->hres > 0);
assert(disp_cfg->vres > 0);
/* Display context */
lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t));
ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!");
memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t));
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->control_handle = disp_cfg->control_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->trans_size = disp_cfg->trans_size;
buffer_size = disp_cfg->buffer_size;
/* Use RGB internal buffers for avoid tearing effect */
if (priv_cfg && priv_cfg->avoid_tearing) {
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#elif CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#endif
trans_sem = xSemaphoreCreateCounting(1, 0);
ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore");
disp_ctx->trans_sem = trans_sem;
} else {
uint32_t buff_caps = MALLOC_CAP_DEFAULT;
if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram && (0 == disp_cfg->trans_size)) {
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!");
} else if (disp_cfg->flags.buff_dma) {
buff_caps = MALLOC_CAP_DMA;
} else if (disp_cfg->flags.buff_spiram) {
buff_caps = MALLOC_CAP_SPIRAM;
}
if (disp_cfg->trans_size) {
buf3 = heap_caps_malloc(disp_cfg->trans_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(buf3, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for buffer(transport) allocation!");
disp_ctx->trans_buf = buf3;
trans_sem = xSemaphoreCreateCounting(1, 0);
ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore");
disp_ctx->trans_sem = trans_sem;
}
/* alloc draw buffers used by LVGL */
/* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */
buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!");
if (disp_cfg->double_buffer) {
buf2 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!");
}
}
lv_disp_draw_buf_t *disp_buf = malloc(sizeof(lv_disp_draw_buf_t));
ESP_GOTO_ON_FALSE(disp_buf, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL display buffer allocation!");
/* initialize LVGL draw buffers */
lv_disp_draw_buf_init(disp_buf, buf1, buf2, buffer_size);
ESP_LOGD(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_ctx->disp_drv);
disp_ctx->disp_drv.hor_res = disp_cfg->hres;
disp_ctx->disp_drv.ver_res = disp_cfg->vres;
disp_ctx->disp_drv.flush_cb = lvgl_port_flush_callback;
disp_ctx->disp_drv.draw_buf = disp_buf;
disp_ctx->disp_drv.user_data = disp_ctx;
disp_ctx->disp_drv.sw_rotate = disp_cfg->flags.sw_rotate;
if (disp_ctx->disp_drv.sw_rotate == false) {
disp_ctx->disp_drv.drv_update_cb = lvgl_port_update_callback;
}
/* Monochrome display settings */
if (disp_cfg->monochrome) {
/* 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->disp_drv.full_refresh = 1;
disp_ctx->disp_drv.set_px_cb = lvgl_port_pix_monochrome_callback;
} 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!");
disp_ctx->disp_drv.direct_mode = 1;
} else if (disp_cfg->flags.full_refresh) {
/* When using full_refresh, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Full refresh must using full buffer!");
disp_ctx->disp_drv.full_refresh = 1;
}
disp = lv_disp_drv_register(&disp_ctx->disp_drv);
/* Apply rotation from initial display configuration */
lvgl_port_update_callback(&disp_ctx->disp_drv);
err:
if (ret != ESP_OK) {
if (buf1) {
free(buf1);
}
if (buf2) {
free(buf2);
}
if (buf3) {
free(buf3);
}
if (trans_sem) {
vSemaphoreDelete(trans_sem);
}
if (disp_ctx) {
free(disp_ctx);
}
}
return disp;
}
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
BaseType_t taskAwake = pdFALSE;
@@ -218,6 +370,59 @@ static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, e
return false;
}
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t taskAwake = pdFALSE;
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data;
assert(disp_ctx != NULL);
lv_disp_flush_ready(disp_drv);
if (disp_ctx->trans_size && disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake);
}
return false;
}
static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data;
assert(disp_ctx != NULL);
if (disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield);
}
return (need_yield == pdTRUE);
}
#endif
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data;
assert(disp_ctx != NULL);
if (disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield);
}
return (need_yield == pdTRUE);
}
#endif
#endif
static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
@@ -248,8 +453,22 @@ static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area,
lv_color_t *from = color_map;
lv_color_t *to = NULL;
if (0 == disp_ctx->trans_size) {
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
if (disp_ctx->trans_size == 0) {
if ((disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) && (drv->direct_mode || drv->full_refresh)) {
if (lv_disp_flush_is_last(drv)) {
/* If the interface is I80 or SPI, this step cannot be used for drawing. */
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
/* Waiting for the last frame buffer to complete transmission */
xSemaphoreTake(disp_ctx->trans_sem, 0);
xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY);
}
} else {
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
}
if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI && (drv->direct_mode || drv->full_refresh))) {
lv_disp_flush_ready(drv);
}
} else {
y_start_tmp = y_start;
max_line = ((disp_ctx->trans_size / width) > height) ? (height) : (disp_ctx->trans_size / width);
@@ -283,36 +502,36 @@ static void lvgl_port_update_callback(lv_disp_drv_t *drv)
assert(drv);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data;
assert(disp_ctx != NULL);
esp_lcd_panel_handle_t panel_handle = disp_ctx->panel_handle;
esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle);
/* Solve rotation screen and touch */
switch (drv->rotated) {
case LV_DISP_ROT_NONE:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
break;
case LV_DISP_ROT_90:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
}
break;
case LV_DISP_ROT_180:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
break;
case LV_DISP_ROT_270:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}
@@ -4,10 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"
+124 -13
View File
@@ -4,25 +4,34 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "lvgl.h"
static const char *TAG = "LVGL";
#define ESP_LVGL_PORT_TASK_MUX_DELAY_MS 10000
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct lvgl_port_ctx_s {
TaskHandle_t lvgl_task;
SemaphoreHandle_t lvgl_mux;
SemaphoreHandle_t timer_mux;
QueueHandle_t lvgl_queue;
SemaphoreHandle_t task_init_mux;
esp_timer_handle_t tick_timer;
bool running;
int task_max_sleep_ms;
@@ -53,24 +62,31 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
/* LVGL init */
lv_init();
/* Tick init */
lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms;
ESP_RETURN_ON_ERROR(lvgl_port_tick_init(), TAG, "");
/* Create task */
lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms;
if (lvgl_port_ctx.task_max_sleep_ms == 0) {
lvgl_port_ctx.task_max_sleep_ms = 500;
}
/* Timer semaphore */
lvgl_port_ctx.timer_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.timer_mux, ESP_ERR_NO_MEM, err, TAG, "Create timer mutex fail!");
/* LVGL semaphore */
lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!");
/* Task init semaphore */
lvgl_port_ctx.task_init_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.task_init_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL task sem fail!");
/* Task queue */
lvgl_port_ctx.lvgl_queue = xQueueCreate(100, sizeof(lvgl_port_event_t));
ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_queue, ESP_ERR_NO_MEM, err, TAG, "Create LVGL queue fail!");
BaseType_t res;
if (cfg->task_affinity < 0) {
res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL);
res = xTaskCreate(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task);
} else {
res = xTaskCreatePinnedToCore(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL, cfg->task_affinity);
res = xTaskCreatePinnedToCore(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task, cfg->task_affinity);
}
ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!");
@@ -118,10 +134,17 @@ esp_err_t lvgl_port_deinit(void)
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
} else {
lvgl_port_task_deinit();
}
/* Wait for stop task */
if (xSemaphoreTake(lvgl_port_ctx.task_init_mux, pdMS_TO_TICKS(ESP_LVGL_PORT_TASK_MUX_DELAY_MS)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to stop LVGL task");
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Stopped LVGL task");
lvgl_port_task_deinit();
return ESP_OK;
}
@@ -139,30 +162,107 @@ void lvgl_port_unlock(void)
xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux);
}
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param)
{
if (!lvgl_port_ctx.lvgl_queue) {
return ESP_ERR_INVALID_STATE;
}
lvgl_port_event_t ev = {
.type = event,
.param = param,
};
if (xPortInIsrContext() == pdTRUE) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(lvgl_port_ctx.lvgl_queue, &ev, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR( );
}
} else {
xQueueSend(lvgl_port_ctx.lvgl_queue, &ev, 0);
}
return ESP_OK;
}
IRAM_ATTR bool lvgl_port_task_notify(uint32_t value)
{
BaseType_t need_yield = pdFALSE;
// Notify LVGL task
if (xPortInIsrContext() == pdTRUE) {
xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield);
} else {
xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction);
}
return (need_yield == pdTRUE);
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_task(void *arg)
{
uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
lvgl_port_event_t event;
uint32_t task_delay_ms = 0;
lv_indev_t *indev = NULL;
/* Take the task semaphore */
if (xSemaphoreTake(lvgl_port_ctx.task_init_mux, 0) != pdTRUE) {
ESP_LOGE(TAG, "Failed to take LVGL task sem");
lvgl_port_task_deinit();
vTaskDelete( NULL );
}
/* LVGL init */
lv_init();
/* Tick init */
lvgl_port_tick_init();
ESP_LOGI(TAG, "Starting LVGL task");
lvgl_port_ctx.running = true;
while (lvgl_port_ctx.running) {
/* Wait for queue or timeout (sleep task) */
TickType_t wait = (pdMS_TO_TICKS(task_delay_ms) >= 1 ? pdMS_TO_TICKS(task_delay_ms) : 1);
xQueueReceive(lvgl_port_ctx.lvgl_queue, &event, wait);
if (lv_display_get_default() && lvgl_port_lock(0)) {
/* Call read input devices */
if (event.type == LVGL_PORT_EVENT_TOUCH) {
xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY);
if (event.param != NULL) {
lv_indev_read(event.param);
} else {
indev = lv_indev_get_next(NULL);
while (indev != NULL) {
lv_indev_read(indev);
indev = lv_indev_get_next(indev);
}
}
xSemaphoreGive(lvgl_port_ctx.timer_mux);
}
/* Handle LVGL */
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
} else {
task_delay_ms = 1; /*Keep trying*/
}
if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) {
if (task_delay_ms == LV_NO_TIMER_READY) {
task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
} else if (task_delay_ms < 1) {
task_delay_ms = 1;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
/* Minimal dealy for the task. When there is too much events, it takes time for other tasks and interrupts. */
vTaskDelay(1);
}
lvgl_port_task_deinit();
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_init_mux);
/* Close task */
vTaskDelete( NULL );
@@ -170,9 +270,18 @@ static void lvgl_port_task(void *arg)
static void lvgl_port_task_deinit(void)
{
if (lvgl_port_ctx.timer_mux) {
vSemaphoreDelete(lvgl_port_ctx.timer_mux);
}
if (lvgl_port_ctx.lvgl_mux) {
vSemaphoreDelete(lvgl_port_ctx.lvgl_mux);
}
if (lvgl_port_ctx.task_init_mux) {
vSemaphoreDelete(lvgl_port_ctx.task_init_mux);
}
if (lvgl_port_ctx.lvgl_queue) {
vQueueDelete(lvgl_port_ctx.lvgl_queue);
}
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
/* Deinitialize LVGL */
@@ -182,8 +291,10 @@ static void lvgl_port_task_deinit(void)
static void lvgl_port_tick_increment(void *arg)
{
xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY);
/* Tell LVGL how many milliseconds have elapsed */
lv_tick_inc(lvgl_port_ctx.timer_period_ms);
xSemaphoreGive(lvgl_port_ctx.timer_mux);
}
static esp_err_t lvgl_port_tick_init(void)
@@ -88,6 +88,7 @@ lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *but
/* Register a touchpad input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(indev, lvgl_port_navigation_buttons_read);
lv_indev_set_disp(indev, buttons_cfg->disp);
lv_indev_set_driver_data(indev, buttons_ctx);
@@ -110,7 +111,7 @@ err:
}
}
return buttons_ctx->indev;
return NULL;
}
esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons)
@@ -180,6 +181,9 @@ static void lvgl_port_btn_down_handler(void *arg, void *arg2)
ctx->btn_enter = true;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_btn_up_handler(void *arg, void *arg2)
@@ -200,4 +204,7 @@ static void lvgl_port_btn_up_handler(void *arg, void *arg2)
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
@@ -4,13 +4,27 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_heap_caps.h"
#include "esp_idf_version.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_lcd_panel_rgb.h"
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
#include "esp_lcd_mipi_dsi.h"
#endif
#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0))
#define LVGL_PORT_HANDLE_FLUSH_READY 0
@@ -25,26 +39,43 @@ static const char *TAG = "LVGL";
*******************************************************************************/
typedef struct {
lvgl_port_disp_type_t disp_type; /* Display type */
esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */
esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */
esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */
lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */
lv_color16_t *draw_buffs[2]; /* Display draw buffers */
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 */
struct {
unsigned int monochrome: 1; /* True, if display is monochrome and using 1bit for 1px */
unsigned int swap_bytes: 1; /* Swap bytes in RGB656 (16-bit) before send to LCD driver */
unsigned int full_refresh: 1; /* Always make the whole screen redrawn */
unsigned int direct_mode: 1; /* Use screen-sized buffers and draw to absolute coordinates */
unsigned int sw_rotate: 1; /* Use software rotation (slower) or PPA if available */
} flags;
} lvgl_port_display_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg);
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
#endif
#endif
static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map);
static void lvgl_port_disp_size_update_callback(lv_event_t *e);
static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx);
static void lvgl_port_display_invalidate_callback(lv_event_t *e);
/*******************************************************************************
* Public API functions
@@ -52,93 +83,106 @@ static void lvgl_port_disp_size_update_callback(lv_event_t *e);
lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)
{
esp_err_t ret = ESP_OK;
lv_display_t *disp = NULL;
lv_color16_t *buf1 = NULL;
lv_color16_t *buf2 = NULL;
assert(disp_cfg != NULL);
assert(disp_cfg->io_handle != NULL);
assert(disp_cfg->panel_handle != NULL);
assert(disp_cfg->buffer_size > 0);
assert(disp_cfg->hres > 0);
assert(disp_cfg->vres > 0);
/* Display context */
lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t));
ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!");
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->flags.swap_bytes = disp_cfg->flags.swap_bytes;
uint32_t buff_caps = MALLOC_CAP_DEFAULT;
if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram) {
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!");
} else if (disp_cfg->flags.buff_dma) {
buff_caps = MALLOC_CAP_DMA;
} else if (disp_cfg->flags.buff_spiram) {
buff_caps = MALLOC_CAP_SPIRAM;
}
/* alloc draw buffers used by LVGL */
/* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */
buf1 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color16_t), buff_caps);
ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!");
if (disp_cfg->double_buffer) {
buf2 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color16_t), buff_caps);
ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!");
}
disp_ctx->draw_buffs[0] = buf1;
disp_ctx->draw_buffs[1] = buf2;
lvgl_port_lock(0);
disp = lv_display_create(disp_cfg->hres, disp_cfg->vres);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
/* Monochrome display settings */
if (disp_cfg->monochrome) {
/* When using monochromatic display, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == disp_cfg->buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!");
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER;
disp_ctx->flags.monochrome = 1;
//lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
lv_display_set_buffers(disp, buf1, buf2, disp_cfg->buffer_size * sizeof(lv_color16_t), LV_DISPLAY_RENDER_MODE_FULL);
} else {
lv_display_set_buffers(disp, buf1, buf2, disp_cfg->buffer_size * sizeof(lv_color16_t), LV_DISPLAY_RENDER_MODE_PARTIAL);
}
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_set_driver_data(disp, disp_ctx);
disp_ctx->disp_drv = disp;
assert(disp_cfg->io_handle != NULL);
#if LVGL_PORT_HANDLE_FLUSH_READY
/* Register done callback */
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_ready_callback,
};
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp_ctx->disp_drv);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_io_ready_callback,
};
/* Register done callback */
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp);
#endif
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
}
lvgl_port_unlock();
err:
if (ret != ESP_OK) {
if (buf1) {
free(buf1);
}
if (buf2) {
free(buf2);
}
if (disp_ctx) {
free(disp_ctx);
return disp;
}
lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_dsi_cfg_t *dsi_cfg)
{
assert(dsi_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = dsi_cfg->flags.avoid_tearing,
};
lvgl_port_lock(0);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI;
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
if (dsi_cfg->flags.avoid_tearing) {
cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback;
} else {
cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
}
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, disp);
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!");
#endif
}
lvgl_port_unlock();
return disp;
}
lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_rgb_cfg_t *rgb_cfg)
{
lvgl_port_lock(0);
assert(rgb_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = rgb_cfg->flags.avoid_tearing,
};
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB;
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
/* Register done callback */
const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = {
.on_vsync = lvgl_port_flush_rgb_vsync_ready_callback,
};
const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
.on_bounce_frame_finish = lvgl_port_flush_rgb_vsync_ready_callback,
#endif
};
if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, disp_ctx->disp_drv));
} else {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, disp_ctx->disp_drv));
}
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!");
#endif
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
}
lvgl_port_unlock();
return disp;
}
@@ -160,6 +204,18 @@ esp_err_t lvgl_port_remove_disp(lv_display_t *disp)
free(disp_ctx->draw_buffs[1]);
}
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->trans_sem) {
vSemaphoreDelete(disp_ctx->trans_sem);
}
free(disp_ctx);
return ESP_OK;
@@ -175,20 +231,237 @@ void lvgl_port_flush_ready(lv_display_t *disp)
* Private functions
*******************************************************************************/
static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg)
{
esp_err_t ret = ESP_OK;
lv_display_t *disp = NULL;
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
uint32_t buffer_size = 0;
SemaphoreHandle_t trans_sem = NULL;
assert(disp_cfg != NULL);
assert(disp_cfg->panel_handle != NULL);
assert(disp_cfg->buffer_size > 0);
assert(disp_cfg->hres > 0);
assert(disp_cfg->vres > 0);
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 || 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 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 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)!");
}
/* Display context */
lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t));
ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!");
memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t));
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->control_handle = disp_cfg->control_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->flags.swap_bytes = disp_cfg->flags.swap_bytes;
disp_ctx->flags.sw_rotate = disp_cfg->flags.sw_rotate;
disp_ctx->current_rotation = LV_DISPLAY_ROTATION_0;
uint32_t buff_caps = 0;
#if SOC_PSRAM_DMA_CAPABLE == 0
if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram) {
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!");
}
#endif
if (disp_cfg->flags.buff_dma) {
buff_caps |= MALLOC_CAP_DMA;
}
if (disp_cfg->flags.buff_spiram) {
buff_caps |= MALLOC_CAP_SPIRAM;
}
if (buff_caps == 0) {
buff_caps |= MALLOC_CAP_DEFAULT;
}
/* Use RGB internal buffers for avoid tearing effect */
if (priv_cfg && priv_cfg->avoid_tearing) {
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#elif CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#endif
trans_sem = xSemaphoreCreateCounting(1, 0);
ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore");
disp_ctx->trans_sem = trans_sem;
} else {
/* alloc draw buffers used by LVGL */
/* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */
buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!");
if (disp_cfg->double_buffer) {
buf2 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!");
}
disp_ctx->draw_buffs[0] = buf1;
disp_ctx->draw_buffs[1] = buf2;
}
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!");
disp_ctx->flags.direct_mode = 1;
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_DIRECT);
} else if (disp_cfg->flags.full_refresh) {
/* When using full_refresh, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Full refresh must using full buffer!");
disp_ctx->flags.full_refresh = 1;
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL);
} else {
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL);
}
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);
lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_REFR_REQUEST, disp_ctx);
lv_display_set_driver_data(disp, disp_ctx);
disp_ctx->disp_drv = disp;
/* Use SW rotation */
if (disp_cfg->flags.sw_rotate) {
disp_ctx->draw_buffs[2] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(disp_ctx->draw_buffs[2], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (rotation buffer) allocation!");
}
err:
if (ret != ESP_OK) {
if (disp_ctx->draw_buffs[0]) {
free(disp_ctx->draw_buffs[0]);
}
if (disp_ctx->draw_buffs[1]) {
free(disp_ctx->draw_buffs[1]);
}
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);
}
if (trans_sem) {
vSemaphoreDelete(trans_sem);
}
}
return disp;
}
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
lv_disp_flush_ready(disp_drv);
return false;
}
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
lv_disp_flush_ready(disp_drv);
return false;
}
static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp_drv);
assert(disp_ctx != NULL);
if (disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield);
}
return (need_yield == pdTRUE);
}
#endif
static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t *color_map)
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
uint8_t *buf = color_map;
lv_color16_t *color = (lv_color16_t *)color_map;
BaseType_t need_yield = pdFALSE;
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp_drv);
assert(disp_ctx != NULL);
if (disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield);
}
return (need_yield == pdTRUE);
}
#endif
#endif
static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_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_driver_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;
@@ -199,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;
@@ -216,77 +503,174 @@ 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)
{
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
int32_t w = lv_area_get_width(area);
int32_t h = lv_area_get_height(area);
int32_t hres = lv_display_get_horizontal_resolution(disp);
int32_t vres = lv_display_get_vertical_resolution(disp);
if (rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270) {
vres = lv_display_get_horizontal_resolution(disp);
hres = lv_display_get_vertical_resolution(disp);
}
switch (rotation) {
case LV_DISPLAY_ROTATION_0:
return;
case LV_DISPLAY_ROTATION_90:
area->y2 = vres - area->x1 - 1;
area->x1 = area->y1;
area->x2 = area->x1 + h - 1;
area->y1 = area->y2 - w + 1;
break;
case LV_DISPLAY_ROTATION_180:
area->y2 = vres - area->y1 - 1;
area->y1 = area->y2 - h + 1;
area->x2 = hres - area->x1 - 1;
area->x1 = area->x2 - w + 1;
break;
case LV_DISPLAY_ROTATION_270:
area->x1 = hres - area->y2 - 1;
area->y2 = area->x2;
area->x2 = area->x1 + h - 1;
area->y1 = area->y2 - w + 1;
break;
}
}
static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map)
{
assert(drv != NULL);
assert(area != NULL);
assert(color_map != NULL);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(drv);
assert(disp_ctx != NULL);
//TODO: try to use SPI_SWAP_DATA_RX from https://docs.espressif.com/projects/esp-idf/en/v5.1/esp32s3/api-reference/peripherals/spi_master.html#c.SPI_SWAP_DATA_TX
if (disp_ctx->flags.swap_bytes) {
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
/* SW rotation enabled */
if (disp_ctx->flags.sw_rotate && (disp_ctx->current_rotation > LV_DISPLAY_ROTATION_0 || disp_ctx->flags.swap_bytes)) {
/* SW rotation */
if (disp_ctx->draw_buffs[2]) {
int32_t ww = lv_area_get_width(area);
int32_t hh = lv_area_get_height(area);
lv_color_format_t cf = lv_display_get_color_format(drv);
uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf);
uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf);
if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_180) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], hh, ww, h_stride, h_stride, LV_DISPLAY_ROTATION_180, cf);
} else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_90) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_90, cf);
} else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_270) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_270, cf);
}
color_map = (uint8_t *)disp_ctx->draw_buffs[2];
lvgl_port_rotate_area(drv, (lv_area_t *)area);
offsetx1 = area->x1;
offsetx2 = area->x2;
offsety1 = area->y1;
offsety2 = area->y2;
}
} else if (disp_ctx->flags.swap_bytes) {
size_t len = lv_area_get_size(area);
lv_draw_sw_rgb565_swap(color_map, len);
}
/* Transfor data in buffer for monochromatic screen */
/* 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);
}
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
// copy a buffer's content to a specific area of the display
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, 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)) {
if (lv_disp_flush_is_last(drv)) {
/* If the interface is I80 or SPI, this step cannot be used for drawing. */
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, 0, 0, lv_disp_get_hor_res(drv), lv_disp_get_ver_res(drv), color_map);
/* Waiting for the last frame buffer to complete transmission */
xSemaphoreTake(disp_ctx->trans_sem, 0);
xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY);
}
} else {
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, 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))) {
lv_disp_flush_ready(drv);
}
}
static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx)
{
assert(disp_ctx != NULL);
disp_ctx->current_rotation = lv_display_get_rotation(disp_ctx->disp_drv);
if (disp_ctx->flags.sw_rotate) {
return;
}
esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle);
/* Solve rotation screen and touch */
switch (lv_display_get_rotation(disp_ctx->disp_drv)) {
case LV_DISPLAY_ROTATION_0:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_90:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
}
break;
case LV_DISPLAY_ROTATION_180:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_270:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, disp_ctx->disp_drv);
}
static void lvgl_port_disp_size_update_callback(lv_event_t *e)
{
assert(e);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)e->user_data;
assert(disp_ctx != NULL);
esp_lcd_panel_handle_t panel_handle = disp_ctx->panel_handle;
/* Solve rotation screen and touch */
switch (lv_display_get_rotation(disp_ctx->disp_drv)) {
case LV_DISPLAY_ROTATION_0:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_90:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
}
break;
case LV_DISPLAY_ROTATION_180:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_270:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_event_get_user_data(e);
lvgl_port_disp_rotation_update(disp_ctx);
}
static void lvgl_port_display_invalidate_callback(lv_event_t *e)
{
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, NULL);
}
@@ -29,6 +29,7 @@ typedef struct {
static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2);
static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2);
static void lvgl_port_encoder_knob_handler(void *arg, void *arg2);
/*******************************************************************************
* Public API functions
@@ -52,6 +53,9 @@ lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
if (encoder_cfg->encoder_a_b != NULL) {
encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b);
ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!");
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_LEFT, lvgl_port_encoder_knob_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_knob_handler, encoder_ctx));
}
/* Encoder Enter */
@@ -69,12 +73,15 @@ lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
/* Register a encoder input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(indev, lvgl_port_encoder_read);
lv_indev_set_disp(indev, encoder_cfg->disp);
lv_indev_set_driver_data(indev, encoder_ctx);
encoder_ctx->indev = indev;
lvgl_port_unlock();
return indev;
err:
if (ret != ESP_OK) {
if (encoder_ctx->knob_handle != NULL) {
@@ -89,7 +96,7 @@ err:
free(encoder_ctx);
}
}
return encoder_ctx->indev;
return NULL;
}
esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder)
@@ -150,6 +157,9 @@ static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2)
ctx->btn_enter = true;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2)
@@ -162,4 +172,14 @@ static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2)
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_encoder_knob_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
@@ -26,6 +26,7 @@ typedef struct {
*******************************************************************************/
static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp);
/*******************************************************************************
* Public API functions
@@ -33,7 +34,8 @@ static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data
lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
{
lv_indev_t *indev;
esp_err_t ret = ESP_OK;
lv_indev_t *indev = NULL;
assert(touch_cfg != NULL);
assert(touch_cfg->disp != NULL);
assert(touch_cfg->handle != NULL);
@@ -46,16 +48,33 @@ lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
}
touch_ctx->handle = touch_cfg->handle;
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
/* Register touch interrupt callback */
ret = esp_lcd_touch_register_interrupt_callback_with_data(touch_ctx->handle, lvgl_port_touch_interrupt_callback, touch_ctx);
ESP_GOTO_ON_ERROR(ret, err, TAG, "Error in register touch interrupt.");
}
lvgl_port_lock(0);
/* Register a touchpad input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
/* Event mode can be set only, when touch interrupt enabled */
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
}
lv_indev_set_read_cb(indev, lvgl_port_touchpad_read);
lv_indev_set_disp(indev, touch_cfg->disp);
lv_indev_set_driver_data(indev, touch_ctx);
touch_ctx->indev = indev;
lvgl_port_unlock();
err:
if (ret != ESP_OK) {
if (touch_ctx) {
free(touch_ctx);
}
}
return indev;
}
@@ -69,6 +88,11 @@ esp_err_t lvgl_port_remove_touch(lv_indev_t *touch)
lv_indev_delete(touch);
lvgl_port_unlock();
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
/* Unregister touch interrupt callback */
esp_lcd_touch_register_interrupt_callback(touch_ctx->handle, NULL);
}
if (touch_ctx) {
free(touch_ctx);
}
@@ -105,3 +129,11 @@ static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void IRAM_ATTR lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp)
{
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *) tp->config.user_data;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, touch_ctx->indev);
}
@@ -4,10 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"
@@ -91,6 +95,7 @@ lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *m
/* Register a mouse input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_mouse);
lv_indev_set_disp(indev, mouse_cfg->disp);
lv_indev_set_driver_data(indev, hid_ctx);
@@ -124,6 +129,7 @@ lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cf
/* Register a mouse input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_kb);
lv_indev_set_disp(indev, keyboard_cfg->disp);
lv_indev_set_driver_data(indev, hid_ctx);
@@ -325,6 +331,8 @@ static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t h
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->kb.indev);
} else if (dev.proto == HID_PROTOCOL_MOUSE) {
hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data;
if (data_length < sizeof(hid_mouse_input_report_boot_t)) {
@@ -333,6 +341,9 @@ static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t h
hid_ctx->mouse.left_button = mouse->buttons.button1;
hid_ctx->mouse.x += mouse->x_displacement;
hid_ctx->mouse.y += mouse->y_displacement;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->mouse.indev);
}
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL ARGB8888 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_argb8888_esp
.type lv_color_blend_to_argb8888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_argb8888_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint32_t
l32i.n a5, a2, 12 // a5 - dest_h in uint32_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w
movi a7, 0xff000000 // oppactiy mask
or a10, a7, a8 // apply oppacity
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3
addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_mod_4_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
@@ -0,0 +1,326 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL ARGB8888 simple fill for ESP32S3 processor
.section .text
.align 4
.global lv_color_blend_to_argb8888_esp
.type lv_color_blend_to_argb8888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_argb8888_esp:
entry a1, 32
ee.zero.q q0 // dummy TIE instruction, to enable the TIE
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint32_t
l32i.n a5, a2, 12 // a5 - dest_h in uint32_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w
movi a7, 0xff000000 // oppactiy mask
or a10, a7, a8 // apply oppacity
// Check for short lengths
// dest_w should be at least 8, othewise it's not worth using esp32s3 TIE
bgei a4, 8, _esp32s3_implementation // Branch if dest_w is greater than or equal to 8
j .lv_color_blend_to_argb8888_esp32_body // Jump to esp32 implementation
_esp32s3_implementation:
ee.movi.32.q q0, a10, 0 // fill q0 register from a10 by 32 bits
ee.movi.32.q q0, a10, 1
ee.movi.32.q q0, a10, 2
ee.movi.32.q q0, a10, 3
// Check dest_buff alignment
movi.n a7, 0xf // 0xf alignment mask (16-byte alignment)
and a15, a7, a3 // 16-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 16-byte alignment mask AND dest_stride
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_w_bytes alignment
and a15, a7, a11 // 16-byte alignment mask AND dest_w_bytes
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// all aligned, the most ideal case
// dest_buff (a3) - 16-byte aligned
// dest_stride (a6) - 16-byte multiple
// dest_w (a4) - 16-byte multiple
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop_aligned:
loopnez a9, ._main_loop_aligned // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_aligned:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_4byte:
// Check dest_buff alignment
movi.n a7, 0x3 // 0x3 alignment mask (4-byte alignment)
and a15, a7, a3 // 4-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 4-byte alignment mask AND dest_stride pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 16-byte aligned
// dest_w is always 4-byte multiple
// all of the following are 4-byte aligned
// dest_buff (a3) - 16-byte, or 4-byte aligned
// dest_stride (a6) - 16-byte, or 4-byte multiple
// dest_w (a4) - 4-byte multiple
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_4byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_4byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_4byte // branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_4byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_4byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_4byte:
_dest_buff_aligned_by_4byte:
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_4byte // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_4byte:
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_4byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
_aligned_mod_8_check_4byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_4byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_4byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_4byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_1byte:
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 4-byte aligned
// dest_w is always 4-byte multiple
// dest_buff (a3) - 4-byte, or 1-byte aligned
// dest_stride (a6) - 4-byte, or 1-byte multiple
// dest_w (a4) - 4-byte multiple
ee.zero.q q1 // clear q1
ee.orq q1, q1, q0 // copy q0 to q1
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_1byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_1byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_1byte// branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_1byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_1byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_1byte:
// Check modulo 2 and 1 (the following 2 ifs do the same correction)
// modulo 2 and modulo 1 requires the same action, just once
bbci a15, 1, _aligning_mod_2_check_1byte
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _dest_buff_aligned_by_1byte
_aligning_mod_2_check_1byte:
bbci a15, 0, _dest_buff_aligned_by_1byte
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_dest_buff_aligned_by_1byte:
// Shift q reg, allowing to set 16-byte unaligned adata
wur.sar_byte a15 // apply unalignment to the SAR_BYTE
ee.src.q q2, q0, q1 // shift concat. of q0 and q1 to q2 by SAR_BYTE amount
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_1byte // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q2, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_1byte:
// Firstly check mod 1 and mod 2 - correcting the aligned memory access
// Go back in one Byte, allow to correct after ee.vst.128.ip aligned access
addi a3, a3, -4
// Check modulo 2 of the dest_w, if - then set 2 bytes
// set SSSS in 0xSSSS0000
bbci a12, 1, _aligned_mod_2_check_1byte // branch if 1-st bit of dest_w a12 is clear
srli a14, a10, 16 // shift a10 in 16, allowing s16i (saving of lower 16 bits)
s16i a14, a3, 2 // save 16 bits from a10 to dest_buff a3, offset 2 bytes
// Check modulo 1 of the dest_w, if - then set 1 byte
// additionally set SS in 0x0000SS00
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 8 // shift a10 in 8, allowing s8i
s8i a14, a3, 1 // save 8 bits from a10 to dest_buff a3, offset 1 byte
j _aligned_end
_aligned_mod_2_check_1byte:
// Check modulo 1 of the dest_w, if - then set 1 byte
// set SS in 0xSS000000
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 24 // shift a10 in 24, allowing s8i (saving of lower 8 bits)
s8i a14, a3, 3 // save 8 bits from a10 to dest_buff a3, offset 3 bytes
_aligned_end:
addi a3, a3, 4 // Increase the pointer back, correction for addi a3, a3, -4
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_1byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 4 bytes
//ee.vst.l.64.ip q2, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
_aligned_mod_8_check_1byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_1byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_1byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_1byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
.lv_color_blend_to_argb8888_esp32_body:
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3
addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_mod_4_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
@@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB565 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_rgb565_esp
.type lv_color_blend_to_rgb565_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_rgb565_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint16_t
l32i.n a5, a2, 12 // a5 - dest_h in uint16_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// Convert color to rgb656
l8ui a15, a7, 2 // red
movi.n a14, 0xf8
and a13, a15, a14
slli a10, a13, 8
l8ui a15, a7, 0 // blue
and a13, a15, a14
srli a12, a13, 3
add a10, a10, a12
l8ui a15, a7, 1 // green
movi.n a14, 0xfc
and a13, a15, a14
slli a12, a13, 3
add a12, a10, a12 // a12 = 16-bit color
slli a10, a12, 16
movi.n a13, 0xFFFF0000
and a10, a10, a13
or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16))
movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// cache init
// Prepare main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w
addi a4, a4, -1 // a4-- (decrement a4)
s32i.n a9, a1, 0 // cache.orig.loop_len
s32i.n a11, a1, 4 // cache.orig.dest_w_bytes
// Prepare decreased main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1)
s32i.n a9, a1, 8 // cache.decr.loop_len
s32i.n a11, a1, 12 // cache.decr.dest_w_bytes
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
.outer_loop:
// Check if the des_buff is 2-byte aligned
beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len
l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2
j _dest_buff_unaligned
_dest_buff_2_byte_aligned:
l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len
l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes
_dest_buff_unaligned:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16
s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20
s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24
s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28
addi.n a3, a3, 32 // increment dest_buff pointer by 32
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 16 bytes
bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
addi.n a3, a3, 16 // increment dest_buff pointer by 16
_mod_16_check:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
addi.n a3, a3, 4 // increment dest_buff pointer by 4
_mod_4_check:
// Check modulo 2 of the dest_w_bytes, if - then set 2 bytes
bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_mod_2_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
@@ -0,0 +1,405 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB565 simple fill for ESP32S3 processor
.section .text
.align 4
.global lv_color_blend_to_rgb565_esp
.type lv_color_blend_to_rgb565_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_rgb565_esp:
entry a1, 32
ee.zero.q q0 // dummy TIE instruction, to enable the TIE
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint16_t
l32i.n a5, a2, 12 // a5 - dest_h in uint16_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// Convert color to rgb656
l8ui a15, a7, 2 // red
movi.n a14, 0xf8
and a13, a15, a14
slli a10, a13, 8
l8ui a15, a7, 0 // blue
and a13, a15, a14
srli a12, a13, 3
add a10, a10, a12
l8ui a15, a7, 1 // green
movi.n a14, 0xfc
and a13, a15, a14
slli a12, a13, 3
add a12, a10, a12 // a12 = 16-bit color
slli a10, a12, 16
movi.n a13, 0xFFFF0000
and a10, a10, a13
or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16))
// Check for short lengths
// dest_w should be at least 16, othewise it's not worth using esp32s3 TIE
bgei a4, 16, _esp32s3_implementation // Branch if dest_w is greater than or equal to 16
j .lv_color_blend_to_rgb565_esp32_body // Jump to esp32 implementation
_esp32s3_implementation:
ee.movi.32.q q0, a10, 0 // fill q0 register from a10 by 32 bits
ee.movi.32.q q0, a10, 1
ee.movi.32.q q0, a10, 2
ee.movi.32.q q0, a10, 3
// Check dest_buff alignment
movi.n a7, 0xf // 0xf alignment mask (16-byte alignment)
and a15, a7, a3 // 16-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 16-byte alignment mask AND dest_stride
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_w_bytes alignment
and a15, a7, a11 // 16-byte alignment mask AND dest_w_bytes
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// all aligned, the most ideal case
// dest_buff (a3) - 16-byte aligned
// dest_stride (a6) - 16-byte multiple
// dest_w (a4) - 16-byte multiple
srli a9, a4, 3 // a9 - loop_len = dest_w / 8
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop_aligned:
loopnez a9, ._main_loop_aligned // 16 bytes (8 rgb565) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_aligned:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_4byte:
// Check dest_buff alignment
movi.n a7, 0x3 // 0x3 alignment mask (4-byte alignment)
and a15, a7, a3 // 4-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 4-byte alignment mask AND dest_stride pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 16-byte aligned
// dest_w is always 4-byte multiple
// all of the following are 4-byte aligned
// dest_buff (a3) - 16-byte, or 4-byte aligned
// dest_stride (a6) - 16-byte, or 4-byte multiple
// dest_w (a4) - 4-byte multiple
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_4byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_4byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_4byte // branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_4byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_4byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_4byte:
_dest_buff_aligned_by_4byte:
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_4byte // 16 bytes (8 rgb565) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_4byte:
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_4byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
_aligned_mod_8_check_4byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_4byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_4byte:
// Check modulo 2 of the dest_w, if - then set 2 bytes
bbci a12, 1, _aligned_mod_2_check_4byte // branch if 1-st bit of local_dest_w_bytes a12 is clear
s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_aligned_mod_2_check_4byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_4byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_1byte:
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 4-byte aligned
// dest_w is always 4-byte multiple
// dest_buff (a3) - 4-byte, or 1-byte aligned
// dest_stride (a6) - 4-byte, or 1-byte multiple
// dest_w (a4) - 4-byte multiple
ee.zero.q q1 // clear q1
ee.orq q1, q1, q0 // copy q0 to q1
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_1byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_1byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_1byte// branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_1byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_1byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_1byte:
// Check modulo 2 and 1
// modulo 2 and modulo 1 requires the same action
bbci a15, 1, _aligning_mod_2_check_1byte // branch if 1-st bit unalignment a15 is clear
s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_aligning_mod_2_check_1byte:
bbci a15, 0, _dest_buff_aligned_by_1byte // branch if 0-st bit unalignment a15 is clear
s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_dest_buff_aligned_by_1byte:
// Shift q reg, allowing to set 16-byte unaligned adata
wur.sar_byte a15 // apply unalignment to the SAR_BYTE
ee.src.q q2, q0, q1 // shift concat. of q0 and q1 to q2 by SAR_BYTE amount
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_1byte // 16 bytes (8 rgb565) in one loop
ee.vst.128.ip q2, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_1byte:
// Firstly check mod 1 and mod 2 - correcting the aligned memory access
// Go back in one Byte, allow to correct after ee.vst.128.ip aligned access
addi a3, a3, -4
// Check modulo 2 of the dest_w, if - then set 2 bytes
// set SSSS in 0xSSSS0000
bbci a12, 1, _aligned_mod_2_check_1byte_corr // branch if 1-st bit of dest_w a12 is clear
srli a14, a10, 16 // shift a10 in 16, allowing s16i (saving of lower 16 bits)
s16i a14, a3, 2 // save 16 bits from a10 to dest_buff a3, offset 2 bytes
// Check modulo 1 of the dest_w, if - then set 1 byte
// additionally set SS in 0x0000SS00
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 8 // shift a10 in 8, allowing s8i
s8i a14, a3, 1 // save 8 bits from a10 to dest_buff a3, offset 1 byte
j _aligned_end
_aligned_mod_2_check_1byte_corr:
// Check modulo 1 of the dest_w, if - then set 1 byte
// set SS in 0xSS000000
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 24 // shift a10 in 24, allowing s8i (saving of lower 8 bits)
s8i a14, a3, 3 // save 8 bits from a10 to dest_buff a3, offset 3 bytes
_aligned_end:
addi a3, a3, 4 // Increase the pointer back, correction for addi a3, a3, -4
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_1byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 4 bytes
_aligned_mod_8_check_1byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_1byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_1byte:
// Check modulo 2 of the dest_w, if - then set 2 bytes
bbci a12, 1, _aligned_mod_2_check_1byte // branch if 1-st bit of local_dest_w_bytes a12 is clear
s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_aligned_mod_2_check_1byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_1byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
.lv_color_blend_to_rgb565_esp32_body:
movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// cache init
// Prepare main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w
addi a4, a4, -1 // a4-- (decrement a4)
s32i.n a9, a1, 0 // cache.orig.loop_len
s32i.n a11, a1, 4 // cache.orig.dest_w_bytes
// Prepare decreased main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1)
s32i.n a9, a1, 8 // cache.decr.loop_len
s32i.n a11, a1, 12 // cache.decr.dest_w_bytes
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
.outer_loop:
// Check if the des_buff is 2-byte aligned
beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len
l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2
j _dest_buff_unaligned
_dest_buff_2_byte_aligned:
l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len
l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes
_dest_buff_unaligned:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16
s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20
s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24
s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28
addi.n a3, a3, 32 // increment dest_buff pointer by 32
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 16 bytes
bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
addi.n a3, a3, 16 // increment dest_buff pointer by 16
_mod_16_check:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
addi.n a3, a3, 4 // increment dest_buff pointer by 4
_mod_4_check:
// Check modulo 2 of the dest_w_bytes, if - then set 2 bytes
bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_mod_2_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return