dashboard: add waveshare lvgl baseline
This commit is contained in:
@@ -1,58 +1,95 @@
|
||||
/*
|
||||
Overland Controller
|
||||
ESP32-S3 Dashboard Bring-Up
|
||||
#include <Arduino.h>
|
||||
#include <esp_display_panel.hpp>
|
||||
|
||||
Target:
|
||||
Waveshare ESP32-S3 Touch LCD 5B
|
||||
1024x600
|
||||
#include <lvgl.h>
|
||||
#include "lvgl_v8_port.h"
|
||||
#include <demos/lv_demos.h>
|
||||
|
||||
Purpose:
|
||||
Verify build environment and establish
|
||||
the first dashboard firmware entry point.
|
||||
using namespace esp_panel::drivers;
|
||||
using namespace esp_panel::board;
|
||||
|
||||
Future:
|
||||
LVGL
|
||||
Cargo ESP API integration
|
||||
CAN
|
||||
OBD-II
|
||||
*/
|
||||
/**
|
||||
* To use the built-in examples and demos of LVGL uncomment the includes below respectively.
|
||||
*/
|
||||
// #include <demos/lv_demos.h>
|
||||
// #include <examples/lv_examples.h>
|
||||
|
||||
#include <WiFi.h>
|
||||
void setup()
|
||||
{
|
||||
String title = "LVGL porting example";
|
||||
|
||||
const char* DASHBOARD_VERSION = "0.0.1-bringup";
|
||||
|
||||
unsigned long lastPrint = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
Serial.println();
|
||||
Serial.println("================================");
|
||||
Serial.println("Overland Controller Dashboard");
|
||||
Serial.println("================================");
|
||||
Serial.print("Version: ");
|
||||
Serial.println(DASHBOARD_VERSION);
|
||||
Serial.println("Target: Waveshare ESP32-S3 Touch LCD 5B");
|
||||
Serial.println("Display: 1024x600");
|
||||
Serial.println("API Base: /api/v1");
|
||||
Serial.println();
|
||||
}
|
||||
Serial.println("Initializing board");
|
||||
Board *board = new Board();
|
||||
board->init();
|
||||
|
||||
void loop() {
|
||||
if (millis() - lastPrint > 5000) {
|
||||
lastPrint = millis();
|
||||
|
||||
Serial.println("--- Dashboard Heartbeat ---");
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.print("WiFi Connected: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
} else {
|
||||
Serial.println("WiFi Not Connected");
|
||||
}
|
||||
|
||||
Serial.println("Cargo API: /api/v1");
|
||||
Serial.println();
|
||||
#if LVGL_PORT_AVOID_TEARING_MODE
|
||||
auto lcd = board->getLCD();
|
||||
// When avoid tearing function is enabled, the frame buffer number should be set in the board driver
|
||||
lcd->configFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);
|
||||
#if ESP_PANEL_DRIVERS_BUS_ENABLE_RGB && CONFIG_IDF_TARGET_ESP32S3
|
||||
auto lcd_bus = lcd->getBus();
|
||||
/**
|
||||
* As the anti-tearing feature typically consumes more PSRAM bandwidth, for the ESP32-S3, we need to utilize the
|
||||
* "bounce buffer" functionality to enhance the RGB data bandwidth.
|
||||
* This feature will consume `bounce_buffer_size * bytes_per_pixel * 2` of SRAM memory.
|
||||
*/
|
||||
if (lcd_bus->getBasicAttributes().type == ESP_PANEL_BUS_TYPE_RGB) {
|
||||
static_cast<BusRGB *>(lcd_bus)->configRGB_BounceBufferSize(lcd->getFrameWidth() * 10);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
assert(board->begin());
|
||||
|
||||
Serial.println("Initializing LVGL");
|
||||
lvgl_port_init(board->getLCD(), board->getTouch());
|
||||
|
||||
Serial.println("Creating UI");
|
||||
/* Lock the mutex due to the LVGL APIs are not thread-safe */
|
||||
lvgl_port_lock(-1);
|
||||
|
||||
/**
|
||||
* Create the simple labels
|
||||
*/
|
||||
lv_obj_t *label_1 = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(label_1, "Hello World!");
|
||||
lv_obj_set_style_text_font(label_1, &lv_font_montserrat_30, 0);
|
||||
lv_obj_align(label_1, LV_ALIGN_CENTER, 0, -20);
|
||||
lv_obj_t *label_2 = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text_fmt(
|
||||
label_2, "ESP32_Display_Panel (%d.%d.%d)",
|
||||
ESP_PANEL_VERSION_MAJOR, ESP_PANEL_VERSION_MINOR, ESP_PANEL_VERSION_PATCH
|
||||
);
|
||||
lv_obj_set_style_text_font(label_2, &lv_font_montserrat_16, 0);
|
||||
lv_obj_align_to(label_2, label_1, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
|
||||
lv_obj_t *label_3 = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text_fmt(label_3, "LVGL (%d.%d.%d)", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
|
||||
lv_obj_set_style_text_font(label_3, &lv_font_montserrat_16, 0);
|
||||
lv_obj_align_to(label_3, label_2, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
|
||||
|
||||
/**
|
||||
* Try an example. Don't forget to uncomment header.
|
||||
* See all the examples online: https://docs.lvgl.io/master/examples.html
|
||||
* source codes: https://github.com/lvgl/lvgl/tree/e7f88efa5853128bf871dde335c0ca8da9eb7731/examples
|
||||
*/
|
||||
// lv_example_btn_1();
|
||||
|
||||
/**
|
||||
* Or try out a demo.
|
||||
* Don't forget to uncomment header and enable the demos in `lv_conf.h`. E.g. `LV_USE_DEMO_WIDGETS`
|
||||
*/
|
||||
lv_demo_widgets();
|
||||
// lv_demo_benchmark();
|
||||
// lv_demo_music();
|
||||
// lv_demo_stress();
|
||||
|
||||
/* Release the mutex */
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
Serial.println("IDLE loop");
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/**
|
||||
* @file esp_panel_board_custom_conf.h
|
||||
* @brief Configuration file for custom ESP development boards
|
||||
* @author
|
||||
* @link
|
||||
*
|
||||
* This file contains all the configurations needed for a custom board using ESP Panel.
|
||||
* Users can modify these configurations according to their hardware design.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define ESP_PANEL_USE_1024_600_LCD (1) // 0: 800x480, 1: 1024x600
|
||||
#define ESP_OPEN_TOUCH 1 // 1 initiates the touch, 0 closes the touch.
|
||||
|
||||
/**
|
||||
* @brief Flag to enable custom board configuration (0/1)
|
||||
*
|
||||
* Set to `1` to enable custom board configuration, `0` to disable
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_DEFAULT_USE_CUSTOM (1)
|
||||
|
||||
#if ESP_PANEL_BOARD_DEFAULT_USE_CUSTOM
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////// Please update the following macros to configure general parameters ///////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief Board name (format: "Manufacturer:Model")
|
||||
*/
|
||||
#if ESP_PANEL_USE_1024_600_LCD
|
||||
#define ESP_PANEL_BOARD_NAME "Waveshare:ESP32-S3-Touch-LCD-5B"
|
||||
#else
|
||||
#define ESP_PANEL_BOARD_NAME "Waveshare:ESP32-S3-Touch-LCD-5"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Panel resolution configuration in pixels
|
||||
*/
|
||||
#if ESP_PANEL_USE_1024_600_LCD
|
||||
/* LCD resolution in pixels */
|
||||
#define ESP_PANEL_BOARD_WIDTH (1024)
|
||||
#define ESP_PANEL_BOARD_HEIGHT (600)
|
||||
#else
|
||||
/* LCD resolution in pixels */
|
||||
#define ESP_PANEL_BOARD_WIDTH (800)
|
||||
#define ESP_PANEL_BOARD_HEIGHT (480)
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////// Please update the following macros to configure the LCD panel /////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief LCD panel configuration flag (0/1)
|
||||
*
|
||||
* Set to `1` to enable LCD panel support, `0` to disable
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_USE_LCD (1)
|
||||
|
||||
#if ESP_PANEL_BOARD_USE_LCD
|
||||
/**
|
||||
* @brief LCD controller selection
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_CONTROLLER ST7262
|
||||
|
||||
/**
|
||||
* @brief LCD bus type selection
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB)
|
||||
|
||||
|
||||
/**
|
||||
* @brief LCD bus parameters configuration
|
||||
*
|
||||
* Configure parameters based on the selected bus type. Parameters for other bus types will be ignored.
|
||||
* For detailed parameter explanations, see:
|
||||
* https://docs.espressif.com/projects/esp-idf/en/v5.3.1/esp32s3/api-reference/peripherals/lcd/index.html
|
||||
* https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html
|
||||
*/
|
||||
#if ESP_PANEL_BOARD_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB
|
||||
|
||||
/**
|
||||
* @brief RGB bus
|
||||
*/
|
||||
/**
|
||||
* Set to 0 if using simple "RGB" interface which does not contain "3-wire SPI" interface.
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_USE_CONTROL_PANEL (0) // 0/1. Typically set to 1
|
||||
|
||||
/* For refresh panel (RGB) */
|
||||
#if ESP_PANEL_USE_1024_600_LCD
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_CLK_HZ (21 * 1000 * 1000)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HPW (30)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HBP (145)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HFP (170)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VPW (2)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VBP (23)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VFP (12)
|
||||
#else
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_CLK_HZ (16 * 1000 * 1000)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HPW (4)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HBP (8)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_HFP (8)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VPW (4)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VBP (16)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_VFP (16)
|
||||
#endif
|
||||
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_PCLK_ACTIVE_NEG (1) // 0: rising edge, 1: falling edge. Typically set to 0
|
||||
// The following sheet shows the valid combinations of
|
||||
// data width and pixel bits:
|
||||
// ┏---------------------------------┳- -------------------------------┓
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_DATA_WIDTH (16) // | 16 | 8 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_PIXEL_BITS (ESP_PANEL_LCD_COLOR_BITS_RGB565) // | ESP_PANEL_LCD_COLOR_BITS_RGB565 | ESP_PANEL_LCD_COLOR_BITS_RGB888 |
|
||||
// ┗---------------------------------┻---------------------------------┛
|
||||
// To understand color format of RGB LCD, see: https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/rgb_lcd.html#color-formats
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_BOARD_WIDTH * 10)
|
||||
// Bounce buffer size in bytes. It is used to avoid screen drift
|
||||
// for ESP32-S3. Typically set to `ESP_PANEL_BOARD_WIDTH * 10`
|
||||
// The size should satisfy `size * N = LCD_width * LCD_height`,
|
||||
// where N is an even number.
|
||||
// For more details, see: https://github.com/esp-arduino-libs/ESP32_Display_Panel/blob/master/docs/FAQ.md#how-to-fix-screen-drift-issue-when-driving-rgb-lcd-with-esp32-s3
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_HSYNC (46)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_VSYNC (3)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DE (5) // -1 if not used
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_PCLK (7)
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DISP (-1) // -1 if not used. Typically set to -1
|
||||
|
||||
// The following sheet shows the mapping of ESP GPIOs to
|
||||
// LCD data pins with different data width and color format:
|
||||
// ┏------┳- ------------┳--------------------------┓
|
||||
// | ESP: | 8-bit RGB888 | 16-bit RGB565 |
|
||||
// |------|--------------|--------------------------|
|
||||
// | LCD: | RGB888 | RGB565 | RGB666 | RGB888 |
|
||||
// ┗------|--------------|--------|--------|--------|
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA0 (14) // | D0 | B0 | B0-1 | B0-3 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA1 (38) // | D1 | B1 | B2 | B4 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA2 (18) // | D2 | B2 | B3 | B5 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA3 (17) // | D3 | B3 | B4 | B6 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA4 (10) // | D4 | B4 | B5 | B7 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA5 (39) // | D5 | G0 | G0 | G0-2 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA6 (0) // | D6 | G1 | G1 | G3 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA7 (45) // | D7 | G2 | G2 | G4 |
|
||||
#if ESP_PANEL_BOARD_LCD_RGB_DATA_WIDTH > 8 // ┗--------------┫--------|--------|--------|
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA8 (48) // | G3 | G3 | G5 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA9 (47) // | G4 | G4 | G6 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA10 (21) // | G5 | G5 | G7 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA11 (1) // | R0 | R0-1 | R0-3 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA12 (2) // | R1 | R2 | R4 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA13 (42) // | R2 | R3 | R5 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA14 (41) // | R3 | R4 | R6 |
|
||||
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA15 (40) // | R4 | R5 | R7 |
|
||||
// ┗--------┻--------┻--------┛
|
||||
#endif // ESP_PANEL_BOARD_LCD_RGB_DATA_WIDTH
|
||||
|
||||
#endif // ESP_PANEL_BOARD_LCD_BUS_TYPE
|
||||
|
||||
/**
|
||||
* @brief LCD color configuration
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_COLOR_BITS (ESP_PANEL_LCD_COLOR_BITS_RGB565)
|
||||
// ESP_PANEL_LCD_COLOR_BITS_RGB565/RGB666/RGB888
|
||||
#define ESP_PANEL_BOARD_LCD_COLOR_BGR_ORDER (0) // 0: RGB, 1: BGR
|
||||
#define ESP_PANEL_BOARD_LCD_COLOR_INEVRT_BIT (0) // 0/1
|
||||
|
||||
/**
|
||||
* @brief LCD transformation configuration
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_SWAP_XY (0) // 0/1
|
||||
#define ESP_PANEL_BOARD_LCD_MIRROR_X (0) // 0/1
|
||||
#define ESP_PANEL_BOARD_LCD_MIRROR_Y (0) // 0/1
|
||||
#define ESP_PANEL_BOARD_LCD_GAP_X (0) // [0, ESP_PANEL_BOARD_WIDTH]
|
||||
#define ESP_PANEL_BOARD_LCD_GAP_Y (0) // [0, ESP_PANEL_BOARD_HEIGHT]
|
||||
|
||||
/**
|
||||
* @brief LCD reset pin configuration
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_RST_IO (-1) // Reset pin, -1 if not used
|
||||
#define ESP_PANEL_BOARD_LCD_RST_LEVEL (0) // Reset active level, 0: low, 1: high
|
||||
|
||||
#endif // ESP_PANEL_BOARD_USE_LCD
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////// Please update the following macros to configure the touch panel ///////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief Touch panel configuration flag (0/1)
|
||||
*
|
||||
* Set to `1` to enable touch panel support, `0` to disable
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_USE_TOUCH (ESP_OPEN_TOUCH)
|
||||
|
||||
#if ESP_PANEL_BOARD_USE_TOUCH
|
||||
/**
|
||||
* @brief Touch controller selection
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_CONTROLLER GT911
|
||||
|
||||
/**
|
||||
* @brief Touch bus type selection
|
||||
*
|
||||
* Supported types:
|
||||
* - `ESP_PANEL_BUS_TYPE_I2C`
|
||||
* - `ESP_PANEL_BUS_TYPE_SPI`
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C)
|
||||
|
||||
#if (ESP_PANEL_BOARD_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C) || \
|
||||
(ESP_PANEL_BOARD_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI)
|
||||
/**
|
||||
* If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance.
|
||||
*
|
||||
* For drivers which created by this library, even if they use the same host, the host will be initialized only once.
|
||||
* So it is not necessary to set the macro to `1`. For other drivers (like `Wire`), please set the macro to `1`
|
||||
* ensure that the host is initialized only once.
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_BUS_SKIP_INIT_HOST (0) // 0/1. Typically set to 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Touch bus parameters configuration
|
||||
*/
|
||||
#if ESP_PANEL_BOARD_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C
|
||||
|
||||
/**
|
||||
* @brief I2C bus
|
||||
*/
|
||||
/* For general */
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_HOST_ID (0) // Typically set to 0
|
||||
#if !ESP_PANEL_BOARD_TOUCH_BUS_SKIP_INIT_HOST
|
||||
/* For host */
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_CLK_HZ (400 * 1000)
|
||||
// Typically set to 400K
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_SCL_PULLUP (1) // 0/1. Typically set to 1
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_SDA_PULLUP (1) // 0/1. Typically set to 1
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_IO_SCL (9)
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_IO_SDA (8)
|
||||
#endif
|
||||
/* For panel */
|
||||
#define ESP_PANEL_BOARD_TOUCH_I2C_ADDRESS (0) // Typically set to 0 to use the default address.
|
||||
// - For touchs with only one address, set to 0
|
||||
// - For touchs with multiple addresses, set to 0 or
|
||||
// the address. Like GT911, there are two addresses:
|
||||
// 0x5D(default) and 0x14
|
||||
|
||||
#endif // ESP_PANEL_BOARD_TOUCH_BUS_TYPE
|
||||
|
||||
/**
|
||||
* @brief Touch panel transformation flags
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_SWAP_XY (0) // 0/1
|
||||
#define ESP_PANEL_BOARD_TOUCH_MIRROR_X (0) // 0/1
|
||||
#define ESP_PANEL_BOARD_TOUCH_MIRROR_Y (0) // 0/1
|
||||
|
||||
/**
|
||||
* @brief Touch panel control pins
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_RST_IO (-1) // Reset pin, -1 if not used
|
||||
#define ESP_PANEL_BOARD_TOUCH_RST_LEVEL (0) // Reset active level, 0: low, 1: high
|
||||
#define ESP_PANEL_BOARD_TOUCH_INT_IO (4) // Interrupt pin, -1 if not used
|
||||
#define ESP_PANEL_BOARD_TOUCH_INT_LEVEL (0) // Interrupt active level, 0: low, 1: high
|
||||
|
||||
#endif // ESP_PANEL_BOARD_USE_TOUCH
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// Please update the following macros to configure the backlight ////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief Backlight configuration flag (0/1)
|
||||
*
|
||||
* Set to `1` to enable backlight support, `0` to disable
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_USE_BACKLIGHT (1)
|
||||
|
||||
#if ESP_PANEL_BOARD_USE_BACKLIGHT
|
||||
/**
|
||||
* @brief Backlight control type selection
|
||||
*
|
||||
* Supported types:
|
||||
* - `ESP_PANEL_BACKLIGHT_TYPE_SWITCH_GPIO`: Use GPIO switch to control the backlight, only support on/off
|
||||
* - `ESP_PANEL_BACKLIGHT_TYPE_SWITCH_EXPANDER`: Use IO expander to control the backlight, only support on/off
|
||||
* - `ESP_PANEL_BACKLIGHT_TYPE_PWM_LEDC`: Use LEDC PWM to control the backlight, support brightness adjustment
|
||||
* - `ESP_PANEL_BACKLIGHT_TYPE_CUSTOM`: Use custom function to control the backlight
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_BACKLIGHT_TYPE (ESP_PANEL_BACKLIGHT_TYPE_SWITCH_EXPANDER)
|
||||
|
||||
#if (ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_SWITCH_GPIO) || \
|
||||
(ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_SWITCH_EXPANDER) || \
|
||||
(ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_PWM_LEDC)
|
||||
|
||||
/**
|
||||
* @brief Backlight control pin configuration
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_BACKLIGHT_IO (2) // Output GPIO pin number
|
||||
#define ESP_PANEL_BOARD_BACKLIGHT_ON_LEVEL (1) // Active level, 0: low, 1: high
|
||||
|
||||
#endif // ESP_PANEL_BOARD_BACKLIGHT_TYPE
|
||||
|
||||
/**
|
||||
* @brief Backlight idle state configuration (0/1)
|
||||
*
|
||||
* Set to 1 if want to turn off the backlight after initializing. Otherwise, the backlight will be on.
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_BACKLIGHT_IDLE_OFF (0)
|
||||
|
||||
#endif // ESP_PANEL_BOARD_USE_BACKLIGHT
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// Please update the following macros to configure the IO expander //////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief IO expander configuration flag (0/1)
|
||||
*
|
||||
* Set to `1` to enable IO expander support, `0` to disable
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_USE_EXPANDER (1)
|
||||
|
||||
#if ESP_PANEL_BOARD_USE_EXPANDER
|
||||
/**
|
||||
* @brief IO expander chip selection
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_EXPANDER_CHIP CH422G
|
||||
|
||||
/**
|
||||
* @brief IO expander I2C bus parameters configuration
|
||||
*/
|
||||
/**
|
||||
* If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance.
|
||||
*
|
||||
* For drivers which created by this library, even if they use the same host, the host will be initialized only once.
|
||||
* So it is not necessary to set the macro to `1`. For other devices, please set the macro to `1` ensure that the
|
||||
* host is initialized only once.
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_EXPANDER_SKIP_INIT_HOST (0) // 0/1
|
||||
/* For general */
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_HOST_ID (0) // Typically set to 0
|
||||
/* For host */
|
||||
#if !ESP_PANEL_BOARD_EXPANDER_SKIP_INIT_HOST
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_CLK_HZ (400 * 1000)
|
||||
// Typically set to 400K
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_SCL_PULLUP (1) // 0/1. Typically set to 1
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_SDA_PULLUP (1) // 0/1. Typically set to 1
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_IO_SCL (9)
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_IO_SDA (8)
|
||||
#endif // ESP_PANEL_BOARD_EXPANDER_SKIP_INIT_HOST
|
||||
/* For device */
|
||||
#define ESP_PANEL_BOARD_EXPANDER_I2C_ADDRESS (0x20) // The actual I2C address. Even for the same model of IC,
|
||||
// the I2C address may be different, and confirmation based on
|
||||
// the actual hardware connection is required
|
||||
#endif // ESP_PANEL_BOARD_USE_EXPANDER
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////// Please utilize the following macros to execute any additional code if required /////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @brief Post-begin function for IO expander initialization
|
||||
*
|
||||
* @param[in] p Pointer to the board object
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_EXPANDER_POST_BEGIN_FUNCTION(p) \
|
||||
{ \
|
||||
auto board = static_cast<Board *>(p); \
|
||||
auto expander = static_cast<esp_expander::CH422G*>(board->getIO_Expander()->getBase()); \
|
||||
expander->enableAllIO_Output(); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pre-begin function for LCD initialization
|
||||
*
|
||||
* @param[in] p Pointer to the board object
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_LCD_PRE_BEGIN_FUNCTION(p) \
|
||||
{ \
|
||||
constexpr int LCD_RST = 3; \
|
||||
auto board = static_cast<Board *>(p); \
|
||||
auto expander = board->getIO_Expander()->getBase(); \
|
||||
expander->digitalWrite(LCD_RST, 0); \
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); \
|
||||
expander->digitalWrite(LCD_RST, 1); \
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#if ESP_PANEL_BOARD_USE_TOUCH
|
||||
/**
|
||||
* @brief Pre-begin function for touch panel initialization
|
||||
*
|
||||
* @param[in] p Pointer to the board object
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_TOUCH_PRE_BEGIN_FUNCTION(p) \
|
||||
{ \
|
||||
constexpr gpio_num_t TP_INT = static_cast<gpio_num_t>(ESP_PANEL_BOARD_TOUCH_INT_IO); \
|
||||
constexpr int TP_RST = 1; \
|
||||
auto board = static_cast<Board *>(p); \
|
||||
auto expander = board->getIO_Expander()->getBase(); \
|
||||
gpio_set_direction(TP_INT, GPIO_MODE_OUTPUT); \
|
||||
gpio_set_level(TP_INT, 0); \
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); \
|
||||
expander->digitalWrite(TP_RST, 0); \
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); \
|
||||
expander->digitalWrite(TP_RST, 1); \
|
||||
vTaskDelay(pdMS_TO_TICKS(200)); \
|
||||
gpio_reset_pin(TP_INT); \
|
||||
return true; \
|
||||
}
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////// File Version ///////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Do not change the following versions. These version numbers are used to check compatibility between this
|
||||
* configuration file and the library. Rules for version numbers:
|
||||
* 1. Major version mismatch: Configurations are incompatible, must use library version
|
||||
* 2. Minor version mismatch: May be missing new configurations, recommended to update
|
||||
* 3. Patch version mismatch: No impact on functionality
|
||||
*/
|
||||
#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 1
|
||||
#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 0
|
||||
#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0
|
||||
|
||||
#endif // ESP_PANEL_BOARD_USE_CUSTOM
|
||||
|
||||
// *INDENT-ON*
|
||||
@@ -0,0 +1,859 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "esp_timer.h"
|
||||
#undef ESP_UTILS_LOG_TAG
|
||||
#define ESP_UTILS_LOG_TAG "LvPort"
|
||||
#include "esp_lib_utils.h"
|
||||
#include "lvgl_v8_port.h"
|
||||
|
||||
using namespace esp_panel::drivers;
|
||||
|
||||
#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1)
|
||||
#define LVGL_PORT_BUFFER_NUM_MAX (2)
|
||||
|
||||
static SemaphoreHandle_t lvgl_mux = nullptr; // LVGL mutex
|
||||
static TaskHandle_t lvgl_task_handle = nullptr;
|
||||
static esp_timer_handle_t lvgl_tick_timer = NULL;
|
||||
static void *lvgl_buf[LVGL_PORT_BUFFER_NUM_MAX] = {};
|
||||
|
||||
#if LVGL_PORT_ROTATION_DEGREE != 0
|
||||
static void *get_next_frame_buffer(LCD *lcd)
|
||||
{
|
||||
static void *next_fb = NULL;
|
||||
static void *fbs[2] = { NULL };
|
||||
|
||||
if (next_fb == NULL) {
|
||||
fbs[0] = lcd->getFrameBufferByIndex(0);
|
||||
fbs[1] = lcd->getFrameBufferByIndex(1);
|
||||
next_fb = fbs[1];
|
||||
} else {
|
||||
next_fb = (next_fb == fbs[0]) ? fbs[1] : fbs[0];
|
||||
}
|
||||
|
||||
return next_fb;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*(uint16_t *)to++ = *(const uint16_t *)from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
#define _COPY_PIXEL(_bpp, to, from) copy_pixel_##_bpp##bpp(to, from)
|
||||
#define COPY_PIXEL(_bpp, to, from) _COPY_PIXEL(_bpp, to, from)
|
||||
|
||||
#define ROTATE_90_ALL_BPP() \
|
||||
{ \
|
||||
to_bytes_per_line = h * to_bytes_per_piexl; \
|
||||
to_index_const = (w - x_start - 1) * to_bytes_per_line; \
|
||||
for (int from_y = y_start; from_y < y_end + 1; from_y++) { \
|
||||
from_index = from_y * from_bytes_per_line + x_start * from_bytes_per_piexl; \
|
||||
to_index = to_index_const + from_y * to_bytes_per_piexl; \
|
||||
for (int from_x = x_start; from_x < x_end + 1; from_x++) { \
|
||||
COPY_PIXEL(LV_COLOR_DEPTH, to + to_index, from + from_index); \
|
||||
from_index += from_bytes_per_piexl; \
|
||||
to_index -= to_bytes_per_line; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Optimized transpose function for RGB565 format.
|
||||
*
|
||||
* @note ESP32-P4 1024x600 full-screen: 738ms -> 34ms
|
||||
* @note ESP32-S3 480x480 full-screen: 380ms -> 37ms
|
||||
*/
|
||||
#define ROTATE_90_OPTIMIZED_16BPP(block_w, block_h) \
|
||||
{ \
|
||||
for (int i = 0; i < h; i += block_h) { \
|
||||
max_height = (i + block_h > h) ? h : (i + block_h); \
|
||||
for (int j = 0; j < w; j += block_w) { \
|
||||
max_width = (j + block_w > w) ? w : (j + block_w); \
|
||||
start_y = w - 1 - j; \
|
||||
for (int x = i; x < max_height; x++) { \
|
||||
from_next = (uint16_t *)from + x * w; \
|
||||
for (int y = j, mirrored_y = start_y; y < max_width; y += 4, mirrored_y -= 4) { \
|
||||
((uint16_t *)to)[(mirrored_y) * h + x] = *((uint32_t *)(from_next + y)) & 0xFFFF; \
|
||||
((uint16_t *)to)[(mirrored_y - 1) * h + x] = (*((uint32_t *)(from_next + y)) >> 16) & 0xFFFF; \
|
||||
((uint16_t *)to)[(mirrored_y - 2) * h + x] = *((uint32_t *)(from_next + y + 2)) & 0xFFFF; \
|
||||
((uint16_t *)to)[(mirrored_y - 3) * h + x] = (*((uint32_t *)(from_next + y + 2)) >> 16) & 0xFFFF; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ROTATE_180_ALL_BPP() \
|
||||
{ \
|
||||
to_bytes_per_line = w * to_bytes_per_piexl; \
|
||||
to_index_const = (h - 1) * to_bytes_per_line + (w - x_start - 1) * to_bytes_per_piexl; \
|
||||
for (int from_y = y_start; from_y < y_end + 1; from_y++) { \
|
||||
from_index = from_y * from_bytes_per_line + x_start * from_bytes_per_piexl; \
|
||||
to_index = to_index_const - from_y * to_bytes_per_line; \
|
||||
for (int from_x = x_start; from_x < x_end + 1; from_x++) { \
|
||||
COPY_PIXEL(LV_COLOR_DEPTH, to + to_index, from + from_index); \
|
||||
from_index += from_bytes_per_piexl; \
|
||||
to_index -= to_bytes_per_piexl; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ROTATE_270_OPTIMIZED_16BPP(block_w, block_h) \
|
||||
{ \
|
||||
for (int i = 0; i < h; i += block_h) { \
|
||||
max_height = i + block_h > h ? h : i + block_h; \
|
||||
for (int j = 0; j < w; j += block_w) { \
|
||||
max_width = j + block_w > w ? w : j + block_w; \
|
||||
for (int x = i; x < max_height; x++) { \
|
||||
from_next = (uint16_t *)from + x * w; \
|
||||
for (int y = j; y < max_width; y += 4) { \
|
||||
((uint16_t *)to)[y * h + (h - 1 - x)] = *((uint32_t *)(from_next + y)) & 0xFFFF; \
|
||||
((uint16_t *)to)[(y + 1) * h + (h - 1 - x)] = (*((uint32_t *)(from_next + y)) >> 16) & 0xFFFF; \
|
||||
((uint16_t *)to)[(y + 2) * h + (h - 1 - x)] = *((uint32_t *)(from_next + y + 2)) & 0xFFFF; \
|
||||
((uint16_t *)to)[(y + 3) * h + (h - 1 - x)] = (*((uint32_t *)(from_next + y + 2)) >> 16) & 0xFFFF; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ROTATE_270_ALL_BPP() \
|
||||
{ \
|
||||
to_bytes_per_line = h * to_bytes_per_piexl; \
|
||||
from_index_const = x_start * from_bytes_per_piexl; \
|
||||
to_index_const = x_start * to_bytes_per_line + (h - 1) * to_bytes_per_piexl; \
|
||||
for (int from_y = y_start; from_y < y_end + 1; from_y++) { \
|
||||
from_index = from_y * from_bytes_per_line + from_index_const; \
|
||||
to_index = to_index_const - from_y * to_bytes_per_piexl; \
|
||||
for (int from_x = x_start; from_x < x_end + 1; from_x++) { \
|
||||
COPY_PIXEL(LV_COLOR_DEPTH, to + to_index, from + from_index); \
|
||||
from_index += from_bytes_per_piexl; \
|
||||
to_index += to_bytes_per_line; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
IRAM_ATTR static inline void rotate_copy_pixel(
|
||||
const uint8_t *from, uint8_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 rotate
|
||||
)
|
||||
{
|
||||
int from_bytes_per_piexl = sizeof(lv_color_t);
|
||||
int from_bytes_per_line = w * from_bytes_per_piexl;
|
||||
int from_index = 0;
|
||||
|
||||
int to_bytes_per_piexl = LV_COLOR_DEPTH >> 3;
|
||||
int to_bytes_per_line;
|
||||
int to_index = 0;
|
||||
int to_index_const = 0;
|
||||
|
||||
#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED
|
||||
int max_height = 0;
|
||||
int max_width = 0;
|
||||
int start_y = 0;
|
||||
uint16_t *from_next = NULL;
|
||||
#endif
|
||||
|
||||
// uint32_t time = esp_log_timestamp();
|
||||
switch (rotate) {
|
||||
case 90:
|
||||
#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED
|
||||
ROTATE_90_OPTIMIZED_16BPP(32, 256);
|
||||
#else
|
||||
ROTATE_90_ALL_BPP();
|
||||
#endif
|
||||
break;
|
||||
case 180:
|
||||
ROTATE_180_ALL_BPP();
|
||||
break;
|
||||
case 270:
|
||||
#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED
|
||||
ROTATE_270_OPTIMIZED_16BPP(32, 256);
|
||||
#else
|
||||
int from_index_const = 0;
|
||||
ROTATE_270_ALL_BPP();
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// ESP_LOGI(TAG, "rotate: end, time used:%d", (int)(esp_log_timestamp() - time));
|
||||
}
|
||||
#endif /* LVGL_PORT_ROTATION_DEGREE */
|
||||
|
||||
#if LVGL_PORT_AVOID_TEAR
|
||||
#if LVGL_PORT_DIRECT_MODE
|
||||
#if 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;
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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_disp_drv_t *drv)
|
||||
{
|
||||
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 == drv->ver_res) && (flush_hor == drv->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(LCD *lcd)
|
||||
{
|
||||
return get_next_frame_buffer(lcd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(
|
||||
(uint8_t *)src, (uint8_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES,
|
||||
LVGL_PORT_ROTATION_DEGREE
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
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;
|
||||
lv_disp_t *disp = lv_disp_get_default();
|
||||
|
||||
/* Action after last area refresh */
|
||||
if (lv_disp_flush_is_last(drv)) {
|
||||
/* Check if the `full_refresh` flag has been triggered */
|
||||
if (drv->full_refresh) {
|
||||
/* Reset flag */
|
||||
drv->full_refresh = 0;
|
||||
|
||||
// Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer
|
||||
next_fb = flush_get_next_buf(lcd);
|
||||
rotate_copy_pixel(
|
||||
(uint8_t *)color_map, (uint8_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2,
|
||||
LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE
|
||||
);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
lcd->switchFrameBufferTo(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(lcd), color_map, &dirty_area);
|
||||
flush_get_next_buf(lcd);
|
||||
} else {
|
||||
/* Probe the copy method for the current dirty area */
|
||||
probe_result = flush_copy_probe(drv);
|
||||
|
||||
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 */
|
||||
drv->full_refresh = 1;
|
||||
disp->rendering_in_progress = false;
|
||||
lv_disp_flush_ready(drv);
|
||||
|
||||
/* 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(lcd);
|
||||
flush_dirty_save(&dirty_area);
|
||||
flush_dirty_copy(next_fb, color_map, &dirty_area);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
lcd->switchFrameBufferTo(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(lcd), color_map, &dirty_area);
|
||||
flush_get_next_buf(lcd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
|
||||
/* Action after last area refresh */
|
||||
if (lv_disp_flush_is_last(drv)) {
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
lcd->switchFrameBufferTo(color_map);
|
||||
|
||||
/* Waiting for the last frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
}
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
#endif /* LVGL_PORT_ROTATION_DEGREE */
|
||||
|
||||
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 2
|
||||
|
||||
static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
lcd->switchFrameBufferTo(color_map);
|
||||
|
||||
/* Waiting for the last frame buffer to complete transmission */
|
||||
ulTaskNotifyValueClear(NULL, ULONG_MAX);
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
|
||||
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3
|
||||
|
||||
#if LVGL_PORT_ROTATION_DEGREE == 0
|
||||
static void *lvgl_port_lcd_last_buf = NULL;
|
||||
static void *lvgl_port_lcd_next_buf = NULL;
|
||||
static void *lvgl_port_flush_next_buf = NULL;
|
||||
#endif
|
||||
|
||||
void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
|
||||
#if 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(lcd);
|
||||
|
||||
/* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */
|
||||
rotate_copy_pixel(
|
||||
(uint8_t *)color_map, (uint8_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES,
|
||||
LV_VER_RES, LVGL_PORT_ROTATION_DEGREE
|
||||
);
|
||||
|
||||
/* Switch the current LCD frame buffer to `next_fb` */
|
||||
lcd->switchFrameBufferTo(next_fb);
|
||||
#else
|
||||
drv->draw_buf->buf1 = color_map;
|
||||
drv->draw_buf->buf2 = lvgl_port_flush_next_buf;
|
||||
lvgl_port_flush_next_buf = color_map;
|
||||
|
||||
/* Switch the current LCD frame buffer to `color_map` */
|
||||
lcd->switchFrameBufferTo(color_map);
|
||||
|
||||
lvgl_port_lcd_next_buf = color_map;
|
||||
#endif
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
#endif
|
||||
|
||||
IRAM_ATTR bool onLcdVsyncCallback(void *user_data)
|
||||
{
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
#if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0)
|
||||
if (lvgl_port_lcd_next_buf != lvgl_port_lcd_last_buf) {
|
||||
lvgl_port_flush_next_buf = lvgl_port_lcd_last_buf;
|
||||
lvgl_port_lcd_last_buf = lvgl_port_lcd_next_buf;
|
||||
}
|
||||
#else
|
||||
TaskHandle_t task_handle = (TaskHandle_t)user_data;
|
||||
// Notify that the current LCD frame buffer has been transmitted
|
||||
xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield);
|
||||
#endif
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
const int offsetx1 = area->x1;
|
||||
const int offsetx2 = area->x2;
|
||||
const int offsety1 = area->y1;
|
||||
const int offsety2 = area->y2;
|
||||
|
||||
lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map);
|
||||
// For RGB LCD, directly notify LVGL that the buffer is ready
|
||||
if (lcd->getBus()->getBasicAttributes().type == ESP_PANEL_BUS_TYPE_RGB) {
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_callback(lv_disp_drv_t *drv)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
auto transformation = lcd->getTransformation();
|
||||
static bool disp_init_mirror_x = transformation.mirror_x;
|
||||
static bool disp_init_mirror_y = transformation.mirror_y;
|
||||
static bool disp_init_swap_xy = transformation.swap_xy;
|
||||
|
||||
switch (drv->rotated) {
|
||||
case LV_DISP_ROT_NONE:
|
||||
lcd->swapXY(disp_init_swap_xy);
|
||||
lcd->mirrorX(disp_init_mirror_x);
|
||||
lcd->mirrorY(disp_init_mirror_y);
|
||||
break;
|
||||
case LV_DISP_ROT_90:
|
||||
lcd->swapXY(!disp_init_swap_xy);
|
||||
lcd->mirrorX(disp_init_mirror_x);
|
||||
lcd->mirrorY(!disp_init_mirror_y);
|
||||
break;
|
||||
case LV_DISP_ROT_180:
|
||||
lcd->swapXY(disp_init_swap_xy);
|
||||
lcd->mirrorX(!disp_init_mirror_x);
|
||||
lcd->mirrorY(!disp_init_mirror_y);
|
||||
break;
|
||||
case LV_DISP_ROT_270:
|
||||
lcd->swapXY(!disp_init_swap_xy);
|
||||
lcd->mirrorX(!disp_init_mirror_x);
|
||||
lcd->mirrorY(disp_init_mirror_y);
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_UTILS_LOGD("Update display rotation to %d", drv->rotated);
|
||||
|
||||
#if ESP_UTILS_CONF_LOG_LEVEL == ESP_UTILS_LOG_LEVEL_DEBUG
|
||||
transformation = lcd->getTransformation();
|
||||
disp_init_mirror_x = transformation.mirror_x;
|
||||
disp_init_mirror_y = transformation.mirror_y;
|
||||
disp_init_swap_xy = transformation.swap_xy;
|
||||
ESP_UTILS_LOGD("Current mirror x: %d, mirror y: %d, swap xy: %d", disp_init_mirror_x, disp_init_mirror_y, disp_init_swap_xy);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* LVGL_PORT_AVOID_TEAR */
|
||||
|
||||
void rounder_callback(lv_disp_drv_t *drv, lv_area_t *area)
|
||||
{
|
||||
LCD *lcd = (LCD *)drv->user_data;
|
||||
uint8_t x_align = lcd->getBasicAttributes().basic_bus_spec.x_coord_align;
|
||||
uint8_t y_align = lcd->getBasicAttributes().basic_bus_spec.y_coord_align;
|
||||
|
||||
if (x_align > 1) {
|
||||
// round the start of coordinate down to the nearest aligned value
|
||||
area->x1 &= ~(x_align - 1);
|
||||
// round the end of coordinate up to the nearest aligned value
|
||||
area->x2 = (area->x2 & ~(x_align - 1)) + x_align - 1;
|
||||
}
|
||||
|
||||
if (y_align > 1) {
|
||||
// round the start of coordinate down to the nearest aligned value
|
||||
area->y1 &= ~(y_align - 1);
|
||||
// round the end of coordinate up to the nearest aligned value
|
||||
area->y2 = (area->y2 & ~(y_align - 1)) + y_align - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static lv_disp_t *display_init(LCD *lcd)
|
||||
{
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(lcd != nullptr, nullptr, "Invalid LCD device");
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(lcd->getRefreshPanelHandle() != nullptr, nullptr, "LCD device is not initialized");
|
||||
|
||||
static lv_disp_draw_buf_t disp_buf;
|
||||
static lv_disp_drv_t disp_drv;
|
||||
|
||||
// Alloc draw buffers used by LVGL
|
||||
auto lcd_width = lcd->getFrameWidth();
|
||||
auto lcd_height = lcd->getFrameHeight();
|
||||
int buffer_size = 0;
|
||||
|
||||
ESP_UTILS_LOGD("Malloc memory for LVGL buffer");
|
||||
#if !LVGL_PORT_AVOID_TEAR
|
||||
// Avoid tearing function is disabled
|
||||
buffer_size = lcd_width * LVGL_PORT_BUFFER_SIZE_HEIGHT;
|
||||
for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) {
|
||||
lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS);
|
||||
assert(lvgl_buf[i]);
|
||||
ESP_UTILS_LOGD("Buffer[%d] address: %p, size: %d", i, lvgl_buf[i], buffer_size * sizeof(lv_color_t));
|
||||
}
|
||||
#else
|
||||
// To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh
|
||||
buffer_size = lcd_width * lcd_height;
|
||||
#if (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (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 LCD's sync signal
|
||||
lvgl_port_lcd_last_buf = lcd->getFrameBufferByIndex(0);
|
||||
lvgl_buf[0] = lcd->getFrameBufferByIndex(1);
|
||||
lvgl_buf[1] = lcd->getFrameBufferByIndex(2);
|
||||
lvgl_port_lcd_next_buf = lvgl_port_lcd_last_buf;
|
||||
lvgl_port_flush_next_buf = lvgl_buf[1];
|
||||
|
||||
#elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0)
|
||||
|
||||
lvgl_buf[0] = lcd->getFrameBufferByIndex(2);
|
||||
|
||||
#elif LVGL_PORT_DISP_BUFFER_NUM >= 2
|
||||
|
||||
for (int i = 0; (i < LVGL_PORT_DISP_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) {
|
||||
lvgl_buf[i] = lcd->getFrameBufferByIndex(i);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* LVGL_PORT_AVOID_TEAR */
|
||||
|
||||
// initialize LVGL draw buffers
|
||||
lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size);
|
||||
|
||||
ESP_UTILS_LOGD("Register display driver to LVGL");
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
disp_drv.flush_cb = flush_callback;
|
||||
#if (LVGL_PORT_ROTATION_DEGREE == 90) || (LVGL_PORT_ROTATION_DEGREE == 270)
|
||||
disp_drv.hor_res = lcd_height;
|
||||
disp_drv.ver_res = lcd_width;
|
||||
#else
|
||||
disp_drv.hor_res = lcd_width;
|
||||
disp_drv.ver_res = lcd_height;
|
||||
#endif
|
||||
#if LVGL_PORT_AVOID_TEAR // Only available when the tearing effect is enabled
|
||||
#if LVGL_PORT_FULL_REFRESH
|
||||
disp_drv.full_refresh = 1;
|
||||
#elif LVGL_PORT_DIRECT_MODE
|
||||
disp_drv.direct_mode = 1;
|
||||
#endif
|
||||
#else // Only available when the tearing effect is disabled
|
||||
if (lcd->getBasicAttributes().basic_bus_spec.isFunctionValid(LCD::BasicBusSpecification::FUNC_SWAP_XY) &&
|
||||
lcd->getBasicAttributes().basic_bus_spec.isFunctionValid(LCD::BasicBusSpecification::FUNC_MIRROR_X) &&
|
||||
lcd->getBasicAttributes().basic_bus_spec.isFunctionValid(LCD::BasicBusSpecification::FUNC_MIRROR_Y)) {
|
||||
disp_drv.drv_update_cb = update_callback;
|
||||
} else {
|
||||
disp_drv.sw_rotate = 1;
|
||||
}
|
||||
#endif /* LVGL_PORT_AVOID_TEAR */
|
||||
disp_drv.draw_buf = &disp_buf;
|
||||
disp_drv.user_data = (void *)lcd;
|
||||
// Only available when the coordinate alignment is enabled
|
||||
if ((lcd->getBasicAttributes().basic_bus_spec.x_coord_align > 1) ||
|
||||
(lcd->getBasicAttributes().basic_bus_spec.y_coord_align > 1)) {
|
||||
disp_drv.rounder_cb = rounder_callback;
|
||||
}
|
||||
|
||||
return lv_disp_drv_register(&disp_drv);
|
||||
}
|
||||
|
||||
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
|
||||
{
|
||||
Touch *tp = (Touch *)indev_drv->user_data;
|
||||
TouchPoint point;
|
||||
|
||||
/* Read data from touch controller */
|
||||
int read_touch_result = tp->readPoints(&point, 1, 0);
|
||||
if (read_touch_result > 0) {
|
||||
data->point.x = point.x;
|
||||
data->point.y = point.y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
}
|
||||
|
||||
static lv_indev_t *indev_init(Touch *tp)
|
||||
{
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(tp != nullptr, nullptr, "Invalid touch device");
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(tp->getPanelHandle() != nullptr, nullptr, "Touch device is not initialized");
|
||||
|
||||
static lv_indev_drv_t indev_drv_tp;
|
||||
|
||||
ESP_UTILS_LOGD("Register input driver to LVGL");
|
||||
lv_indev_drv_init(&indev_drv_tp);
|
||||
indev_drv_tp.type = LV_INDEV_TYPE_POINTER;
|
||||
indev_drv_tp.read_cb = touchpad_read;
|
||||
indev_drv_tp.user_data = (void *)tp;
|
||||
|
||||
return lv_indev_drv_register(&indev_drv_tp);
|
||||
}
|
||||
|
||||
#if !LV_TICK_CUSTOM
|
||||
static void tick_increment(void *arg)
|
||||
{
|
||||
/* Tell LVGL how many milliseconds have elapsed */
|
||||
lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static bool 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_UTILS_CHECK_ERROR_RETURN(
|
||||
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed"
|
||||
);
|
||||
ESP_UTILS_CHECK_ERROR_RETURN(
|
||||
esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000), false,
|
||||
"Start LVGL tick timer failed"
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tick_deinit(void)
|
||||
{
|
||||
ESP_UTILS_CHECK_ERROR_RETURN(
|
||||
esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed"
|
||||
);
|
||||
ESP_UTILS_CHECK_ERROR_RETURN(
|
||||
esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void lvgl_port_task(void *arg)
|
||||
{
|
||||
ESP_UTILS_LOGD("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));
|
||||
}
|
||||
}
|
||||
|
||||
IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data)
|
||||
{
|
||||
lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data;
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lvgl_port_init(LCD *lcd, Touch *tp)
|
||||
{
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(lcd != nullptr, false, "Invalid LCD device");
|
||||
|
||||
auto bus_type = lcd->getBus()->getBasicAttributes().type;
|
||||
#if LVGL_PORT_AVOID_TEAR
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(
|
||||
(bus_type == ESP_PANEL_BUS_TYPE_RGB) || (bus_type == ESP_PANEL_BUS_TYPE_MIPI_DSI), false,
|
||||
"Avoid tearing function only works with RGB/MIPI-DSI LCD now"
|
||||
);
|
||||
ESP_UTILS_LOGI(
|
||||
"Avoid tearing is enabled, mode: %d, rotation: %d", LVGL_PORT_AVOID_TEARING_MODE, LVGL_PORT_ROTATION_DEGREE
|
||||
);
|
||||
#endif
|
||||
|
||||
lv_disp_t *disp = nullptr;
|
||||
lv_indev_t *indev = nullptr;
|
||||
|
||||
lv_init();
|
||||
#if !LV_TICK_CUSTOM
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(tick_init(), false, "Initialize LVGL tick failed");
|
||||
#endif
|
||||
|
||||
ESP_UTILS_LOGI("Initializing LVGL display driver");
|
||||
disp = display_init(lcd);
|
||||
ESP_UTILS_CHECK_NULL_RETURN(disp, false, "Initialize LVGL display driver failed");
|
||||
// Record the initial rotation of the display
|
||||
lv_disp_set_rotation(disp, LV_DISP_ROT_NONE);
|
||||
|
||||
// For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished
|
||||
if (bus_type != ESP_PANEL_BUS_TYPE_RGB) {
|
||||
ESP_UTILS_LOGD("Attach refresh finish callback to LCD");
|
||||
lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver);
|
||||
}
|
||||
|
||||
if (tp != nullptr) {
|
||||
ESP_UTILS_LOGD("Initialize LVGL input driver");
|
||||
indev = indev_init(tp);
|
||||
ESP_UTILS_CHECK_NULL_RETURN(indev, false, "Initialize LVGL input driver failed");
|
||||
|
||||
#if LVGL_PORT_ROTATION_DEGREE != 0
|
||||
auto &transformation = tp->getTransformation();
|
||||
#if LVGL_PORT_ROTATION_DEGREE == 90
|
||||
tp->swapXY(!transformation.swap_xy);
|
||||
tp->mirrorY(!transformation.mirror_y);
|
||||
#elif LVGL_PORT_ROTATION_DEGREE == 180
|
||||
tp->mirrorX(!transformation.mirror_x);
|
||||
tp->mirrorY(!transformation.mirror_y);
|
||||
#elif LVGL_PORT_ROTATION_DEGREE == 270
|
||||
tp->swapXY(!transformation.swap_xy);
|
||||
tp->mirrorX(!transformation.mirror_x);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
ESP_UTILS_LOGD("Create mutex for LVGL");
|
||||
lvgl_mux = xSemaphoreCreateRecursiveMutex();
|
||||
ESP_UTILS_CHECK_NULL_RETURN(lvgl_mux, false, "Create LVGL mutex failed");
|
||||
|
||||
ESP_UTILS_LOGD("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);
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(ret == pdPASS, false, "Create LVGL task failed");
|
||||
|
||||
#if LVGL_PORT_AVOID_TEAR
|
||||
lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lvgl_port_lock(int timeout_ms)
|
||||
{
|
||||
ESP_UTILS_CHECK_NULL_RETURN(lvgl_mux, false, "LVGL mutex is not initialized");
|
||||
|
||||
const TickType_t timeout_ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
|
||||
return (xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE);
|
||||
}
|
||||
|
||||
bool lvgl_port_unlock(void)
|
||||
{
|
||||
ESP_UTILS_CHECK_NULL_RETURN(lvgl_mux, false, "LVGL mutex is not initialized");
|
||||
|
||||
xSemaphoreGiveRecursive(lvgl_mux);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lvgl_port_deinit(void)
|
||||
{
|
||||
#if !LV_TICK_CUSTOM
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(tick_deinit(), false, "Deinitialize LVGL tick failed");
|
||||
#endif
|
||||
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(lvgl_port_lock(-1), false, "Lock LVGL failed");
|
||||
if (lvgl_task_handle != nullptr) {
|
||||
vTaskDelete(lvgl_task_handle);
|
||||
lvgl_task_handle = nullptr;
|
||||
}
|
||||
ESP_UTILS_CHECK_FALSE_RETURN(lvgl_port_unlock(), false, "Unlock LVGL failed");
|
||||
|
||||
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
|
||||
lv_deinit();
|
||||
#else
|
||||
ESP_UTILS_LOGW("LVGL memory is custom, `lv_deinit()` will not work");
|
||||
#endif
|
||||
#if !LVGL_PORT_AVOID_TEAR
|
||||
for (int i = 0; i < LVGL_PORT_BUFFER_NUM; i++) {
|
||||
if (lvgl_buf[i] != nullptr) {
|
||||
free(lvgl_buf[i]);
|
||||
lvgl_buf[i] = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (lvgl_mux != nullptr) {
|
||||
vSemaphoreDelete(lvgl_mux);
|
||||
lvgl_mux = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#ifdef CONFIG_ARDUINO_RUNNING_CORE
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#include "esp_display_panel.hpp"
|
||||
#include "lvgl.h"
|
||||
|
||||
// *INDENT-OFF*
|
||||
|
||||
/**
|
||||
* LVGL related parameters, can be adjusted by users
|
||||
*/
|
||||
#define LVGL_PORT_TICK_PERIOD_MS (2) // The period of the LVGL tick task, in milliseconds
|
||||
|
||||
/**
|
||||
*
|
||||
* 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)
|
||||
* (For SPI/QSPI LCD, it is recommended to allocate the buffer in SRAM, because the SPI DMA does not directly support PSRAM now)
|
||||
*
|
||||
* - The size (in bytes) and number of buffers:
|
||||
* - Lager buffer size can improve FPS, but it will occupy more memory. Maximum buffer size is `width * height`.
|
||||
* - The number of buffers should be 1 or 2.
|
||||
*/
|
||||
#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) // Allocate LVGL buffer in SRAM
|
||||
// #define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_SPIRAM) // Allocate LVGL buffer in PSRAM
|
||||
#define LVGL_PORT_BUFFER_SIZE_HEIGHT (20)
|
||||
#define LVGL_PORT_BUFFER_NUM (2)
|
||||
|
||||
/**
|
||||
* LVGL timer handle task related parameters, can be adjusted by users
|
||||
*/
|
||||
#define LVGL_PORT_TASK_MAX_DELAY_MS (500) // The maximum delay of the LVGL timer task, in milliseconds
|
||||
#define LVGL_PORT_TASK_MIN_DELAY_MS (2) // The minimum delay of the LVGL timer task, in milliseconds
|
||||
#define LVGL_PORT_TASK_STACK_SIZE (6 * 1024) // The stack size of the LVGL timer task, in bytes
|
||||
#define LVGL_PORT_TASK_PRIORITY (2) // The priority of the LVGL timer task
|
||||
#ifdef ARDUINO_RUNNING_CORE
|
||||
#define LVGL_PORT_TASK_CORE (ARDUINO_RUNNING_CORE) // Valid if using Arduino
|
||||
#else
|
||||
#define LVGL_PORT_TASK_CORE (1) // Valid if using ESP-IDF
|
||||
#endif
|
||||
// The core of the LVGL timer task, `-1` means the don't specify the core
|
||||
// Default is the same as the main core
|
||||
// This can be set to `1` only if the SoCs support dual-core,
|
||||
// otherwise it should be set to `-1` or `0`
|
||||
|
||||
/**
|
||||
* Avoid tering related configurations, can be adjusted by users.
|
||||
*
|
||||
* (Currently, This function only supports RGB LCD and the version of LVGL must be >= 8.3.9)
|
||||
*/
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
#ifdef CONFIG_LVGL_PORT_AVOID_TEARING_MODE
|
||||
#define LVGL_PORT_AVOID_TEARING_MODE (CONFIG_LVGL_PORT_AVOID_TEARING_MODE)
|
||||
// Valid if using ESP-IDF
|
||||
#else
|
||||
#define LVGL_PORT_AVOID_TEARING_MODE (3) // Valid if using Arduino
|
||||
#endif
|
||||
|
||||
#if LVGL_PORT_AVOID_TEARING_MODE != 0
|
||||
/**
|
||||
* When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported.
|
||||
* But users can set the rotation degree(0/90/180/270) here, but this function will reduce FPS.
|
||||
*
|
||||
* Set the rotation degree:
|
||||
* - 0: 0 degree
|
||||
* - 90: 90 degree
|
||||
* - 180: 180 degree
|
||||
* - 270: 270 degree
|
||||
*/
|
||||
#ifdef CONFIG_LVGL_PORT_ROTATION_DEGREE
|
||||
#define LVGL_PORT_ROTATION_DEGREE (CONFIG_LVGL_PORT_ROTATION_DEGREE)
|
||||
// Valid if using ESP-IDF
|
||||
#else
|
||||
#define LVGL_PORT_ROTATION_DEGREE (0) // Valid if using Arduino
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Here, some important configurations will be set based on different anti-tearing modes and rotation angles.
|
||||
* No modification is required here.
|
||||
*
|
||||
* Users should use `lcd_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);` to set the buffer number before. If screen drifting occurs, please refer to the Troubleshooting section in the README.
|
||||
* initializing the LCD bus
|
||||
*/
|
||||
#define LVGL_PORT_AVOID_TEAR (1)
|
||||
// Set the buffer number and refresh mode according to the different modes
|
||||
#if LVGL_PORT_AVOID_TEARING_MODE == 1
|
||||
#define LVGL_PORT_DISP_BUFFER_NUM (2)
|
||||
#define LVGL_PORT_FULL_REFRESH (1)
|
||||
#elif LVGL_PORT_AVOID_TEARING_MODE == 2
|
||||
#define LVGL_PORT_DISP_BUFFER_NUM (3)
|
||||
#define LVGL_PORT_FULL_REFRESH (1)
|
||||
#elif LVGL_PORT_AVOID_TEARING_MODE == 3
|
||||
#define LVGL_PORT_DISP_BUFFER_NUM (2)
|
||||
#define LVGL_PORT_DIRECT_MODE (1)
|
||||
#else
|
||||
#error "Invalid avoid tearing mode, please set macro `LVGL_PORT_AVOID_TEARING_MODE` to one of `LVGL_PORT_AVOID_TEARING_MODE_*`"
|
||||
#endif
|
||||
// Check rotation
|
||||
#if (LVGL_PORT_ROTATION_DEGREE != 0) && (LVGL_PORT_ROTATION_DEGREE != 90) && (LVGL_PORT_ROTATION_DEGREE != 180) && \
|
||||
(LVGL_PORT_ROTATION_DEGREE != 270)
|
||||
#error "Invalid rotation degree, please set to 0, 90, 180 or 270"
|
||||
#elif LVGL_PORT_ROTATION_DEGREE != 0
|
||||
#ifdef LVGL_PORT_DISP_BUFFER_NUM
|
||||
#undef LVGL_PORT_DISP_BUFFER_NUM
|
||||
#define LVGL_PORT_DISP_BUFFER_NUM (3)
|
||||
#endif
|
||||
#endif
|
||||
#endif /* LVGL_PORT_AVOID_TEARING_MODE */
|
||||
|
||||
// *INDENT-ON*
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Porting LVGL with LCD and touch panel. This function should be called after the initialization of the LCD and touch panel.
|
||||
*
|
||||
* @param lcd The pointer to the LCD panel device, mustn't be nullptr
|
||||
* @param tp The pointer to the touch panel device, set to nullptr if is not used
|
||||
*
|
||||
* @return true if success, otherwise false
|
||||
*/
|
||||
bool lvgl_port_init(esp_panel::drivers::LCD *lcd, esp_panel::drivers::Touch *tp);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the LVGL porting.
|
||||
*
|
||||
* @return true if success, otherwise false
|
||||
*/
|
||||
bool lvgl_port_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Lock the LVGL mutex. This function should be called before calling any LVGL APIs when not in LVGL task,
|
||||
* and the `lvgl_port_unlock()` function should be called later.
|
||||
*
|
||||
* @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely.
|
||||
*
|
||||
* @return true if success, otherwise false
|
||||
*/
|
||||
bool lvgl_port_lock(int timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the
|
||||
* `lvgl_port_lock()` function should be called before.
|
||||
*
|
||||
* @return true if success, otherwise false
|
||||
*/
|
||||
bool lvgl_port_unlock(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user