add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_err.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "soc/soc_caps.h"
#include "lcd_ppa.h"
#define PPA_LCD_ENABLE_CB 0
#if SOC_PPA_SUPPORTED
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
struct lvgl_port_ppa_t {
uint8_t *buffer;
uint32_t buffer_size;
ppa_client_handle_t srm_handle;
uint32_t color_type_id;
};
static const char *TAG = "PPA";
/*******************************************************************************
* Function definitions
*******************************************************************************/
#if PPA_LCD_ENABLE_CB
static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data);
#endif
/*******************************************************************************
* Public API functions
*******************************************************************************/
lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg)
{
esp_err_t ret = ESP_OK;
assert(cfg != NULL);
lvgl_port_ppa_t *ppa_ctx = malloc(sizeof(lvgl_port_ppa_t));
ESP_GOTO_ON_FALSE(ppa_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for PPA context allocation!");
memset(ppa_ctx, 0, sizeof(lvgl_port_ppa_t));
uint32_t buffer_caps = 0;
if (cfg->flags.buff_dma) {
buffer_caps |= MALLOC_CAP_DMA;
}
if (cfg->flags.buff_spiram) {
buffer_caps |= MALLOC_CAP_SPIRAM;
}
if (buffer_caps == 0) {
buffer_caps |= MALLOC_CAP_DEFAULT;
}
ppa_ctx->buffer_size = ALIGN_UP(cfg->buffer_size, CONFIG_CACHE_L2_CACHE_LINE_SIZE);
ppa_ctx->buffer = heap_caps_aligned_calloc(CONFIG_CACHE_L2_CACHE_LINE_SIZE, ppa_ctx->buffer_size, sizeof(uint8_t), buffer_caps);
assert(ppa_ctx->buffer != NULL);
ppa_client_config_t ppa_client_config = {
.oper_type = PPA_OPERATION_SRM,
};
ESP_GOTO_ON_ERROR(ppa_register_client(&ppa_client_config, &ppa_ctx->srm_handle), err, TAG, "Error when registering PPA client!");
#if PPA_LCD_ENABLE_CB
ppa_event_callbacks_t ppa_cbs = {
.on_trans_done = _lvgl_port_ppa_callback,
};
ESP_GOTO_ON_ERROR(ppa_client_register_event_callbacks(ppa_ctx->srm_handle, &ppa_cbs), err, TAG, "Error when registering PPA callbacks!");
#endif
ppa_ctx->color_type_id = COLOR_TYPE_ID(cfg->color_space, cfg->pixel_format);
err:
if (ret != ESP_OK) {
if (ppa_ctx->buffer) {
free(ppa_ctx->buffer);
}
if (ppa_ctx) {
free(ppa_ctx);
}
}
return ppa_ctx;
}
void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle)
{
lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle;
assert(ppa_ctx != NULL);
if (ppa_ctx->buffer) {
free(ppa_ctx->buffer);
}
ppa_unregister_client(ppa_ctx->srm_handle);
free(ppa_ctx);
}
uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle)
{
lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle;
assert(ppa_ctx != NULL);
return ppa_ctx->buffer;
}
esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg)
{
lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle;
assert(ppa_ctx != NULL);
assert(rotate_cfg != NULL);
const int w = rotate_cfg->area.x2 - rotate_cfg->area.x1 + 1;
const int h = rotate_cfg->area.y2 - rotate_cfg->area.y1 + 1;
/* Set dimension by screen size and rotation */
int out_w = w;
int out_h = h;
int x1 = rotate_cfg->area.x1;
int x2 = rotate_cfg->area.x2;
int y1 = rotate_cfg->area.y1;
int y2 = rotate_cfg->area.y2;
/* Rotate coordinates */
switch (rotate_cfg->rotation) {
case PPA_SRM_ROTATION_ANGLE_0:
break;
case PPA_SRM_ROTATION_ANGLE_90:
out_w = h;
out_h = w;
x1 = rotate_cfg->area.y1;
x2 = rotate_cfg->area.y2;
y1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1;
y2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1;
break;
case PPA_SRM_ROTATION_ANGLE_180:
x1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1;
x2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1;
y1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1;
y2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1;
break;
case PPA_SRM_ROTATION_ANGLE_270:
out_w = h;
out_h = w;
x1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1;
x2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1;
y1 = rotate_cfg->area.x1;
y2 = rotate_cfg->area.x2;
break;
}
/* Return new coordinates */
rotate_cfg->area.x1 = x1;
rotate_cfg->area.x2 = x2;
rotate_cfg->area.y1 = y1;
rotate_cfg->area.y2 = y2;
/* Prepare Operation */
ppa_srm_oper_config_t srm_oper_config = {
.in.buffer = rotate_cfg->in_buff,
.in.pic_w = w,
.in.pic_h = h,
.in.block_w = w,
.in.block_h = h,
.in.block_offset_x = 0,
.in.block_offset_y = 0,
.in.srm_cm = ppa_ctx->color_type_id,
.out.buffer = ppa_ctx->buffer,
.out.buffer_size = ppa_ctx->buffer_size,
.out.pic_w = out_w,
.out.pic_h = out_h,
.out.block_offset_x = 0,
.out.block_offset_y = 0,
.out.srm_cm = ppa_ctx->color_type_id,
.rotation_angle = rotate_cfg->rotation,
.scale_x = 1.0,
.scale_y = 1.0,
.byte_swap = rotate_cfg->swap_bytes,
.mode = rotate_cfg->ppa_mode,
.user_data = rotate_cfg->user_data,
};
return ppa_do_scale_rotate_mirror(ppa_ctx->srm_handle, &srm_oper_config);
}
#if PPA_LCD_ENABLE_CB
static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data)
{
return false;
}
#endif
#endif

View File

@@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief LCD PPA
*/
#pragma once
#include "driver/ppa.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct lvgl_port_ppa_t lvgl_port_ppa_t;
typedef lvgl_port_ppa_t *lvgl_port_ppa_handle_t;
/**
* @brief Init configuration structure
*/
typedef struct {
uint32_t buffer_size; /*!< Size of the buffer for the PPA */
color_space_t color_space; /*!< Color space of input/output data */
uint32_t pixel_format; /*!< Pixel format of input/output data */
struct {
unsigned int buff_dma: 1; /*!< Allocated buffer will be DMA capable */
unsigned int buff_spiram: 1; /*!< Allocated buffer will be in PSRAM */
} flags;
} lvgl_port_ppa_cfg_t;
/**
* @brief Display area structure
*/
typedef struct {
uint16_t x1;
uint16_t x2;
uint16_t y1;
uint16_t y2;
} lvgl_port_ppa_disp_area_t;
/**
* @brief Display size structure
*/
typedef struct {
uint32_t hres;
uint32_t vres;
} lvgl_port_ppa_disp_size_t;
/**
* @brief Rotation configuration
*/
typedef struct {
uint8_t *in_buff; /*!< Input buffer for rotation */
lvgl_port_ppa_disp_area_t area; /*!< Coordinates of area */
lvgl_port_ppa_disp_size_t disp_size; /*!< Display size */
ppa_srm_rotation_angle_t rotation; /*!< Output rotation */
ppa_trans_mode_t ppa_mode; /*!< Blocking or non-blocking mode */
bool swap_bytes; /*!< SWAP bytes */
void *user_data;
} lvgl_port_ppa_disp_rotate_t;
/**
* @brief Initialize PPA
*
* @note This function initialize PPA SRM Client and create buffer for process.
*
* @param cfg Configuration structure
*
* @return
* - PPA LCD handle
*/
lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg);
/**
* @brief Remove PPA
*
* @param handle PPA LCD handle
*
* @note This function free buffer and deinitialize PPA.
*/
void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle);
/**
* @brief Get output buffer
*
* @param handle PPA LCD handle
*
* @note This function get allocated buffer for output of PPA operation.
*/
uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle);
/**
* @brief Do rotation
*
* @param handle PPA LCD handle
* @param rotate_cfg Rotation settings
*
* @return
* - ESP_OK on success
* - ESP_ERR_NO_MEM if memory allocation fails
*/
esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,246 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* 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/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;
int timer_period_ms;
} lvgl_port_ctx_t;
/*******************************************************************************
* Local variables
*******************************************************************************/
static lvgl_port_ctx_t lvgl_port_ctx;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_task(void *arg);
static esp_err_t lvgl_port_tick_init(void);
static void lvgl_port_task_deinit(void);
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1));
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;
}
/* 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, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task);
} else {
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!");
err:
if (ret != ESP_OK) {
lvgl_port_deinit();
}
return ret;
}
esp_err_t lvgl_port_resume(void)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (lvgl_port_ctx.tick_timer != NULL) {
lv_timer_enable(true);
ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000);
}
return ret;
}
esp_err_t lvgl_port_stop(void)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (lvgl_port_ctx.tick_timer != NULL) {
lv_timer_enable(false);
ret = esp_timer_stop(lvgl_port_ctx.tick_timer);
}
return ret;
}
esp_err_t lvgl_port_deinit(void)
{
/* Stop and delete timer */
if (lvgl_port_ctx.tick_timer != NULL) {
esp_timer_stop(lvgl_port_ctx.tick_timer);
esp_timer_delete(lvgl_port_ctx.tick_timer);
lvgl_port_ctx.tick_timer = NULL;
}
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
}
/* 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;
}
bool lvgl_port_lock(uint32_t timeout_ms)
{
assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first");
const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE;
}
void lvgl_port_unlock(void)
{
assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first");
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
*******************************************************************************/
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) {
if (lvgl_port_lock(0)) {
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
}
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 < 5) {
task_delay_ms = 5;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_mux);
/* Close task */
vTaskDelete( NULL );
}
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 */
lv_deinit();
#endif
}
static void lvgl_port_tick_increment(void *arg)
{
/* Tell LVGL how many milliseconds have elapsed */
lv_tick_inc(lvgl_port_ctx.timer_period_ms);
}
static esp_err_t lvgl_port_tick_init(void)
{
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_port_tick_increment,
.name = "LVGL tick",
};
ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, "Creating LVGL timer filed!");
return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000);
}

View File

@@ -0,0 +1,210 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef enum {
LVGL_PORT_NAV_BTN_PREV,
LVGL_PORT_NAV_BTN_NEXT,
LVGL_PORT_NAV_BTN_ENTER,
LVGL_PORT_NAV_BTN_CNT,
} lvgl_port_nav_btns_t;
typedef struct {
button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */
lv_indev_drv_t indev_drv; /* LVGL input device driver */
bool btn_prev; /* Button prev state */
bool btn_next; /* Button next state */
bool btn_enter; /* Button enter state */
} lvgl_port_nav_btns_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_btn_down_handler(void *arg, void *arg2);
static void lvgl_port_btn_up_handler(void *arg, void *arg2);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg)
{
esp_err_t ret = ESP_OK;
lv_indev_t *indev = NULL;
assert(buttons_cfg != NULL);
assert(buttons_cfg->disp != NULL);
/* Touch context */
lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t));
if (buttons_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for buttons context allocation!");
return NULL;
}
#if BUTTON_VER_MAJOR < 4
/* Previous button */
if (buttons_cfg->button_prev != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
/* Next button */
if (buttons_cfg->button_next != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
/* Enter button */
if (buttons_cfg->button_enter != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
#else
ESP_GOTO_ON_FALSE(buttons_cfg->button_prev && buttons_cfg->button_next && buttons_cfg->button_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid some button handler!");
buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = buttons_cfg->button_prev;
buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = buttons_cfg->button_next;
buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = buttons_cfg->button_enter;
#endif
/* Button handlers */
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
#if BUTTON_VER_MAJOR < 4
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx));
#else
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, NULL, lvgl_port_btn_down_handler, buttons_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, NULL, lvgl_port_btn_up_handler, buttons_ctx));
#endif
}
buttons_ctx->btn_prev = false;
buttons_ctx->btn_next = false;
buttons_ctx->btn_enter = false;
/* Register a touchpad input device */
lv_indev_drv_init(&buttons_ctx->indev_drv);
buttons_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER;
buttons_ctx->indev_drv.disp = buttons_cfg->disp;
buttons_ctx->indev_drv.read_cb = lvgl_port_navigation_buttons_read;
buttons_ctx->indev_drv.user_data = buttons_ctx;
buttons_ctx->indev_drv.long_press_repeat_time = 300;
indev = lv_indev_drv_register(&buttons_ctx->indev_drv);
err:
if (ret != ESP_OK) {
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
if (buttons_ctx->btn[i] != NULL) {
iot_button_delete(buttons_ctx->btn[i]);
}
}
if (buttons_ctx != NULL) {
free(buttons_ctx);
}
}
return indev;
}
esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons)
{
assert(buttons);
lv_indev_drv_t *indev_drv = buttons->driver;
assert(indev_drv);
lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data;
/* Remove input device driver */
lv_indev_delete(buttons);
if (buttons_ctx) {
free(buttons_ctx);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
static uint32_t last_key = 0;
assert(indev_drv);
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data;
assert(ctx);
/* Buttons */
if (ctx->btn_prev) {
data->key = LV_KEY_LEFT;
last_key = LV_KEY_LEFT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_next) {
data->key = LV_KEY_RIGHT;
last_key = LV_KEY_RIGHT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_enter) {
data->key = LV_KEY_ENTER;
last_key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->key = last_key;
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void lvgl_port_btn_down_handler(void *arg, void *arg2)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = true;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = true;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
ctx->btn_enter = true;
}
}
}
static void lvgl_port_btn_up_handler(void *arg, void *arg2)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = false;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = false;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
ctx->btn_enter = false;
}
}
}

View File

@@ -0,0 +1,561 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* 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
#else
#define LVGL_PORT_HANDLE_FLUSH_READY 1
#endif
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
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 */
uint32_t trans_size; /* Maximum size for one transport */
SemaphoreHandle_t trans_sem; /* Idle transfer mutex */
} lvgl_port_display_ctx_t;
/*******************************************************************************
* 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_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);
static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)
{
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
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;
assert(disp_ctx->io_handle != NULL);
#if LVGL_PORT_HANDLE_FLUSH_READY
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
}
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,
};
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;
}
/* 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;
}
esp_err_t lvgl_port_remove_disp(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;
if (disp_ctx->trans_sem) {
vSemaphoreDelete(disp_ctx->trans_sem);
}
lv_disp_remove(disp);
if (disp_drv) {
if (disp_drv->draw_ctx) {
disp_drv->draw_ctx_deinit(disp_drv, disp_drv->draw_ctx);
lv_mem_free(disp_drv->draw_ctx);
disp_drv->draw_ctx = NULL;
}
if (disp_drv->draw_buf && disp_drv->draw_buf->buf1) {
free(disp_drv->draw_buf->buf1);
disp_drv->draw_buf->buf1 = NULL;
}
if (disp_drv->draw_buf && disp_drv->draw_buf->buf2) {
free(disp_drv->draw_buf->buf2);
disp_drv->draw_buf->buf2 = NULL;
}
if (disp_drv->draw_buf) {
free(disp_drv->draw_buf);
disp_drv->draw_buf = NULL;
}
}
free(disp_ctx);
return ESP_OK;
}
void lvgl_port_flush_ready(lv_disp_t *disp)
{
assert(disp);
assert(disp->driver);
lv_disp_flush_ready(disp->driver);
}
/*******************************************************************************
* 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_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;
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;
}
#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)
{
assert(drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data;
assert(disp_ctx != NULL);
int x_draw_start;
int x_draw_end;
int y_draw_start;
int y_draw_end;
int y_start_tmp;
int y_end_tmp;
int trans_count;
int trans_line;
int max_line;
const int x_start = area->x1;
const int x_end = area->x2;
const int y_start = area->y1;
const int y_end = area->y2;
const int width = x_end - x_start + 1;
const int height = y_end - y_start + 1;
lv_color_t *from = color_map;
lv_color_t *to = NULL;
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);
trans_count = height / max_line + (height % max_line ? (1) : (0));
for (int i = 0; i < trans_count; i++) {
trans_line = (y_end - y_start_tmp + 1) > max_line ? max_line : (y_end - y_start_tmp + 1);
y_end_tmp = (y_end - y_start_tmp + 1) > max_line ? (y_start_tmp + max_line - 1) : y_end;
to = disp_ctx->trans_buf;
for (int y = 0; y < trans_line; y++) {
for (int x = 0; x < width; x++) {
*(to + y * (width) + x) = *(from + y * (width) + x);
}
}
x_draw_start = x_start;
x_draw_end = x_end;
y_draw_start = y_start_tmp;
y_draw_end = y_end_tmp;
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to);
from += max_line * width;
y_start_tmp += max_line;
xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY);
}
}
}
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 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(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(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_DISP_ROT_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_DISP_ROT_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;
}
}
static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa)
{
if (drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) {
lv_coord_t tmp_x = x;
x = y;
y = tmp_x;
}
/* Write to the buffer as required for the display.
* It writes only 1-bit for monochrome displays mapped vertically.*/
buf += drv->hor_res * (y >> 3) + x;
if (lv_color_to1(color)) {
(*buf) &= ~(1 << (y % 8));
} else {
(*buf) |= (1 << (y % 8));
}
}

View File

@@ -0,0 +1,214 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
knob_handle_t knob_handle; /* Encoder knob handlers */
button_handle_t btn_handle; /* Encoder button handlers */
lv_indev_drv_t indev_drv; /* LVGL input device driver */
bool btn_enter; /* Encoder button enter state */
int32_t diff; /* Encoder diff */
} lvgl_port_encoder_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_encoder_read(lv_indev_drv_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_left_handler(void *arg, void *arg2);
static void lvgl_port_encoder_right_handler(void *arg, void *arg2);
static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
{
esp_err_t ret = ESP_OK;
lv_indev_t *indev = NULL;
assert(encoder_cfg != NULL);
assert(encoder_cfg->disp != NULL);
/* Encoder context */
lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t));
if (encoder_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for encoder context allocation!");
return NULL;
}
/* Encoder_a/b */
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_left_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_right_handler, encoder_ctx));
/* Encoder Enter */
if (encoder_cfg->encoder_enter != NULL) {
#if BUTTON_VER_MAJOR < 4
encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter);
ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
#else
ESP_GOTO_ON_FALSE(encoder_cfg->encoder_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid button handler!");
encoder_ctx->btn_handle = encoder_cfg->encoder_enter;
#endif
}
#if BUTTON_VER_MAJOR < 4
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, encoder_ctx));
#else
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, NULL, lvgl_port_encoder_btn_down_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, NULL, lvgl_port_encoder_btn_up_handler, encoder_ctx));
#endif
encoder_ctx->btn_enter = false;
encoder_ctx->diff = 0;
/* Register a encoder input device */
lv_indev_drv_init(&encoder_ctx->indev_drv);
encoder_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER;
encoder_ctx->indev_drv.disp = encoder_cfg->disp;
encoder_ctx->indev_drv.read_cb = lvgl_port_encoder_read;
encoder_ctx->indev_drv.user_data = encoder_ctx;
indev = lv_indev_drv_register(&encoder_ctx->indev_drv);
err:
if (ret != ESP_OK) {
if (encoder_ctx->knob_handle != NULL) {
iot_knob_delete(encoder_ctx->knob_handle);
}
if (encoder_ctx->btn_handle != NULL) {
iot_button_delete(encoder_ctx->btn_handle);
}
if (encoder_ctx != NULL) {
free(encoder_ctx);
}
}
return indev;
}
esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder)
{
assert(encoder);
lv_indev_drv_t *indev_drv = encoder->driver;
assert(indev_drv);
lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data;
if (encoder_ctx->knob_handle != NULL) {
iot_knob_delete(encoder_ctx->knob_handle);
}
if (encoder_ctx->btn_handle != NULL) {
iot_button_delete(encoder_ctx->btn_handle);
}
if (encoder_ctx != NULL) {
free(encoder_ctx);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data;
assert(ctx);
data->enc_diff = ctx->diff;
data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
ctx->diff = 0;
}
static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* ENTER */
if (button == ctx->btn_handle) {
ctx->btn_enter = true;
}
}
}
static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* ENTER */
if (button == ctx->btn_handle) {
ctx->btn_enter = false;
}
}
}
static void lvgl_port_encoder_left_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
knob_handle_t knob = (knob_handle_t)arg;
if (ctx && knob) {
/* LEFT */
if (knob == ctx->knob_handle) {
int32_t diff = lvgl_port_calculate_diff(knob, KNOB_LEFT);
ctx->diff = (ctx->diff > 0) ? diff : ctx->diff + diff;
}
}
}
static void lvgl_port_encoder_right_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
knob_handle_t knob = (knob_handle_t)arg;
if (ctx && knob) {
/* RIGHT */
if (knob == ctx->knob_handle) {
int32_t diff = lvgl_port_calculate_diff(knob, KNOB_RIGHT);
ctx->diff = (ctx->diff < 0) ? diff : ctx->diff + diff;
}
}
}
static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event)
{
static int32_t last_v = 0;
int32_t diff = 0;
int32_t invd = iot_knob_get_count_value(knob);
if (last_v ^ invd) {
diff = (int32_t)((uint32_t)invd - (uint32_t)last_v);
diff += (event == KNOB_RIGHT && invd < last_v) ? CONFIG_KNOB_HIGH_LIMIT :
(event == KNOB_LEFT && invd > last_v) ? CONFIG_KNOB_LOW_LIMIT : 0;
last_v = invd;
}
return diff;
}

View File

@@ -0,0 +1,107 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lcd_touch.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
esp_lcd_touch_handle_t handle; /* LCD touch IO handle */
lv_indev_drv_t indev_drv; /* LVGL input device driver */
struct {
float x;
float y;
} scale; /* Touch scale */
} lvgl_port_touch_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
{
assert(touch_cfg != NULL);
assert(touch_cfg->disp != NULL);
assert(touch_cfg->handle != NULL);
/* Touch context */
lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t));
if (touch_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for touch context allocation!");
return NULL;
}
touch_ctx->handle = touch_cfg->handle;
touch_ctx->scale.x = (touch_cfg->scale.x ? touch_cfg->scale.x : 1);
touch_ctx->scale.y = (touch_cfg->scale.y ? touch_cfg->scale.y : 1);
/* Register a touchpad input device */
lv_indev_drv_init(&touch_ctx->indev_drv);
touch_ctx->indev_drv.type = LV_INDEV_TYPE_POINTER;
touch_ctx->indev_drv.disp = touch_cfg->disp;
touch_ctx->indev_drv.read_cb = lvgl_port_touchpad_read;
touch_ctx->indev_drv.user_data = touch_ctx;
return lv_indev_drv_register(&touch_ctx->indev_drv);
}
esp_err_t lvgl_port_remove_touch(lv_indev_t *touch)
{
assert(touch);
lv_indev_drv_t *indev_drv = touch->driver;
assert(indev_drv);
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data;
/* Remove input device driver */
lv_indev_delete(touch);
if (touch_ctx) {
free(touch_ctx);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data;
assert(touch_ctx->handle);
uint16_t touchpad_x[1] = {0};
uint16_t touchpad_y[1] = {0};
uint8_t touchpad_cnt = 0;
/* Read data from touch controller into memory */
esp_lcd_touch_read_data(touch_ctx->handle);
/* Read data from touch controller */
bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);
if (touchpad_pressed && touchpad_cnt > 0) {
data->point.x = touch_ctx->scale.x * touchpad_x[0];
data->point.y = touch_ctx->scale.y * touchpad_y[0];
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}

View File

@@ -0,0 +1,469 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* 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"
#include "usb/hid_usage_mouse.h"
/* LVGL image of cursor */
LV_IMG_DECLARE(img_cursor)
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
QueueHandle_t queue; /* USB HID queue */
TaskHandle_t task; /* USB HID task */
bool running; /* USB HID task running */
struct {
lv_indev_drv_t drv; /* LVGL mouse input device driver */
uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */
int16_t x; /* Mouse X coordinate */
int16_t y; /* Mouse Y coordinate */
bool left_button; /* Mouse left button state */
} mouse;
struct {
lv_indev_drv_t drv; /* LVGL keyboard input device driver */
uint32_t last_key;
bool pressed;
} kb;
} lvgl_port_usb_hid_ctx_t;
typedef struct {
hid_host_device_handle_t hid_device_handle;
hid_host_driver_event_t event;
void *arg;
} lvgl_port_usb_hid_event_t;
/*******************************************************************************
* Local variables
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void);
static void lvgl_port_usb_hid_task(void *arg);
static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg)
{
lv_indev_t *indev;
assert(mouse_cfg);
assert(mouse_cfg->disp);
assert(mouse_cfg->disp->driver);
/* Initialize USB HID */
lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init();
if (hid_ctx == NULL) {
return NULL;
}
/* Mouse sensitivity cannot be zero */
hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity);
/* Default coordinates to screen center */
hid_ctx->mouse.x = (mouse_cfg->disp->driver->hor_res * hid_ctx->mouse.sensitivity) / 2;
hid_ctx->mouse.y = (mouse_cfg->disp->driver->ver_res * hid_ctx->mouse.sensitivity) / 2;
/* Register a mouse input device */
lv_indev_drv_init(&hid_ctx->mouse.drv);
hid_ctx->mouse.drv.type = LV_INDEV_TYPE_POINTER;
hid_ctx->mouse.drv.disp = mouse_cfg->disp;
hid_ctx->mouse.drv.read_cb = lvgl_port_usb_hid_read_mouse;
hid_ctx->mouse.drv.user_data = hid_ctx;
indev = lv_indev_drv_register(&hid_ctx->mouse.drv);
/* Set image of cursor */
lv_obj_t *cursor = mouse_cfg->cursor_img;
if (cursor == NULL) {
cursor = lv_img_create(lv_scr_act());
lv_img_set_src(cursor, &img_cursor);
}
lv_indev_set_cursor(indev, cursor);
return indev;
}
lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg)
{
lv_indev_t *indev;
assert(keyboard_cfg);
assert(keyboard_cfg->disp);
/* Initialize USB HID */
lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init();
if (hid_ctx == NULL) {
return NULL;
}
/* Register a keyboard input device */
lv_indev_drv_init(&hid_ctx->kb.drv);
hid_ctx->kb.drv.type = LV_INDEV_TYPE_KEYPAD;
hid_ctx->kb.drv.disp = keyboard_cfg->disp;
hid_ctx->kb.drv.read_cb = lvgl_port_usb_hid_read_kb;
hid_ctx->kb.drv.user_data = hid_ctx;
indev = lv_indev_drv_register(&hid_ctx->kb.drv);
return indev;
}
esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid)
{
assert(hid);
lv_indev_drv_t *indev_drv = hid->driver;
assert(indev_drv);
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data;
/* Remove input device driver */
lv_indev_delete(hid);
/* If all hid input devices are removed, stop task and clean all */
if (lvgl_hid_ctx.mouse.drv.disp == NULL && lvgl_hid_ctx.kb.drv.disp) {
hid_ctx->running = false;
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void)
{
esp_err_t ret;
/* USB HID is already initialized */
if (lvgl_hid_ctx.task) {
return &lvgl_hid_ctx;
}
/* USB HID host driver config */
const hid_host_driver_config_t hid_host_driver_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = lvgl_port_usb_hid_callback,
.callback_arg = &lvgl_hid_ctx,
};
ret = hid_host_install(&hid_host_driver_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "USB HID install failed!");
return NULL;
}
lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t));
xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task);
return &lvgl_hid_ctx;
}
static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift)
{
char ret_key = 0;
const uint8_t keycode2ascii [57][2] = {
{0, 0}, /* HID_KEY_NO_PRESS */
{0, 0}, /* HID_KEY_ROLLOVER */
{0, 0}, /* HID_KEY_POST_FAIL */
{0, 0}, /* HID_KEY_ERROR_UNDEFINED */
{'a', 'A'}, /* HID_KEY_A */
{'b', 'B'}, /* HID_KEY_B */
{'c', 'C'}, /* HID_KEY_C */
{'d', 'D'}, /* HID_KEY_D */
{'e', 'E'}, /* HID_KEY_E */
{'f', 'F'}, /* HID_KEY_F */
{'g', 'G'}, /* HID_KEY_G */
{'h', 'H'}, /* HID_KEY_H */
{'i', 'I'}, /* HID_KEY_I */
{'j', 'J'}, /* HID_KEY_J */
{'k', 'K'}, /* HID_KEY_K */
{'l', 'L'}, /* HID_KEY_L */
{'m', 'M'}, /* HID_KEY_M */
{'n', 'N'}, /* HID_KEY_N */
{'o', 'O'}, /* HID_KEY_O */
{'p', 'P'}, /* HID_KEY_P */
{'q', 'Q'}, /* HID_KEY_Q */
{'r', 'R'}, /* HID_KEY_R */
{'s', 'S'}, /* HID_KEY_S */
{'t', 'T'}, /* HID_KEY_T */
{'u', 'U'}, /* HID_KEY_U */
{'v', 'V'}, /* HID_KEY_V */
{'w', 'W'}, /* HID_KEY_W */
{'x', 'X'}, /* HID_KEY_X */
{'y', 'Y'}, /* HID_KEY_Y */
{'z', 'Z'}, /* HID_KEY_Z */
{'1', '!'}, /* HID_KEY_1 */
{'2', '@'}, /* HID_KEY_2 */
{'3', '#'}, /* HID_KEY_3 */
{'4', '$'}, /* HID_KEY_4 */
{'5', '%'}, /* HID_KEY_5 */
{'6', '^'}, /* HID_KEY_6 */
{'7', '&'}, /* HID_KEY_7 */
{'8', '*'}, /* HID_KEY_8 */
{'9', '('}, /* HID_KEY_9 */
{'0', ')'}, /* HID_KEY_0 */
{'\r', '\r'}, /* HID_KEY_ENTER */
{0, 0}, /* HID_KEY_ESC */
{'\b', 0}, /* HID_KEY_DEL */
{0, 0}, /* HID_KEY_TAB */
{' ', ' '}, /* HID_KEY_SPACE */
{'-', '_'}, /* HID_KEY_MINUS */
{'=', '+'}, /* HID_KEY_EQUAL */
{'[', '{'}, /* HID_KEY_OPEN_BRACKET */
{']', '}'}, /* HID_KEY_CLOSE_BRACKET */
{'\\', '|'}, /* HID_KEY_BACK_SLASH */
{'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
{';', ':'}, /* HID_KEY_COLON */
{'\'', '"'}, /* HID_KEY_QUOTE */
{'`', '~'}, /* HID_KEY_TILDE */
{',', '<'}, /* HID_KEY_LESS */
{'.', '>'}, /* HID_KEY_GREATER */
{'/', '?'} /* HID_KEY_SLASH */
};
if (shift > 1) {
shift = 1;
}
if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) {
ret_key = keycode2ascii[key][shift];
}
return ret_key;
}
static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, const hid_host_interface_event_t event, void *arg)
{
hid_host_dev_params_t dev;
hid_host_device_get_params(hid_device_handle, &dev);
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg;
uint8_t data[10];
unsigned int data_length = 0;
assert(hid_ctx != NULL);
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length);
if (dev.proto == HID_PROTOCOL_KEYBOARD) {
hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data;
if (data_length < sizeof(hid_keyboard_input_report_boot_t)) {
return;
}
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) {
char key = 0;
/* LVGL special keys */
if (keyboard->key[i] == HID_KEY_TAB) {
if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) {
key = LV_KEY_PREV;
} else {
key = LV_KEY_NEXT;
}
} else if (keyboard->key[i] == HID_KEY_RIGHT) {
key = LV_KEY_RIGHT;
} else if (keyboard->key[i] == HID_KEY_LEFT) {
key = LV_KEY_LEFT;
} else if (keyboard->key[i] == HID_KEY_DOWN) {
key = LV_KEY_DOWN;
} else if (keyboard->key[i] == HID_KEY_UP) {
key = LV_KEY_UP;
} else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) {
key = LV_KEY_ENTER;
} else if (keyboard->key[i] == HID_KEY_DELETE) {
key = LV_KEY_DEL;
} else if (keyboard->key[i] == HID_KEY_HOME) {
key = LV_KEY_HOME;
} else if (keyboard->key[i] == HID_KEY_END) {
key = LV_KEY_END;
} else {
/* Get ASCII char */
key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift));
}
if (key == 0) {
ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]);
}
hid_ctx->kb.last_key = key;
hid_ctx->kb.pressed = true;
}
}
} 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)) {
break;
}
hid_ctx->mouse.left_button = mouse->buttons.button1;
hid_ctx->mouse.x += mouse->x_displacement;
hid_ctx->mouse.y += mouse->y_displacement;
}
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
hid_host_device_close(hid_device_handle);
break;
default:
break;
}
}
static void lvgl_port_usb_hid_task(void *arg)
{
hid_host_dev_params_t dev;
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg;
hid_host_device_handle_t hid_device_handle = NULL;
lvgl_port_usb_hid_event_t msg;
assert(ctx);
ctx->running = true;
while (ctx->running) {
if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) {
hid_device_handle = msg.hid_device_handle;
hid_host_device_get_params(hid_device_handle, &dev);
switch (msg.event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
/* Handle mouse or keyboard */
if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) {
const hid_host_device_config_t dev_config = {
.callback = lvgl_port_usb_hid_host_interface_callback,
.callback_arg = ctx
};
ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) );
ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) );
ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) );
ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) );
}
break;
default:
break;
}
}
}
xQueueReset(ctx->queue);
vQueueDelete(ctx->queue);
hid_host_uninstall();
memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t));
vTaskDelete(NULL);
}
static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
int16_t width = 0;
int16_t height = 0;
assert(indev_drv);
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data;
assert(ctx);
if (indev_drv->disp->driver->rotated == LV_DISP_ROT_NONE || indev_drv->disp->driver->rotated == LV_DISP_ROT_180) {
width = indev_drv->disp->driver->hor_res;
height = indev_drv->disp->driver->ver_res;
} else {
width = indev_drv->disp->driver->ver_res;
height = indev_drv->disp->driver->hor_res;
}
/* Screen borders */
if (ctx->mouse.x < 0) {
ctx->mouse.x = 0;
} else if (ctx->mouse.x > width * ctx->mouse.sensitivity) {
ctx->mouse.x = width * ctx->mouse.sensitivity;
}
if (ctx->mouse.y < 0) {
ctx->mouse.y = 0;
} else if (ctx->mouse.y > height * ctx->mouse.sensitivity) {
ctx->mouse.y = height * ctx->mouse.sensitivity;
}
/* Get coordinates by rotation with sensitivity */
switch (indev_drv->disp->driver->rotated) {
case LV_DISP_ROT_NONE:
data->point.x = ctx->mouse.x / ctx->mouse.sensitivity;
data->point.y = ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISP_ROT_90:
data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity;
data->point.x = ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISP_ROT_180:
data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity;
data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISP_ROT_270:
data->point.y = ctx->mouse.x / ctx->mouse.sensitivity;
data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity;
break;
}
if (ctx->mouse.left_button) {
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data;
assert(ctx);
data->key = ctx->kb.last_key;
if (ctx->kb.pressed) {
data->state = LV_INDEV_STATE_PRESSED;
ctx->kb.pressed = false;
} else {
data->state = LV_INDEV_STATE_RELEASED;
ctx->kb.last_key = 0;
}
}
static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg)
{
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg;
const lvgl_port_usb_hid_event_t msg = {
.hid_device_handle = hid_device_handle,
.event = event,
.arg = arg
};
xQueueSend(hid_ctx->queue, &msg, 0);
}

View File

@@ -0,0 +1,321 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* 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 "freertos/event_groups.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;
EventGroupHandle_t lvgl_events;
SemaphoreHandle_t task_init_mux;
esp_timer_handle_t tick_timer;
bool running;
int task_max_sleep_ms;
int timer_period_ms;
} lvgl_port_ctx_t;
/*******************************************************************************
* Local variables
*******************************************************************************/
static lvgl_port_ctx_t lvgl_port_ctx;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_task(void *arg);
static esp_err_t lvgl_port_tick_init(void);
static void lvgl_port_task_deinit(void);
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1));
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
/* Tick init */
lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms;
/* 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_events = xEventGroupCreate();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_events, ESP_ERR_NO_MEM, err, TAG, "Create LVGL Event Group fail!");
BaseType_t res;
if (cfg->task_affinity < 0) {
res = xTaskCreate(lvgl_port_task, "taskLVGL", cfg->task_stack, xTaskGetCurrentTaskHandle(), cfg->task_priority, &lvgl_port_ctx.lvgl_task);
} else {
res = xTaskCreatePinnedToCore(lvgl_port_task, "taskLVGL", cfg->task_stack, xTaskGetCurrentTaskHandle(), 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!");
// Wait until taskLVGL starts
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) {
ret = ESP_ERR_TIMEOUT;
}
err:
if (ret != ESP_OK) {
lvgl_port_deinit();
}
return ret;
}
esp_err_t lvgl_port_resume(void)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (lvgl_port_ctx.tick_timer != NULL) {
lv_timer_enable(true);
ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000);
}
return ret;
}
esp_err_t lvgl_port_stop(void)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (lvgl_port_ctx.tick_timer != NULL) {
lv_timer_enable(false);
ret = esp_timer_stop(lvgl_port_ctx.tick_timer);
}
return ret;
}
esp_err_t lvgl_port_deinit(void)
{
/* Stop and delete timer */
if (lvgl_port_ctx.tick_timer != NULL) {
esp_timer_stop(lvgl_port_ctx.tick_timer);
esp_timer_delete(lvgl_port_ctx.tick_timer);
lvgl_port_ctx.tick_timer = NULL;
}
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
}
/* 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;
}
bool lvgl_port_lock(uint32_t timeout_ms)
{
assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first");
const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE;
}
void lvgl_port_unlock(void)
{
assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first");
xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux);
}
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param)
{
EventBits_t bits = 0;
if (!lvgl_port_ctx.lvgl_events) {
return ESP_ERR_INVALID_STATE;
}
/* Get unprocessed bits */
if (xPortInIsrContext() == pdTRUE) {
bits = xEventGroupGetBitsFromISR(lvgl_port_ctx.lvgl_events);
} else {
bits = xEventGroupGetBits(lvgl_port_ctx.lvgl_events);
}
/* Set event */
bits |= event;
/* Save */
if (xPortInIsrContext() == pdTRUE) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(lvgl_port_ctx.lvgl_events, bits, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR( );
}
} else {
xEventGroupSetBits(lvgl_port_ctx.lvgl_events, bits);
}
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)
{
TaskHandle_t task_to_notify = (TaskHandle_t)arg;
EventBits_t events = 0;
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();
/* LVGL is initialized, notify lvgl_port_init() function about it */
xTaskNotifyGive(task_to_notify);
/* 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);
events = xEventGroupWaitBits(lvgl_port_ctx.lvgl_events, 0xFF, pdTRUE, pdFALSE, wait);
if (lv_display_get_default() && lvgl_port_lock(0)) {
/* Call read input devices */
if (events & LVGL_PORT_EVENT_TOUCH) {
xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY);
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 == LV_NO_TIMER_READY) {
task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
}
/* Minimal dealy for the task. When there is too much events, it takes time for other tasks and interrupts. */
vTaskDelay(1);
}
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_init_mux);
/* Close task */
vTaskDelete( NULL );
}
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_events) {
vEventGroupDelete(lvgl_port_ctx.lvgl_events);
}
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
/* Deinitialize LVGL */
lv_deinit();
#endif
}
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)
{
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_port_tick_increment,
.name = "LVGL tick",
};
ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, "Creating LVGL timer filed!");
return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000);
}

View File

@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef enum {
LVGL_PORT_NAV_BTN_PREV,
LVGL_PORT_NAV_BTN_NEXT,
LVGL_PORT_NAV_BTN_ENTER,
LVGL_PORT_NAV_BTN_CNT,
} lvgl_port_nav_btns_t;
typedef struct {
button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */
lv_indev_t *indev; /* LVGL input device driver */
bool btn_prev; /* Button prev state */
bool btn_next; /* Button next state */
bool btn_enter; /* Button enter state */
} lvgl_port_nav_btns_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_btn_down_handler(void *arg, void *arg2);
static void lvgl_port_btn_up_handler(void *arg, void *arg2);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg)
{
lv_indev_t *indev;
esp_err_t ret = ESP_OK;
assert(buttons_cfg != NULL);
assert(buttons_cfg->disp != NULL);
/* Touch context */
lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t));
if (buttons_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for buttons context allocation!");
return NULL;
}
#if BUTTON_VER_MAJOR < 4
/* Previous button */
if (buttons_cfg->button_prev != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
/* Next button */
if (buttons_cfg->button_next != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
/* Enter button */
if (buttons_cfg->button_enter != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}
#else
ESP_GOTO_ON_FALSE(buttons_cfg->button_prev && buttons_cfg->button_next && buttons_cfg->button_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid some button handler!");
buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = buttons_cfg->button_prev;
buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = buttons_cfg->button_next;
buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = buttons_cfg->button_enter;
#endif
/* Button handlers */
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
#if BUTTON_VER_MAJOR < 4
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx));
#else
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, NULL, lvgl_port_btn_down_handler, buttons_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, NULL, lvgl_port_btn_up_handler, buttons_ctx));
#endif
}
buttons_ctx->btn_prev = false;
buttons_ctx->btn_next = false;
buttons_ctx->btn_enter = false;
lvgl_port_lock(0);
/* 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);
//buttons_ctx->indev->long_press_repeat_time = 300;
buttons_ctx->indev = indev;
lvgl_port_unlock();
return indev;
err:
if (ret != ESP_OK) {
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
if (buttons_ctx->btn[i] != NULL) {
iot_button_delete(buttons_ctx->btn[i]);
}
}
if (buttons_ctx != NULL) {
free(buttons_ctx);
}
}
return NULL;
}
esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons)
{
assert(buttons);
lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_driver_data(buttons);
lvgl_port_lock(0);
/* Remove input device driver */
lv_indev_delete(buttons);
lvgl_port_unlock();
if (buttons_ctx) {
free(buttons_ctx);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
static uint32_t last_key = 0;
assert(indev_drv);
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_driver_data(indev_drv);
assert(ctx);
/* Buttons */
if (ctx->btn_prev) {
data->key = LV_KEY_LEFT;
last_key = LV_KEY_LEFT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_next) {
data->key = LV_KEY_RIGHT;
last_key = LV_KEY_RIGHT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_enter) {
data->key = LV_KEY_ENTER;
last_key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->key = last_key;
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void lvgl_port_btn_down_handler(void *arg, void *arg2)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = true;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = true;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
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)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = false;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = false;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}

View File

@@ -0,0 +1,754 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.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"
#define LVGL_PORT_PPA (CONFIG_LVGL_PORT_ENABLE_PPA)
#if LVGL_PORT_PPA
#include "../common/ppa/lcd_ppa.h"
#endif
#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
#else
#define LVGL_PORT_HANDLE_FLUSH_READY 1
#endif
#ifndef CONFIG_LV_DRAW_BUF_ALIGN
#define CONFIG_LV_DRAW_BUF_ALIGN 1
#endif
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
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_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 */
#if LVGL_PORT_PPA
lvgl_port_ppa_handle_t ppa_handle;
#endif //LVGL_PORT_PPA
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_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
*******************************************************************************/
lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)
{
lvgl_port_lock(0);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
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;
assert(disp_cfg->io_handle != NULL);
#if LVGL_PORT_HANDLE_FLUSH_READY
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();
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;
}
esp_err_t lvgl_port_remove_disp(lv_display_t *disp)
{
assert(disp);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp);
lvgl_port_lock(0);
lv_disp_remove(disp);
lvgl_port_unlock();
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->trans_sem) {
vSemaphoreDelete(disp_ctx->trans_sem);
}
#if LVGL_PORT_PPA
if (disp_ctx->ppa_handle) {
lvgl_port_ppa_delete(disp_ctx->ppa_handle);
}
#endif //LVGL_PORT_PPA
free(disp_ctx);
return ESP_OK;
}
void lvgl_port_flush_ready(lv_display_t *disp)
{
assert(disp);
lv_disp_flush_ready(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);
uint8_t color_bytes = lv_color_format_get_size(display_color_format);
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 aligned 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_aligned_alloc(CONFIG_LV_DRAW_BUF_ALIGN, buffer_size * color_bytes, 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_aligned_alloc(CONFIG_LV_DRAW_BUF_ALIGN, buffer_size * color_bytes, 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 * color_bytes, 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 * color_bytes, 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 * color_bytes, LV_DISPLAY_RENDER_MODE_FULL);
} else {
lv_display_set_buffers(disp, buf1, buf2, buffer_size * color_bytes, 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) {
#if LVGL_PORT_PPA
ESP_LOGI(TAG, "Setting PPA context for SW rotation");
uint32_t pixel_format = COLOR_PIXEL_RGB565;
if (disp_cfg->color_format == LV_COLOR_FORMAT_RGB888) {
pixel_format = COLOR_PIXEL_RGB888;
}
/* Create LCD PPA for rotation */
lvgl_port_ppa_cfg_t ppa_cfg = {
.buffer_size = disp_cfg->buffer_size * color_bytes,
.color_space = COLOR_SPACE_RGB,
.pixel_format = pixel_format,
.flags = {
.buff_dma = disp_cfg->flags.buff_dma,
.buff_spiram = disp_cfg->flags.buff_spiram,
}
};
disp_ctx->ppa_handle = lvgl_port_ppa_create(&ppa_cfg);
assert(disp_ctx->ppa_handle != NULL);
#else
disp_ctx->draw_buffs[2] = heap_caps_malloc(buffer_size * color_bytes, 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!");
#endif //LVGL_PORT_PPA
}
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_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
#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_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;
bool swap_xy = (lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90 || lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_270);
int x1 = area->x1;
int x2 = area->x2;
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 = 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;
out_y = x;
res = ver_res;
} else {
out_x = x;
out_y = y;
res = hor_res;
}
/* Write to the buffer as required for the display.
* It writes only 1-bit for monochrome displays mapped vertically.*/
uint8_t *outbuf = NULL;
outbuf = *color_map + res * (out_y >> 3) + (out_x);
if (chroma_color) {
(*outbuf) &= ~(1 << (out_y % 8));
} else {
(*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);
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)) {
#if LVGL_PORT_PPA
if (disp_ctx->ppa_handle) {
/* Screen vertical size */
int32_t hres = lv_display_get_horizontal_resolution(drv);
int32_t vres = lv_display_get_vertical_resolution(drv);
lvgl_port_ppa_disp_rotate_t rotate_cfg = {
.in_buff = color_map,
.area = {
.x1 = area->x1,
.x2 = area->x2,
.y1 = area->y1,
.y2 = area->y2,
},
.disp_size = {
.hres = hres,
.vres = vres,
},
.rotation = disp_ctx->current_rotation,
.ppa_mode = PPA_TRANS_MODE_BLOCKING,
.swap_bytes = (disp_ctx->flags.swap_bytes ? true : false),
.user_data = disp_ctx
};
/* Do operation */
esp_err_t err = lvgl_port_ppa_rotate(disp_ctx->ppa_handle, &rotate_cfg);
if (err == ESP_OK) {
color_map = lvgl_port_ppa_get_output_buffer(disp_ctx->ppa_handle);
offsetx1 = rotate_cfg.area.x1;
offsetx2 = rotate_cfg.area.x2;
offsety1 = rotate_cfg.area.y1;
offsety2 = rotate_cfg.area.y2;
}
}
#else
/* 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;
}
#endif //LVGL_PORT_PPA
}
if (disp_ctx->flags.swap_bytes) {
size_t len = lv_area_get_size(area);
lv_draw_sw_rgb565_swap(color_map, len);
}
/* Transfer data in buffer for monochromatic screen */
if (disp_ctx->flags.monochrome) {
_lvgl_port_transform_monochrome(drv, area, &color_map);
}
if ((disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) && (disp_ctx->flags.direct_mode || disp_ctx->flags.full_refresh)) {
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 *)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);
}

View File

@@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
knob_handle_t knob_handle; /* Encoder knob handlers */
button_handle_t btn_handle; /* Encoder button handlers */
lv_indev_t *indev; /* LVGL input device driver */
bool btn_enter; /* Encoder button enter state */
int32_t diff; /* Encoder diff */
} lvgl_port_encoder_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
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 *button_handle, void *usr_data);
static void lvgl_port_encoder_btn_up_handler(void *button_handle, void *usr_data);
static void lvgl_port_encoder_left_handler(void *arg, void *arg2);
static void lvgl_port_encoder_right_handler(void *arg, void *arg2);
static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event);
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
{
lv_indev_t *indev;
esp_err_t ret = ESP_OK;
assert(encoder_cfg != NULL);
assert(encoder_cfg->disp != NULL);
/* Encoder context */
lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t));
if (encoder_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for encoder context allocation!");
return NULL;
}
/* Encoder_a/b */
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_left_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_right_handler, encoder_ctx));
}
/* Encoder Enter */
if (encoder_cfg->encoder_enter != NULL) {
#if BUTTON_VER_MAJOR < 4
encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter);
ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
#else
ESP_GOTO_ON_FALSE(encoder_cfg->encoder_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid button handler!");
encoder_ctx->btn_handle = encoder_cfg->encoder_enter;
#endif
}
#if BUTTON_VER_MAJOR < 4
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, encoder_ctx));
#else
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, NULL, lvgl_port_encoder_btn_down_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, NULL, lvgl_port_encoder_btn_up_handler, encoder_ctx));
#endif
encoder_ctx->btn_enter = false;
encoder_ctx->diff = 0;
lvgl_port_lock(0);
/* 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) {
iot_knob_delete(encoder_ctx->knob_handle);
}
if (encoder_ctx->btn_handle != NULL) {
iot_button_delete(encoder_ctx->btn_handle);
}
if (encoder_ctx != NULL) {
free(encoder_ctx);
}
}
return NULL;
}
esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder)
{
assert(encoder);
lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_driver_data(encoder);
if (encoder_ctx->knob_handle != NULL) {
iot_knob_delete(encoder_ctx->knob_handle);
}
if (encoder_ctx->btn_handle != NULL) {
iot_button_delete(encoder_ctx->btn_handle);
}
lvgl_port_lock(0);
/* Remove input device driver */
lv_indev_delete(encoder);
lvgl_port_unlock();
if (encoder_ctx != NULL) {
free(encoder_ctx);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_driver_data(indev_drv);
assert(ctx);
data->enc_diff = ctx->diff;
data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
ctx->diff = 0;
}
static void lvgl_port_encoder_btn_down_handler(void *button_handle, void *usr_data)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) usr_data;
button_handle_t button = (button_handle_t)button_handle;
if (ctx && button) {
/* ENTER */
if (button == ctx->btn_handle) {
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 *button_handle, void *usr_data)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) usr_data;
button_handle_t button = (button_handle_t)button_handle;
if (ctx && button) {
/* ENTER */
if (button == ctx->btn_handle) {
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_encoder_left_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
knob_handle_t knob = (knob_handle_t)arg;
if (ctx && knob) {
/* LEFT */
if (knob == ctx->knob_handle) {
int32_t diff = lvgl_port_calculate_diff(knob, KNOB_LEFT);
ctx->diff = (ctx->diff > 0) ? diff : ctx->diff + diff;
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
}
static void lvgl_port_encoder_right_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
knob_handle_t knob = (knob_handle_t)arg;
if (ctx && knob) {
/* RIGHT */
if (knob == ctx->knob_handle) {
int32_t diff = lvgl_port_calculate_diff(knob, KNOB_RIGHT);
ctx->diff = (ctx->diff < 0) ? diff : ctx->diff + diff;
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
}
static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event)
{
static int32_t last_v = 0;
int32_t diff = 0;
int32_t invd = iot_knob_get_count_value(knob);
if (last_v ^ invd) {
diff = (int32_t)((uint32_t)invd - (uint32_t)last_v);
diff += (event == KNOB_RIGHT && invd < last_v) ? CONFIG_KNOB_HIGH_LIMIT :
(event == KNOB_LEFT && invd > last_v) ? CONFIG_KNOB_LOW_LIMIT : 0;
last_v = invd;
}
return diff;
}

View File

@@ -0,0 +1,145 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lcd_touch.h"
#include "esp_lvgl_port.h"
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
esp_lcd_touch_handle_t handle; /* LCD touch IO handle */
lv_indev_t *indev; /* LVGL input device driver */
struct {
float x;
float y;
} scale; /* Touch scale */
} lvgl_port_touch_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
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
*******************************************************************************/
lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
{
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);
/* Touch context */
lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t));
if (touch_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for touch context allocation!");
return NULL;
}
touch_ctx->handle = touch_cfg->handle;
touch_ctx->scale.x = (touch_cfg->scale.x ? touch_cfg->scale.x : 1);
touch_ctx->scale.y = (touch_cfg->scale.y ? touch_cfg->scale.y : 1);
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;
}
esp_err_t lvgl_port_remove_touch(lv_indev_t *touch)
{
assert(touch);
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_driver_data(touch);
lvgl_port_lock(0);
/* Remove input device driver */
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);
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_driver_data(indev_drv);
assert(touch_ctx);
assert(touch_ctx->handle);
uint16_t touchpad_x[1] = {0};
uint16_t touchpad_y[1] = {0};
uint8_t touchpad_cnt = 0;
/* Read data from touch controller into memory */
esp_lcd_touch_read_data(touch_ctx->handle);
/* Read data from touch controller */
bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);
if (touchpad_pressed && touchpad_cnt > 0) {
data->point.x = touch_ctx->scale.x * touchpad_x[0];
data->point.y = touch_ctx->scale.y * touchpad_y[0];
data->state = LV_INDEV_STATE_PRESSED;
} else {
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);
}

View File

@@ -0,0 +1,490 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* 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"
#include "usb/hid_usage_mouse.h"
/* LVGL image of cursor */
LV_IMG_DECLARE(img_cursor)
static const char *TAG = "LVGL";
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct {
QueueHandle_t queue; /* USB HID queue */
TaskHandle_t task; /* USB HID task */
bool running; /* USB HID task running */
struct {
lv_indev_t *indev; /* LVGL mouse input device driver */
uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */
int16_t x; /* Mouse X coordinate */
int16_t y; /* Mouse Y coordinate */
bool left_button; /* Mouse left button state */
} mouse;
struct {
lv_indev_t *indev; /* LVGL keyboard input device driver */
uint32_t last_key;
bool pressed;
} kb;
} lvgl_port_usb_hid_ctx_t;
typedef struct {
hid_host_device_handle_t hid_device_handle;
hid_host_driver_event_t event;
void *arg;
} lvgl_port_usb_hid_event_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void);
static void lvgl_port_usb_hid_task(void *arg);
static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg);
/*******************************************************************************
* Local variables
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx;
/*******************************************************************************
* Public API functions
*******************************************************************************/
lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg)
{
lv_indev_t *indev;
assert(mouse_cfg);
assert(mouse_cfg->disp);
/* Initialize USB HID */
lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init();
if (hid_ctx == NULL) {
return NULL;
}
/* Mouse sensitivity cannot be zero */
hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity);
int32_t ver_res = lv_display_get_vertical_resolution(mouse_cfg->disp);
int32_t hor_res = lv_display_get_physical_horizontal_resolution(mouse_cfg->disp);
/* Default coordinates to screen center */
hid_ctx->mouse.x = (hor_res * hid_ctx->mouse.sensitivity) / 2;
hid_ctx->mouse.y = (ver_res * hid_ctx->mouse.sensitivity) / 2;
lvgl_port_lock(0);
/* 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);
hid_ctx->mouse.indev = indev;
lvgl_port_unlock();
/* Set image of cursor */
lv_obj_t *cursor = mouse_cfg->cursor_img;
if (cursor == NULL) {
cursor = lv_img_create(lv_scr_act());
lv_img_set_src(cursor, &img_cursor);
}
lv_indev_set_cursor(indev, cursor);
return indev;
}
lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg)
{
lv_indev_t *indev;
assert(keyboard_cfg);
assert(keyboard_cfg->disp);
/* Initialize USB HID */
lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init();
if (hid_ctx == NULL) {
return NULL;
}
lvgl_port_lock(0);
/* 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);
hid_ctx->kb.indev = indev;
lvgl_port_unlock();
return indev;
}
esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid)
{
assert(hid);
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(hid);
lvgl_port_lock(0);
/* Remove input device driver */
lv_indev_delete(hid);
lvgl_port_unlock();
if (lvgl_hid_ctx.mouse.indev == hid) {
lvgl_hid_ctx.mouse.indev = NULL;
} else if (lvgl_hid_ctx.kb.indev == hid) {
lvgl_hid_ctx.kb.indev = NULL;
}
/* If all hid input devices are removed, stop task and clean all */
if (lvgl_hid_ctx.mouse.indev == NULL && lvgl_hid_ctx.kb.indev) {
hid_ctx->running = false;
}
return ESP_OK;
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void)
{
esp_err_t ret;
/* USB HID is already initialized */
if (lvgl_hid_ctx.task) {
return &lvgl_hid_ctx;
}
/* USB HID host driver config */
const hid_host_driver_config_t hid_host_driver_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = lvgl_port_usb_hid_callback,
.callback_arg = &lvgl_hid_ctx,
};
ret = hid_host_install(&hid_host_driver_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "USB HID install failed!");
return NULL;
}
lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t));
xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task);
return &lvgl_hid_ctx;
}
static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift)
{
char ret_key = 0;
const uint8_t keycode2ascii [57][2] = {
{0, 0}, /* HID_KEY_NO_PRESS */
{0, 0}, /* HID_KEY_ROLLOVER */
{0, 0}, /* HID_KEY_POST_FAIL */
{0, 0}, /* HID_KEY_ERROR_UNDEFINED */
{'a', 'A'}, /* HID_KEY_A */
{'b', 'B'}, /* HID_KEY_B */
{'c', 'C'}, /* HID_KEY_C */
{'d', 'D'}, /* HID_KEY_D */
{'e', 'E'}, /* HID_KEY_E */
{'f', 'F'}, /* HID_KEY_F */
{'g', 'G'}, /* HID_KEY_G */
{'h', 'H'}, /* HID_KEY_H */
{'i', 'I'}, /* HID_KEY_I */
{'j', 'J'}, /* HID_KEY_J */
{'k', 'K'}, /* HID_KEY_K */
{'l', 'L'}, /* HID_KEY_L */
{'m', 'M'}, /* HID_KEY_M */
{'n', 'N'}, /* HID_KEY_N */
{'o', 'O'}, /* HID_KEY_O */
{'p', 'P'}, /* HID_KEY_P */
{'q', 'Q'}, /* HID_KEY_Q */
{'r', 'R'}, /* HID_KEY_R */
{'s', 'S'}, /* HID_KEY_S */
{'t', 'T'}, /* HID_KEY_T */
{'u', 'U'}, /* HID_KEY_U */
{'v', 'V'}, /* HID_KEY_V */
{'w', 'W'}, /* HID_KEY_W */
{'x', 'X'}, /* HID_KEY_X */
{'y', 'Y'}, /* HID_KEY_Y */
{'z', 'Z'}, /* HID_KEY_Z */
{'1', '!'}, /* HID_KEY_1 */
{'2', '@'}, /* HID_KEY_2 */
{'3', '#'}, /* HID_KEY_3 */
{'4', '$'}, /* HID_KEY_4 */
{'5', '%'}, /* HID_KEY_5 */
{'6', '^'}, /* HID_KEY_6 */
{'7', '&'}, /* HID_KEY_7 */
{'8', '*'}, /* HID_KEY_8 */
{'9', '('}, /* HID_KEY_9 */
{'0', ')'}, /* HID_KEY_0 */
{'\r', '\r'}, /* HID_KEY_ENTER */
{0, 0}, /* HID_KEY_ESC */
{'\b', 0}, /* HID_KEY_DEL */
{0, 0}, /* HID_KEY_TAB */
{' ', ' '}, /* HID_KEY_SPACE */
{'-', '_'}, /* HID_KEY_MINUS */
{'=', '+'}, /* HID_KEY_EQUAL */
{'[', '{'}, /* HID_KEY_OPEN_BRACKET */
{']', '}'}, /* HID_KEY_CLOSE_BRACKET */
{'\\', '|'}, /* HID_KEY_BACK_SLASH */
{'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
{';', ':'}, /* HID_KEY_COLON */
{'\'', '"'}, /* HID_KEY_QUOTE */
{'`', '~'}, /* HID_KEY_TILDE */
{',', '<'}, /* HID_KEY_LESS */
{'.', '>'}, /* HID_KEY_GREATER */
{'/', '?'} /* HID_KEY_SLASH */
};
if (shift > 1) {
shift = 1;
}
if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) {
ret_key = keycode2ascii[key][shift];
}
return ret_key;
}
static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, const hid_host_interface_event_t event, void *arg)
{
hid_host_dev_params_t dev;
hid_host_device_get_params(hid_device_handle, &dev);
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg;
uint8_t data[10];
unsigned int data_length = 0;
assert(hid_ctx != NULL);
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length);
if (dev.proto == HID_PROTOCOL_KEYBOARD) {
hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data;
if (data_length < sizeof(hid_keyboard_input_report_boot_t)) {
return;
}
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) {
char key = 0;
/* LVGL special keys */
if (keyboard->key[i] == HID_KEY_TAB) {
if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) {
key = LV_KEY_PREV;
} else {
key = LV_KEY_NEXT;
}
} else if (keyboard->key[i] == HID_KEY_RIGHT) {
key = LV_KEY_RIGHT;
} else if (keyboard->key[i] == HID_KEY_LEFT) {
key = LV_KEY_LEFT;
} else if (keyboard->key[i] == HID_KEY_DOWN) {
key = LV_KEY_DOWN;
} else if (keyboard->key[i] == HID_KEY_UP) {
key = LV_KEY_UP;
} else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) {
key = LV_KEY_ENTER;
} else if (keyboard->key[i] == HID_KEY_DELETE) {
key = LV_KEY_DEL;
} else if (keyboard->key[i] == HID_KEY_HOME) {
key = LV_KEY_HOME;
} else if (keyboard->key[i] == HID_KEY_END) {
key = LV_KEY_END;
} else {
/* Get ASCII char */
key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift));
}
if (key == 0) {
ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]);
}
hid_ctx->kb.last_key = key;
hid_ctx->kb.pressed = true;
}
}
/* 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)) {
break;
}
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:
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
hid_host_device_close(hid_device_handle);
break;
default:
break;
}
}
static void lvgl_port_usb_hid_task(void *arg)
{
hid_host_dev_params_t dev;
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg;
hid_host_device_handle_t hid_device_handle = NULL;
lvgl_port_usb_hid_event_t msg;
assert(ctx);
ctx->running = true;
while (ctx->running) {
if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) {
hid_device_handle = msg.hid_device_handle;
hid_host_device_get_params(hid_device_handle, &dev);
switch (msg.event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
/* Handle mouse or keyboard */
if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) {
const hid_host_device_config_t dev_config = {
.callback = lvgl_port_usb_hid_host_interface_callback,
.callback_arg = ctx
};
ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) );
ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) );
ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) );
ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) );
}
break;
default:
break;
}
}
}
xQueueReset(ctx->queue);
vQueueDelete(ctx->queue);
hid_host_uninstall();
memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t));
vTaskDelete(NULL);
}
static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
int16_t width = 0;
int16_t height = 0;
assert(indev_drv);
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(indev_drv);
assert(ctx);
lv_display_t *disp = lv_indev_get_display(indev_drv);
assert(disp);
if (lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_0 || lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_180) {
width = lv_display_get_physical_horizontal_resolution(disp);
height = lv_display_get_vertical_resolution(disp);
} else {
width = lv_display_get_vertical_resolution(disp);
height = lv_display_get_physical_horizontal_resolution(disp);
}
/* Screen borders */
if (ctx->mouse.x < 0) {
ctx->mouse.x = 0;
} else if (ctx->mouse.x > width * ctx->mouse.sensitivity) {
ctx->mouse.x = width * ctx->mouse.sensitivity;
}
if (ctx->mouse.y < 0) {
ctx->mouse.y = 0;
} else if (ctx->mouse.y > height * ctx->mouse.sensitivity) {
ctx->mouse.y = height * ctx->mouse.sensitivity;
}
/* Get coordinates by rotation with sensitivity */
switch (lv_display_get_rotation(disp)) {
case LV_DISPLAY_ROTATION_0:
data->point.x = ctx->mouse.x / ctx->mouse.sensitivity;
data->point.y = ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISPLAY_ROTATION_90:
data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity;
data->point.x = ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISPLAY_ROTATION_180:
data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity;
data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity;
break;
case LV_DISPLAY_ROTATION_270:
data->point.y = ctx->mouse.x / ctx->mouse.sensitivity;
data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity;
break;
}
if (ctx->mouse.left_button) {
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(indev_drv);
assert(ctx);
data->key = ctx->kb.last_key;
if (ctx->kb.pressed) {
data->state = LV_INDEV_STATE_PRESSED;
ctx->kb.pressed = false;
} else {
data->state = LV_INDEV_STATE_RELEASED;
ctx->kb.last_key = 0;
}
}
static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg)
{
lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg;
const lvgl_port_usb_hid_event_t msg = {
.hid_device_handle = hid_device_handle,
.event = event,
.arg = arg
};
xQueueSend(hid_ctx->queue, &msg, 0);
}

View File

@@ -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

View File

@@ -0,0 +1,325 @@
/*
* 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
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

View File

@@ -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

View File

@@ -0,0 +1,404 @@
/*
* 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
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

View File

@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB888 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_rgb888_esp
.type lv_color_blend_to_rgb888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb888(_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_rgb888_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint24_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
// a11 - dest_w_bytes = sizeof(uint24_t) * dest_w = 3 * a4
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
add a11, a11, a4 // a11 - dest_w_bytes = a11 + a4
// Prepare register combinations
// a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR
l8ui a13, a7, 0 // blue 000B
slli a13, a13, 24 // shift to B000
or a13, a13, a8 // a13 BRGB
srli a14, a8, 8 // a14 00RG
slli a10, a8, 16 // a10 GB00
or a14, a14, a10 // a14 GBRG
slli a15, a8, 8 // a15 RGB0
l8ui a10, a7, 2 // a7 000R
or a15, a15, a10 // a15 RGBR
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// Prepare main loop length and dest_w_bytes
srli a9, a4, 2 // a9 = loop_len = dest_w / 4, calculate main loop_len for original dest_w
movi.n a8, 0x3 // a8 = 0x3, remainder mask
and a10, a4, a8 // a10 - remainder after division by 4 = a4 and 0x3
.outer_loop:
// Run main loop which sets 12 bytes (4 rgb888) in one loop run
loopnez a9, ._main_loop
s32i.n a13, a3, 0 // save 32 bits from 32-bit color a13 to dest_buff a3, offset 0
s32i.n a14, a3, 4 // save 32 bits from 32-bit color a14 to dest_buff a3, offset 4
s32i.n a15, a3, 8 // save 32 bits from 32-bit color a15 to dest_buff a3, offset 8
addi.n a3, a3, 12 // increment dest_buff pointer by 12
._main_loop:
bnei a10, 0x3, _less_than_3 // branch if less than 3 values left
s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
s32i.n a14, a3, 4 // save 32 bits from a14 to dest_buff a3, offset 4 bytes
s8i a15, a3, 8 // save 8 bits from a15 to dest_buff a3, offset 8 bytes
addi.n a3, a3, 9 // increment dest_buff pointer by 9 bytes
j _less_than_1
_less_than_3:
bnei a10, 0x2, _less_than_2 // branch if less than 2 values left
s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
s16i a14, a3, 4 // save 16 bits from a14 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 6 // increment dest_buff pointer by 6 bytes
j _less_than_1
_less_than_2:
bnei a10, 0x1, _less_than_1 // branch if less than 1 value left
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes
s8i a15, a3, 2 // save 8 bits from a15 to dest_buff a3, offset 2 bytes
addi.n a3, a3, 3 // increment dest_buff pointer by 3 bytes
_less_than_1:
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 (check if the address is 4-byte aligned)
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return

View File

@@ -0,0 +1,346 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB888 simple fill for ESP32S3 processor
.section .text
.align 4
.global lv_color_blend_to_rgb888_esp
.type lv_color_blend_to_rgb888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb888(_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_rgb888_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint24_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
// a11 - dest_w_bytes = sizeof(uint24_t) * dest_w = 3 * a4
slli a11, a4, 1 // a11 - dest_w_bytes = 2 * dest_w
add a11, a11, a4 // a11 - dest_w_bytes = a11 + a4
// Prepare register combinations
// a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR
l8ui a13, a7, 0 // blue 000B
slli a13, a13, 24 // shift to B000
or a13, a13, a8 // a13 BRGB
srli a14, a8, 8 // a14 00RG
slli a10, a8, 16 // a10 GB00
or a14, a14, a10 // a14 GBRG
slli a15, a8, 8 // a15 RGB0
l8ui a10, a7, 2 // a7 000R
or a15, a15, a10 // a15 RGBR
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// Check for short lengths
// dest_w should be at least 12, othewise it's not worth using esp32s3 TIE
bgei a4, 12, _esp32s3_implementation // Branch if dest_w is greater than or equal to 12
j .lv_color_blend_to_rgb888_esp32_body // Jump to esp32 implementation
_esp32s3_implementation:
// Prepare q registers for the main loop
ee.movi.32.q q3, a13, 0 // fill q3 register from a13 by 32 bits
ee.movi.32.q q3, a14, 1 // fill q3 register from a14 by 32 bits
ee.movi.32.q q3, a15, 2 // fill q3 register from a15 by 32 bits
ee.movi.32.q q3, a13, 3 // fill q3 register from a13 by 32 bits
ee.movi.32.q q4, a14, 0 // fill q4 register from a14 by 32 bits
ee.movi.32.q q4, a15, 1 // fill q4 register from a15 by 32 bits
ee.movi.32.q q4, a13, 2 // fill q4 register from a13 by 32 bits
ee.movi.32.q q4, a14, 3 // fill q4 register from a14 by 32 bits
ee.movi.32.q q5, a15, 0 // fill q5 register from a15 by 32 bits
ee.movi.32.q q5, a13, 1 // fill q5 register from a13 by 32 bits
ee.movi.32.q q5, a14, 2 // fill q5 register from a14 by 32 bits
ee.movi.32.q q5, a15, 3 // fill q5 register from a15 by 32 bits
.outer_loop_aligned:
// q registers will get shifted and clobbered, need to reinitialize them before using them again
// Clear q registers
ee.zero.q q0 // clear q0
ee.zero.q q1 // clear q1
ee.zero.q q2 // clear q2
// Reinitialize q registers
ee.orq q0, q0, q3 // copy q3 to q0
ee.orq q1, q1, q4 // copy q4 to q1
ee.orq q2, q2, q5 // copy q5 to q2
// alignment check
extui a8, a3, 0, 4 // address_alignment (a8) = dest_buff address (a3) AND 0xf
movi.n a12, 16 // a12 = 16
mov.n a2, a8 // unalignment (a2) = a8
// following instruction is here to avoid branching
// need to adjust a8 == 0 to 16 to make the unalignment computation work
moveqz a2, a12, a8 // modified unalignment (a2) = 16 if unalignment (a8) == 0
sub a2, a12, a2 // a2 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a10, a11, a2 // local_dest_w_bytes = len - (16 - unalignment)
movi.n a12, 48 // a12 = 48 (main loop copies 48 bytes)
quou a9, a10, a12 // main_loop counter (a9) = local_dest_w_bytes (a10) DIV 48 (a12)
remu a10, a10, a12 // a10 = local_dest_w_bytes (a10) MOD 48 (a12)
beqz a8, _dest_buff_aligned // If already aligned, skip aligning
movi.n a7, unalignment_table // Load unalignment_table address
addx4 a7, a8, a7 // jump_table handle (a7) = offset (a8) * 4 + jump_table address (a7)
l32i a7, a7, 0 // Load target address from jump table
jx a7 // Jump to the corresponding handler
// a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR
handle_0:
handle_1:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
j _shift_q_regs
handle_2:
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
s32i a15, a3, 0 // save 32 bits from a15 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
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
j _shift_q_regs
handle_3:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s32i a14, a3, 0 // save 32 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // 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
j _shift_q_regs
handle_4:
s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
j _shift_q_regs
handle_5:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
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
j _shift_q_regs
handle_6:
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 byte
addi.n a3, a3, 2 // increment dest_buff pointer by 2 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
j _shift_q_regs
handle_7:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
j _shift_q_regs
handle_8:
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
j _shift_q_regs
handle_9:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _shift_q_regs
handle_10:
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
s32i a15, a3, 0 // save 32 bits from a15 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _shift_q_regs
handle_11:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s32i a14, a3, 0 // save 32 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _shift_q_regs
handle_12:
s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _shift_q_regs
handle_13:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
j _shift_q_regs
handle_14:
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
j _shift_q_regs
handle_15:
s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
j _shift_q_regs
.align 4
unalignment_table:
.word handle_0 // Case 0: Dummy case for easier address computation
.word handle_1 // Case 1: Align 15 bytes
.word handle_2 // Case 2: Align 14 bytes
.word handle_3 // Case 3: Align 13 bytes
.word handle_4 // Case 4: Align 12 bytes
.word handle_5 // Case 5: Align 11 bytes
.word handle_6 // Case 6: Align 10 bytes
.word handle_7 // Case 7: Align 9 bytes
.word handle_8 // Case 8: Align 8 bytes
.word handle_9 // Case 9: Align 7 bytes
.word handle_10 // Case 10: Align 6 bytes
.word handle_11 // Case 11: Align 5 bytes
.word handle_12 // Case 12: Align 4 bytes
.word handle_13 // Case 13: Align 3 bytes
.word handle_14 // Case 14: Align 2 bytes
.word handle_15 // Case 15: Align 1 byte
_shift_q_regs:
wur.sar_byte a2 // apply unalignment to the SAR_BYTE
ee.src.q q0, q0, q1 // shift concat. of q0 and q1 to q0 by SAR_BYTE amount
ee.src.q q1, q1, q2 // shift concat. of q1 and q2 to q1 by SAR_BYTE amount
ee.src.q q2, q2, q3 // shift concat. of q2 and q3 to q2 by SAR_BYTE amount
_dest_buff_aligned:
loopnez a9, ._main_loop_aligned // 48 bytes (16 rgb888) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
ee.vst.128.ip q1, a3, 16 // store 16 bytes from q1 to dest_buff a3
ee.vst.128.ip q2, a3, 16 // store 16 bytes from q2 to dest_buff a3
._main_loop_aligned:
// Check modulo 32 of the unalignment, if - then set 32 bytes
bbci a10, 5, .lt_32 // branch if 5-th bit of local_dest_w_bytes a10 is clear
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
ee.vst.128.ip q1, a3, 16 // store 16 bytes from q1 to dest_buff a3
ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB
.lt_32:
// Check modulo 16 of the unalignment, if - then set 16 bytes
bbci a10, 4, .lt_16 // branch if 4-th bit of local_dest_w_bytes a10 is clear
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
ee.srci.2q q0, q1, 0 // shift q0 register to have next bytes to store ready from LSB
.lt_16:
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a10, 3, .lt_8
ee.vst.l.64.ip q0, a3, 8 // store 8 bytes from q0 to dest_buff a3
ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB
.lt_8:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a10, 2, .lt_4
ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2
s32i.n a2, a3, 0 // save 32 bits from a2 to dest_buff a3, offset 0
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
ee.srci.2q q0, q1, 0 // shift q0 register to have next bytes to store ready from LSB
.lt_4:
// Check modulo 2 of the unalignment, if - then set 2 bytes
bbci a10, 1, .lt_2
ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2
s16i a2, a3, 0 // save 16 bits from a2 to dest_buff a3, offset 0
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB
.lt_2:
// Check modulo 1 of the unalignment, if - then set 1 byte
bbci a10, 0, .lt_1
ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2
s8i a2, a3, 0 // save 8 bits from a2 to dest_buff a3, offset 0
addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte
.lt_1:
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
.lv_color_blend_to_rgb888_esp32_body:
// Prepare main loop length and dest_w_bytes
srli a9, a4, 2 // a9 = loop_len = dest_w / 4, calculate main loop_len for original dest_w
movi.n a8, 0x3 // a8 = 0x3, remainder mask
and a10, a4, a8 // a10 - remainder after division by 4 = a4 & 0x3
.outer_loop:
// Run main loop which sets 12 bytes (4 rgb888) in one loop run
loopnez a9, ._main_loop
s32i.n a13, a3, 0 // save 32 bits from 32-bit color a13 to dest_buff a3, offset 0
s32i.n a14, a3, 4 // save 32 bits from 32-bit color a14 to dest_buff a3, offset 4
s32i.n a15, a3, 8 // save 32 bits from 32-bit color a15 to dest_buff a3, offset 8
addi.n a3, a3, 12 // increment dest_buff pointer by 12
._main_loop:
bnei a10, 0x3, _less_than_3 // branch if less than 3 values left
s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
s32i.n a14, a3, 4 // save 32 bits from a14 to dest_buff a3, offset 4 bytes
s8i a15, a3, 8 // save 8 bits from a15 to dest_buff a3, offset 8 bytes
addi.n a3, a3, 9 // increment dest_buff pointer by 9 bytes
j _less_than_1
_less_than_3:
bnei a10, 0x2, _less_than_2 // branch if less than 2 values left
s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes
s16i a14, a3, 4 // save 16 bits from a14 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 6 // increment dest_buff pointer by 6 bytes
j _less_than_1
_less_than_2:
bnei a10, 0x1, _less_than_1 // branch if less than 1 value left
s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes
s8i a15, a3, 2 // save 8 bits from a15 to dest_buff a3, offset 2 bytes
addi.n a3, a3, 3 // increment dest_buff pointer by 3 bytes
_less_than_1:
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

View File

@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// Memcpy macros for modulo checking
// After running the main loop, there is need to check remaining bytes to be copied out of the main loop
// Macros work with both, aligned and unaligned (4-byte boundary) memories
// but performance is significantly lower when using unaligned memory, because of the unaligned memory access exception
// Macro for checking modulo 8
.macro macro_memcpy_mod_8 src_buf, dest_buf, condition, x1, x2, JUMP_TAG
// Check modulo 8 of the \condition, if - then copy 8 bytes
bbci \condition, 3, ._mod_8_check_\JUMP_TAG // Branch if 3-rd bit of \condition is clear
l32i.n \x1, \src_buf, 0 // Load 32 bits from \src_buff to \x1, offset 0
l32i.n \x2, \src_buf, 4 // Load 32 bits from \src_buff to \x2, offset 4
s32i.n \x1, \dest_buf, 0 // Save 32 bits from \x1 to \dest_buff, offset 0
s32i.n \x2, \dest_buf, 4 // Save 32 bits from \x2 to \dest_buff, offset 4
addi.n \src_buf, \src_buf, 8 // Increment \src_buff pointer by 8
addi.n \dest_buf, \dest_buf, 8 // Increment \dest_buff pointer 8
._mod_8_check_\JUMP_TAG:
.endm // macro_memcpy_mod_8
// Macro for checking modulo 4
.macro macro_memcpy_mod_4 src_buf, dest_buf, condition, x1, JUMP_TAG
// Check modulo 4 of the \condition, if - then copy 4 bytes
bbci \condition, 2, ._mod_4_check_\JUMP_TAG // Branch if 2-nd bit of \condition is clear
l32i.n \x1, \src_buf, 0 // Load 32 bits from \src_buff to \x1, offset 0
addi.n \src_buf, \src_buf, 4 // Increment \src_buff pointer by 4
s32i.n \x1, \dest_buf, 0 // Save 32 bits from \x1 to \dest_buff, offset 0
addi.n \dest_buf, \dest_buf, 4 // Increment \dest_buff pointer 4
._mod_4_check_\JUMP_TAG:
.endm // macro_memcpy_mod_4
// Macro for checking modulo 2
.macro macro_memcpy_mod_2 src_buf, dest_buf, condition, x1, JUMP_TAG
// Check modulo 2 of the \condition, if - then copy 2 bytes
bbci \condition, 1, ._mod_2_check_\JUMP_TAG // Branch if 1-st bit of \condition is clear
l16ui \x1, \src_buf, 0 // Load 16 bits from \src_buff to \x1, offset 0
addi.n \src_buf, \src_buf, 2 // Increment \src_buff pointer by 2
s16i \x1, \dest_buf, 0 // Save 16 bits from \x1 to \dest_buff, offset 0
addi.n \dest_buf, \dest_buf, 2 // Increment \dest_buff pointer 2
._mod_2_check_\JUMP_TAG:
.endm // macro_memcpy_mod_2
// Macro for checking modulo 1
.macro macro_memcpy_mod_1 src_buf, dest_buf, condition, x1, JUMP_TAG
// Check modulo 1 of the \condition, if - then copy 1 byte
bbci \condition, 0, ._mod_1_check_\JUMP_TAG // Branch if 0-th bit of \condition is clear
l8ui \x1, \src_buf, 0 // Load 8 bits from \src_buff to \x1, offset 0
addi.n \src_buf, \src_buf, 1 // Increment \src_buff pointer by 1
s8i \x1, \dest_buf, 0 // Save 8 bits from \x1 to \dest_buff, offset 0
addi.n \dest_buf, \dest_buf, 1 // Increment \dest_buff pointer 1
._mod_1_check_\JUMP_TAG:
.endm // macro_memcpy_mod_1

View File

@@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lv_macro_memcpy.S" // Memcpy macros
// This is LVGL RGB565 image blend to RGB565 for ESP32 processor
.section .text
.align 4
.global lv_rgb565_blend_normal_to_rgb565_esp
.type lv_rgb565_blend_normal_to_rgb565_esp,@function
// The function implements the following C code:
// void rgb565_image_blend(_lv_draw_sw_blend_image_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_rgb565_blend_normal_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
l32i.n a8, a2, 24 // a8 - src_stride in bytes
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// No need to convert any colors here, we are copying from rgb565 to rgb565
// Check dest_w length
bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8
// Check memory alignment and input parameters lengths and decide which implementation to use
movi.n a10, 0x3 // a10 = 0x3 alignment mask (4-byte alignment)
or a15, a7, a3 // a15 = src_buff (a7) OR dest_buff (a3)
or a15, a15, a6 // a15 = a15 OR dest_stride (a6)
or a15, a15, a8 // a15 = a15 OR src_stride (a8)
or a15, a15, a11 // a15 = a15 OR dest_w_bytes (a11)
and a15, a15, a10 // a15 = a15 AND alignment mask (a10)
bnez a15, _alignment_check // Branch if a15 not equals to zero
//**********************************************************************************************************************
// The most ideal case - both arrays aligned, both strides and dest_w are multiples of 4
// dest_buff (a3) - 4-byte aligned
// src_buff (a7) - 4-byte aligned
// dest_stride (a6) - 4-byte multiple
// src_stride (a8) - 4-byte multiple
// dest_w (a4) - 4-byte multiple
srli a9, a4, 3 // a9 - loop_len = dest_w / 8
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_align:
// Run main loop which copies 16 bytes (8 RGB565 pixels) in one loop run
loopnez a9, ._main_loop_aligned
l32i.n a15, a7, 0 // Load 32 bits from src_buff a7 to a15, offset 0
l32i.n a14, a7, 4 // Load 32 bits from src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from src_buff a7 to a13, offset 8
l32i.n a12, a7, 12 // Load 32 bits from src_buff a7 to a12, offset 12
s32i.n a15, a3, 0 // Save 32 bits from a15 to dest_buff a3, offset 0
s32i.n a14, a3, 4 // Save 32 bits from a15 to dest_buff a3, offset 4
s32i.n a13, a3, 8 // Save 32 bits from a15 to dest_buff a3, offset 8
s32i.n a12, a3, 12 // Save 32 bits from a15 to dest_buff a3, offset 12
addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16
addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16
._main_loop_aligned:
// Finish the remaining bytes out of the main loop
// Check modulo 8 of the dest_w_bytes (a11), if - then copy 8 bytes (4 RGB565 pixels)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy registers a14 a15
macro_memcpy_mod_8 a7, a3, a11, a14, a15 __LINE__
// Check modulo 4 of the dest_w_bytes (a11), if - then copy 4 bytes (2 RGB565 pixels)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_4 a7, a3, a11, a15 __LINE__
// Check modulo 2 of the dest_w_bytes (a11), if - then copy 2 bytes (1 RGB565 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_2 a7, a3, a11, a15 __LINE__
// Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_1 a7, a3, a11, a15 __LINE__
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_align
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// The most general case - at leas one array is not aligned, or one parameter is not multiple of 4
_alignment_check:
// dest_buff (a3) - 4-byte aligned, or not
// src_buff (a7) - 4-byte aligned, or not
// dest_stride (a6) - 4-byte multiple, or not
// src_stride (a8) - 4-byte multiple, or not
// dest_w (a4) - 4-byte multiple, or not
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_unalign:
extui a13, a3, 0, 2 // Get last two bits of the dest_buff address a3, to a13
movi.n a15, 4 // Move 4 to a15, for calculation of the destination alignment loop
sub a14, a15, a13 // Calculate destination alignment loop length (a14 = 4 - a13)
// In case of the dest_buff a3 being already aligned (for example by matrix padding), correct a14 value,
// to prevent the destination aligning loop to run 4 times (to prevent aligning already aligned memory)
moveqz a14, a13, a13 // If a13 is zero, move a13 to a14, move 0 to a14
sub a10, a11, a14 // Get the dest_w_bytes after the aligning loop
srli a9, a10, 4 // Calculate main loop len (a9 = dest_w_bytes_local / 16)
// Run dest_buff aligning loop byte by byte
loopnez a14, ._dest_aligning_loop
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
addi.n a7, a7, 1 // Increment src_buff pointer a7 by 1
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
addi.n a3, a3, 1 // Increment dest_buff pointer a3 by 1
._dest_aligning_loop:
// Destination is aligned, source is unaligned
// For more information about this implementation, see chapter 3.3.2 Shifts and the Shift Amount Register (SAR)
// in Xtensa Instruction Set Architecture (ISA) Reference Manual
ssa8l a7 // Set SAR_BYTE from src_buff a7 unalignment
extui a4, a7, 0, 2 // Get last 2 bits of the src_buff, a4 = src_buff_unalignment
sub a7, a7, a4 // "align" the src_buff a7, to 4-byte boundary by decreasing it's pointer to the nearest aligned boundary
// First preload for the loopnez cycle
l32i.n a15, a7, 0 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 0
// Run main loop which copies 16 bytes (8 RGB565 pixels) in one loop run
loopnez a9, ._main_loop_unalign
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
l32i.n a12, a7, 12 // Load 32 bits from 4-byte aligned src_buff a7 to a12, offset 12
src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14
s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4
l32i.n a15, a7, 16 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 16
src a13, a12, a13 // Concatenate a12 and a13 and shift by SAR_BYTE amount to a13
s32i.n a13, a3, 8 // Save 32 bits from shift-corrected a13 to dest_buff a3, offset 8
addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16
src a12, a15, a12 // Concatenate a15 and a12 and shift by SAR_BYTE amount to a12
s32i.n a12, a3, 12 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 12
addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16
._main_loop_unalign:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes_local (a10), if - then copy 8 bytes
bbci a10, 3, _mod_8_check // Branch if 3-rd bit of dest_w_bytes_local is clear
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
addi.n a7, a7, 8 // Increment src_buff pointer a7 by 8
src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14
s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4
addi.n a3, a3, 8 // Increment dest_buff pointer a3 by 8
mov a15, a13 // Prepare a15 for the next steps (copy a13 to a15)
_mod_8_check:
// Check modulo 4 of the dest_w_bytes_local (a10), if - then copy 4 bytes
bbci a10, 2, _mod_4_check // Branch if 2-nd bit of dest_w_bytes_local is clear
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
addi.n a7, a7, 4 // Increment src_buff pointer a7 by 4
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
addi.n a3, a3, 4 // Increment dest_buff pointer a3 by 4
mov a15, a14 // Prepare a15 for the next steps (copy a14 to a15)
_mod_4_check:
extui a13, a10, 0, 2 // Get the last 2 bytes of the dest_w_bytes_local (a10), a13 = a10[1:0], to find out how many bytes are needs copied and to increase src and dest pointer accordingly
beqz a13, _mod_1_2_check // Branch if a13 equal to zero, E.G. if there are no bytes to be copied
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a12, a3, 0 // Get dest_buff value: Load 32 bits from 4-byte aligned dest_buff a3 to a12, offset 0
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
ssa8l a10 // Set SAR_BYTE from dest_w_bytes_local a10 length
sll a15, a15 // Shift the dest word a15 by SAR_BYTE amount
srl a12, a12 // Shift the src word a12 by SAR_BYTE amount
ssa8b a10 // Set SAR_BYTE from dest_w_bytes_local a10 length
src a12, a12, a15 // Concatenate a12 and a15 and shift by SAR_BYTE amount to a12
s32i.n a12, a3, 0 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 0
add a7, a7, a13 // Increment src_buff pointer a7, by amount of copied bytes (a13)
add a3, a3, a13 // Increment dest_buff pointer a3, by amount of copied bytes (a13)
_mod_1_2_check:
add a7, a7, a4 // Correct the src_buff back by src_buff_unalignment (a4), after we have force-aligned it to 4-byte boundary before the main loop
add a3, a3, a6 // dest_buff + dest_stride
add a7, a7, a8 // src_buff + src_stride
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_unalign
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// Small matrix width, keep it simple for lengths less than 8 pixels
_matrix_width_check: // Matrix width is greater or equal 8 pixels
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_short_matrix_length:
// Run main loop which copies 2 bytes (one RGB565 pixel) in one loop run
loopnez a4, ._main_loop_short_matrix_length
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1
addi.n a7, a7, 2 // Increment src_buff pointer a7 by 1
addi.n a3, a3, 2 // Increment dest_buff pointer a3 by 2
._main_loop_short_matrix_length:
// Finish remaining byte out of the main loop
// Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_1 a7, a3, a11, a15, __LINE__
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_short_matrix_length
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return

View File

@@ -0,0 +1,372 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lv_macro_memcpy.S" // Memcpy macros
// This is LVGL RGB565 image blend to RGB565 for ESP32S3 processor
.section .text
.align 4
.global lv_rgb565_blend_normal_to_rgb565_esp
.type lv_rgb565_blend_normal_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_rgb565_blend_normal_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
l32i.n a8, a2, 24 // a8 - src_stride in bytes
movi.n a10, 0xf // 0xf alignment mask (16-byte alignment)
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// No need to convert any colors here, we are copying from rgb565 to rgb565
// Check dest_w length
bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8
// Check dest_buff alignment fist
and a15, a10, a3 // 16-byte alignment mask AND dest_buff pointer a3
bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero
// Jump straight to the last implementation, since this is the only one which deals with unaligned destination arrays
// Check src_buff alignment
and a15, a10, a7 // 16-byte alignment mask AND src_buff pointer a7
bnez a15, _src_align_dest_unalign // Branch if a15 not equals to zero
// Jump to check, if the second or third implementation can be used (depends on both strides and dest_w)
// Check dest_stride alignment
and a15, a10, a6 // 16-byte alignment mask AND dest_stride a6
bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero
// Jump straight to the last implementation, since this is the only one which deals with destination stride not aligned
// Check src_stride alignment
and a15, a10, a8 // 16-byte alignment mask AND src_stride a8
bnez a15, _src_align_dest_unalign // Branch if a15 not equals to zero
// Jump to check, if the second or third implementation can be used (depends on dest_w_bytes)
// Check dest_w_bytes alignment
and a15, a10, a11 // 16-byte alignment mask AND dest_w_bytes
bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero
// Jump straight to the last implementation, since this is the only one which deals with dest_w_bytes not aligned
//**********************************************************************************************************************
// The most ideal case - both arrays aligned, both strides and dest_w are multiples of 16
// dest_buff (a3) - 16-byte aligned
// src_buff (a7) - 16-byte aligned
// dest_stride (a6) - 16-byte multiple
// src_stride (a8) - 16-byte multiple
// dest_w (a4) - 16-byte multiple
srli a9, a4, 4 // a9 - loop_len = dest_w / 16
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_align:
// Run main loop which copies 32 bytes (16 RGB565 pixels) in one loop run
loopnez a9, ._main_loop_align // 32 bytes (16 RGB565 pixels) in one loop run
ee.vld.128.ip q0, a7, 16 // Load 16 bytes from src_buff a7 to q0, increase src_buf pointer a7 by 16
ee.vld.128.ip q1, a7, 16 // Load 16 bytes from src_buff a7 to q1, increase src_buf pointer a7 by 16
ee.vst.128.ip q0, a3, 16 // Store 16 bytes from q0 to dest_buff a3, increase dest_buff pointer a3 by 16
ee.vst.128.ip q1, a3, 16 // Store 16 bytes from q1 to dest_buff a3, increase dest_buff pointer a3 by 16
._main_loop_align:
// Finish remaining bytes out of the main loop
// Check modulo 16 of the dest_w, if - then copy 16 bytes (8 RGB565 pixels)
bbci a11, 4, _align_mod_16_check // Branch if 4-th bit of dest_w_bytes a11 is clear
ee.vld.128.ip q0, a7, 16 // Load 16 bytes from src_buff a7 to q0, increase src_buf pointer a7 by 16
ee.vst.128.ip q0, a3, 16 // Store 16 bytes from q0 to dest_buff a3, increase dest_buff pointer a3 by 16
_align_mod_16_check:
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_align
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
_src_align_dest_unalign:
// Check dest_stride alignment
and a15, a10, a6 // 16-byte alignment mask AND dest_stride a6
bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero
// Check dest_w_bytes alignment
and a15, a10, a11 // 16-byte alignment mask AND dest_w_bytes a11
bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero
// We don't check src_stride alignment for this implementation, as it can be either align, or unalign
//**********************************************************************************************************************
// Less ideal case - Only destination array is aligned, src array is unaligned
// Source stride is either aligned or unaligned, destination stride must be aligned, dest_w_bytes must be aligned
// dest_buff (a3) - 16-byte aligned
// src_buff (a7) - unaligned
// dest_stride (a6) - 16-byte multiple
// src_stride (a8) - does not matter if 16-byte multiple
// dest_w (a4) - 16-byte multiple
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
// Calculate modulo for non-aligned data
movi a15, 48 // a15 = 48 (main loop copies 48 bytes)
quou a9, a11, a15 // a9 = dest_w_bytes (a11) DIV 48 (15)
remu a12, a11, a15 // a12 = dest_w_bytes (a11) remainder after DIV 48 (15)
.outer_loop_src_unalign_dest_align:
ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
// Run main loop which copies 48 bytes (24 RGB565 pixels) in one loop run
loopnez a9, ._main_loop_src_unalign_dest_align // 48 bytes (24 RGB565 pixels) in one loop
ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
._main_loop_src_unalign_dest_align:
// Finish the main loop outside of the loop from Q registers preloads
// Check modulo 32 of the loop_len_remainder, if - then copy 32 bytes (16 RGB565 pixels)
bbci a12, 5, _unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear
ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
j _end_of_row_src_unalign_dest_align
_unalign_mod_32_check:
// Check modulo 16 of the loop_len_remainder, if - then copy 16 bytes (8 RGB565 pixels)
bbci a12, 4, _unalign_mod_16_check // Branch if 4-th bit of loop_len_remainder a12 is clear
ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload
j _end_of_row_src_unalign_dest_align
_unalign_mod_16_check:
// Nothing to copy outside of the main loop
addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload
_end_of_row_src_unalign_dest_align:
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_src_unalign_dest_align
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
_src_unalign_dest_unalign:
//**********************************************************************************************************************
// The most general case, can handle all the possible combinations
// dest_buff (a3) - unaligned
// src_buff (a7) - unaligned
// dest_stride (a6) - not 16-byte multiple
// src_stride (a8) - not 16-byte multiple
// dest_w (a4) - not 16-byte multiple
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_all_unalign:
// dest_buff alignment check
and a13, a10, a3 // Alignment mask 0xf (a10) AND dest_buff pointer
beqz a13, _dest_buff_aligned // Branch if a13 = 0 (if dest_buff is aligned)
movi.n a14, 16 // a14 = 16
sub a13, a14, a13 // a13 = 16 - unalignment
// Check modulo 8 of the unalignment a13, if - then copy 8 bytes (4 RGB565 pixels)
// src_buff a7, dest_buff a3, unalignment a13, copy registers a14, a15
macro_memcpy_mod_8 a7, a3, a13, a15, a14, __LINE__
// Check modulo 4 of the unalignment, if - then copy 4 bytes (2 RGB565 pixels)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_4 a7, a3, a13, a15, __LINE__
// Check modulo 2 of the unalignment, if - then copy 2 bytes (1 RGB565 pixel)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_2 a7, a3, a13, a15, __LINE__
// Check modulo 1 of the unalignment, if - then copy 1 byte (1/2 of RGB565 pixel)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_1 a7, a3, a13, a15, __LINE__
_dest_buff_aligned:
// Calculate modulo for non-aligned data
sub a11, a11, a13 // a11 = local_dest_w_bytes (a11) = dest_w_bytes (a11) - (16 - unalignment)
movi a15, 48 // a15 = 48
quou a9, a11, a15 // a9 = local_dest_w_bytes (a11) DIV 48 (a15)
remu a12, a11, a15 // a12 = local_dest_w_bytes (a11) remainder after div 48 (a15)
ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
// Run main loop which copies 48 bytes (24 RGB565 pixels) in one loop run
loopnez a9, ._main_loop_all_unalign // 48 bytes (24 RGB565 pixels) in one loop
ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
._main_loop_all_unalign:
// Finish the main loop outside of the loop from Q registers preloads
// Check modulo 32 and modulo 8 of the loop_len_remainder a12
bbci a12, 5, _all_unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear
bbsi a12, 3, _all_unalign_mod_32_mod_8_check // Branch if 3-rd bif of loop_len_remainder a12 is set
// Copy 32 bytes (16 RGB565 pixels) (47 - 40)
ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
j _skip_mod16
_all_unalign_mod_32_mod_8_check:
// Copy 40 bytes (20 RGB565 pixels)
ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q2, a7, 0, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q4, q4, q2 // Concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount
ee.vst.l.64.ip q4, a3, 8 // Store lower 8 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_32_check:
// Check modulo 16 and modulo 8 of the loop_len_remainder a12
bbci a12, 4, _all_unalign_mod_16_check // branch if 4-th bit of loop_len_remainder a12 is clear
bbsi a12, 3, _all_unalign_mod_16_mod_8_check // branch if 3-rd bit of loop_len_remainder a12 is set
// Copy 16 bytes (8 RGB565 pixels)
ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_16_mod_8_check:
// Copy 24 bytes (12 RGB565 pixels)
ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount
ee.vst.l.64.ip q3, a3, 8 // Store lower 8 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_16_check:
bbci a12, 3, _all_unalign_mod_8_check // Branch if 3-rd bit of loop_len_remainder a12 is clear
// Copy 8 bytes (4 RGB565 pixels)
ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount
ee.vst.l.64.ip q2, a3, 8 // Store lower 8 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -24 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_8_check:
addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload
_skip_mod16:
// Check modulo 4 of the loop_len_remainder, if - then copy 4 bytes (2 RGB565 pixels)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_4 a7, a3, a12, a15, __LINE__
// Check modulo 2 of the loop_len_remainder, if - then copy 2 bytes (1 RGB565 pixel)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_2 a7, a3, a12, a15, __LINE__
// Check modulo 1 of the loop_len_remainder, if - then copy 1 byte (1/2 RGB565 pixel)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_1 a7, a3, a12, a15, __LINE_
slli a11, a4, 1 // Refresh dest_w_bytes
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_all_unalign
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// Small matrix width, keep it simple for lengths less than 8 pixels
_matrix_width_check: // Matrix width is greater or equal 8 pixels
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_short_matrix_length:
// Run main loop which copies 2 bytes (one RGB565 pixel) in one loop run
loopnez a4, ._main_loop_short_matrix_length
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1
addi.n a7, a7, 2 // Increment src_buff pointer a7 by 1
addi.n a3, a3, 2 // Increment dest_buff pointer a3 by 2
._main_loop_short_matrix_length:
// Finish remaining byte out of the main loop
// Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_1 a7, a3, a11, a15, __LINE__
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_short_matrix_length
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return

View File

@@ -0,0 +1,261 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lv_macro_memcpy.S" // Memcpy macros
// This is LVGL RGB888 image blend to RGB888 for ESP32 processor
.section .text
.align 4
.global lv_rgb888_blend_normal_to_rgb888_esp
.type lv_rgb888_blend_normal_to_rgb888_esp,@function
// The function implements the following C code:
// void rgb888_image_blend(_lv_draw_sw_blend_image_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_rgb888_blend_normal_to_rgb888_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
l32i.n a8, a2, 24 // a8 - src_stride in bytes
slli a11, a4, 1 // a11 = (a4 << 1) + a4
add a11, a11, a4 // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w
// No need to convert any colors here, we are copying from rgb888 to rgb888
// Check dest_w length
bltui a4, 5, _matrix_width_check // Branch if dest_w (a4) is lower than 5 (dest_w_bytes 15)
// Check memory alignment and input parameters lengths and decide which implementation to use
movi.n a10, 0x3 // a10 = 0x3 alignment mask (4-byte alignment)
or a15, a7, a3 // a15 = src_buff (a7) OR dest_buff (a3)
or a15, a15, a6 // a15 = a15 OR dest_stride (a6)
or a15, a15, a8 // a15 = a15 OR src_stride (a8)
or a15, a15, a11 // a15 = a15 OR dest_w_bytes (a11)
and a15, a15, a10 // a15 = a15 AND alignment mask (a10)
bnez a15, _alignment_check // Branch if a15 not equals to zero
//**********************************************************************************************************************
// The most ideal case - both arrays aligned, both strides and dest_w are multiples of 4
// dest_buff (a3) - 4-byte aligned
// src_buff (a7) - 4-byte aligned
// dest_stride (a6) - 4-byte multiple
// src_stride (a8) - 4-byte multiple
// dest_w (a4) - 4-byte multiple
srli a9, a11, 4 // a9 - loop_len = dest_w_bytes / 16
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_align:
// Run main loop which copies 16 bytes (5 and 1/3 of RGB888 pixels) in one loop run
loopnez a9, ._main_loop_aligned
l32i.n a15, a7, 0 // Load 32 bits from src_buff a7 to a15, offset 0
l32i.n a14, a7, 4 // Load 32 bits from src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from src_buff a7 to a13, offset 8
l32i.n a12, a7, 12 // Load 32 bits from src_buff a7 to a12, offset 12
s32i.n a15, a3, 0 // Save 32 bits from a15 to dest_buff a3, offset 0
s32i.n a14, a3, 4 // Save 32 bits from a15 to dest_buff a3, offset 4
s32i.n a13, a3, 8 // Save 32 bits from a15 to dest_buff a3, offset 8
s32i.n a12, a3, 12 // Save 32 bits from a15 to dest_buff a3, offset 12
addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16
addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16
._main_loop_aligned:
// Finish the remaining bytes out of the main loop
// Check modulo 8 of the dest_w_bytes (a11), if - then copy 8 bytes (2 and 2/3 of RGB888 pixels)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy registers a14 a15
macro_memcpy_mod_8 a7, a3, a11, a14, a15 __LINE__
// Check modulo 4 of the dest_w_bytes (a11), if - then copy 4 bytes (1 and 1/3 of RGB888 pixels)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_4 a7, a3, a11, a15 __LINE__
// Check modulo 2 of the dest_w_bytes (a11), if - then copy 2 bytes (2/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_2 a7, a3, a11, a15 __LINE__
// Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15
macro_memcpy_mod_1 a7, a3, a11, a15 __LINE__
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_align
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// The most general case - at leas one array is not aligned, or one parameter is not multiple of 4
_alignment_check:
// dest_buff (a3) - 4-byte aligned, or not
// src_buff (a7) - 4-byte aligned, or not
// dest_stride (a6) - 4-byte multiple, or not
// src_stride (a8) - 4-byte multiple, or not
// dest_w (a4) - 4-byte multiple, or not
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_unalign:
extui a13, a3, 0, 2 // Get last two bits of the dest_buff address a3, to a13
movi.n a15, 4 // Move 4 to a15, for calculation of the destination alignment loop
sub a14, a15, a13 // Calculate destination alignment loop length (a14 = 4 - a13)
// In case of the dest_buff a3 being already aligned (for example by matrix padding), correct a14 value,
// to prevent the destination aligning loop to run 4 times (to prevent aligning already aligned memory)
moveqz a14, a13, a13 // If a13 is zero, move a13 to a14, move 0 to a14
sub a10, a11, a14 // Get the dest_w_bytes after the aligning loop
srli a9, a10, 4 // Calculate main loop len (a9 = dest_w_bytes_local / 16)
// Run dest_buff aligning loop byte by byte
loopnez a14, ._dest_aligning_loop
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
addi.n a7, a7, 1 // Increment src_buff pointer a7 by 1
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
addi.n a3, a3, 1 // Increment dest_buff pointer a3 by 1
._dest_aligning_loop:
// Destination is aligned, source is unaligned
// For more information about this implementation, see chapter 3.3.2 Shifts and the Shift Amount Register (SAR)
// in Xtensa Instruction Set Architecture (ISA) Reference Manual
ssa8l a7 // Set SAR_BYTE from src_buff a7 unalignment
extui a4, a7, 0, 2 // Get last 2 bits of the src_buff, a4 = src_buff_unalignment
sub a7, a7, a4 // "align" the src_buff a7, to 4-byte boundary by decreasing it's pointer to the nearest aligned boundary
// First preload for the loopnez cycle
l32i.n a15, a7, 0 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 0
// Run main loop which copies 16 bytes (5 and 1/3 of RGB888 pixels) in one loop run
loopnez a9, ._main_loop_unalign
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
l32i.n a12, a7, 12 // Load 32 bits from 4-byte aligned src_buff a7 to a12, offset 12
src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14
s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4
l32i.n a15, a7, 16 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 16
src a13, a12, a13 // Concatenate a12 and a13 and shift by SAR_BYTE amount to a13
s32i.n a13, a3, 8 // Save 32 bits from shift-corrected a13 to dest_buff a3, offset 8
addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16
src a12, a15, a12 // Concatenate a15 and a12 and shift by SAR_BYTE amount to a12
s32i.n a12, a3, 12 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 12
addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16
._main_loop_unalign:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes_local (a10), if - then copy 8 bytes
bbci a10, 3, _mod_8_check // Branch if 3-rd bit of dest_w_bytes_local is clear
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
addi.n a7, a7, 8 // Increment src_buff pointer a7 by 8
src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14
s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4
addi.n a3, a3, 8 // Increment dest_buff pointer a3 by 8
mov a15, a13 // Prepare a15 for the next steps (copy a13 to a15)
_mod_8_check:
// Check modulo 4 of the dest_w_bytes_local (a10), if - then copy 4 bytes
bbci a10, 2, _mod_4_check // Branch if 2-nd bit of dest_w_bytes_local is clear
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
addi.n a7, a7, 4 // Increment src_buff pointer a7 by 4
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0
addi.n a3, a3, 4 // Increment dest_buff pointer a3 by 4
mov a15, a14 // Prepare a15 for the next steps (copy a14 to a15)
_mod_4_check:
extui a13, a10, 0, 2 // Get the last 2 bytes of the dest_w_bytes_local (a10), a13 = a10[1:0], to find out how many bytes are needs copied and to increase src and dest pointer accordingly
beqz a13, _mod_1_2_check // Branch if a13 equal to zero, E.G. if there are no bytes to be copied
l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4
l32i.n a12, a3, 0 // Get dest_buff value: Load 32 bits from 4-byte aligned dest_buff a3 to a12, offset 0
src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps)
ssa8l a10 // Set SAR_BYTE from dest_w_bytes_local a10 length
sll a15, a15 // Shift the dest word a15 by SAR_BYTE amount
srl a12, a12 // Shift the src word a12 by SAR_BYTE amount
ssa8b a10 // Set SAR_BYTE from dest_w_bytes_local a10 length
src a12, a12, a15 // Concatenate a12 and a15 and shift by SAR_BYTE amount to a12
s32i.n a12, a3, 0 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 0
add a7, a7, a13 // Increment src_buff pointer a7, by amount of copied bytes (a13)
add a3, a3, a13 // Increment dest_buff pointer a3, by amount of copied bytes (a13)
_mod_1_2_check:
add a7, a7, a4 // Correct the src_buff back by src_buff_unalignment (a4), after we have force-aligned it to 4-byte boundary before the main loop
add a3, a3, a6 // dest_buff + dest_stride
add a7, a7, a8 // src_buff + src_stride
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_unalign
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// Small matrix width, keep it simple for lengths less than 8 pixels
_matrix_width_check: // Matrix width is greater or equal 8 pixels
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_short_matrix_length:
// Run main loop which copies 3 bytes (one RGB888 pixel) in one loop run
loopnez a4, ._main_loop_short_matrix_length
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1
l8ui a13, a7, 2 // Load 8 bits from src_buff a7 to a13, offset 2
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1
s8i a13, a3, 2 // Save 8 bits from a13 to dest_buff a3, offset 2
addi.n a7, a7, 3 // Increment src_buff pointer a7 by 3
addi.n a3, a3, 3 // Increment dest_buff pointer a3 by 3
._main_loop_short_matrix_length:
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_short_matrix_length
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return

View File

@@ -0,0 +1,221 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lv_macro_memcpy.S" // Memcpy macros
// This is LVGL RGB888 image blend to RGB888 for ESP32S3 processor
.section .text
.align 4
.global lv_rgb888_blend_normal_to_rgb888_esp
.type lv_rgb888_blend_normal_to_rgb888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb888(_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_rgb888_blend_normal_to_rgb888_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
l32i.n a8, a2, 24 // a8 - src_stride in bytes
movi.n a10, 0xf // 0xf alignment mask (16-byte alignment)
slli a11, a4, 1 // a11 = (a4 << 1) + a4
add a11, a11, a4 // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w
// No need to convert any colors here, we are copying from rgb888 to rgb888
// Check dest_w length
bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8
//**********************************************************************************************************************
// The most general case, can handle all the possible combinations
// dest_buff (a3) - any alignment
// src_buff (a7) - any alignment
// dest_stride (a6) - any length
// src_stride (a8) - any length
// dest_w (a4) - any length
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_all_unalign:
// dest_buff alignment check
and a13, a10, a3 // Alignment mask 0xf (a10) AND dest_buff pointer
beqz a13, _dest_buff_aligned // Branch if a13 = 0 (if dest_buff is aligned)
movi.n a14, 16 // a14 = 16
sub a13, a14, a13 // a13 = 16 - unalignment
// Check modulo 8 of the unalignment a13, if - then copy 8 bytes (2 and 2/3 of RGB888 pixels)
// src_buff a7, dest_buff a3, unalignment a13, copy registers a14, a15
macro_memcpy_mod_8 a7, a3, a13, a15, a14, __LINE__
// Check modulo 4 of the unalignment, if - then copy 4 bytes (1 and 1/3 of RGB888 pixels)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_4 a7, a3, a13, a15, __LINE__
// Check modulo 2 of the unalignment, if - then copy 2 bytes (2/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_2 a7, a3, a13, a15, __LINE__
// Check modulo 1 of the unalignment, if - then copy 1 byte (1/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, unalignment a13, copy register a15
macro_memcpy_mod_1 a7, a3, a13, a15, __LINE__
_dest_buff_aligned:
// Calculate modulo for non-aligned data
sub a11, a11, a13 // a11 = local_dest_w_bytes (a11) = dest_w_bytes (a11) - (16 - unalignment)
movi a15, 48 // a15 = 48
quou a9, a11, a15 // a9 = local_dest_w_bytes (a11) DIV 48 (a15)
remu a12, a11, a15 // a12 = local_dest_w_bytes (a11) remainder after div 48 (a15)
ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16
// Run main loop which copies 48 bytes (16 RGB888 pixels) in one loop run
loopnez a9, ._main_loop_all_unalign // 48 bytes (16 RGB888 pixels) in one loop
ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
._main_loop_all_unalign:
// Finish the main loop outside of the loop from Q registers preloads
// Check modulo 32 and modulo 8 of the loop_len_remainder a12
bbci a12, 5, _all_unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear
bbsi a12, 3, _all_unalign_mod_32_mod_8_check // Branch if 3-rd bif of loop_len_remainder a12 is set
// Copy 32 bytes (10 and 2/3 of RGB888 pixels)
ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
j _skip_mod16
_all_unalign_mod_32_mod_8_check:
// Copy 40 bytes (13 and 1/3 of RGB888 pixels)
ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q.ld.ip q2, a7, 0, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q4, q4, q2 // Concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount
ee.vst.l.64.ip q4, a3, 8 // Store lower 8 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_32_check:
// Check modulo 16 and modulo 8 of the loop_len_remainder a12
bbci a12, 4, _all_unalign_mod_16_check // branch if 4-th bit of loop_len_remainder a12 is clear
bbsi a12, 3, _all_unalign_mod_16_mod_8_check // branch if 3-rd bit of loop_len_remainder a12 is set
// Copy 16 bytes (5 and 1/3 of RGB888 pixels)
ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_16_mod_8_check:
// Copy 24 bytes (8 RGB888 pixels)
ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7
ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16
ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount
ee.vst.l.64.ip q3, a3, 8 // Store lower 8 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_16_check:
bbci a12, 3, _all_unalign_mod_8_check // Branch if 3-rd bit of loop_len_remainder a12 is clear
// Copy 8 bytes (2 and 2/3 of RGB888 pixels)
ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount
ee.vst.l.64.ip q2, a3, 8 // Store lower 8 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 8
addi a7, a7, -24 // Correct the src_buff pointer a7, caused by q reg preload
j _skip_mod16
_all_unalign_mod_8_check:
addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload
_skip_mod16:
// Check modulo 4 of the loop_len_remainder, if - then copy 4 bytes (1 and 1/3 of RGB888 pixels)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_4 a7, a3, a12, a15, __LINE__
// Check modulo 2 of the loop_len_remainder, if - then copy 2 bytes (2/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_2 a7, a3, a12, a15, __LINE__
// Check modulo 1 of the loop_len_remainder, if - then copy 1 byte (1/3 of RGB888 pixel)
// src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15
macro_memcpy_mod_1 a7, a3, a12, a15, __LINE_
slli a11, a4, 1 // Refresh dest_w_bytes
add a11, a11, a4
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_all_unalign
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return
//**********************************************************************************************************************
// Small matrix width, keep it simple for lengths less than 8 pixels
_matrix_width_check: // Matrix width is greater or equal 8 pixels
// Convert strides to matrix paddings
sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11)
sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11)
.outer_loop_short_matrix_length:
// Run main loop which copies 3 bytes (one RGB888 pixel) in one loop run
loopnez a4, ._main_loop_short_matrix_length
l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0
l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1
l8ui a13, a7, 2 // Load 8 bits from src_buff a7 to a13, offset 2
s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0
s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1
s8i a13, a3, 2 // Save 8 bits from a13 to dest_buff a3, offset 2
addi.n a7, a7, 3 // Increment src_buff pointer a7 by 3
addi.n a3, a3, 3 // Increment dest_buff pointer a3 by 3
._main_loop_short_matrix_length:
add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6)
add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8)
addi.n a5, a5, -1 // Decrease the outer loop
bnez a5, .outer_loop_short_matrix_length
movi.n a2, 1 // Return LV_RESULT_OK = 1
retw.n // Return