Files
Dual-PID/P4_Display_Firmware/lvgl_port_v9.c
T
raw-designsandClaude Opus 5 16d8465b0c perf(P4): LVGL zeichnet direkt in den Bildspeicher (1.9.0)
Die Messung aus 1.8.8 hat es gezeigt: Jeder Bildaufbau kostete 240-300 ms,
dauerhaft, auch im Leerlauf. Die fünf Sekunden für den Aufweckdialog waren
rund zwanzig solcher Durchgänge - meine bisherigen Erklärungen (Fades,
Scrims) waren Symptome, nicht die Ursache.

Grund: zwei Durchgänge durch je 2,7 MB PSRAM pro Bild. LVGL rendert in einen
eigenen Draw-Buffer, esp_lcd_panel_draw_bitmap kopiert ihn danach komplett
in den Framebuffer.

WS7_DIRECT_FB holt den Framebuffer über esp_lcd_dpi_panel_get_frame_buffer
und übergibt ihn als LVGL-Draw-Buffer. Der IDF-Treiber erkennt das
(draw_buffer im Framebuffer-Bereich) und macht nur noch Cache-Writeback statt
Kopie; dazu LV_DISPLAY_RENDER_MODE_DIRECT, sodass nur geänderte Bereiche neu
gezeichnet werden.

Außerdem:
- Shot-Timer und Waage behalten während eines Bezugs die normale Schrift;
  der Wechsel auf den fetten 96er ist entfernt.
- Größe der Standby-Uhr einstellbar (120/180/240 px, Info-Seite,
  NVS ui/uhrgr). Wirkt sofort - sie hängt an einem einzigen Label.
- Die Knöpfe des Aufweckdialogs skalieren mit der Schriftgröße.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 01:27:14 +02:00

737 lines
26 KiB
C

/*
* 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 "src/touch/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);
#if WS7_DIRECT_FB
// PROJEKTAENDERUNG: LVGL zeichnet direkt in den Bildspeicher.
//
// Der Hersteller-Port legt einen eigenen Zeichenpuffer an; der fertige Inhalt wird
// danach in den Bildspeicher kopiert. Bei 1280x720 in 24 Bit sind das zwei Durchgaenge
// durch je 2,7 MB PSRAM - gemessen rund 250 bis 300 ms pro Bild, und das dauerhaft.
// Genau deshalb hing die Oberflaeche eine gute Viertelsekunde hinterher, und Vorgaenge
// mit mehreren Bildern brauchten Sekunden.
//
// Ist der Zeichenpuffer derselbe Speicher wie der Bildspeicher, laesst der
// esp_lcd-Treiber das Kopieren weg und schreibt nur den Prozessor-Cache zurueck. Dazu
// kommt LV_DISPLAY_RENDER_MODE_DIRECT: LVGL zeichnet dann nur die geaenderten Bereiche
// neu, statt jedes Mal die ganze Flaeche.
//
// Preis: Gezeichnet wird in den Speicher, der gerade angezeigt wird - bei schnellen
// Wechseln kann kurz eine Kante durchs Bild laufen. Mit nur einem Bildspeicher (den das
// Panel verlangt, siehe WS7_SINGLE_FB) ist das ohnehin nicht zu vermeiden.
void* fb0 = NULL;
if (esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb0) == ESP_OK && fb0) {
buf1 = fb0;
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
ESP_LOGI(TAG, "LVGL zeichnet direkt in den Bildspeicher (%dKB)",
(int)(buffer_size * sizeof(lv_color_t) / 1024));
}
#endif
#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 || WS7_DIRECT_FB
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
// 270 = 90 + 180Grad: gegenueber 90 (swap_xy + mirror_x) beide Spiegelachsen umkehren.
esp_lcd_touch_set_swap_xy(param->tp_handle, true);
esp_lcd_touch_set_mirror_x(param->tp_handle, false);
esp_lcd_touch_set_mirror_y(param->tp_handle, true);
#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);
}