427 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_touch.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lvgl_port.h"
static const char *TAG = "lv_port";
static SemaphoreHandle_t lvgl_mux;
static TaskHandle_t lvgl_task_handle = NULL;
/* -------------------------------------------------------------------------
* Software rotation helpers (used with AVOID_TEAR + ROTATION != 0)
* ---------------------------------------------------------------------- */
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
static void *get_next_frame_buffer(esp_lcd_panel_handle_t panel_handle)
{
static void *next_fb = NULL;
static void *fb[2] = { NULL };
if (next_fb == NULL) {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &fb[0], &fb[1]));
next_fb = fb[1];
} else {
next_fb = (next_fb == fb[0]) ? fb[1] : fb[0];
}
return next_fb;
}
IRAM_ATTR static void rotate_copy_pixel(const uint16_t *from, uint16_t *to,
int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end,
int32_t w, int32_t h, int32_t rotation)
{
int from_index, to_index, to_index_const;
switch (rotation) {
case 90:
to_index_const = (w - x_start - 1) * h;
for (int from_y = y_start; from_y <= y_end; from_y++) {
from_index = from_y * w + x_start;
to_index = to_index_const + from_y;
for (int from_x = x_start; from_x <= x_end; from_x++) {
*(to + to_index) = *(from + from_index);
from_index++;
to_index -= h;
}
}
break;
case 180:
to_index_const = h * w - x_start - 1;
for (int from_y = y_start; from_y <= y_end; from_y++) {
from_index = from_y * w + x_start;
to_index = to_index_const - from_y * w;
for (int from_x = x_start; from_x <= x_end; from_x++) {
*(to + to_index) = *(from + from_index);
from_index++;
to_index--;
}
}
break;
case 270:
to_index_const = (x_start + 1) * h - 1;
for (int from_y = y_start; from_y <= y_end; from_y++) {
from_index = from_y * w + x_start;
to_index = to_index_const - from_y;
for (int from_x = x_start; from_x <= x_end; from_x++) {
*(to + to_index) = *(from + from_index);
from_index++;
to_index += h;
}
}
break;
default:
break;
}
}
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
/* -------------------------------------------------------------------------
* Flush callbacks selected at compile time via Kconfig
* ---------------------------------------------------------------------- */
#if LVGL_PORT_AVOID_TEAR_ENABLE
#if LVGL_PORT_DIRECT_MODE
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
/* Direct mode + rotation: on last flush rotate the full frame into the next
* hardware frame buffer, then wait for VSYNC before telling LVGL we're done. */
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
if (lv_display_flush_is_last(disp)) {
int32_t w = LVGL_PORT_H_RES;
int32_t h = LVGL_PORT_V_RES;
void *next_fb = get_next_frame_buffer(panel_handle);
rotate_copy_pixel((uint16_t *)px_map, next_fb,
0, 0, w - 1, h - 1, w, h,
EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, w, h, next_fb);
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
/* Keep the second hardware frame buffer in sync */
void *next_fb2 = get_next_frame_buffer(panel_handle);
rotate_copy_pixel((uint16_t *)px_map, next_fb2,
0, 0, w - 1, h - 1, w, h,
EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
}
lv_display_flush_ready(disp);
}
#else /* ROTATION == 0 */
/* Direct mode, no rotation: switch the hardware frame buffer on last flush */
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
if (lv_display_flush_is_last(disp)) {
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1,
offsetx2 + 1, offsety2 + 1, px_map);
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
lv_display_flush_ready(disp);
}
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_RGB_BUFFER_NUMS == 2
/* Full refresh, double buffer: switch frame buffer and wait for VSYNC */
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1,
offsetx2 + 1, offsety2 + 1, px_map);
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
lv_display_flush_ready(disp);
}
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3
/* Full refresh, triple buffer: zero-copy using all three hardware frame buffers */
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0
static void *lvgl_port_rgb_last_buf = NULL;
static void *lvgl_port_rgb_next_buf = NULL;
static void *lvgl_port_flush_next_buf = NULL;
#endif
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
void *next_fb = get_next_frame_buffer(panel_handle);
rotate_copy_pixel((uint16_t *)px_map, next_fb,
offsetx1, offsety1, offsetx2, offsety2,
LVGL_PORT_H_RES, LVGL_PORT_V_RES,
EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1,
offsetx2 + 1, offsety2 + 1, next_fb);
#else
/* Zero-copy triple buffer: rotate LVGL render buffers to follow the HW
* frame buffers. px_map is one of the three HW frame buffers returned by
* esp_lcd_rgb_panel_get_frame_buffer(), so draw_bitmap is zero-copy. */
lv_display_set_buffers(disp, px_map, lvgl_port_flush_next_buf,
LVGL_PORT_H_RES * LVGL_PORT_V_RES * sizeof(lv_color_t),
LV_DISPLAY_RENDER_MODE_FULL);
lvgl_port_flush_next_buf = px_map;
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1,
offsetx2 + 1, offsety2 + 1, px_map);
lvgl_port_rgb_next_buf = px_map;
#endif
lv_display_flush_ready(disp);
}
#endif /* LVGL_PORT_DIRECT_MODE / LVGL_PORT_FULL_REFRESH */
#else /* !LVGL_PORT_AVOID_TEAR_ENABLE */
/* Simple partial-refresh flush: copy the dirty area to the frame buffer */
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1,
offsetx2 + 1, offsety2 + 1, px_map);
lv_display_flush_ready(disp);
}
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
/* -------------------------------------------------------------------------
* Display initialisation (LVGL 9)
* ---------------------------------------------------------------------- */
static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
{
assert(panel_handle);
void *buf1 = NULL;
void *buf2 = NULL;
int buffer_size = 0;
lv_display_render_mode_t render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL;
#if EXAMPLE_LVGL_PORT_ROTATION_90 || EXAMPLE_LVGL_PORT_ROTATION_270
lv_display_t *disp = lv_display_create(LVGL_PORT_V_RES, LVGL_PORT_H_RES);
#else
lv_display_t *disp = lv_display_create(LVGL_PORT_H_RES, LVGL_PORT_V_RES);
#endif
assert(disp);
lv_display_set_user_data(disp, panel_handle);
ESP_LOGD(TAG, "Malloc memory for LVGL buffer");
#if LVGL_PORT_AVOID_TEAR_ENABLE
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
#if (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0) && LVGL_PORT_FULL_REFRESH
ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 3,
&lvgl_port_rgb_last_buf, &buf1, &buf2));
lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf;
lvgl_port_flush_next_buf = buf2;
render_mode = LV_DISPLAY_RENDER_MODE_FULL;
#elif (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0)
void *fbs[3];
ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 3,
&fbs[0], &fbs[1], &fbs[2]));
buf1 = fbs[2];
buf2 = NULL;
#if LVGL_PORT_FULL_REFRESH
render_mode = LV_DISPLAY_RENDER_MODE_FULL;
#else
render_mode = LV_DISPLAY_RENDER_MODE_DIRECT;
#endif
#else
ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2));
#if LVGL_PORT_FULL_REFRESH
render_mode = LV_DISPLAY_RENDER_MODE_FULL;
#elif LVGL_PORT_DIRECT_MODE
render_mode = LV_DISPLAY_RENDER_MODE_DIRECT;
#endif
#endif
#else /* !LVGL_PORT_AVOID_TEAR_ENABLE */
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_BUFFER_HEIGHT;
buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS);
assert(buf1);
ESP_LOGI(TAG, "LVGL buffer size: %dKB", buffer_size * sizeof(lv_color_t) / 1024);
render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL;
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
lv_display_set_flush_cb(disp, flush_callback);
lv_display_set_buffers(disp, buf1, buf2,
buffer_size * sizeof(lv_color_t), render_mode);
return disp;
}
/* -------------------------------------------------------------------------
* Touch input (LVGL 9)
* ---------------------------------------------------------------------- */
static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = lv_indev_get_user_data(indev);
assert(tp);
uint16_t touchpad_x;
uint16_t touchpad_y;
uint8_t touchpad_cnt = 0;
esp_lcd_touch_read_data(tp);
bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, &touchpad_x, &touchpad_y,
NULL, &touchpad_cnt, 1);
if (touchpad_pressed && touchpad_cnt > 0) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGD(TAG, "Touch position: %d,%d", touchpad_x, touchpad_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
static lv_indev_t *indev_init(esp_lcd_touch_handle_t tp, lv_display_t *disp)
{
assert(tp);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touchpad_read);
lv_indev_set_user_data(indev, tp);
lv_indev_set_display(indev, disp);
return indev;
}
/* -------------------------------------------------------------------------
* LVGL tick via esp_timer
* ---------------------------------------------------------------------- */
static void tick_increment(void *arg)
{
lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS);
}
static esp_err_t tick_init(void)
{
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &tick_increment,
.name = "LVGL tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
return esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000);
}
/* -------------------------------------------------------------------------
* LVGL task
* ---------------------------------------------------------------------- */
static void lvgl_port_task(void *arg)
{
ESP_LOGD(TAG, "Starting LVGL task");
uint32_t task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS;
while (1) {
if (lvgl_port_lock(-1)) {
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
}
if (task_delay_ms > LVGL_PORT_TASK_MAX_DELAY_MS) {
task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < LVGL_PORT_TASK_MIN_DELAY_MS) {
task_delay_ms = LVGL_PORT_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
/* -------------------------------------------------------------------------
* Public API
* ---------------------------------------------------------------------- */
esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle)
{
lv_init();
ESP_ERROR_CHECK(tick_init());
lv_display_t *disp = display_init(lcd_handle);
assert(disp);
if (tp_handle) {
lv_indev_t *indev = indev_init(tp_handle, disp);
assert(indev);
#if EXAMPLE_LVGL_PORT_ROTATION_90
esp_lcd_touch_set_swap_xy(tp_handle, true);
esp_lcd_touch_set_mirror_y(tp_handle, true);
#elif EXAMPLE_LVGL_PORT_ROTATION_180
esp_lcd_touch_set_mirror_x(tp_handle, true);
esp_lcd_touch_set_mirror_y(tp_handle, true);
#elif EXAMPLE_LVGL_PORT_ROTATION_270
esp_lcd_touch_set_swap_xy(tp_handle, true);
esp_lcd_touch_set_mirror_x(tp_handle, true);
#endif
}
lvgl_mux = xSemaphoreCreateRecursiveMutex();
assert(lvgl_mux);
ESP_LOGI(TAG, "Create LVGL task");
BaseType_t core_id = (LVGL_PORT_TASK_CORE < 0) ? tskNO_AFFINITY : LVGL_PORT_TASK_CORE;
BaseType_t ret = xTaskCreatePinnedToCore(lvgl_port_task, "lvgl",
LVGL_PORT_TASK_STACK_SIZE, NULL,
LVGL_PORT_TASK_PRIORITY,
&lvgl_task_handle, core_id);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create LVGL task");
return ESP_FAIL;
}
return ESP_OK;
}
bool lvgl_port_lock(int timeout_ms)
{
assert(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_mux, timeout_ticks) == pdTRUE;
}
void lvgl_port_unlock(void)
{
assert(lvgl_mux && "lvgl_port_init must be called first");
xSemaphoreGiveRecursive(lvgl_mux);
}
bool lvgl_port_notify_rgb_vsync(void)
{
BaseType_t need_yield = pdFALSE;
#if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0)
if (lvgl_port_rgb_next_buf != lvgl_port_rgb_last_buf) {
lvgl_port_flush_next_buf = lvgl_port_rgb_last_buf;
lvgl_port_rgb_last_buf = lvgl_port_rgb_next_buf;
}
#elif LVGL_PORT_AVOID_TEAR_ENABLE
xTaskNotifyFromISR(lvgl_task_handle, ULONG_MAX, eNoAction, &need_yield);
#endif
return (need_yield == pdTRUE);
}