first commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "lvgl_sw_rotation.c" "lvgl_port_v9.c"
|
||||
INCLUDE_DIRS ".")
|
||||
idf_component_get_property(lvgl_lib lvgl__lvgl COMPONENT_LIB)
|
||||
target_compile_options(${lvgl_lib} PRIVATE -Wno-format)
|
||||
@@ -0,0 +1,204 @@
|
||||
menu "Example Configuration"
|
||||
menu "Touch Controller"
|
||||
config EXAMPLE_LCD_TOUCH_CONTROLLER_GT911
|
||||
bool "Enable LCD GT911 Touch"
|
||||
default n
|
||||
help
|
||||
Enable this option if you wish to use display touch.
|
||||
endmenu
|
||||
menu "Display"
|
||||
config EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS
|
||||
int "LVGL timer task maximum delay (ms)"
|
||||
default 500
|
||||
range 2 2000 # Example range, adjust as needed
|
||||
help
|
||||
The maximum delay of the LVGL timer task, in milliseconds.
|
||||
|
||||
config EXAMPLE_LVGL_PORT_TASK_MIN_DELAY_MS
|
||||
int "LVGL timer task minimum delay (ms)"
|
||||
default 5
|
||||
range 1 100 # Example range, adjust as needed
|
||||
help
|
||||
The minimum delay of the LVGL timer task, in milliseconds.
|
||||
|
||||
config EXAMPLE_LVGL_PORT_TASK_PRIORITY
|
||||
int "LVGL task priority"
|
||||
default 4
|
||||
help
|
||||
The Board Support Package will create a task that will periodically handle LVGL operation in lv_timer_handler().
|
||||
|
||||
config EXAMPLE_LVGL_PORT_TASK_STACK_SIZE_KB
|
||||
int "LVGL task stack size (KB)"
|
||||
default 6
|
||||
help
|
||||
Size(KB) of LVGL task stack.
|
||||
|
||||
config EXAMPLE_LVGL_PORT_TASK_CORE
|
||||
int "LVGL timer task core"
|
||||
default -1
|
||||
range -1 1
|
||||
help
|
||||
The core of the LVGL timer task.
|
||||
Set to -1 to not specify the core.
|
||||
Set to 1 only if the SoCs support dual-core, otherwise set to -1 or 0.
|
||||
|
||||
config EXAMPLE_LVGL_PORT_TICK
|
||||
int "LVGL tick period"
|
||||
default 2
|
||||
range 1 100
|
||||
help
|
||||
Period of LVGL tick timer.
|
||||
|
||||
config EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
bool "Avoid tearing effect"
|
||||
default "n"
|
||||
help
|
||||
Avoid tearing effect through LVGL buffer mode and double frame buffers of RGB LCD. This feature is only available for RGB LCD.
|
||||
|
||||
choice
|
||||
depends on EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
prompt "Select Avoid Tearing Mode"
|
||||
default EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_3
|
||||
config EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_1
|
||||
bool "Mode1: LCD double-buffer & LVGL full-refresh"
|
||||
config EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_2
|
||||
bool "Mode2: LCD triple-buffer & LVGL full-refresh"
|
||||
config EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_3
|
||||
bool "Mode3: LCD double-buffer & LVGL direct-mode"
|
||||
help
|
||||
The current tearing prevention mode supports both full refresh mode and direct mode. Tearing prevention mode may consume more PSRAM space
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE
|
||||
depends on EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
int
|
||||
default 1 if EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_1
|
||||
default 2 if EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_2
|
||||
default 3 if EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE_3
|
||||
|
||||
choice
|
||||
depends on EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
prompt "Select rotation"
|
||||
default EXAMPLE_LVGL_PORT_ROTATION_0
|
||||
config EXAMPLE_LVGL_PORT_ROTATION_0
|
||||
bool "Rotation 0"
|
||||
config EXAMPLE_LVGL_PORT_ROTATION_90
|
||||
bool "Rotation 90"
|
||||
config EXAMPLE_LVGL_PORT_ROTATION_180
|
||||
bool "Rotation 180"
|
||||
config EXAMPLE_LVGL_PORT_ROTATION_270
|
||||
bool "Rotation 270"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_LVGL_PORT_PPA_ROTATION_ENABLE
|
||||
depends on EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE && IDF_TARGET_ESP32P4 && !EXAMPLE_LVGL_PORT_ROTATION_0
|
||||
bool "Enable PPA Rotation"
|
||||
default n
|
||||
help
|
||||
Enable this option to use PPA (Pixel Processor Assembly) for display rotation.
|
||||
This feature allows hardware-based rotation for improved performance
|
||||
|
||||
config EXAMPLE_LVGL_PORT_ROTATION_DEGREE
|
||||
int
|
||||
default 0 if EXAMPLE_LVGL_PORT_ROTATION_0
|
||||
default 90 if EXAMPLE_LVGL_PORT_ROTATION_90
|
||||
default 180 if EXAMPLE_LVGL_PORT_ROTATION_180
|
||||
default 270 if EXAMPLE_LVGL_PORT_ROTATION_270
|
||||
|
||||
choice
|
||||
depends on !EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
prompt "Select LVGL buffer memory capability"
|
||||
default EXAMPLE_LVGL_PORT_BUF_PSRAM
|
||||
config EXAMPLE_LVGL_PORT_BUF_PSRAM
|
||||
bool "PSRAM memory"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_LVGL_PORT_BUF_HEIGHT
|
||||
depends on !EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
int "LVGL buffer height"
|
||||
default 100
|
||||
help
|
||||
Height of LVGL buffer. The width of the buffer is the same as that of the LCD.
|
||||
endmenu
|
||||
# menu "camer"
|
||||
|
||||
# config EXAMPLE_ENABLE_MIPI_CSI_CAM_SENSOR
|
||||
# bool "Enable MIPI CSI Camera Sensor"
|
||||
# default y
|
||||
# depends on SOC_MIPI_CSI_SUPPORTED
|
||||
|
||||
# if EXAMPLE_ENABLE_MIPI_CSI_CAM_SENSOR
|
||||
# config EXAMPLE_MIPI_CSI_SCCB_I2C_PORT
|
||||
# int "MIPI CSI SCCB I2C Port Number"
|
||||
# default 0
|
||||
# range 0 1
|
||||
|
||||
# config EXAMPLE_MIPI_CSI_SCCB_I2C_SCL_PIN
|
||||
# int "MIPI CSI SCCB I2C SCL Pin"
|
||||
# default 34
|
||||
# range -1 56
|
||||
|
||||
# config EXAMPLE_MIPI_CSI_SCCB_I2C_SDA_PIN
|
||||
# int "MIPI CSI SCCB I2C SDA Pin"
|
||||
# default 31
|
||||
# range -1 56
|
||||
|
||||
# config EXAMPLE_MIPI_CSI_SCCB_I2C_FREQ
|
||||
# int "MIPI CSI SCCB I2C Frequency"
|
||||
# default 100000
|
||||
# range 100000 400000
|
||||
# help
|
||||
# Increasing this value can reduce the initialization time of the camera sensor.
|
||||
# Please refer to the relevant instructions of the camera sensor to adjust the value.
|
||||
|
||||
# config EXAMPLE_MIPI_CSI_CAM_SENSOR_RESET_PIN
|
||||
# int "MIPI CSI Camera Sensor Reset Pin"
|
||||
# default -1
|
||||
# range -1 56
|
||||
|
||||
# config EXAMPLE_MIPI_CSI_CAM_SENSOR_PWDN_PIN
|
||||
# int "MIPI CSI Camera Sensor Power Down Pin"
|
||||
# default -1
|
||||
# range -1 56
|
||||
# endif
|
||||
|
||||
# config EXAMPLE_ENABLE_PRINT_FPS_RATE_VALUE
|
||||
# bool "enable print fps rate value"
|
||||
# default y
|
||||
|
||||
# config EXAMPLE_USE_MEMORY_MAPPING
|
||||
# bool "Use Memory Mapping for Buffer Allocation"
|
||||
# default n
|
||||
# help
|
||||
# Enable this option if you want to allocate memory using memory mapping.
|
||||
# This is typically useful for performance optimization or when working
|
||||
# with hardware that requires mapped memory access
|
||||
|
||||
# config EXAMPLE_CAM_BUF_COUNT
|
||||
# int "Camera Buffer Count"
|
||||
# default 2
|
||||
# range 2 3
|
||||
|
||||
# choice
|
||||
# prompt "Choose the color format of the LCD"
|
||||
# default LCD_PIXEL_FORMAT_RGB565
|
||||
# config LCD_PIXEL_FORMAT_RGB565
|
||||
# bool "RGB565"
|
||||
|
||||
# config LCD_PIXEL_FORMAT_RGB888
|
||||
# bool "RGB888"
|
||||
# endchoice
|
||||
|
||||
# config EXAMPLE_ENABLE_CAM_SENSOR_PIC_VFLIP
|
||||
# bool "Enable Camera Sensor Picture Vertical Flip"
|
||||
# default y
|
||||
# help
|
||||
# Select this option, enable camera sensor picture vertical flip.
|
||||
|
||||
# config EXAMPLE_ENABLE_CAM_SENSOR_PIC_HFLIP
|
||||
# bool "Enable Camera Sensor Picture Horizontal Flip"
|
||||
# default y
|
||||
# help
|
||||
# Select this option, enable camera sensor picture horizontal flip.
|
||||
# endmenu
|
||||
endmenu
|
||||
@@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
esp_lcd_touch_gt911: ^1
|
||||
waveshare/esp_lcd_jd9365_10_1: ^1.0.3
|
||||
|
||||
lvgl/lvgl: ^9.2.2
|
||||
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#if SOC_LCDCAM_RGB_LCD_SUPPORTED
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
#endif
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#endif
|
||||
#include "esp_lcd_touch.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
#include "esp_private/esp_cache_private.h"
|
||||
#include "driver/ppa.h"
|
||||
#endif
|
||||
#include "lvgl.h"
|
||||
#include "lvgl_private.h"
|
||||
#include "lvgl_port_v9.h"
|
||||
|
||||
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
#define BLOCK_SIZE_SMALL (32)
|
||||
#define BLOCK_SIZE_LARGE (256)
|
||||
|
||||
static const char *TAG = "lv_port";
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_handle_t lcd_handle;
|
||||
esp_lcd_touch_handle_t tp_handle;
|
||||
bool is_init;
|
||||
} lvgl_port_task_param_t;
|
||||
|
||||
typedef esp_err_t (*get_lcd_frame_buffer_cb_t)(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...);
|
||||
|
||||
#if LVGL_PORT_PPA_ROTATION_ENABLE
|
||||
static ppa_client_handle_t ppa_srm_handle = NULL;
|
||||
static size_t data_cache_line_size = 0;
|
||||
#endif
|
||||
|
||||
static SemaphoreHandle_t lvgl_mux; // LVGL mutex
|
||||
static TaskHandle_t lvgl_task_handle = NULL;
|
||||
static lvgl_port_interface_t lvgl_port_interface = LVGL_PORT_INTERFACE_RGB;
|
||||
|
||||
#if LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
static get_lcd_frame_buffer_cb_t lvgl_get_lcd_frame_buffer = NULL;
|
||||
#endif
|
||||
|
||||
#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(lvgl_get_lcd_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;
|
||||
}
|
||||
|
||||
#if !LVGL_PORT_PPA_ROTATION_ENABLE
|
||||
static void rotate_image(const void *src, void *dst, int width, int height, int rotation, int bpp)
|
||||
{
|
||||
int bytes_per_pixel = bpp / 8;
|
||||
int block_w = rotation == 90 || rotation == 270 ? BLOCK_SIZE_SMALL : BLOCK_SIZE_LARGE;
|
||||
int block_h = rotation == 90 || rotation == 270 ? BLOCK_SIZE_LARGE : BLOCK_SIZE_SMALL;
|
||||
|
||||
for (int i = 0; i < height; i += block_h) {
|
||||
int max_height = i + block_h > height ? height : i + block_h;
|
||||
|
||||
for (int j = 0; j < width; j += block_w) {
|
||||
int max_width = j + block_w > width ? width : j + block_w;
|
||||
|
||||
for (int x = i; x < max_height; x++) {
|
||||
for (int y = j; y < max_width; y++) {
|
||||
void *src_pixel = (uint8_t *)src + (x * width + y) * bytes_per_pixel;
|
||||
void *dst_pixel;
|
||||
|
||||
switch (rotation) {
|
||||
case 270:
|
||||
dst_pixel = (uint8_t *)dst + ((width - 1 - y) * height + x) * bytes_per_pixel;
|
||||
break;
|
||||
case 180:
|
||||
dst_pixel = (uint8_t *)dst + ((height - 1 - x) * width + (width - 1 - y)) * bytes_per_pixel;
|
||||
break;
|
||||
case 90:
|
||||
dst_pixel = (uint8_t *)dst + (y * height + (height - 1 - x)) * bytes_per_pixel;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (bpp == 16) {
|
||||
*(uint16_t *)dst_pixel = *(uint16_t *)src_pixel;
|
||||
} else if (bpp == 24) {
|
||||
((uint8_t *)dst_pixel)[0] = ((uint8_t *)src_pixel)[0];
|
||||
((uint8_t *)dst_pixel)[1] = ((uint8_t *)src_pixel)[1];
|
||||
((uint8_t *)dst_pixel)[2] = ((uint8_t *)src_pixel)[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
IRAM_ATTR static void rotate_copy_pixel(const uint16_t *from, uint16_t *to, uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotation)
|
||||
{
|
||||
#if LVGL_PORT_PPA_ROTATION_ENABLE
|
||||
ppa_srm_rotation_angle_t ppa_rotation;
|
||||
int x_offset = 0, y_offset = 0;
|
||||
|
||||
// Determine rotation settings once and reuse
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
ppa_rotation = PPA_SRM_ROTATION_ANGLE_270;
|
||||
x_offset = h - y_end - 1;
|
||||
y_offset = x_start;
|
||||
break;
|
||||
case 180:
|
||||
ppa_rotation = PPA_SRM_ROTATION_ANGLE_180;
|
||||
x_offset = w - x_end - 1;
|
||||
y_offset = h - y_end - 1;
|
||||
break;
|
||||
case 270:
|
||||
ppa_rotation = PPA_SRM_ROTATION_ANGLE_90;
|
||||
x_offset = y_start;
|
||||
y_offset = w - x_end - 1;
|
||||
break;
|
||||
default:
|
||||
ppa_rotation = PPA_SRM_ROTATION_ANGLE_0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill operation config for PPA rotation, without recalculating each time
|
||||
ppa_srm_oper_config_t oper_config = {
|
||||
.in.buffer = from,
|
||||
.in.pic_w = w,
|
||||
.in.pic_h = h,
|
||||
.in.block_w = x_end - x_start + 1,
|
||||
.in.block_h = y_end - y_start + 1,
|
||||
.in.block_offset_x = x_start,
|
||||
.in.block_offset_y = y_start,
|
||||
.in.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,
|
||||
|
||||
.out.buffer = to,
|
||||
.out.buffer_size = ALIGN_UP_BY(sizeof(lv_color_t) * w * h, data_cache_line_size),
|
||||
.out.pic_w = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? h : w,
|
||||
.out.pic_h = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? w : h,
|
||||
.out.block_offset_x = x_offset,
|
||||
.out.block_offset_y = y_offset,
|
||||
.out.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,
|
||||
|
||||
.rotation_angle = ppa_rotation,
|
||||
.scale_x = 1.0,
|
||||
.scale_y = 1.0,
|
||||
.rgb_swap = 0,
|
||||
.byte_swap = 0,
|
||||
.mode = PPA_TRANS_MODE_BLOCKING,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config));
|
||||
|
||||
#else
|
||||
// Fallback: optimized transpose for non-PPA systems
|
||||
rotate_image(from, to, w, h, rotation, LV_COLOR_DEPTH);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
|
||||
|
||||
#if LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
|
||||
static void switch_lcd_frame_buffer_to(esp_lcd_panel_handle_t panel_handle, void *fb)
|
||||
{
|
||||
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LVGL_PORT_H_RES, LVGL_PORT_V_RES, fb);
|
||||
}
|
||||
|
||||
#if LVGL_PORT_DIRECT_MODE
|
||||
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
|
||||
typedef struct {
|
||||
uint16_t inv_p;
|
||||
uint8_t inv_area_joined[LV_INV_BUF_SIZE];
|
||||
lv_area_t inv_areas[LV_INV_BUF_SIZE];
|
||||
} lv_port_dirty_area_t;
|
||||
|
||||
typedef enum {
|
||||
FLUSH_STATUS_PART,
|
||||
FLUSH_STATUS_FULL
|
||||
} lv_port_flush_status_t;
|
||||
|
||||
typedef enum {
|
||||
FLUSH_PROBE_PART_COPY,
|
||||
FLUSH_PROBE_SKIP_COPY,
|
||||
FLUSH_PROBE_FULL_COPY,
|
||||
} lv_port_flush_probe_t;
|
||||
|
||||
static lv_port_dirty_area_t dirty_area;
|
||||
|
||||
static void flush_dirty_save(lv_port_dirty_area_t *dirty_area)
|
||||
{
|
||||
lv_disp_t *disp = lv_refr_get_disp_refreshing();
|
||||
dirty_area->inv_p = disp->inv_p;
|
||||
for (int i = 0; i < disp->inv_p; i++) {
|
||||
dirty_area->inv_area_joined[i] = disp->inv_area_joined[i];
|
||||
dirty_area->inv_areas[i] = disp->inv_areas[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Probe dirty area to copy
|
||||
*
|
||||
* @note This function is used to avoid tearing effect, and only work with LVGL direct-mode.
|
||||
*
|
||||
*/
|
||||
static lv_port_flush_probe_t flush_copy_probe(lv_display_t *disp)
|
||||
{
|
||||
static lv_port_flush_status_t prev_status = FLUSH_STATUS_PART;
|
||||
lv_port_flush_status_t cur_status;
|
||||
lv_port_flush_probe_t probe_result;
|
||||
lv_disp_t *disp_refr = lv_refr_get_disp_refreshing();
|
||||
|
||||
uint32_t flush_ver = 0;
|
||||
uint32_t flush_hor = 0;
|
||||
for (int i = 0; i < disp_refr->inv_p; i++) {
|
||||
if (disp_refr->inv_area_joined[i] == 0) {
|
||||
flush_ver = (disp_refr->inv_areas[i].y2 + 1 - disp_refr->inv_areas[i].y1);
|
||||
flush_hor = (disp_refr->inv_areas[i].x2 + 1 - disp_refr->inv_areas[i].x1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Check if the current full screen refreshes */
|
||||
cur_status = ((flush_ver == disp->ver_res) && (flush_hor == disp->hor_res)) ? (FLUSH_STATUS_FULL) : (FLUSH_STATUS_PART);
|
||||
|
||||
if (prev_status == FLUSH_STATUS_FULL) {
|
||||
if ((cur_status == FLUSH_STATUS_PART)) {
|
||||
probe_result = FLUSH_PROBE_FULL_COPY;
|
||||
} else {
|
||||
probe_result = FLUSH_PROBE_SKIP_COPY;
|
||||
}
|
||||
} else {
|
||||
probe_result = FLUSH_PROBE_PART_COPY;
|
||||
}
|
||||
prev_status = cur_status;
|
||||
|
||||
return probe_result;
|
||||
}
|
||||
|
||||
static inline void *flush_get_next_buf(void *panel_handle)
|
||||
{
|
||||
return get_next_frame_buffer(panel_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy dirty area
|
||||
*
|
||||
* @note This function is used to avoid tearing effect, and only work with LVGL direct-mode.
|
||||
*
|
||||
*/
|
||||
static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_area)
|
||||
{
|
||||
lv_coord_t x_start, x_end, y_start, y_end;
|
||||
for (int i = 0; i < dirty_area->inv_p; i++) {
|
||||
/* Refresh the unjoined areas*/
|
||||
if (dirty_area->inv_area_joined[i] == 0) {
|
||||
x_start = dirty_area->inv_areas[i].x1;
|
||||
x_end = dirty_area->inv_areas[i].x2;
|
||||
y_start = dirty_area->inv_areas[i].y1;
|
||||
y_end = dirty_area->inv_areas[i].y2;
|
||||
|
||||
rotate_copy_pixel(src, dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)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;
|
||||
void *next_fb = NULL;
|
||||
lv_port_flush_probe_t probe_result = FLUSH_PROBE_PART_COPY;
|
||||
|
||||
/* Action after last area refresh */
|
||||
if (lv_disp_flush_is_last(disp)) {
|
||||
/* Check if the `full_refresh` flag has been triggered */
|
||||
if (disp->render_mode == LV_DISPLAY_RENDER_MODE_FULL) {
|
||||
/* Reset flag */
|
||||
disp->render_mode = LV_DISPLAY_RENDER_MODE_DIRECT;
|
||||
|
||||
// Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer
|
||||
next_fb = flush_get_next_buf(panel_handle);
|
||||
rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, next_fb);
|
||||
|
||||
/* Waiting for the current frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
/* Synchronously update the dirty area for another frame buffer */
|
||||
flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area);
|
||||
flush_get_next_buf(panel_handle);
|
||||
} else {
|
||||
/* Probe the copy method for the current dirty area */
|
||||
probe_result = flush_copy_probe(disp);
|
||||
|
||||
if (probe_result == FLUSH_PROBE_FULL_COPY) {
|
||||
/* Save current dirty area for next frame buffer */
|
||||
flush_dirty_save(&dirty_area);
|
||||
|
||||
/* Set LVGL full-refresh flag and set flush ready in advance */
|
||||
disp->render_mode = LV_DISPLAY_RENDER_MODE_FULL;
|
||||
disp->rendering_in_progress = false;
|
||||
lv_disp_flush_ready(disp);
|
||||
|
||||
/* Force to refresh whole screen, and will invoke `flush_callback` recursively */
|
||||
lv_refr_now(lv_refr_get_disp_refreshing());
|
||||
} else {
|
||||
/* Update current dirty area for next frame buffer */
|
||||
next_fb = flush_get_next_buf(panel_handle);
|
||||
flush_dirty_save(&dirty_area);
|
||||
flush_dirty_copy(next_fb, color_map, &dirty_area);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, next_fb);
|
||||
|
||||
/* Waiting for the current frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
if (probe_result == FLUSH_PROBE_PART_COPY) {
|
||||
/* Synchronously update the dirty area for another frame buffer */
|
||||
flush_dirty_save(&dirty_area);
|
||||
flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area);
|
||||
flush_get_next_buf(panel_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
|
||||
|
||||
/* Action after last area refresh */
|
||||
if (lv_disp_flush_is_last(disp)) {
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, color_map);
|
||||
|
||||
/* Waiting for the last frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
}
|
||||
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
|
||||
|
||||
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_BUFFER_NUMS == 2
|
||||
|
||||
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
|
||||
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, color_map);
|
||||
|
||||
/* Waiting for the last frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
|
||||
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_BUFFER_NUMS == 3
|
||||
|
||||
#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
|
||||
|
||||
void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
|
||||
|
||||
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
|
||||
const int offsetx1 = area->x1;
|
||||
const int offsetx2 = area->x2;
|
||||
const int offsety1 = area->y1;
|
||||
const int offsety2 = area->y2;
|
||||
void *next_fb = get_next_frame_buffer(panel_handle);
|
||||
|
||||
/* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */
|
||||
rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, next_fb);
|
||||
#else
|
||||
if (disp->buf_act == disp->buf_1) {
|
||||
disp->buf_2->data = lvgl_port_flush_next_buf;
|
||||
} else {
|
||||
disp->buf_1->data = lvgl_port_flush_next_buf;
|
||||
}
|
||||
lvgl_port_flush_next_buf = color_map;
|
||||
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
switch_lcd_frame_buffer_to(panel_handle, color_map);
|
||||
|
||||
lvgl_port_rgb_next_buf = color_map;
|
||||
#endif
|
||||
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)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;
|
||||
|
||||
/* Just copy data from the color map to the LCD frame buffer */
|
||||
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
|
||||
|
||||
if (lvgl_port_interface != LVGL_PORT_INTERFACE_MIPI_DSI_DMA) {
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
|
||||
|
||||
static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
|
||||
{
|
||||
#if LVGL_PORT_PPA_ROTATION_ENABLE
|
||||
// Initialize the PPA
|
||||
ppa_client_config_t ppa_srm_config = {
|
||||
.oper_type = PPA_OPERATION_SRM,
|
||||
};
|
||||
ESP_ERROR_CHECK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle));
|
||||
ESP_ERROR_CHECK(esp_cache_get_alignment(MALLOC_CAP_DMA|MALLOC_CAP_SPIRAM, &data_cache_line_size));
|
||||
#endif
|
||||
|
||||
assert(panel_handle);
|
||||
|
||||
// alloc draw buffers used by LVGL
|
||||
void *buf1 = NULL;
|
||||
void *buf2 = NULL;
|
||||
int buffer_size = 0;
|
||||
|
||||
ESP_LOGD(TAG, "Malloc memory for LVGL buffer");
|
||||
#if LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
// To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for RGB output
|
||||
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
|
||||
#if (LVGL_PORT_LCD_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0) && LVGL_PORT_FULL_REFRESH
|
||||
// With the usage of three buffers and full-refresh, we always have one buffer available for rendering, eliminating the need to wait for the RGB's sync signal
|
||||
ESP_ERROR_CHECK(lvgl_get_lcd_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;
|
||||
#elif (LVGL_PORT_LCD_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0)
|
||||
// Here we are using three frame buffers, one for LVGL rendering, and the other two for RGB driver (one of them is used for rotation)
|
||||
void *fbs[3];
|
||||
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 3, &fbs[0], &fbs[1], &fbs[2]));
|
||||
buf1 = fbs[2];
|
||||
#else
|
||||
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 2, &buf1, &buf2));
|
||||
#endif
|
||||
#else
|
||||
// Normmaly, for RGB LCD, we just use one buffer for LVGL rendering
|
||||
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);
|
||||
// buffer_size = LVGL_PORT_H_RES * LVGL_PORT_BUFFER_HEIGHT;
|
||||
// buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
||||
assert(buf1);
|
||||
ESP_LOGI(TAG, "LVGL buffer size: %dKB", buffer_size * sizeof(lv_color_t) / 1024);
|
||||
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
|
||||
|
||||
ESP_LOGD(TAG, "Register display driver to LVGL");
|
||||
lv_display_t *display = lv_display_create(
|
||||
#if (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 90) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 270)
|
||||
LVGL_PORT_H_RES, LVGL_PORT_V_RES
|
||||
#else
|
||||
LVGL_PORT_V_RES, LVGL_PORT_H_RES
|
||||
#endif
|
||||
);
|
||||
|
||||
lv_display_set_buffers(
|
||||
display, buf1, buf2, buffer_size * sizeof(lv_color_t),
|
||||
#if LVGL_PORT_FULL_REFRESH
|
||||
LV_DISPLAY_RENDER_MODE_FULL
|
||||
#elif LVGL_PORT_DIRECT_MODE
|
||||
LV_DISPLAY_RENDER_MODE_DIRECT
|
||||
#else
|
||||
LV_DISPLAY_RENDER_MODE_PARTIAL
|
||||
#endif
|
||||
);
|
||||
lv_display_set_flush_cb(display, flush_callback);
|
||||
lv_display_set_user_data(display, panel_handle);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
static void touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
|
||||
{
|
||||
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)lv_indev_get_user_data(indev_drv);
|
||||
assert(tp);
|
||||
|
||||
uint16_t touchpad_x;
|
||||
uint16_t touchpad_y;
|
||||
uint8_t touchpad_cnt = 0;
|
||||
/* Read data from touch controller into memory */
|
||||
esp_lcd_touch_read_data(tp);
|
||||
|
||||
/* Read data from touch controller */
|
||||
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)
|
||||
{
|
||||
assert(tp);
|
||||
|
||||
lv_indev_t *indev = lv_indev_create();
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*See below.*/
|
||||
lv_indev_set_user_data(indev, tp);
|
||||
lv_indev_set_read_cb(indev, touchpad_read); /*See below.*/
|
||||
|
||||
return indev;
|
||||
}
|
||||
|
||||
static void tick_increment(void *arg)
|
||||
{
|
||||
/* Tell LVGL how many milliseconds have elapsed */
|
||||
lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static esp_err_t 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 = &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);
|
||||
}
|
||||
|
||||
static void lvgl_port_task(void *arg)
|
||||
{
|
||||
ESP_LOGD(TAG, "Starting LVGL task");
|
||||
|
||||
lvgl_port_task_param_t *param = (lvgl_port_task_param_t *)arg;
|
||||
|
||||
lv_init();
|
||||
ESP_ERROR_CHECK(tick_init());
|
||||
|
||||
lv_display_t *disp = display_init(param->lcd_handle);
|
||||
assert(disp);
|
||||
|
||||
if (param->tp_handle) {
|
||||
lv_indev_t *indev = indev_init(param->tp_handle);
|
||||
assert(indev);
|
||||
|
||||
#if EXAMPLE_LVGL_PORT_ROTATION_90
|
||||
esp_lcd_touch_set_swap_xy(param->tp_handle, true);
|
||||
esp_lcd_touch_set_mirror_x(param->tp_handle, true);
|
||||
#elif EXAMPLE_LVGL_PORT_ROTATION_180
|
||||
esp_lcd_touch_set_mirror_x(param->tp_handle, false);
|
||||
esp_lcd_touch_set_mirror_y(param->tp_handle, false);
|
||||
#elif EXAMPLE_LVGL_PORT_ROTATION_270
|
||||
esp_lcd_touch_set_swap_xy(param->tp_handle, true);
|
||||
esp_lcd_touch_set_mirror_y(param->tp_handle, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
param->is_init = true;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle, lvgl_port_interface_t interface)
|
||||
{
|
||||
lvgl_port_task_param_t lvgl_task_param = {
|
||||
.lcd_handle = lcd_handle,
|
||||
.tp_handle = tp_handle,
|
||||
.is_init = false
|
||||
};
|
||||
|
||||
lvgl_port_interface = interface;
|
||||
#if LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
switch (interface) {
|
||||
#if SOC_LCDCAM_RGB_LCD_SUPPORTED
|
||||
case LVGL_PORT_INTERFACE_RGB:
|
||||
lvgl_get_lcd_frame_buffer = esp_lcd_rgb_panel_get_frame_buffer;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
case LVGL_PORT_INTERFACE_MIPI_DSI_DMA:
|
||||
case LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA:
|
||||
lvgl_get_lcd_frame_buffer = esp_lcd_dpi_panel_get_frame_buffer;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid interface type");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
#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, &lvgl_task_param,
|
||||
LVGL_PORT_TASK_PRIORITY, &lvgl_task_handle, core_id);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create LVGL task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
while (!lvgl_task_param.is_init) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
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_lcd_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
|
||||
// Notify that the current LCD frame buffer has been transmitted
|
||||
if (lvgl_task_handle) {
|
||||
xTaskNotifyFromISR(lvgl_task_handle, ULONG_MAX, eNoAction, &need_yield);
|
||||
}
|
||||
#else
|
||||
if (lvgl_port_interface == LVGL_PORT_INTERFACE_MIPI_DSI_DMA) {
|
||||
lv_display_t *disp = lv_disp_get_default();
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
#endif
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_lcd_types.h"
|
||||
#include "esp_lcd_touch.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief LVGL port interface type
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
LVGL_PORT_INTERFACE_RGB,
|
||||
LVGL_PORT_INTERFACE_MIPI_DSI_DMA,
|
||||
LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA,
|
||||
LVGL_PORT_INTERFACE_MAX,
|
||||
} lvgl_port_interface_t;
|
||||
|
||||
/**
|
||||
* LVGL related parameters, can be adjusted by users
|
||||
*
|
||||
*/
|
||||
#define LVGL_PORT_H_RES (800)
|
||||
#define LVGL_PORT_V_RES (1280)
|
||||
#define LVGL_PORT_TICK_PERIOD_MS (CONFIG_EXAMPLE_LVGL_PORT_TICK)
|
||||
|
||||
/**
|
||||
* LVGL timer handle task related parameters, can be adjusted by users
|
||||
*
|
||||
*/
|
||||
#define LVGL_PORT_TASK_MAX_DELAY_MS (CONFIG_EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS) // The maximum delay of the LVGL timer task, in milliseconds
|
||||
#define LVGL_PORT_TASK_MIN_DELAY_MS (CONFIG_EXAMPLE_LVGL_PORT_TASK_MIN_DELAY_MS) // The minimum delay of the LVGL timer task, in milliseconds
|
||||
#define LVGL_PORT_TASK_STACK_SIZE (CONFIG_EXAMPLE_LVGL_PORT_TASK_STACK_SIZE_KB * 1024) // The stack size of the LVGL timer task, in bytes
|
||||
#define LVGL_PORT_TASK_PRIORITY (CONFIG_EXAMPLE_LVGL_PORT_TASK_PRIORITY) // The priority of the LVGL timer task
|
||||
#define LVGL_PORT_TASK_CORE (CONFIG_EXAMPLE_LVGL_PORT_TASK_CORE) // The core of the LVGL timer task,
|
||||
// `-1` means the don't specify the core
|
||||
/**
|
||||
*
|
||||
* LVGL buffer related parameters, can be adjusted by users:
|
||||
* (These parameters will be useless if the avoid tearing function is enabled)
|
||||
*
|
||||
* - Memory type for buffer allocation:
|
||||
* - MALLOC_CAP_SPIRAM: Allocate LVGL buffer in PSRAM
|
||||
* - MALLOC_CAP_INTERNAL: Allocate LVGL buffer in SRAM
|
||||
* (The SRAM is faster than PSRAM, but the PSRAM has a larger capacity)
|
||||
*
|
||||
*/
|
||||
#if CONFIG_EXAMPLE_LVGL_PORT_BUF_PSRAM
|
||||
#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_SPIRAM)
|
||||
#elif CONFIG_EXAMPLE_LVGL_PORT_BUF_INTERNAL
|
||||
#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#endif
|
||||
#define LVGL_PORT_BUFFER_HEIGHT (CONFIG_EXAMPLE_LVGL_PORT_BUF_HEIGHT)
|
||||
|
||||
/**
|
||||
* Avoid tering related configurations, can be adjusted by users.
|
||||
*
|
||||
*/
|
||||
#define LVGL_PORT_AVOID_TEAR_ENABLE (CONFIG_EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE) // Set to 1 to enable
|
||||
#if LVGL_PORT_AVOID_TEAR_ENABLE
|
||||
/**
|
||||
* Set the avoid tearing mode:
|
||||
* - 0: Disable avoid tearing function
|
||||
* - 1: LCD double-buffer & LVGL full-refresh
|
||||
* - 2: LCD triple-buffer & LVGL full-refresh
|
||||
* - 3: LCD double-buffer & LVGL direct-mode (recommended)
|
||||
*
|
||||
*/
|
||||
#define LVGL_PORT_AVOID_TEAR_MODE (CONFIG_EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE)
|
||||
|
||||
/**
|
||||
* Set the PPA rotation enable:
|
||||
* - 0: Disable PPA rotation
|
||||
* - 1: Enable PPA rotation
|
||||
*
|
||||
*/
|
||||
#define LVGL_PORT_PPA_ROTATION_ENABLE (CONFIG_EXAMPLE_LVGL_PORT_PPA_ROTATION_ENABLE)
|
||||
|
||||
/**
|
||||
* Set the rotation degree of the LCD panel when the avoid tearing function is enabled:
|
||||
* - 0: 0 degree
|
||||
* - 90: 90 degree
|
||||
* - 180: 180 degree
|
||||
* - 270: 270 degree
|
||||
*
|
||||
*/
|
||||
#define EXAMPLE_LVGL_PORT_ROTATION_DEGREE (CONFIG_EXAMPLE_LVGL_PORT_ROTATION_DEGREE)
|
||||
|
||||
/**
|
||||
* Below configurations are automatically set according to the above configurations, users do not need to modify them.
|
||||
*
|
||||
*/
|
||||
#if LVGL_PORT_AVOID_TEAR_MODE == 1
|
||||
#define LVGL_PORT_LCD_BUFFER_NUMS (2)
|
||||
#define LVGL_PORT_FULL_REFRESH (1)
|
||||
#elif LVGL_PORT_AVOID_TEAR_MODE == 2
|
||||
#define LVGL_PORT_LCD_BUFFER_NUMS (3)
|
||||
#define LVGL_PORT_FULL_REFRESH (1)
|
||||
#elif LVGL_PORT_AVOID_TEAR_MODE == 3
|
||||
#define LVGL_PORT_LCD_BUFFER_NUMS (2)
|
||||
#define LVGL_PORT_DIRECT_MODE (1)
|
||||
#endif /* LVGL_PORT_AVOID_TEAR_MODE */
|
||||
|
||||
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0
|
||||
#define EXAMPLE_LVGL_PORT_ROTATION_0 (1)
|
||||
#else
|
||||
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 90
|
||||
#define EXAMPLE_LVGL_PORT_ROTATION_90 (1)
|
||||
#elif EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 180
|
||||
#define EXAMPLE_LVGL_PORT_ROTATION_180 (1)
|
||||
#elif EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 270
|
||||
#define EXAMPLE_LVGL_PORT_ROTATION_270 (1)
|
||||
#endif
|
||||
#ifdef LVGL_PORT_LCD_BUFFER_NUMS
|
||||
#undef LVGL_PORT_LCD_BUFFER_NUMS
|
||||
#define LVGL_PORT_LCD_BUFFER_NUMS (3)
|
||||
#endif
|
||||
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
|
||||
#else
|
||||
#define LVGL_PORT_LCD_BUFFER_NUMS (1)
|
||||
#define LVGL_PORT_FULL_REFRESH (0)
|
||||
#define LVGL_PORT_DIRECT_MODE (0)
|
||||
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
|
||||
|
||||
/**
|
||||
* @brief Initialize LVGL port
|
||||
*
|
||||
* @param[in] lcd_handle: LCD panel handle
|
||||
* @param[in] tp_handle: Touch panel handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
* - Others: Fail
|
||||
*/
|
||||
esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle, lvgl_port_interface_t interface);
|
||||
|
||||
/**
|
||||
* @brief Take LVGL mutex
|
||||
*
|
||||
* @param[in] timeout_ms: Timeout in [ms]. 0 will block indefinitely.
|
||||
*
|
||||
* @return
|
||||
* - true: Mutex was taken
|
||||
* - false: Mutex was NOT taken
|
||||
*/
|
||||
bool lvgl_port_lock(int timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Give LVGL mutex
|
||||
*
|
||||
*/
|
||||
void lvgl_port_unlock(void);
|
||||
|
||||
/**
|
||||
* @brief Notifies the LVGL task when the transmission of the RGB frame buffer is completed.
|
||||
*
|
||||
* @return
|
||||
* - true: The tasks need to be re-scheduled
|
||||
* - false: The tasks don't need to be re-scheduled
|
||||
*/
|
||||
bool lvgl_port_notify_lcd_vsync(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_ldo_regulator.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_private/esp_cache_private.h"
|
||||
#include "esp_lcd_touch_gt911.h"
|
||||
#include "esp_lcd_jd9365_10_1.h"
|
||||
#include "lvgl_port_v9.h"
|
||||
#include "lv_demos.h"
|
||||
#include "driver/ppa.h"
|
||||
|
||||
#define TAG "main"
|
||||
|
||||
|
||||
#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3) // LDO_VO3 is connected to VDD_MIPI_DPHY
|
||||
#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
|
||||
|
||||
#define BSP_LCD_DPI_BUFFER_NUMS (1)
|
||||
|
||||
#define BSP_LCD_H_RES (800)
|
||||
#define BSP_LCD_V_RES (1280)
|
||||
|
||||
#define BSP_I2C_NUM (I2C_NUM_1)
|
||||
#define BSP_I2C_SDA (GPIO_NUM_7)
|
||||
#define BSP_I2C_SCL (GPIO_NUM_8)
|
||||
|
||||
#define BSP_LCD_TOUCH_RST (GPIO_NUM_NC)
|
||||
#define BSP_LCD_TOUCH_INT (GPIO_NUM_NC)
|
||||
|
||||
#define BSP_LCD_RST (GPIO_NUM_NC)
|
||||
#define BSP_LCD_BACKLIGHT (GPIO_NUM_NC)
|
||||
|
||||
i2c_master_bus_handle_t i2c_handle = NULL;
|
||||
|
||||
IRAM_ATTR static bool mipi_dsi_lcd_on_vsync_event(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
return lvgl_port_notify_lcd_vsync();
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t bsp_display_brightness_set(int brightness_percent)
|
||||
{
|
||||
if (brightness_percent > 100) {
|
||||
brightness_percent = 100;
|
||||
}
|
||||
if (brightness_percent < 0) {
|
||||
brightness_percent = 0;
|
||||
}
|
||||
|
||||
uint8_t data = (uint8_t)(255 * brightness_percent * 0.01);
|
||||
uint8_t chip_addr = 0x45;
|
||||
|
||||
uint8_t data_addr = 0x96;
|
||||
uint8_t data_to_send[2] = {data_addr, data};
|
||||
|
||||
i2c_device_config_t i2c_dev_conf = {
|
||||
.scl_speed_hz = 100 * 1000,
|
||||
.device_address = chip_addr,
|
||||
};
|
||||
|
||||
i2c_master_dev_handle_t dev_handle = NULL;
|
||||
if (i2c_master_bus_add_device(i2c_handle, &i2c_dev_conf, &dev_handle) != ESP_OK)
|
||||
{
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t ret = i2c_master_transmit(dev_handle, data_to_send, sizeof(data_to_send), 50);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
i2c_master_bus_rm_device(dev_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_master_bus_rm_device(dev_handle);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
i2c_master_bus_config_t i2c_bus_conf = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.sda_io_num = BSP_I2C_SDA,
|
||||
.scl_io_num = BSP_I2C_SCL,
|
||||
.i2c_port = BSP_I2C_NUM,
|
||||
};
|
||||
i2c_new_master_bus(&i2c_bus_conf, &i2c_handle);
|
||||
|
||||
static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
|
||||
esp_ldo_channel_config_t ldo_cfg = {
|
||||
.chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN,
|
||||
.voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||
};
|
||||
esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
|
||||
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
|
||||
|
||||
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
|
||||
esp_lcd_dsi_bus_config_t bus_config = JD9365_PANEL_BUS_DSI_2CH_CONFIG();
|
||||
|
||||
esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
|
||||
|
||||
ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
|
||||
// we use DBI interface to send LCD commands and parameters
|
||||
esp_lcd_panel_io_handle_t io = NULL;
|
||||
esp_lcd_dbi_io_config_t dbi_config =JD9365_PANEL_IO_DBI_CONFIG();
|
||||
|
||||
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
|
||||
|
||||
esp_lcd_panel_handle_t disp_panel = NULL;
|
||||
esp_lcd_dpi_panel_config_t dpi_config = JD9365_800_1280_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565);
|
||||
|
||||
dpi_config.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS;
|
||||
|
||||
jd9365_vendor_config_t vendor_config = {
|
||||
.flags = {
|
||||
.use_mipi_interface = 1,
|
||||
},
|
||||
.mipi_config = {
|
||||
.dsi_bus = mipi_dsi_bus,
|
||||
.dpi_config = &dpi_config,
|
||||
.lane_num = 2,
|
||||
},
|
||||
};
|
||||
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||
.bits_per_pixel = 16,
|
||||
.rgb_ele_order = ESP_LCD_COLOR_SPACE_RGB,
|
||||
.reset_gpio_num = BSP_LCD_RST,
|
||||
.vendor_config = &vendor_config,
|
||||
};
|
||||
esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel);
|
||||
esp_lcd_panel_reset(disp_panel);
|
||||
esp_lcd_panel_init(disp_panel);
|
||||
|
||||
esp_lcd_dpi_panel_event_callbacks_t cbs = {
|
||||
#if LVGL_PORT_AVOID_TEAR_MODE
|
||||
.on_refresh_done = mipi_dsi_lcd_on_vsync_event,
|
||||
#else
|
||||
.on_color_trans_done = mipi_dsi_lcd_on_vsync_event,
|
||||
#endif
|
||||
};
|
||||
esp_lcd_dpi_panel_register_event_callbacks(disp_panel, &cbs, NULL);
|
||||
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
|
||||
esp_lcd_touch_handle_t tp_handle;
|
||||
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
|
||||
tp_io_config.scl_speed_hz = 100000;
|
||||
esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle);
|
||||
const esp_lcd_touch_config_t tp_cfg = {
|
||||
.x_max = BSP_LCD_H_RES,
|
||||
.y_max = BSP_LCD_V_RES,
|
||||
.rst_gpio_num = BSP_LCD_TOUCH_RST, // Shared with LCD reset
|
||||
.int_gpio_num = BSP_LCD_TOUCH_INT,
|
||||
.levels = {
|
||||
.reset = 0,
|
||||
.interrupt = 0,
|
||||
},
|
||||
.flags = {
|
||||
.swap_xy = 0,
|
||||
.mirror_x = 0,
|
||||
.mirror_y = 0,
|
||||
},
|
||||
};
|
||||
|
||||
esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp_handle);
|
||||
|
||||
lvgl_port_interface_t interface = (dpi_config.flags.use_dma2d) ? LVGL_PORT_INTERFACE_MIPI_DSI_DMA : LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA;
|
||||
ESP_LOGI(TAG,"interface is %d",interface);
|
||||
ESP_ERROR_CHECK(lvgl_port_init(disp_panel, tp_handle, interface));
|
||||
|
||||
bsp_display_brightness_set(100);
|
||||
|
||||
if(lvgl_port_lock(-1))
|
||||
{
|
||||
// lv_demo_music();
|
||||
// lv_demo_benchmark();
|
||||
lv_demo_widgets();
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user