From e496f612542fae0bee8d48bbe5fcdbe551f48ac3 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Tue, 5 Nov 2024 16:16:14 +0800 Subject: [PATCH 01/11] feat(bus & lcd): support MIPI-DSI LCD --- examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino | 122 +++-- examples/LCD/QSPI/QSPI.ino | 18 +- examples/LCD/RGB/RGB.ino | 60 ++- examples/LCD/SPI/SPI.ino | 31 +- examples/LVGL/v8/Porting/Porting.ino | 5 +- examples/LVGL/v8/Porting/lvgl_port_v8.cpp | 22 +- examples/LVGL/v8/Rotation/lvgl_port_v8.cpp | 22 +- examples/PlatformIO/src/lvgl_port_v8.cpp | 22 +- examples/PlatformIO/src/lvgl_port_v8.h | 7 +- .../SquareLine/v8/Porting/lvgl_port_v8.cpp | 22 +- .../SquareLine/v8/WiFiClock/lvgl_port_v8.cpp | 22 +- src/ESP_Panel.cpp | 26 +- src/ESP_PanelLog.h | 10 +- src/ESP_PanelTypes.h | 13 +- src/ESP_Panel_Library.h | 1 + src/bus/DSI.cpp | 126 +++++ src/bus/DSI.h | 169 ++++++ src/bus/ESP_PanelBus.cpp | 34 +- src/bus/ESP_PanelBus.h | 38 +- src/bus/I2C.cpp | 9 +- src/bus/QSPI.cpp | 4 +- src/bus/QSPI.h | 5 - src/bus/RGB.cpp | 18 +- src/bus/RGB.h | 5 +- src/bus/SPI.cpp | 4 +- src/lcd/ESP_PanelLcd.cpp | 506 +++++++++++++----- src/lcd/ESP_PanelLcd.h | 169 +++++- src/lcd/GC9503.cpp | 6 +- src/lcd/GC9503.h | 2 +- src/lcd/GC9A01.cpp | 2 +- src/lcd/GC9A01.h | 2 +- src/lcd/GC9B71.cpp | 2 +- src/lcd/GC9B71.h | 2 +- src/lcd/ILI9341.cpp | 2 +- src/lcd/ILI9341.h | 2 +- src/lcd/NV3022B.cpp | 2 +- src/lcd/NV3022B.h | 2 +- src/lcd/SH8601.cpp | 2 +- src/lcd/SH8601.h | 2 +- src/lcd/SPD2010.cpp | 2 +- src/lcd/SPD2010.h | 2 +- src/lcd/ST7701.cpp | 6 +- src/lcd/ST7701.h | 2 +- src/lcd/ST7789.cpp | 2 +- src/lcd/ST7789.h | 2 +- src/lcd/ST77916.cpp | 2 +- src/lcd/ST77916.h | 2 +- src/lcd/ST77922.cpp | 2 +- src/lcd/ST77922.h | 2 +- src/lcd/ST7796.cpp | 2 +- src/lcd/ST7796.h | 2 +- src/lcd/base/esp_lcd_gc9503.c | 2 +- src/lcd/base/esp_lcd_gc9a01.c | 2 +- src/lcd/base/esp_lcd_gc9b71.c | 2 +- src/lcd/base/esp_lcd_ili9341.c | 2 +- src/lcd/base/esp_lcd_nv3022b.c | 2 +- src/lcd/base/esp_lcd_sh8601.c | 2 +- src/lcd/base/esp_lcd_spd2010.c | 2 +- src/lcd/base/esp_lcd_st7701.c | 2 +- src/lcd/base/esp_lcd_st7789.c | 2 +- src/lcd/base/esp_lcd_st77916.c | 2 +- src/lcd/base/esp_lcd_st77922.c | 2 +- src/lcd/base/esp_lcd_st7796.c | 2 +- ..._custom_types.h => esp_lcd_vendor_types.h} | 33 +- src/touch/CST816S.cpp | 2 +- src/touch/FT5x06.cpp | 2 +- src/touch/GT1151.cpp | 2 +- src/touch/GT911.cpp | 2 +- src/touch/ST1633.cpp | 2 +- src/touch/ST7123.cpp | 2 +- src/touch/TT21100.cpp | 2 +- src/touch/XPT2046.cpp | 2 +- .../main/test_3wire_spi_rgb_lcd.cpp | 112 ++-- .../lcd/3wire_spi_rgb/sdkconfig.defaults | 5 - .../3wire_spi_rgb/sdkconfig.defaults.esp32s3 | 10 + test_apps/lcd/qspi/main/test_qspi_lcd.cpp | 8 +- test_apps/lcd/rgb/main/test_rgb_lcd.cpp | 43 +- test_apps/lcd/rgb/sdkconfig.defaults | 5 - test_apps/lcd/rgb/sdkconfig.defaults.esp32s3 | 16 + test_apps/lcd/spi/main/test_spi_lcd.cpp | 41 +- test_apps/lvgl_port/main/lvgl_port_v8.cpp | 14 +- .../sdkconfig.ci.elecrow_crowpanel_7_0 | 9 +- .../sdkconfig.ci.espressif_esp32_s3_box | 9 +- .../sdkconfig.ci.espressif_esp32_s3_box_3 | 9 +- ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 9 +- .../sdkconfig.ci.espressif_esp32_s3_box_lite | 9 +- .../sdkconfig.ci.espressif_esp32_s3_eye | 9 +- .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 9 +- ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 9 +- ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 9 +- ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 9 +- ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 9 +- ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 9 +- .../lvgl_port/sdkconfig.ci.m5stack_m5core3 | 9 +- ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 9 +- ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 11 +- ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 9 +- test_apps/panel/main/test_app_main.c | 2 +- test_apps/panel/main/test_panel.cpp | 8 +- .../panel/sdkconfig.ci.elecrow_crowpanel_7_0 | 9 +- .../panel/sdkconfig.ci.espressif_esp32_s3_box | 9 +- .../sdkconfig.ci.espressif_esp32_s3_box_3 | 9 +- ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 9 +- .../sdkconfig.ci.espressif_esp32_s3_box_lite | 9 +- .../panel/sdkconfig.ci.espressif_esp32_s3_eye | 9 +- .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 9 +- ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 9 +- ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 9 +- ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 9 +- ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 9 +- ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 9 +- test_apps/panel/sdkconfig.ci.m5stack_m5core3 | 9 +- test_apps/panel/sdkconfig.ci.m5stack_m5dial | 9 +- ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 9 +- ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 9 +- ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 9 +- test_apps/touch/i2c/main/test_i2c_touch.cpp | 11 +- test_apps/touch/spi/main/test_spi_touch.cpp | 3 + 118 files changed, 1630 insertions(+), 566 deletions(-) create mode 100644 src/bus/DSI.cpp create mode 100644 src/bus/DSI.h rename src/lcd/base/{esp_lcd_custom_types.h => esp_lcd_vendor_types.h} (59%) create mode 100644 test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults.esp32s3 create mode 100644 test_apps/lcd/rgb/sdkconfig.defaults.esp32s3 diff --git a/examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino b/examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino index b8b4a8cf..483c3dc0 100644 --- a/examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino +++ b/examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino @@ -43,6 +43,7 @@ #include #include +/* The following default configurations are for the board 'jingcai: ESP32_4848S040C_I_Y_3, ST7701' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -64,7 +65,8 @@ #define EXAMPLE_LCD_RGB_TIMING_VPW (10) #define EXAMPLE_LCD_RGB_TIMING_VBP (10) #define EXAMPLE_LCD_RGB_TIMING_VFP (10) -#define EXAMPLE_LCD_USE_EXTERNAL_CMD (0) +#define EXAMPLE_LCD_RGB_BOUNCE_BUFFER_SIZE (EXAMPLE_LCD_WIDTH * 10) +#define EXAMPLE_LCD_USE_EXTERNAL_CMD (1) #if EXAMPLE_LCD_USE_EXTERNAL_CMD /** * LCD initialization commands. @@ -86,10 +88,44 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, // {0x29, (uint8_t []){0x00}, 0, 120}, // // or - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), - // ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB0, {0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, + 0x0F, 0xAA, 0x31, 0x18}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, + 0x11, 0xA9, 0x32, 0x18}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB0, {0x60}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {0x32}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB2, {0x07}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB3, {0x80}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB5, {0x49}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB7, {0x85}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB8, {0x21}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x78}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x78}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE0, {0x00, 0x1B, 0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE1, {0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE2, {0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE3, {0x00, 0x00, 0x11, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE4, {0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE5, {0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, + 0x10, 0xEF, 0xD8, 0xA0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE6, {0x00, 0x00, 0x11, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE7, {0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE8, {0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, + 0x0F, 0xEE, 0xD8, 0xA0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xEB, {0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xEC, {0x3C, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xED, {0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, + 0x45, 0x67, 0x98, 0xBA}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x13}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE5, {0xE4}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x00}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), }; #endif @@ -169,43 +205,43 @@ void setup() #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 Serial.println("Initialize backlight and turn it off"); - ESP_PanelBacklight *backlight = new ESP_PanelBacklight(EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true); + ESP_PanelBacklight *backlight = new ESP_PanelBacklight( + EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true + ); backlight->begin(); backlight->off(); #endif Serial.println("Create 3-wire SPI + RGB LCD bus"); #if EXAMPLE_LCD_RGB_DATA_WIDTH == 8 - ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, - EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, - EXAMPLE_LCD_PIN_NUM_SPI_SDA, - EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, - EXAMPLE_LCD_PIN_NUM_RGB_DATA2, EXAMPLE_LCD_PIN_NUM_RGB_DATA3, - EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, - EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, - EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, - EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, - EXAMPLE_LCD_PIN_NUM_RGB_DISP); + ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB( + EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, + EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, EXAMPLE_LCD_PIN_NUM_SPI_SDA, + EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, EXAMPLE_LCD_PIN_NUM_RGB_DATA2, + EXAMPLE_LCD_PIN_NUM_RGB_DATA3, EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, + EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, + EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, + EXAMPLE_LCD_PIN_NUM_RGB_DISP + ); #elif EXAMPLE_LCD_RGB_DATA_WIDTH == 16 - ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, - EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, - EXAMPLE_LCD_PIN_NUM_SPI_SDA, - EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, - EXAMPLE_LCD_PIN_NUM_RGB_DATA2, EXAMPLE_LCD_PIN_NUM_RGB_DATA3, - EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, - EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, - EXAMPLE_LCD_PIN_NUM_RGB_DATA8, EXAMPLE_LCD_PIN_NUM_RGB_DATA9, - EXAMPLE_LCD_PIN_NUM_RGB_DATA10, EXAMPLE_LCD_PIN_NUM_RGB_DATA11, - EXAMPLE_LCD_PIN_NUM_RGB_DATA12, EXAMPLE_LCD_PIN_NUM_RGB_DATA13, - EXAMPLE_LCD_PIN_NUM_RGB_DATA14, EXAMPLE_LCD_PIN_NUM_RGB_DATA15, - EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, - EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, - EXAMPLE_LCD_PIN_NUM_RGB_DISP); + ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB( + EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, + EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, EXAMPLE_LCD_PIN_NUM_SPI_SDA, + EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, EXAMPLE_LCD_PIN_NUM_RGB_DATA2, + EXAMPLE_LCD_PIN_NUM_RGB_DATA3, EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, + EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, EXAMPLE_LCD_PIN_NUM_RGB_DATA8, + EXAMPLE_LCD_PIN_NUM_RGB_DATA9, EXAMPLE_LCD_PIN_NUM_RGB_DATA10, EXAMPLE_LCD_PIN_NUM_RGB_DATA11, + EXAMPLE_LCD_PIN_NUM_RGB_DATA12, EXAMPLE_LCD_PIN_NUM_RGB_DATA13, EXAMPLE_LCD_PIN_NUM_RGB_DATA14, + EXAMPLE_LCD_PIN_NUM_RGB_DATA15, EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, + EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, EXAMPLE_LCD_PIN_NUM_RGB_DISP + ); #endif lcd_bus->configRgbTimingFreqHz(EXAMPLE_LCD_RGB_TIMING_FREQ_HZ); - lcd_bus->configRgbTimingPorch(EXAMPLE_LCD_RGB_TIMING_HPW, EXAMPLE_LCD_RGB_TIMING_HBP, EXAMPLE_LCD_RGB_TIMING_HFP, - EXAMPLE_LCD_RGB_TIMING_VPW, EXAMPLE_LCD_RGB_TIMING_VBP, EXAMPLE_LCD_RGB_TIMING_VFP); - // lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); // Set bounce buffer to avoid screen drift + lcd_bus->configRgbTimingPorch( + EXAMPLE_LCD_RGB_TIMING_HPW, EXAMPLE_LCD_RGB_TIMING_HBP, EXAMPLE_LCD_RGB_TIMING_HFP, + EXAMPLE_LCD_RGB_TIMING_VPW, EXAMPLE_LCD_RGB_TIMING_VBP, EXAMPLE_LCD_RGB_TIMING_VFP + ); + lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_RGB_BOUNCE_BUFFER_SIZE); // Set bounce buffer to avoid screen drift lcd_bus->begin(); Serial.println("Create LCD device"); @@ -214,23 +250,23 @@ void setup() // Configure external initialization commands, should called before `init()` lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd)/sizeof(lcd_init_cmd[0])); #endif - // lcd->configAutoReleaseBus(true); // If the "3-wire SPI" interface are sharing pins of the "RGB" interface to - // save GPIOs, please enable this function to release the bus object and pins - // (except CS signal). And then, the "3-wire SPI" interface cannot be used to - // transmit commands any more. - // lcd->configMirrorByCommand(true); // This function is conflict with `configAutoReleaseBus(true)`, please don't - // enable them at the same time + // lcd->configEnableIO_Multiplex(true); // If the "3-wire SPI" interface are sharing pins of the "RGB" interface to + // save GPIOs, please enable this function to release the bus object and pins + // (except CS signal). And then, the "3-wire SPI" interface cannot be used to + // transmit commands any more. + // lcd->configMirrorByCommand(true); // This function is conflict with `configAutoReleaseBus(true)`, please don't + // enable them at the same time lcd->init(); - lcd->reset(); // If the `configAutoReleaseBus(true)` is called, here should not call `reset()` - // to deinit the LCD device + lcd->reset(); lcd->begin(); - lcd->displayOn(); // This function is conflict with `configAutoReleaseBus(true)`, please don't - // enable them at the same time + lcd->displayOn(); #if EXAMPLE_ENABLE_PRINT_LCD_FPS + /* Attach a callback function which will be called when the Vsync signal is detected */ lcd->attachRefreshFinishCallback(onVsyncEndCallback, nullptr); #endif Serial.println("Draw color bar from top left to bottom right, the order is B - G - R"); + /* Users can refer to the implementation within `colorBardTest()` to draw patterns on the LCD */ lcd->colorBarTest(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT); #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 diff --git a/examples/LCD/QSPI/QSPI.ino b/examples/LCD/QSPI/QSPI.ino index 64ddc310..bbf2ae88 100644 --- a/examples/LCD/QSPI/QSPI.ino +++ b/examples/LCD/QSPI/QSPI.ino @@ -56,11 +56,12 @@ #include #include +/* The following default configurations are for the board 'Espressif: Custom, ST77922' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** - * Currently, the library supports the following SPI LCDs: + * Currently, the library supports the following QSPI LCDs: * - GC9B71 * - SH8601 * - SPD2010 @@ -136,15 +137,18 @@ void setup() #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 Serial.println("Initialize backlight control pin and turn it off"); - ESP_PanelBacklight *backlight = new ESP_PanelBacklight(EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true); + ESP_PanelBacklight *backlight = new ESP_PanelBacklight( + EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true + ); backlight->begin(); backlight->off(); #endif Serial.println("Create QSPI LCD bus"); - ESP_PanelBus_QSPI *panel_bus = new ESP_PanelBus_QSPI(EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, - EXAMPLE_LCD_PIN_NUM_SPI_DATA0, EXAMPLE_LCD_PIN_NUM_SPI_DATA1, - EXAMPLE_LCD_PIN_NUM_SPI_DATA2, EXAMPLE_LCD_PIN_NUM_SPI_DATA3); + ESP_PanelBus_QSPI *panel_bus = new ESP_PanelBus_QSPI( + EXAMPLE_LCD_PIN_NUM_SPI_CS, EXAMPLE_LCD_PIN_NUM_SPI_SCK, EXAMPLE_LCD_PIN_NUM_SPI_DATA0, + EXAMPLE_LCD_PIN_NUM_SPI_DATA1, EXAMPLE_LCD_PIN_NUM_SPI_DATA2, EXAMPLE_LCD_PIN_NUM_SPI_DATA3 + ); panel_bus->configQspiFreqHz(EXAMPLE_LCD_SPI_FREQ_HZ); panel_bus->begin(); @@ -159,10 +163,12 @@ void setup() lcd->begin(); lcd->displayOn(); #if EXAMPLE_ENABLE_ATTACH_CALLBACK - lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, NULL); + /* Attach a callback function which will be called when every bitmap drawing is completed */ + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, NULL); #endif Serial.println("Draw color bar from top left to bottom right, the order is B - G - R"); + /* Users can refer to the implementation within `colorBardTest()` to draw patterns on the LCD */ lcd->colorBarTest(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT); #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 diff --git a/examples/LCD/RGB/RGB.ino b/examples/LCD/RGB/RGB.ino index c66fd7ed..d0ab2140 100644 --- a/examples/LCD/RGB/RGB.ino +++ b/examples/LCD/RGB/RGB.ino @@ -41,6 +41,7 @@ #include #include +/* The following default configurations are for the board 'Espressif: ESP32_S3_LCD_EV_BOARD_2_V1_5, ST7262' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -53,7 +54,7 @@ #define EXAMPLE_LCD_WIDTH (800) #define EXAMPLE_LCD_HEIGHT (480) // | 8-bit RGB888 | 16-bit RGB565 | -#define EXAMPLE_LCD_COLOR_BITS (18) // | 24 | 16/18/24 | +#define EXAMPLE_LCD_COLOR_BITS (16) // | 24 | 16/18/24 | #define EXAMPLE_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define EXAMPLE_LCD_RGB_TIMING_FREQ_HZ (16 * 1000 * 1000) #define EXAMPLE_LCD_RGB_TIMING_HPW (40) @@ -62,6 +63,7 @@ #define EXAMPLE_LCD_RGB_TIMING_VPW (23) #define EXAMPLE_LCD_RGB_TIMING_VBP (32) #define EXAMPLE_LCD_RGB_TIMING_VFP (13) +#define EXAMPLE_LCD_RGB_BOUNCE_BUFFER_SIZE (EXAMPLE_LCD_WIDTH * 10) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your board spec //////////////////////////// @@ -79,8 +81,8 @@ #define EXAMPLE_LCD_PIN_NUM_RGB_DATA3 (13) // | B3 | B4 | B6 | #define EXAMPLE_LCD_PIN_NUM_RGB_DATA4 (14) // | B4 | B5 | B7 | #define EXAMPLE_LCD_PIN_NUM_RGB_DATA5 (21) // | G0 | G0 | G0-2 | -#define EXAMPLE_LCD_PIN_NUM_RGB_DATA6 (47) // | G1 | G1 | G3 | -#define EXAMPLE_LCD_PIN_NUM_RGB_DATA7 (48) // | G2 | G2 | G4 | +#define EXAMPLE_LCD_PIN_NUM_RGB_DATA6 (8) // | G1 | G1 | G3 | +#define EXAMPLE_LCD_PIN_NUM_RGB_DATA7 (18) // | G2 | G2 | G4 | #if EXAMPLE_LCD_RGB_DATA_WIDTH > 8 #define EXAMPLE_LCD_PIN_NUM_RGB_DATA8 (45) // | G3 | G3 | G5 | #define EXAMPLE_LCD_PIN_NUM_RGB_DATA9 (38) // | G4 | G4 | G6 | @@ -136,39 +138,39 @@ void setup() #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 Serial.println("Initialize backlight control pin and turn it off"); - ESP_PanelBacklight *backlight = new ESP_PanelBacklight(EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true); + ESP_PanelBacklight *backlight = new ESP_PanelBacklight( + EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true + ); backlight->begin(); backlight->off(); #endif Serial.println("Create RGB LCD bus"); + ESP_PanelBus_RGB *panel_bus = new ESP_PanelBus_RGB( #if EXAMPLE_LCD_RGB_DATA_WIDTH == 8 - ESP_PanelBus_RGB *panel_bus = new ESP_PanelBus_RGB(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, - EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, - EXAMPLE_LCD_PIN_NUM_RGB_DATA2, EXAMPLE_LCD_PIN_NUM_RGB_DATA3, - EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, - EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, - EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, - EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, - EXAMPLE_LCD_PIN_NUM_RGB_DISP); + EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, + EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, EXAMPLE_LCD_PIN_NUM_RGB_DATA2, + EXAMPLE_LCD_PIN_NUM_RGB_DATA3, EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, + EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, + EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, + EXAMPLE_LCD_PIN_NUM_RGB_DISP #elif EXAMPLE_LCD_RGB_DATA_WIDTH == 16 - ESP_PanelBus_RGB *panel_bus = new ESP_PanelBus_RGB(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, - EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, - EXAMPLE_LCD_PIN_NUM_RGB_DATA2, EXAMPLE_LCD_PIN_NUM_RGB_DATA3, - EXAMPLE_LCD_PIN_NUM_RGB_DATA4, EXAMPLE_LCD_PIN_NUM_RGB_DATA5, - EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, - EXAMPLE_LCD_PIN_NUM_RGB_DATA8, EXAMPLE_LCD_PIN_NUM_RGB_DATA9, - EXAMPLE_LCD_PIN_NUM_RGB_DATA10, EXAMPLE_LCD_PIN_NUM_RGB_DATA11, - EXAMPLE_LCD_PIN_NUM_RGB_DATA12, EXAMPLE_LCD_PIN_NUM_RGB_DATA13, - EXAMPLE_LCD_PIN_NUM_RGB_DATA14, EXAMPLE_LCD_PIN_NUM_RGB_DATA15, - EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, - EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, - EXAMPLE_LCD_PIN_NUM_RGB_DISP); + EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, EXAMPLE_LCD_PIN_NUM_RGB_DATA0, EXAMPLE_LCD_PIN_NUM_RGB_DATA1, + EXAMPLE_LCD_PIN_NUM_RGB_DATA2, EXAMPLE_LCD_PIN_NUM_RGB_DATA3, EXAMPLE_LCD_PIN_NUM_RGB_DATA4, + EXAMPLE_LCD_PIN_NUM_RGB_DATA5, EXAMPLE_LCD_PIN_NUM_RGB_DATA6, EXAMPLE_LCD_PIN_NUM_RGB_DATA7, + EXAMPLE_LCD_PIN_NUM_RGB_DATA8, EXAMPLE_LCD_PIN_NUM_RGB_DATA9, EXAMPLE_LCD_PIN_NUM_RGB_DATA10, + EXAMPLE_LCD_PIN_NUM_RGB_DATA11, EXAMPLE_LCD_PIN_NUM_RGB_DATA12, EXAMPLE_LCD_PIN_NUM_RGB_DATA13, + EXAMPLE_LCD_PIN_NUM_RGB_DATA14, EXAMPLE_LCD_PIN_NUM_RGB_DATA15, EXAMPLE_LCD_PIN_NUM_RGB_HSYNC, + EXAMPLE_LCD_PIN_NUM_RGB_VSYNC, EXAMPLE_LCD_PIN_NUM_RGB_PCLK, EXAMPLE_LCD_PIN_NUM_RGB_DE, + EXAMPLE_LCD_PIN_NUM_RGB_DISP #endif + ); panel_bus->configRgbTimingFreqHz(EXAMPLE_LCD_RGB_TIMING_FREQ_HZ); - panel_bus->configRgbTimingPorch(EXAMPLE_LCD_RGB_TIMING_HPW, EXAMPLE_LCD_RGB_TIMING_HBP, EXAMPLE_LCD_RGB_TIMING_HFP, - EXAMPLE_LCD_RGB_TIMING_VPW, EXAMPLE_LCD_RGB_TIMING_VBP, EXAMPLE_LCD_RGB_TIMING_VFP); - // panel_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); // Set bounce buffer to avoid screen drift + panel_bus->configRgbTimingPorch( + EXAMPLE_LCD_RGB_TIMING_HPW, EXAMPLE_LCD_RGB_TIMING_HBP, EXAMPLE_LCD_RGB_TIMING_HFP, + EXAMPLE_LCD_RGB_TIMING_VPW, EXAMPLE_LCD_RGB_TIMING_VBP, EXAMPLE_LCD_RGB_TIMING_VFP + ); + panel_bus->configRgbBounceBufferSize(EXAMPLE_LCD_RGB_BOUNCE_BUFFER_SIZE); // Set bounce buffer to avoid screen drift panel_bus->begin(); Serial.println("Create LCD device"); @@ -176,14 +178,14 @@ void setup() lcd->init(); lcd->reset(); lcd->begin(); -#if EXAMPLE_LCD_PIN_NUM_RGB_DISP >= 0 lcd->displayOn(); -#endif #if EXAMPLE_ENABLE_PRINT_LCD_FPS + /* Attach a callback function which will be called when the Vsync signal is detected */ lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time); #endif Serial.println("Draw color bar from top left to bottom right, the order is B - G - R"); + /* Users can refer to the implementation within `colorBardTest()` to draw patterns on the LCD */ lcd->colorBarTest(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT); #if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 diff --git a/examples/LCD/SPI/SPI.ino b/examples/LCD/SPI/SPI.ino index 64d7c9d9..129a7760 100644 --- a/examples/LCD/SPI/SPI.ino +++ b/examples/LCD/SPI/SPI.ino @@ -73,7 +73,7 @@ #define EXAMPLE_LCD_HEIGHT (240) #define EXAMPLE_LCD_COLOR_BITS (16) #define EXAMPLE_LCD_SPI_FREQ_HZ (40 * 1000 * 1000) -#define EXAMPLE_LCD_USE_EXTERNAL_CMD (0) +#define EXAMPLE_LCD_USE_EXTERNAL_CMD (1) #if EXAMPLE_LCD_USE_EXTERNAL_CMD /** * LCD initialization commands. @@ -95,10 +95,22 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, // {0x29, (uint8_t []){0x00}, 0, 120}, // // or - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), - // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), - // ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC8, {0xFF, 0x93, 0x42}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x0E, 0x0E}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC5, {0xD0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB4, {0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE0, { + 0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, + 0x17, 0x17, 0x0F + }), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE1, { + 0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, + 0x37, 0x38, 0x0F + }), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {00, 0x1B}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB7, {0x06}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(100, 0x11), }; #endif @@ -110,8 +122,8 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { #define EXAMPLE_LCD_PIN_NUM_SPI_SCK (7) #define EXAMPLE_LCD_PIN_NUM_SPI_SDA (6) #define EXAMPLE_LCD_PIN_NUM_SPI_SDO (-1) -#define EXAMPLE_LCD_PIN_NUM_RST (-1) // Set to -1 if not used -#define EXAMPLE_LCD_PIN_NUM_BK_LIGHT (45) // Set to -1 if not used +#define EXAMPLE_LCD_PIN_NUM_RST (48) // Set to -1 if not used +#define EXAMPLE_LCD_PIN_NUM_BK_LIGHT (47) // Set to -1 if not used #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL (1) #define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL @@ -155,11 +167,16 @@ void setup() // Configure external initialization commands, should called before `init()` lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd)/sizeof(lcd_init_cmd[0])); #endif + // lcd->configColorRgbOrder(true); + // lcd->configResetActiveLevel(1); lcd->init(); lcd->reset(); lcd->begin(); + // lcd->mirrorX(true); + // lcd->mirrorY(true); lcd->displayOn(); #if EXAMPLE_ENABLE_ATTACH_CALLBACK + /* Attach a callback function which will be called when every bitmap drawing is completed */ lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, NULL); #endif diff --git a/examples/LVGL/v8/Porting/Porting.ino b/examples/LVGL/v8/Porting/Porting.ino index e7728911..0bc750e9 100644 --- a/examples/LVGL/v8/Porting/Porting.ino +++ b/examples/LVGL/v8/Porting/Porting.ino @@ -85,7 +85,10 @@ void setup() /* Lock the mutex due to the LVGL APIs are not thread-safe */ lvgl_port_lock(-1); - /* Create a simple label */ + /** + * Create a simple label + * + */ lv_obj_t *label = lv_label_create(lv_scr_act()); lv_label_set_text(label, title.c_str()); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); diff --git a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp index cfd9f29d..8a2ca67c 100644 --- a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp @@ -21,8 +21,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -338,7 +338,7 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) @@ -460,20 +460,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif @@ -582,7 +582,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -616,7 +616,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -647,7 +647,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); #endif return true; diff --git a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp index cfd9f29d..8a2ca67c 100644 --- a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp @@ -21,8 +21,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -338,7 +338,7 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) @@ -460,20 +460,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif @@ -582,7 +582,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -616,7 +616,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -647,7 +647,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); #endif return true; diff --git a/examples/PlatformIO/src/lvgl_port_v8.cpp b/examples/PlatformIO/src/lvgl_port_v8.cpp index cfd9f29d..8a2ca67c 100644 --- a/examples/PlatformIO/src/lvgl_port_v8.cpp +++ b/examples/PlatformIO/src/lvgl_port_v8.cpp @@ -21,8 +21,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -338,7 +338,7 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) @@ -460,20 +460,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif @@ -582,7 +582,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -616,7 +616,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -647,7 +647,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); #endif return true; diff --git a/examples/PlatformIO/src/lvgl_port_v8.h b/examples/PlatformIO/src/lvgl_port_v8.h index b0e25ee1..c7b7cdf2 100644 --- a/examples/PlatformIO/src/lvgl_port_v8.h +++ b/examples/PlatformIO/src/lvgl_port_v8.h @@ -5,6 +5,7 @@ */ #pragma once +#include #include #include @@ -36,7 +37,7 @@ * - The number of buffers should be 1 or 2. * */ -#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_INTERNAL) // Allocate LVGL buffer in SRAM +#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 (LVGL_PORT_DISP_WIDTH * 20) #define LVGL_PORT_BUFFER_NUM (2) @@ -49,7 +50,9 @@ #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 -#define LVGL_PORT_TASK_CORE (-1) // The core of the LVGL timer task, `-1` means the don't specify the core +#define LVGL_PORT_TASK_CORE (ARDUINO_RUNNING_CORE) + // The core of the LVGL timer task, `-1` means the don't specify the core + // Default is the same as the Arduino task // This can be set to `1` only if the SoCs support dual-core, // otherwise it should be set to `-1` or `0` diff --git a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp index cfd9f29d..8a2ca67c 100644 --- a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp @@ -21,8 +21,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -338,7 +338,7 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) @@ -460,20 +460,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif @@ -582,7 +582,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -616,7 +616,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -647,7 +647,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); #endif return true; diff --git a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp index cfd9f29d..8a2ca67c 100644 --- a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp @@ -21,8 +21,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -338,7 +338,7 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) @@ -460,20 +460,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif @@ -582,7 +582,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -616,7 +616,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -647,7 +647,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); #endif return true; diff --git a/src/ESP_Panel.cpp b/src/ESP_Panel.cpp index b4eaaf35..46403472 100644 --- a/src/ESP_Panel.cpp +++ b/src/ESP_Panel.cpp @@ -498,17 +498,18 @@ bool ESP_Panel::begin(void) ESP_LOGD(TAG, "Begin LCD"); #if ESP_PANEL_USE_EXPANDER && ((ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER) || (ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER) || \ (ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER)) - shared_ptr lcd_bus_ptr = static_pointer_cast(_lcd_bus_ptr); - lcd_bus_ptr->configSpiLine(ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER, ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER, - ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER, _expander_ptr.get()); + if (_lcd_bus_ptr->getType() == ESP_PANEL_BUS_TYPE_RGB) { + shared_ptr lcd_bus_ptr = static_pointer_cast(_lcd_bus_ptr); + lcd_bus_ptr->configSpiLine( + ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER, ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER, + ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER, _expander_ptr.get() + ); + } #endif ESP_PANEL_CHECK_FALSE_RET(_lcd_bus_ptr->begin(), false, "Begin LCD bus failed"); ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->init(), false, "Initialize LCD failed"); - // Operate LCD device according to the optional configurations -#if (ESP_PANEL_LCD_BUS_TYPE != ESP_PANEL_BUS_TYPE_RGB) || !ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO - // We can't reset the LCD if the bus is RGB bus and the `ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO` is enabled ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->reset(), false, "Reset LCD failed"); -#endif + // Operate LCD device according to the optional configurations #ifdef ESP_PANEL_LCD_SWAP_XY ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->swapXY(ESP_PANEL_LCD_SWAP_XY), false, "Swap XY failed"); #endif @@ -522,18 +523,7 @@ bool ESP_Panel::begin(void) ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->invertColor(ESP_PANEL_LCD_INEVRT_COLOR), false, "Invert color failed"); #endif ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->begin(), false, "Begin LCD failed"); - /** - * Turn on display only when meets one of the following conditions: - * - The LCD bus is not RGB bus - * - The LCD bus is "3wire-SPI + RGB" bus and the `ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO` is disabled - * - The LCD bus is RGB (with or without 3-wire SPI) bus and the `ESP_PANEL_LCD_RGB_IO_DISP` pin is used - * - */ -#if (ESP_PANEL_LCD_BUS_TYPE != ESP_PANEL_BUS_TYPE_RGB) || \ - (defined(ESP_PANEL_LCD_RGB_IO_DISP) && (ESP_PANEL_LCD_RGB_IO_DISP != -1)) || \ - (defined(ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO) && !ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO) ESP_PANEL_CHECK_FALSE_RET(_lcd_ptr->displayOn(), false, "Display on failed"); -#endif // Run additional code after the LCD is started if needed #ifdef ESP_PANEL_BEGIN_LCD_END_FUNCTION ESP_PANEL_BEGIN_LCD_END_FUNCTION(this); diff --git a/src/ESP_PanelLog.h b/src/ESP_PanelLog.h index 9d1387d1..7498cc34 100644 --- a/src/ESP_PanelLog.h +++ b/src/ESP_PanelLog.h @@ -30,15 +30,15 @@ #define ESP_PANEL_CHECK_NULL_RET(x, ...) assert((x) != NULL) #define ESP_PANEL_CHECK_FALSE_RET(x, ...) assert((x) != false) #else -#if ESP_PANEL_ENABLE_LOG +// #if ESP_PANEL_ENABLE_LOG #define ESP_PANEL_ERROR_CHECK_LOG_FORMAT(err, format) "[%s] %s(%u): " format, esp_err_to_name(err), __FUNCTION__, __LINE__ #define ESP_PANEL_ERROR_CHECK_LOGE(tag, err, format, ...) ESP_LOGE(tag, ESP_PANEL_ERROR_CHECK_LOG_FORMAT(err, format), ##__VA_ARGS__) #define ESP_PANEL_OTHER_CHECK_LOG_FORMAT(format) "%s(%u): " format, __FUNCTION__, __LINE__ #define ESP_PANEL_OTHER_CHECK_LOGE(tag, format, ...) ESP_LOGE(tag, ESP_PANEL_OTHER_CHECK_LOG_FORMAT(format), ##__VA_ARGS__) -#else -#define ESP_PANEL_ERROR_CHECK_LOGE(tag, err, format, ...) do {} while(0) -#define ESP_PANEL_OTHER_CHECK_LOGE(tag, format, ...) do {} while(0) -#endif +// #else +// #define ESP_PANEL_ERROR_CHECK_LOGE(tag, err, format, ...) do {} while(0) +// #define ESP_PANEL_OTHER_CHECK_LOGE(tag, format, ...) do {} while(0) +// #endif #define ESP_PANEL_CHECK_ERR_RET(x, ret, fmt, ...) do { \ esp_err_t err_rc_ = (x); \ diff --git a/src/ESP_PanelTypes.h b/src/ESP_PanelTypes.h index c6532daf..9d24f070 100644 --- a/src/ESP_PanelTypes.h +++ b/src/ESP_PanelTypes.h @@ -10,7 +10,7 @@ #include "sdkconfig.h" /** - * @brief Panel bus type macros + * @brief Macros for bus type * */ #define ESP_PANEL_BUS_TYPE_UNKNOWN (0) @@ -19,7 +19,16 @@ #define ESP_PANEL_BUS_TYPE_RGB (3) #define ESP_PANEL_BUS_TYPE_I2C (4) #define ESP_PANEL_BUS_TYPE_I80 (5) -#define ESP_PANEL_BUS_TYPE_MAX (6) +#define ESP_PANEL_BUS_TYPE_MIPI_DSI (6) +#define ESP_PANEL_BUS_TYPE_MAX (7) + +/** + * @brief Macros for LCD color format bits + * + */ +#define ESP_PANEL_LCD_RGB565_COLOR_BITS_16 (16) +#define ESP_PANEL_LCD_RGB666_COLOR_BITS_18 (18) +#define ESP_PANEL_LCD_RGB888_COLOR_BITS_24 (24) /** * @brief This macro is used to generate the I2C panel IO configuration according to the touch panel name. diff --git a/src/ESP_Panel_Library.h b/src/ESP_Panel_Library.h index 23d72bd0..8180ba73 100644 --- a/src/ESP_Panel_Library.h +++ b/src/ESP_Panel_Library.h @@ -21,6 +21,7 @@ #include "bus/SPI.h" #include "bus/RGB.h" #include "bus/QSPI.h" +#include "bus/DSI.h" /* LCD */ #include "lcd/ESP_PanelLcd.h" diff --git a/src/bus/DSI.cpp b/src/bus/DSI.cpp new file mode 100644 index 00000000..5c1ba5cb --- /dev/null +++ b/src/bus/DSI.cpp @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include +#include +#include "ESP_PanelLog.h" +#include "DSI.h" + +#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +static const char *TAG = "ESP_PanelBus_DSI"; + +ESP_PanelBus_DSI::ESP_PanelBus_DSI( + uint8_t dsi_lane_num, uint32_t dsi_lane_rate_mbps, + uint32_t dpi_clk_mhz, uint32_t dpi_bits_per_pixel, uint16_t dpi_w, uint16_t dpi_h, + uint16_t dpi_hpw, uint16_t dpi_hbp, uint16_t dpi_hfp, uint16_t dpi_vpw, uint16_t dpi_vbp, uint16_t dpi_vfp, + int phy_ldo_id +): + ESP_PanelBus(ESP_PANEL_HOST_DSI_ID_DEFAULT, ESP_PANEL_BUS_TYPE_MIPI_DSI, true), + _phy_ldo_id(phy_ldo_id), + _dsi_config((esp_lcd_dsi_bus_config_t)ESP_PANEL_HOST_DSI_CONFIG_DEFAULT(dsi_lane_num, dsi_lane_rate_mbps)), + _dbi_config((esp_lcd_dbi_io_config_t)ESP_PANEL_IO_DBI_CONFIG_DEFAULT()), + _dpi_config( + (esp_lcd_dpi_panel_config_t)ESP_PANEL_DPI_CONFIG_DEFAULT( + dpi_clk_mhz, dpi_bits_per_pixel, dpi_w, dpi_h, dpi_hpw, dpi_hbp, dpi_hfp, dpi_vpw, dpi_vbp, dpi_vfp + ) + ), + _phy_ldo_handle(NULL), + _dsi_handle(NULL) +{ +} + +ESP_PanelBus_DSI::ESP_PanelBus_DSI( + uint8_t dsi_lane_num, uint32_t dsi_lane_rate_mbps, const esp_lcd_dpi_panel_config_t &dpi_config, int phy_ldo_id +): + ESP_PanelBus(ESP_PANEL_HOST_DSI_ID_DEFAULT, ESP_PANEL_BUS_TYPE_MIPI_DSI, true), + _phy_ldo_id(phy_ldo_id), + _dsi_config((esp_lcd_dsi_bus_config_t)ESP_PANEL_HOST_DSI_CONFIG_DEFAULT(dsi_lane_num, dsi_lane_rate_mbps)), + _dbi_config((esp_lcd_dbi_io_config_t)ESP_PANEL_IO_DBI_CONFIG_DEFAULT()), + _dpi_config(dpi_config), + _phy_ldo_handle(NULL), + _dsi_handle(NULL) +{ +} + +ESP_PanelBus_DSI::ESP_PanelBus_DSI( + const esp_lcd_dsi_bus_config_t &dsi_config, const esp_lcd_dpi_panel_config_t &dpi_config, int phy_ldo_id +): + ESP_PanelBus(ESP_PANEL_HOST_DSI_ID_DEFAULT, ESP_PANEL_BUS_TYPE_MIPI_DSI, true), + _phy_ldo_id(phy_ldo_id), + _dsi_config(dsi_config), + _dbi_config((esp_lcd_dbi_io_config_t)ESP_PANEL_IO_DBI_CONFIG_DEFAULT()), + _dpi_config(dpi_config), + _phy_ldo_handle(NULL), + _dsi_handle(NULL) +{ +} + +ESP_PanelBus_DSI::~ESP_PanelBus_DSI() +{ + if (handle == NULL) { + goto end; + } + + if (!del()) { + ESP_LOGE(TAG, "Delete panel io failed"); + } + +end: + ESP_LOGD(TAG, "Destroyed"); +} + +bool ESP_PanelBus_DSI::del(void) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + ESP_PANEL_CHECK_FALSE_RET(ESP_PanelBus::del(), false, "Delete base panel failed"); + if (_dsi_handle != NULL) { + if (esp_lcd_del_dsi_bus(_dsi_handle) != ESP_OK) { + ESP_LOGE(TAG, "Delete _dsi_handle[%d] driver failed", host_id); + } else { + ESP_LOGD(TAG, "Delete _dsi_handle[%d] driver", host_id); + } + _dsi_handle = NULL; + } + if (_phy_ldo_handle != NULL) { + if (esp_ldo_release_channel(_phy_ldo_handle) != ESP_OK) { + ESP_LOGE(TAG, "Release LDO channel[%d] failed", _phy_ldo_id); + } else { + ESP_LOGD(TAG, "MIPI DSI PHY (LDO %d) Powered off", _phy_ldo_id); + } + _phy_ldo_handle = NULL; + } + + return true; +} + +bool ESP_PanelBus_DSI::begin(void) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + if (_phy_ldo_id >= 0) { + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + esp_ldo_channel_config_t ldo_config = { + .chan_id = _phy_ldo_id, + .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + ESP_PANEL_CHECK_ERR_RET(esp_ldo_acquire_channel(&ldo_config, &_phy_ldo_handle), false, "Acquire LDO channel failed"); + ESP_LOGD(TAG, "MIPI DSI PHY (LDO %d) Powered on", _phy_ldo_id); + } + if (flags.host_need_init) { + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_dsi_bus(&_dsi_config, &_dsi_handle), false, "Initialize Host[%d] failed", host_id); + ESP_LOGD(TAG, "Init MIPI DSI _dsi_handle[%d]", host_id); + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_io_dbi(_dsi_handle, &_dbi_config, &handle), false, "Create panel io failed"); + ESP_LOGD(TAG, "Create panel io @%p", handle); + + return true; +} +#endif /* SOC_MIPI_DSI_SUPPORTED */ diff --git a/src/bus/DSI.h b/src/bus/DSI.h new file mode 100644 index 00000000..ed504dee --- /dev/null +++ b/src/bus/DSI.h @@ -0,0 +1,169 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_ldo_regulator.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_io.h" +#include "ESP_PanelBus.h" + +/** + * @brief Macro for MIPI DSI bus configuration + * + */ +#define ESP_PANEL_HOST_DSI_ID_DEFAULT (0) +#define ESP_PANEL_HOST_DSI_CONFIG_DEFAULT(lane_num, lane_rate_mbps) \ + { \ + .bus_id = 0, \ + .num_data_lanes = lane_num, \ + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \ + .lane_bit_rate_mbps = lane_rate_mbps, \ + } + +/** + * @brief Macro for MIPI DBI panel IO configuration + * + */ +#define ESP_PANEL_IO_DBI_CONFIG_DEFAULT() \ + { \ + .virtual_channel = 0, \ + .lcd_cmd_bits = 8, \ + .lcd_param_bits = 8, \ + } + +/** + * @brief Macro for MIPI DPI panel configuration + * + */ +#define ESP_PANEL_DPI_CONFIG_DEFAULT(clk_mhz, bits_per_pixel, w, h, hpw, hbp, hfp, vpw, vbp, vfp) \ + { \ + .virtual_channel = 0, \ + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \ + .dpi_clock_freq_mhz = clk_mhz, \ + .pixel_format = (bits_per_pixel == 16) ? LCD_COLOR_PIXEL_FORMAT_RGB565 : \ + ((bits_per_pixel == 18) ? LCD_COLOR_PIXEL_FORMAT_RGB666 : LCD_COLOR_PIXEL_FORMAT_RGB888), \ + .num_fbs = 1, \ + .video_timing = { \ + .h_size = w, \ + .v_size = h, \ + .hsync_pulse_width = hpw, \ + .hsync_back_porch = hbp, \ + .hsync_front_porch = hfp, \ + .vsync_pulse_width = vpw, \ + .vsync_back_porch = vbp, \ + .vsync_front_porch = vfp, \ + }, \ + .flags = { \ + .use_dma2d = true, \ + }, \ + } + +/** + * @brief MIPI-DSI bus object class + * + * @note This class is a derived class of `ESP_PanelBus`, user can use it directly + */ +class ESP_PanelBus_DSI: public ESP_PanelBus { +public: + /** + * @brief Construct a MIPI-DSI bus object in a common way, the host_handle will be initialized by the driver + * + * @note This function uses some default values (ESP_PANEL_HOST_DSI_CONFIG_DEFAULT && ESP_PANEL_IO_DBI_CONFIG_DEFAULT) + * to config the bus object, use `config*()` functions to change them + * @note The `init()` function should be called after this function + * + * @param ldo_chan_id MIPI-DSI CS pin + */ + ESP_PanelBus_DSI( + uint8_t dsi_lane_num, uint32_t dsi_lane_rate_mbps, + uint32_t dpi_clk_mhz, uint32_t dpi_bits_per_pixel, uint16_t dpi_w, uint16_t dpi_h, + uint16_t dpi_hpw, uint16_t dpi_hbp, uint16_t dpi_hfp, uint16_t dpi_vpw, uint16_t dpi_vbp, uint16_t dpi_vfp, + int phy_ldo_id = -1 + ); + + /** + * @brief Construct a MIPI-DSI bus object in a common way, the host_handle will be initialized by the driver + * + * @note This function uses some default values (ESP_PANEL_HOST_DSI_CONFIG_DEFAULT) to config the bus object, + * use `config*()` functions to change them + * @note The `init()` function should be called after this function + * + * @param sck_io MIPI-DSI SCK pin + * @param mosi_io MIPI-DSI MOSI pin + * @param miso_io MIPI-DSI MISO pin + * @param io_config MIPI-DSI panel IO configuration + */ + ESP_PanelBus_DSI( + uint8_t dsi_lane_num, uint32_t dsi_lane_rate_mbps, const esp_lcd_dpi_panel_config_t &dpi_config, + int phy_ldo_id = -1 + ); + + /** + * @brief Construct a MIPI-DSI bus object in a complex way, the host_handle will be initialized by the driver + * + * @note The `init()` function should be called after this function + * + * @param host_config MIPI-DSI host_handle configuration + * @param io_config MIPI-DSI panel IO configuration + * @param host_id MIPI-DSI host_handle ID, default is `ESP_PANEL_HOST_DSI_ID_DEFAULT` + */ + ESP_PanelBus_DSI( + const esp_lcd_dsi_bus_config_t &dsi_config, const esp_lcd_dpi_panel_config_t &dpi_config, int phy_ldo_id = -1 + ); + + /** + * @brief Destroy the MIPI-DSI bus object + * + */ + ~ESP_PanelBus_DSI() override; + + /** + * @brief Delete the bus object, release the resources + * + * @return true if success, otherwise false + */ + bool del(void) override; + + /** + * @brief Here are some functions to configure the MIPI-DSI bus object. These functions should be called before `begin()` + * + */ + // void configSpiMode(uint8_t mode); + + /** + * @brief Startup the bus + * + * @return true if success, otherwise false + */ + bool begin(void) override; + + esp_lcd_dsi_bus_handle_t getBusHandle(void) + { + return _dsi_handle; + } + + const esp_lcd_dsi_bus_config_t *getDsiConfig(void) + { + return &_dsi_config; + } + + const esp_lcd_dpi_panel_config_t *getDpiConfig(void) + { + return &_dpi_config; + } + +private: + int _phy_ldo_id; + esp_lcd_dsi_bus_config_t _dsi_config; + esp_lcd_dbi_io_config_t _dbi_config; + esp_lcd_dpi_panel_config_t _dpi_config; + esp_ldo_channel_handle_t _phy_ldo_handle; + esp_lcd_dsi_bus_handle_t _dsi_handle; +}; +#endif /* SOC_MIPI_DSI_SUPPORTED */ diff --git a/src/bus/ESP_PanelBus.cpp b/src/bus/ESP_PanelBus.cpp index 3f818f7d..901eb715 100644 --- a/src/bus/ESP_PanelBus.cpp +++ b/src/bus/ESP_PanelBus.cpp @@ -9,11 +9,17 @@ #include "esp_lcd_panel_io.h" #include "ESP_PanelBus.h" +#define FLAGS_DEFAULT(host_init) \ + { \ + .host_need_init = host_init, \ + .del_skip_panel_io = 0, \ + } + static const char *TAG = "ESP_PanelBus"; ESP_PanelBus::ESP_PanelBus(int host_id, uint8_t bus_type, bool host_need_init): + flags(FLAGS_DEFAULT(host_need_init)), host_id(host_id), - host_need_init(host_need_init), bus_type(bus_type), handle(NULL) { @@ -47,35 +53,21 @@ bool ESP_PanelBus::del(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - // RGB bus which needs to initialize the host inside can be deleted - if ((bus_type == ESP_PANEL_BUS_TYPE_RGB) && !host_need_init) { - ESP_LOGD(TAG, "Use RGB bus without host init, skip delete panel IO"); - return true; + // RGB bus which needs to initialize the host inside and not skip panel IO can be deleted + if ((bus_type == ESP_PANEL_BUS_TYPE_RGB) && (!flags.host_need_init || flags.del_skip_panel_io)) { + ESP_LOGD(TAG, "Use RGB bus without host init or enable skip panel IO, skip delete panel IO"); + goto end; } ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_io_del(handle), false, "Delete panel IO failed"); - ESP_LOGD(TAG, "Delete panel IO @%p", handle); + +end: handle = NULL; return true; } -void ESP_PanelBus::configHostId(int id) -{ - host_id = id; -} - -esp_lcd_panel_io_handle_t ESP_PanelBus::getHandle(void) -{ - if (handle == NULL) { - ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - ESP_LOGD(TAG, "Get invalid handle"); - } - - return handle; -} - uint8_t ESP_PanelBus::getType(void) { if ((bus_type == ESP_PANEL_BUS_TYPE_UNKNOWN) || (bus_type >= ESP_PANEL_BUS_TYPE_MAX)) { diff --git a/src/bus/ESP_PanelBus.h b/src/bus/ESP_PanelBus.h index 169ec47d..dacaaf6a 100644 --- a/src/bus/ESP_PanelBus.h +++ b/src/bus/ESP_PanelBus.h @@ -25,7 +25,7 @@ class ESP_PanelBus { * - ESP_PANEL_BUS_TYPE_SPI: SPI bus * - ESP_PANEL_BUS_TYPE_RGB: RGB bus * - ESP_PANEL_BUS_TYPE_QSPI: QSPI bus - * @param host_need_init Whether the host should be initialized inside. + * @param flags.host_need_init Whether the host should be initialized inside. * - true: the host needs to be initialized inside * - false: the host should been initialized by users */ @@ -41,7 +41,10 @@ class ESP_PanelBus { * @brief Here are some functions to configure the bus object. These functions should be called before `begin()` * */ - void configHostId(int id); + void configHostId(int id) + { + host_id = id; + } /** * @brief Startup the bus @@ -57,6 +60,12 @@ class ESP_PanelBus { */ virtual bool del(void); + bool delSkipPanelIO(void) + { + flags.del_skip_panel_io = 1; + return del(); + } + /** * @brief Read the register data * @@ -104,17 +113,36 @@ class ESP_PanelBus { uint8_t getType(void); /** - * @brief Get the IO handle of bus + * @brief Get the panel IO handle + * + * @return + * - NULL: if fail + * - Others: the handle of bus + */ + [[deprecated("This API is deprecated. Please use `getPanelIO_Handle()` instead.")]] + esp_lcd_panel_io_handle_t getHandle(void) + { + return getPanelIO_Handle(); + } + + /** + * @brief Get the panel IO handle * * @return * - NULL: if fail * - Others: the handle of bus */ - esp_lcd_panel_io_handle_t getHandle(void); + esp_lcd_panel_io_handle_t getPanelIO_Handle(void) + { + return handle; + } protected: + struct { + uint8_t host_need_init: 1; + uint8_t del_skip_panel_io: 1; + } flags; int host_id; - bool host_need_init; uint8_t bus_type; esp_lcd_panel_io_handle_t handle; }; diff --git a/src/bus/I2C.cpp b/src/bus/I2C.cpp index 61a44bb3..d1626aa0 100644 --- a/src/bus/I2C.cpp +++ b/src/bus/I2C.cpp @@ -17,8 +17,9 @@ ESP_PanelBus_I2C::ESP_PanelBus_I2C(int scl_io, int sda_io, const esp_lcd_panel_i { } -ESP_PanelBus_I2C::ESP_PanelBus_I2C(const i2c_config_t &host_config, const esp_lcd_panel_io_i2c_config_t &io_config, - i2c_port_t host_id): +ESP_PanelBus_I2C::ESP_PanelBus_I2C( + const i2c_config_t &host_config, const esp_lcd_panel_io_i2c_config_t &io_config, i2c_port_t host_id +): ESP_PanelBus((int)host_id, ESP_PANEL_BUS_TYPE_I2C, true), host_config(host_config), io_config(io_config) @@ -43,7 +44,7 @@ ESP_PanelBus_I2C::~ESP_PanelBus_I2C() ESP_LOGE(TAG, "Delete panel io failed"); } - if (host_need_init) { + if (flags.host_need_init) { if (i2c_driver_delete((i2c_port_t)host_id) != ESP_OK) { ESP_LOGE(TAG, "Delete host[%d] driver failed", host_id); } else { @@ -106,7 +107,7 @@ bool ESP_PanelBus_I2C::begin(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - if (host_need_init) { + if (flags.host_need_init) { ESP_PANEL_CHECK_ERR_RET(i2c_param_config((i2c_port_t)host_id, &host_config), false, "Configure host[%d] failed", host_id); ESP_PANEL_CHECK_ERR_RET(i2c_driver_install((i2c_port_t)host_id, host_config.mode, 0, 0, 0), false, "Install host[%d] failed", host_id); diff --git a/src/bus/QSPI.cpp b/src/bus/QSPI.cpp index fbdf139d..556584d4 100644 --- a/src/bus/QSPI.cpp +++ b/src/bus/QSPI.cpp @@ -51,7 +51,7 @@ ESP_PanelBus_QSPI::~ESP_PanelBus_QSPI() ESP_LOGE(TAG, "Delete panel io failed"); } - if (host_need_init) { + if (flags.host_need_init) { if (spi_bus_free((spi_host_device_t)host_id) != ESP_OK) { ESP_LOGE(TAG, "Delete host[%d] driver failed", host_id); } else { @@ -82,7 +82,7 @@ bool ESP_PanelBus_QSPI::begin(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - if (host_need_init) { + if (flags.host_need_init) { ESP_PANEL_CHECK_ERR_RET(spi_bus_initialize((spi_host_device_t)host_id, &host_config, SPI_DMA_CH_AUTO), false, "Initializeost Host[%d] failed", host_id); ESP_LOGD(TAG, "Init host[%d]", host_id); diff --git a/src/bus/QSPI.h b/src/bus/QSPI.h index f5a8f664..f04b2eea 100644 --- a/src/bus/QSPI.h +++ b/src/bus/QSPI.h @@ -11,11 +11,6 @@ #include "host/ESP_PanelHost.h" #include "ESP_PanelBus.h" -/** - * @brief Macro for QSPI bus default host ID - * - */ - /** * @brief Macro for QSPI panel IO configuration * diff --git a/src/bus/RGB.cpp b/src/bus/RGB.cpp index 7c42671f..c60dc081 100644 --- a/src/bus/RGB.cpp +++ b/src/bus/RGB.cpp @@ -89,17 +89,10 @@ ESP_PanelBus_RGB::~ESP_PanelBus_RGB() { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - if (host_need_init) { - if (handle == NULL) { - goto end; - } - - if (!del()) { - ESP_LOGE(TAG, "Delete panel io failed"); - } + if (flags.host_need_init && (handle != NULL) && !del()) { + ESP_LOGE(TAG, "Delete panel io failed"); } -end: ESP_LOGD(TAG, "Destroyed"); } @@ -168,7 +161,7 @@ bool ESP_PanelBus_RGB::begin(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - if (host_need_init) { + if (flags.host_need_init) { ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_io_3wire_spi(&spi_config, &handle), false, "Create panel io failed"); ESP_LOGD(TAG, "Create panel io @%p", handle); } @@ -176,9 +169,4 @@ bool ESP_PanelBus_RGB::begin(void) return true; } -const esp_lcd_rgb_panel_config_t *ESP_PanelBus_RGB::getRgbConfig() -{ - return &rgb_config; -} - #endif /* SOC_LCD_RGB_SUPPORTED */ diff --git a/src/bus/RGB.h b/src/bus/RGB.h index 662a02e3..c7c4ac80 100644 --- a/src/bus/RGB.h +++ b/src/bus/RGB.h @@ -318,7 +318,10 @@ class ESP_PanelBus_RGB: public ESP_PanelBus { */ bool begin(void) override; - const esp_lcd_rgb_panel_config_t *getRgbConfig(); + const esp_lcd_rgb_panel_config_t *getRgbConfig() + { + return &rgb_config; + } private: esp_lcd_rgb_panel_config_t rgb_config; diff --git a/src/bus/SPI.cpp b/src/bus/SPI.cpp index 22ee6963..d6fb83dd 100644 --- a/src/bus/SPI.cpp +++ b/src/bus/SPI.cpp @@ -66,7 +66,7 @@ bool ESP_PanelBus_SPI::del(void) ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); ESP_PANEL_CHECK_FALSE_RET(ESP_PanelBus::del(), false, "Delete base panel failed"); - if (host_need_init) { + if (flags.host_need_init) { if (spi_bus_free((spi_host_device_t)host_id) != ESP_OK) { ESP_LOGE(TAG, "Delete host[%d] driver failed", host_id); } else { @@ -106,7 +106,7 @@ bool ESP_PanelBus_SPI::begin(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - if (host_need_init) { + if (flags.host_need_init) { ESP_PANEL_CHECK_ERR_RET(spi_bus_initialize((spi_host_device_t)host_id, &host_config, SPI_DMA_CH_AUTO), false, "Initializeost Host[%d] failed", host_id); ESP_LOGD(TAG, "Init host[%d]", host_id); diff --git a/src/lcd/ESP_PanelLcd.cpp b/src/lcd/ESP_PanelLcd.cpp index 28e96038..93298468 100644 --- a/src/lcd/ESP_PanelLcd.cpp +++ b/src/lcd/ESP_PanelLcd.cpp @@ -7,20 +7,16 @@ #include #include "cstring" #include "ESP_PanelLog.h" -#include "soc/soc_caps.h" +#include "sdkconfig.h" #include "esp_heap_caps.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_io.h" -#if SOC_LCD_RGB_SUPPORTED -#include "esp_lcd_panel_rgb.h" -#endif #include "esp_memory_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "driver/spi_master.h" -#include "soc/soc_caps.h" -#include "sdkconfig.h" #include "bus/RGB.h" +#include "bus/DSI.h" #include "bus/ESP_PanelBus.h" #include "ESP_PanelLcd.h" @@ -31,7 +27,10 @@ .flags = { \ .mirror_by_cmd = 1, \ .auto_del_panel_io = 0, \ + .use_spi_interface = 0, \ .use_qspi_interface = 0, \ + .use_rgb_interface = 0, \ + .use_mipi_interface = 0, \ } \ } #define CALLBACK_DATA_DEFAULT() \ @@ -45,51 +44,40 @@ static const char *TAG = "ESP_PanelLcd"; using namespace std; ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, uint8_t color_bits, int rst_io): + disabled_functions{}, x_coord_align(0), y_coord_align(0), bus(bus), panel_config(ESP_PANEL_LCD_DEVICE_CONFIG_DEFAULT(rst_io, color_bits, &vendor_config)), - vendor_config(VENDOR_CONFIG_DEFAULT()), + vendor_config((esp_lcd_panel_vendor_config_t)VENDOR_CONFIG_DEFAULT()), handle(NULL), - _swap_xy(false), - _mirror_x(false), - _mirror_y(false), + _flags{}, _gap_x(0), _gap_y(0), + onDrawBitmapFinishCallback(NULL), onRefreshFinishCallback(NULL), - _refresh_finish_sem(NULL), - callback_data(CALLBACK_DATA_DEFAULT()) + _draw_bitmap_finish_sem(NULL), + _callback_data(CALLBACK_DATA_DEFAULT()) { - switch (bus->getType()) { - case ESP_PANEL_BUS_TYPE_QSPI: - vendor_config.flags.use_qspi_interface = 1; - break; -#if SOC_LCD_RGB_SUPPORTED - /* Retrieve RGB configuration from the bus and register it into the vendor configuration */ - case ESP_PANEL_BUS_TYPE_RGB: - vendor_config.rgb_config = static_cast(bus)->getRgbConfig(); - break; -#endif - default: - break; - } + /* Construct vendor configuration */ + constructVendorConfig(bus); } ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config): + disabled_functions{}, x_coord_align(0), y_coord_align(0), bus(bus), panel_config(panel_config), vendor_config(VENDOR_CONFIG_DEFAULT()), handle(NULL), - _swap_xy(false), - _mirror_x(false), - _mirror_y(false), + _flags{}, _gap_x(0), _gap_y(0), + onDrawBitmapFinishCallback(NULL), onRefreshFinishCallback(NULL), - _refresh_finish_sem(NULL), - callback_data(CALLBACK_DATA_DEFAULT()) + _draw_bitmap_finish_sem(NULL), + _callback_data(CALLBACK_DATA_DEFAULT()) { /* Save vendor configuration to local and register the local one into panel configuration */ if (panel_config.vendor_config != NULL) { @@ -97,17 +85,13 @@ ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t & } this->panel_config.vendor_config = &vendor_config; -#if SOC_LCD_RGB_SUPPORTED - /* Retrieve RGB configuration from the bus and register it into the vendor configuration */ - if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { - const esp_lcd_rgb_panel_config_t *rgb_config = static_cast(bus)->getRgbConfig(); - vendor_config.rgb_config = rgb_config; - } -#endif + /* Construct vendor configuration */ + constructVendorConfig(bus); } bool ESP_PanelLcd::configVendorCommands(const esp_lcd_panel_vendor_init_cmd_t init_cmd[], uint32_t init_cmd_size) { + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); ESP_PANEL_CHECK_FALSE_RET((init_cmd == NULL) || (init_cmd_size > 0), false, "Size of init commands is invalid"); vendor_config.init_cmds = init_cmd; @@ -116,29 +100,52 @@ bool ESP_PanelLcd::configVendorCommands(const esp_lcd_panel_vendor_init_cmd_t in return true; } -void ESP_PanelLcd::configColorRgbOrder(bool BGR_order) +bool ESP_PanelLcd::configColorRgbOrder(bool BGR_order) { + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + if (BGR_order) { panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; } else { panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; } + + return true; +} + +bool ESP_PanelLcd::configResetActiveLevel(int level) +{ + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + + panel_config.flags.reset_active_high = level; + + return true; } -void ESP_PanelLcd::configMirrorByCommand(bool en) +bool ESP_PanelLcd::configMirrorByCommand(bool en) { + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + ESP_PANEL_CHECK_FALSE_RET(bus->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "This function is only for RGB interface"); + vendor_config.flags.mirror_by_cmd = en; + + return true; } -void ESP_PanelLcd::configAutoReleaseBus(bool en) +bool ESP_PanelLcd::configEnableIO_Multiplex(bool en) { - vendor_config.flags.auto_del_panel_io = en; + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + ESP_PANEL_CHECK_FALSE_RET(bus->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "This function is only for RGB interface"); + + vendor_config.flags.enable_io_multiplex = en; + + return true; } bool ESP_PanelLcd::begin(void) { - ESP_PANEL_CHECK_NULL_RET(handle, false, "Invalid handle"); - ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + ESP_PANEL_CHECK_FALSE_RET(!checkIsBegun() || _flags.is_reset, false, "Already begun and not reset"); ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); @@ -147,22 +154,21 @@ bool ESP_PanelLcd::begin(void) /* Initialize LCD panel */ ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_init(handle), false, "Init panel failed"); - /* For non-RGB interface, create Semaphore for using API `drawBitmapWaitUntilFinish()` */ - if (bus->getType() != ESP_PANEL_BUS_TYPE_RGB) { - _refresh_finish_sem = xSemaphoreCreateBinary(); - ESP_PANEL_CHECK_NULL_RET(_refresh_finish_sem, false, "Create semaphore failed"); + /* If the panel is reset, goto end directly */ + if (checkIsBegun() && _flags.is_reset) { + goto end; } - /* Register transimit done callback for non-RGB interface and RGB interface */ - if (bus->getType() != ESP_PANEL_BUS_TYPE_RGB) { - esp_lcd_panel_io_callbacks_t io_cb = { - .on_color_trans_done = (esp_lcd_panel_io_color_trans_done_cb_t)onRefreshFinish, - }; - ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_io_register_event_callbacks(bus->getHandle(), &io_cb, &callback_data), false, - "Register IO callback failed"); + /* For non-RGB interface, create Semaphore for using API `drawBitmapWaitUntilFinish()` */ + if ((bus->getType() != ESP_PANEL_BUS_TYPE_RGB) && (_draw_bitmap_finish_sem == NULL)) { + _draw_bitmap_finish_sem = xSemaphoreCreateBinary(); + ESP_PANEL_CHECK_NULL_RET(_draw_bitmap_finish_sem, false, "Create draw bitmap finish semaphore failed"); } + + /* Register transimit done callback for different interface */ + switch (bus->getType()) { #if SOC_LCD_RGB_SUPPORTED - else { + case ESP_PANEL_BUS_TYPE_RGB: { const esp_lcd_rgb_panel_config_t *rgb_config = static_cast(bus)->getRgbConfig(); esp_lcd_rgb_panel_event_callbacks_t rgb_event_cb = { NULL }; if (rgb_config->bounce_buffer_size_px == 0) { @@ -172,66 +178,113 @@ bool ESP_PanelLcd::begin(void) // When bounce buffer is enabled, use `on_bounce_frame_finish` callback to notify draw bitmap finish rgb_event_cb.on_bounce_frame_finish = (esp_lcd_rgb_panel_bounce_buf_finish_cb_t)onRefreshFinish; } - ESP_PANEL_CHECK_ERR_RET(esp_lcd_rgb_panel_register_event_callbacks(handle, &rgb_event_cb, &callback_data), - false, "Register RGB callback failed"); + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_rgb_panel_register_event_callbacks(handle, &rgb_event_cb, &_callback_data), false, + "Register RGB callback failed" + ); + break; } #endif +#if SOC_MIPI_DSI_SUPPORTED + case ESP_PANEL_BUS_TYPE_MIPI_DSI: { + esp_lcd_dpi_panel_event_callbacks_t dpi_event_cb = { + .on_color_trans_done = (esp_lcd_dpi_panel_color_trans_done_cb_t)onDrawBitmapFinish, + .on_refresh_done = (esp_lcd_dpi_panel_refresh_done_cb_t)onRefreshFinish, + }; + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_dpi_panel_register_event_callbacks(handle, &dpi_event_cb, &_callback_data), false, + "Register MIPI-DSI callback failed" + ); + break; + } +#endif + default: + esp_lcd_panel_io_callbacks_t io_cb = { + .on_color_trans_done = (esp_lcd_panel_io_color_trans_done_cb_t)onDrawBitmapFinish, + }; + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_panel_io_register_event_callbacks(bus->getPanelIO_Handle(), &io_cb, &_callback_data), false, + "Register panel IO callback failed" + ); + break; + } +end: ESP_LOGD(TAG, "Begin end"); + _flags.is_begun = true; + _flags.is_reset = false; return true; } bool ESP_PanelLcd::reset(void) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + +#if SOC_LCD_RGB_SUPPORTED + if ((bus->getType() == ESP_PANEL_BUS_TYPE_RGB) && !checkIsBegun() && vendor_config.flags.auto_del_panel_io) { + ESP_LOGD(TAG, "Ignore reset panel before begun for RGB LCD with auto release bus"); + goto end; + } +#endif + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_reset(handle), false, "Reset panel failed"); +#if SOC_LCD_RGB_SUPPORTED +end: +#endif + _flags.is_reset = true; + _flags.is_begun = false; + return true; } bool ESP_PanelLcd::del(void) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_del(handle), false, "Delete panel failed"); - if (_refresh_finish_sem) { - vSemaphoreDelete(_refresh_finish_sem); - _refresh_finish_sem = NULL; + if (_draw_bitmap_finish_sem) { + vSemaphoreDelete(_draw_bitmap_finish_sem); + _draw_bitmap_finish_sem = NULL; } ESP_LOGD(TAG, "LCD panel @%p deleted", handle); handle = NULL; + _flags = {}; return true; } bool ESP_PanelLcd::drawBitmap(uint16_t x_start, uint16_t y_start, uint16_t width, uint16_t height, const uint8_t *color_data) { - ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_draw_bitmap(handle, x_start, y_start, x_start + width, y_start + height, color_data), - false, "Draw bitmap failed"); + ESP_PANEL_CHECK_FALSE_RET(checkIsBegun(), false, "Not begun"); + + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_panel_draw_bitmap(handle, x_start, y_start, x_start + width, y_start + height, color_data), + false, "Draw bitmap failed" + ); return true; } -bool ESP_PanelLcd::drawBitmapWaitUntilFinish(uint16_t x_start, uint16_t y_start, uint16_t width, uint16_t height, - const uint8_t *color_data, int timeout_ms) +bool ESP_PanelLcd::drawBitmapWaitUntilFinish( + uint16_t x_start, uint16_t y_start, uint16_t width, uint16_t height, const uint8_t *color_data, int timeout_ms +) { - ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + ESP_PANEL_CHECK_FALSE_RET(checkIsBegun(), false, "Not begun"); /* For RGB LCD, since `drawBitmap()` uses `memcpy()` instead of DMA operation, doesn't need to wait */ - if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { - ESP_PANEL_CHECK_FALSE_RET(drawBitmap(x_start, y_start, width, height, color_data), false, "Draw bitmap failed"); - return true; - } - - /* For other LCDs, since `drawBitmap()` use DMA operation, need to use semaphore to wait */ - ESP_PANEL_CHECK_NULL_RET(_refresh_finish_sem, false, "Semaphore is not created"); - ESP_PANEL_CHECK_FALSE_RET(drawBitmap(x_start, y_start, width, height, color_data), false, "Draw bitmap failed"); - /* Wait for the semaphore to be given by the callback function */ - BaseType_t timeout_tick = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); - if (xSemaphoreTake(_refresh_finish_sem, timeout_tick) != pdTRUE) { - ESP_LOGD(TAG, "Draw bitmap wait for finish timeout"); - return false; + /* For other interfaces which uses DMA operation, wait for the drawing to finish */ + if (bus->getType() != ESP_PANEL_BUS_TYPE_RGB) { + /* Wait for the semaphore to be given by the callback function */ + BaseType_t timeout_tick = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + ESP_PANEL_CHECK_FALSE_RET( + xSemaphoreTake(_draw_bitmap_finish_sem, timeout_tick) == pdTRUE, false, + "Draw bitmap wait for finish timeout" + ); } return true; @@ -239,30 +292,58 @@ bool ESP_PanelLcd::drawBitmapWaitUntilFinish(uint16_t x_start, uint16_t y_start, bool ESP_PanelLcd::mirrorX(bool en) { - ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_mirror(handle, en, _mirror_y), false, "Mirror X failed"); - _mirror_x = en; + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.mirror) { + ESP_LOGW(TAG, "Mirror function is disabled"); + return true; + } + + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_mirror(handle, en, _flags.mirror_y), false, "Mirror X failed"); + _flags.mirror_x = en; return true; } bool ESP_PanelLcd::mirrorY(bool en) { - ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_mirror(handle, _mirror_x, en), false, "Mirror X failed"); - _mirror_y = en; + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.mirror) { + ESP_LOGW(TAG, "Mirror function is disabled"); + return true; + } + + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_mirror(handle, _flags.mirror_x, en), false, "Mirror X failed"); + _flags.mirror_y = en; return true; } bool ESP_PanelLcd::swapXY(bool en) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.swap_xy) { + ESP_LOGW(TAG, "Swap XY function is disabled"); + return true; + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_swap_xy(handle, en), false, "Swap XY failed"); - _swap_xy = en; + _flags.swap_xy = en; return true; } bool ESP_PanelLcd::setGapX(uint16_t gap) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.set_gap) { + ESP_LOGW(TAG, "Set gap function is disabled"); + return true; + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_set_gap(handle, gap, _gap_y), false, "Set X gap failed"); _gap_x = gap; @@ -271,6 +352,13 @@ bool ESP_PanelLcd::setGapX(uint16_t gap) bool ESP_PanelLcd::setGapY(uint16_t gap) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.set_gap) { + ESP_LOGW(TAG, "Set gap function is disabled"); + return true; + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_set_gap(handle, _gap_x, gap), false, "Set Y gap failed"); _gap_y = gap; @@ -279,6 +367,8 @@ bool ESP_PanelLcd::setGapY(uint16_t gap) bool ESP_PanelLcd::invertColor(bool en) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_invert_color(handle, en), false, "Invert color failed"); return true; @@ -286,6 +376,28 @@ bool ESP_PanelLcd::invertColor(bool en) bool ESP_PanelLcd::displayOn(void) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + bool is_disable = disabled_functions.display_on_off; + if (is_disable) { + goto end; + } + +#if SOC_LCD_RGB_SUPPORTED + if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { + is_disable = (bus->getPanelIO_Handle() == NULL) && (vendor_config.rgb_config->disp_gpio_num == -1); + if (is_disable) { + goto end; + } + } +#endif + +end: + if (is_disable) { + ESP_LOGW(TAG, "Display on/off function is disabled"); + return true; + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_disp_on_off(handle, true), false, "Display on failed"); return true; @@ -293,18 +405,50 @@ bool ESP_PanelLcd::displayOn(void) bool ESP_PanelLcd::displayOff(void) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + + if (disabled_functions.display_on_off) { + ESP_LOGW(TAG, "Display on/off function is disabled"); + return true; + } + ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_disp_on_off(handle, false), false, "Display off failed"); return true; } +bool ESP_PanelLcd::attachDrawBitmapFinishCallback(std::function callback, void *user_data) +{ + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + ESP_PANEL_CHECK_FALSE_RET( + bus->getType() != ESP_PANEL_BUS_TYPE_RGB, false, "RGB interface doesn't support this callback" + ); + +// *INDENT-OFF* + if ((_callback_data.user_data != NULL) && (_callback_data.user_data != user_data)) { + ESP_LOGW(TAG, "Callback data is not NULL, will overwrite the previous one"); + } + _callback_data.user_data = user_data; + onDrawBitmapFinishCallback = callback; +// *INDENT-OFF* + + return true; +} + bool ESP_PanelLcd::attachRefreshFinishCallback(std::function callback, void *user_data) { + ESP_PANEL_CHECK_FALSE_RET(checkIsInit(), false, "Not initialized"); + ESP_PANEL_CHECK_FALSE_RET( + (bus->getType() != ESP_PANEL_BUS_TYPE_SPI) && (bus->getType() != ESP_PANEL_BUS_TYPE_QSPI), false, + "SPI and QSPI interfaces don't support this callback" + ); + + /* Check the callback function and user data placement */ // For RGB LCD, if the "XIP on PSRAM" function is not enabled, the callback function and user data should be // placed in SRAM -#if SOC_LCD_RGB_SUPPORTED && CONFIG_LCD_RGB_ISR_IRAM_SAFE && \ - !(CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_FETCH_INSTRUCTIONS) - if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { +#if (SOC_MIPI_DSI_SUPPORTED && CONFIG_LCD_DSI_ISR_IRAM_SAFE) || \ + (SOC_LCD_RGB_SUPPORTED && CONFIG_LCD_RGB_ISR_IRAM_SAFE && !(CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_FETCH_INSTRUCTIONS)) + if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB || bus->getType() == ESP_PANEL_BUS_TYPE_MIPI_DSI) { ESP_PANEL_CHECK_FALSE_RET( esp_ptr_in_iram(callback), false, "Callback function should be placed in IRAM, add `IRAM_ATTR` before the function" @@ -314,7 +458,10 @@ bool ESP_PanelLcd::attachRefreshFinishCallback(std::function call #endif // *INDENT-OFF* - callback_data.user_data = user_data; + if ((_callback_data.user_data != NULL) && (_callback_data.user_data != user_data)) { + ESP_LOGW(TAG, "Callback data is not NULL, will overwrite the previous one"); + } + _callback_data.user_data = user_data; onRefreshFinishCallback = callback; // *INDENT-OFF* @@ -323,23 +470,25 @@ bool ESP_PanelLcd::attachRefreshFinishCallback(std::function call bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) { - ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + ESP_PANEL_CHECK_FALSE_RET(checkIsBegun(), false, "Not begun"); int bits_per_piexl = getColorBits(); ESP_PANEL_CHECK_FALSE_RET(bits_per_piexl > 0, false, "Invalid color bits"); + ESP_LOGD(TAG, "Color bar test, width: %d, height: %d, bits per pixel: %d", width, height, bits_per_piexl); + int bytes_per_piexl = bits_per_piexl / 8; - int line_per_bar = height / bits_per_piexl; + int row_per_bar = height / bits_per_piexl; int line_count = 0; int res_line_count = 0; /* Malloc memory for a single color bar */ - shared_ptr single_bar_buf(new uint8_t[line_per_bar * width * bytes_per_piexl]); + shared_ptr single_bar_buf(new uint8_t[row_per_bar * width * bytes_per_piexl]); ESP_PANEL_CHECK_FALSE_RET(single_bar_buf != nullptr, false, "Malloc color buffer failed"); /* Draw color bar from top left to bottom right, the order is B - G - R */ for (int j = 0; j < bits_per_piexl; j++) { - for (int i = 0; i < line_per_bar * width; i++) { + for (int i = 0; i < row_per_bar * width; i++) { for (int k = 0; k < bytes_per_piexl; k++) { if ((bus->getType() == ESP_PANEL_BUS_TYPE_SPI) || (bus->getType() == ESP_PANEL_BUS_TYPE_QSPI)) { // For SPI interface, the data bytes should be swapped since the data is sent by LSB first @@ -349,9 +498,9 @@ bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) } } } - line_count += line_per_bar; + line_count += row_per_bar; ESP_PANEL_CHECK_FALSE_RET( - drawBitmapWaitUntilFinish(0, j * line_per_bar, width, line_per_bar, single_bar_buf.get()), false, + drawBitmapWaitUntilFinish(0, j * row_per_bar, width, row_per_bar, single_bar_buf.get()), false, "Draw bitmap failed" ); } @@ -371,77 +520,155 @@ bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) return true; } -int ESP_PanelLcd::getColorBits(void) +#if SOC_MIPI_DSI_SUPPORTED +bool ESP_PanelLcd::showDsiPattern(DsiPatternType type) { -#if SOC_LCD_RGB_SUPPORTED - ESP_PANEL_CHECK_NULL_RET(bus, -1, "Invalid bus"); - if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { - const esp_lcd_rgb_panel_config_t *rgb_config = static_cast(bus)->getRgbConfig(); - ESP_PANEL_CHECK_NULL_RET(rgb_config, -1, "Invalid RGB config"); + ESP_PANEL_CHECK_FALSE_RET(checkIsBegun(), false, "Not begun"); + ESP_PANEL_CHECK_FALSE_RET( + bus->getType() == ESP_PANEL_BUS_TYPE_MIPI_DSI, false, "Invalid bus type(%d)", bus->getType() + ); - return rgb_config->bits_per_pixel; - } -#endif + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_dpi_panel_set_pattern(handle, static_cast(type)), false, + "Set DPI pattern failed" + ); - return panel_config.bits_per_pixel; + return true; } +#endif /* SOC_MIPI_DSI_SUPPORTED */ -bool ESP_PanelLcd::getSwapXYFlag(void) +int ESP_PanelLcd::getColorBits(void) { - return _swap_xy; -} + ESP_PANEL_CHECK_NULL_RET(bus, -1, "Invalid bus"); -bool ESP_PanelLcd::getMirrorXFlag(void) -{ - return _mirror_x; -} + int bits_per_pixel = panel_config.bits_per_pixel; + switch (bus->getType()) { +#if SOC_LCD_RGB_SUPPORTED + case ESP_PANEL_BUS_TYPE_RGB: { + const esp_lcd_rgb_panel_config_t *rgb_config = static_cast(bus)->getRgbConfig(); + ESP_PANEL_CHECK_NULL_RET(rgb_config, -1, "Invalid RGB config"); + bits_per_pixel = rgb_config->bits_per_pixel; + break; + } +#endif /* SOC_LCD_RGB_SUPPORTED */ +#if SOC_MIPI_DSI_SUPPORTED + case ESP_PANEL_BUS_TYPE_MIPI_DSI: { + const esp_lcd_dpi_panel_config_t *dpi_config = static_cast(bus)->getDpiConfig(); + ESP_PANEL_CHECK_NULL_RET(dpi_config, -1, "Invalid MIPI DPI config"); + switch (dpi_config->pixel_format) { + case LCD_COLOR_PIXEL_FORMAT_RGB565: + bits_per_pixel = 16; + break; + case LCD_COLOR_PIXEL_FORMAT_RGB666: + bits_per_pixel = 18; + break; + case LCD_COLOR_PIXEL_FORMAT_RGB888: + bits_per_pixel = 24; + break; + default: + break; + } + break; + } +#endif /* SOC_MIPI_DSI_SUPPORTED */ + default: + break; + } -bool ESP_PanelLcd::getMirrorYFlag(void) -{ - return _mirror_y; + return bits_per_pixel; } -#if SOC_LCD_RGB_SUPPORTED -void *ESP_PanelLcd::getRgbBufferByIndex(uint8_t index) +void *ESP_PanelLcd::getFrameBufferByIndex(uint8_t index) { - ESP_PANEL_CHECK_NULL_RET(handle, NULL, "Invalid handle"); + ESP_PANEL_CHECK_FALSE_RET(checkIsBegun(), NULL, "Not begun"); + ESP_PANEL_CHECK_FALSE_RET( + index < ESP_PANEL_LCD_FRAME_BUFFER_MAX_NUM, NULL, "Index(%d) out of range(0-%d)", index, + ESP_PANEL_LCD_FRAME_BUFFER_MAX_NUM - 1 + ); - void *buffer[3] = { NULL }; - ESP_PANEL_CHECK_ERR_RET(esp_lcd_rgb_panel_get_frame_buffer(handle, index + 1, &buffer[0], &buffer[1], &buffer[2]), - NULL, "Get RGB buffer failed"); + void *buffer[ESP_PANEL_LCD_FRAME_BUFFER_MAX_NUM] = {}; + switch (bus->getType()) { +#if SOC_LCD_RGB_SUPPORTED + case ESP_PANEL_BUS_TYPE_RGB: + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_rgb_panel_get_frame_buffer(handle, index + 1, &buffer[0], &buffer[1], &buffer[2]), NULL, + "Get RGB buffer failed" + ); + break; +#endif +#if SOC_MIPI_DSI_SUPPORTED + case ESP_PANEL_BUS_TYPE_MIPI_DSI: + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_dpi_panel_get_frame_buffer(handle, index + 1, &buffer[0], &buffer[1], &buffer[2]), NULL, + "Get MIPI DPI buffer failed" + ); + break; +#endif + default: + ESP_PANEL_CHECK_FALSE_RET(false, NULL, "Invalid bus type(%d)", bus->getType()); + break; + } return buffer[index]; } -#endif -uint8_t ESP_PanelLcd::getXCoordAlign(void) +void ESP_PanelLcd::constructVendorConfig(ESP_PanelBus *bus) { - return x_coord_align; -} + if (bus == NULL) { + return; + } -uint8_t ESP_PanelLcd::getYCoordAlign(void) -{ - return y_coord_align; + switch (bus->getType()) { + case ESP_PANEL_BUS_TYPE_SPI: + vendor_config.flags.use_spi_interface = 1; + break; + case ESP_PANEL_BUS_TYPE_QSPI: + vendor_config.flags.use_qspi_interface = 1; + break; +#if SOC_LCD_RGB_SUPPORTED + /* Retrieve RGB configuration from the bus and register it into the vendor configuration */ + case ESP_PANEL_BUS_TYPE_RGB: + vendor_config.flags.use_rgb_interface = 1; + vendor_config.rgb_config = static_cast(bus)->getRgbConfig(); + break; +#endif +#if SOC_MIPI_DSI_SUPPORTED + /* Retrieve MIPI DPI configuration from the bus and register it into the vendor configuration */ + case ESP_PANEL_BUS_TYPE_MIPI_DSI: + vendor_config.flags.use_mipi_interface = 1; + vendor_config.mipi_config = { + .lane_num = static_cast(bus)->getDsiConfig()->num_data_lanes, + .dsi_bus = static_cast(bus)->getBusHandle(), + .dpi_config = static_cast(bus)->getDpiConfig(), + }; + break; +#endif + default: + break; + } } -esp_lcd_panel_handle_t ESP_PanelLcd::getHandle(void) +IRAM_ATTR bool ESP_PanelLcd::onDrawBitmapFinish(void *panel_io, void *edata, void *user_ctx) { - if (handle == NULL) { - ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - ESP_LOGD(TAG, "Get invalid handle"); + ESP_PanelLcdCallbackData_t *callback_data = (ESP_PanelLcdCallbackData_t *)user_ctx; + if (callback_data == NULL) { + return false; } - return handle; -} + ESP_PanelLcd *lcd_ptr = (ESP_PanelLcd *)callback_data->lcd_ptr; + if (lcd_ptr == NULL) { + return false; + } -ESP_PanelBus *ESP_PanelLcd::getBus(void) -{ - if (bus == NULL) { - ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - ESP_LOGD(TAG, "Get invalid bus"); + BaseType_t need_yield = pdFALSE; + if (lcd_ptr->onDrawBitmapFinishCallback != NULL) { + need_yield = lcd_ptr->onDrawBitmapFinishCallback(callback_data->user_data) ? pdTRUE : need_yield; + } + if (lcd_ptr->_draw_bitmap_finish_sem != NULL) { + xSemaphoreGiveFromISR(lcd_ptr->_draw_bitmap_finish_sem, &need_yield); } - return bus; + return (need_yield == pdTRUE); } IRAM_ATTR bool ESP_PanelLcd::onRefreshFinish(void *panel_io, void *edata, void *user_ctx) @@ -460,9 +687,6 @@ IRAM_ATTR bool ESP_PanelLcd::onRefreshFinish(void *panel_io, void *edata, void * if (lcd_ptr->onRefreshFinishCallback != NULL) { need_yield = lcd_ptr->onRefreshFinishCallback(callback_data->user_data) ? pdTRUE : need_yield; } - if (lcd_ptr->_refresh_finish_sem != NULL) { - xSemaphoreGiveFromISR(lcd_ptr->_refresh_finish_sem, &need_yield); - } return (need_yield == pdTRUE); } diff --git a/src/lcd/ESP_PanelLcd.h b/src/lcd/ESP_PanelLcd.h index c8fb4293..c30d3343 100644 --- a/src/lcd/ESP_PanelLcd.h +++ b/src/lcd/ESP_PanelLcd.h @@ -11,15 +11,13 @@ #include "soc/soc_caps.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_vendor.h" -#if SOC_LCD_RGB_SUPPORTED -#include "esp_lcd_panel_rgb.h" -#endif #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -#include "soc/soc_caps.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "bus/ESP_PanelBus.h" +#define ESP_PANEL_LCD_FRAME_BUFFER_MAX_NUM (3) + /** * @brief LCD device default configuration macro * @@ -43,6 +41,15 @@ */ class ESP_PanelLcd { public: +#if SOC_MIPI_DSI_SUPPORTED + enum class DsiPatternType { + NONE = MIPI_DSI_PATTERN_NONE, + BAR_HORIZONTAL = MIPI_DSI_PATTERN_BAR_HORIZONTAL, + BAR_VERTICAL = MIPI_DSI_PATTERN_BAR_VERTICAL, + BER_VERTICAL = MIPI_DSI_PATTERN_BER_VERTICAL, + }; +#endif + /** * @brief Construct a new LCD device in a simple way, the `init()` function should be called after this function * @@ -91,13 +98,20 @@ class ESP_PanelLcd { * @param bgr_order true: BGR order, false: RGB order * */ - void configColorRgbOrder(bool BGR_order); + bool configColorRgbOrder(bool BGR_order); + + /** + * @brief Configure the reset active level of LCD, default is RGB. This function should be called before `init()` + * + * @param level 0: low level, 1: high level + * + */ + bool configResetActiveLevel(int level); /** * @brief Configure driver to mirror by command, default is false (by software). This function should be called before - * `init()` + * `init()`. Only valid for RGB interface. * - * @note This function is only useful for some LCDs without GRAM, like RGB LCD. * @note After using this function, the `mirror()` function will be implemented by LCD command. Otherwise, the * `mirror()`function will be implemented by software * @note This function is conflict with `configAutoReleaseBus()`, please don't use them at the same time @@ -105,13 +119,32 @@ class ESP_PanelLcd { * @param en true: enable, false: disable * */ - void configMirrorByCommand(bool en); + bool configMirrorByCommand(bool en); /** * @brief Configure driver to release bus automatically, default is false. This function should be called before - * `init()` + * `init()`. Only valid for RGB interface. + * + * @note If the "3-wire SPI" interface are sharing pins of the "RGB" interface to save GPIOs, please call + * this function to release the bus object and pins (except CS signal). And then, the "3-wire SPI" interface + * cannot be used to transmit commands any more. + * @note This function is conflict with `configMirrorByCommand()`, please don't use them at the same time + * + * @param en true: enable, false: disable + * + * @return true if success, otherwise false + */ + [[deprecated("This function is deprecated, please use `configEnableIO_Multiplex()` instead")]] + bool configAutoReleaseBus(bool en) + { + return configEnableIO_Multiplex(en); + } + + /** + * @brief Configure driver to enable IO multiplex function, default is false. This function should be called before + * `init()` and the panel IO will be deleted automatically after calling `init()` function. Only valid for + * RGB interface. * - * @note This function is only useful for some LCDs without GRAM, like RGB LCD. * @note If the "3-wire SPI" interface are sharing pins of the "RGB" interface to save GPIOs, please call * this function to release the bus object and pins (except CS signal). And then, the "3-wire SPI" interface * cannot be used to transmit commands any more. @@ -119,8 +152,9 @@ class ESP_PanelLcd { * * @param en true: enable, false: disable * + * @return true if success, otherwise false */ - void configAutoReleaseBus(bool en); + bool configEnableIO_Multiplex(bool en); /** * @brief Initialize the LCD device, the `begin()` function should be called after this function @@ -305,6 +339,19 @@ class ESP_PanelLcd { * by this function * @param user_data The user data which will be passed to the callback function */ + bool attachDrawBitmapFinishCallback(std::function callback, void *user_data = NULL); + + /** + * @brief Attach a callback function, which will be called when the frame buffer refreshing is finished + * + * @note For RGB LCD, the function will be called when VSYNC end signal is detected, which means + * the whole frame refreshing is finished + * @note For other LCDs, the function will be called when every single drawing is finished + * + * @param callback The callback function. Its return value decides whether a high priority task has been waken up + * by this function + * @param user_data The user data which will be passed to the callback function + */ bool attachRefreshFinishCallback(std::function callback, void *user_data = NULL); /** @@ -320,6 +367,10 @@ class ESP_PanelLcd { */ bool colorBarTest(uint16_t width, uint16_t height); +#if SOC_MIPI_DSI_SUPPORTED + bool showDsiPattern(DsiPatternType type); +#endif /* SOC_MIPI_DSI_SUPPORTED */ + /** * @brief Get the bits of pixel color * @@ -334,25 +385,35 @@ class ESP_PanelLcd { * * @return true if swap, otherwise not swap */ - bool getSwapXYFlag(void); + bool getSwapXYFlag(void) + { + return _flags.swap_xy; + } /** * @brief Get the flag of the X axis mirror * * @return true if mirror, otherwise not mirror */ - bool getMirrorXFlag(void); + bool getMirrorXFlag(void) + { + return _flags.mirror_x; + } /** * @brief Get the flag of the Y axis mirror * * @return true if mirror, otherwise not mirror */ - bool getMirrorYFlag(void); + bool getMirrorYFlag(void) + { + return _flags.mirror_y; + } #if SOC_LCD_RGB_SUPPORTED /** - * @brief Get the RGB buffer by index (default is 0), only valid for RGB LCD + * @brief Get the RGB buffer by index (default is 0), only valid for RGB LCD. + * Deprecated function, please use `getFrameBufferByIndex()` instead * * @note This function should be called after `begin()` * @@ -362,38 +423,83 @@ class ESP_PanelLcd { * - NULL: if fail * - others: the pointer of the RGB buffer */ - void *getRgbBufferByIndex(uint8_t index = 0); + [[deprecated("This API is deprecated. Please use `getFrameBufferByIndex()` instead.")]] + void *getRgbBufferByIndex(uint8_t index = 0) + { + return getFrameBufferByIndex(index); + } #endif + /** + * @brief Get the frame buffer by index (default is 0), currently only valid for RGB/MIPI-DSI LCD + * + * @note This function should be called after `begin()` + * + * @param index + * + * @return + * - NULL: if fail + * - others: the pointer of the frame buffer + */ + void *getFrameBufferByIndex(uint8_t index = 0); + /** * @brief Get the X coordinate align * * @return The X coordinate align */ - uint8_t getXCoordAlign(void); + uint8_t getXCoordAlign(void) + { + return x_coord_align; + } /** * @brief Get the Y coordinate align * * @return The Y coordinate align */ - uint8_t getYCoordAlign(void); + uint8_t getYCoordAlign(void) + { + return y_coord_align; + } /** * @brief Get the panel handle * * @return The handle of the LCD panel, or NULL if fail */ - esp_lcd_panel_handle_t getHandle(void); + esp_lcd_panel_handle_t getHandle(void) + { + return handle; + } /** * @brief Get the panel bus * * @return The pointer of the LCD Bus, or NULL if fail */ - ESP_PanelBus *getBus(void); + ESP_PanelBus *getBus(void) + { + return bus; + } protected: + bool checkIsInit(void) + { + return (handle != NULL) && (bus != NULL); + } + + bool checkIsBegun(void) + { + return _flags.is_begun; + } + + struct { + uint8_t mirror: 1; + uint8_t swap_xy: 1; + uint8_t set_gap: 1; + uint8_t display_on_off: 1; + } disabled_functions; uint8_t x_coord_align; uint8_t y_coord_align; ESP_PanelBus *bus; @@ -402,19 +508,26 @@ class ESP_PanelLcd { esp_lcd_panel_handle_t handle; private: - static bool onRefreshFinish(void *panel_io, void *edata, void *user_ctx); - - bool _swap_xy; - bool _mirror_x; - bool _mirror_y; + void constructVendorConfig(ESP_PanelBus *bus); + IRAM_ATTR static bool onDrawBitmapFinish(void *panel_io, void *edata, void *user_ctx); + IRAM_ATTR static bool onRefreshFinish(void *panel_io, void *edata, void *user_ctx); + + struct { + uint8_t is_begun: 1; + uint8_t is_reset: 1; + uint8_t swap_xy: 1; + uint8_t mirror_x: 1; + uint8_t mirror_y: 1; + } _flags; uint16_t _gap_x; uint16_t _gap_y; + std::function onDrawBitmapFinishCallback; std::function onRefreshFinishCallback; - SemaphoreHandle_t _refresh_finish_sem; + SemaphoreHandle_t _draw_bitmap_finish_sem; typedef struct { void *lcd_ptr; void *user_data; } ESP_PanelLcdCallbackData_t; - ESP_PanelLcdCallbackData_t callback_data; + ESP_PanelLcdCallbackData_t _callback_data; }; diff --git a/src/lcd/GC9503.cpp b/src/lcd/GC9503.cpp index 22141908..3abb8db3 100644 --- a/src/lcd/GC9503.cpp +++ b/src/lcd/GC9503.cpp @@ -42,7 +42,11 @@ bool ESP_PanelLcd_GC9503::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9503(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9503(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); + // Delete panel io if enable `auto_del_panel_io` or `enable_io_multiplex` flag + if (((esp_lcd_panel_vendor_config_t *)panel_config.vendor_config)->flags.auto_del_panel_io) { + ESP_PANEL_CHECK_FALSE_RET(bus->delSkipPanelIO(), false, "Delete panel io failed"); + } return true; } diff --git a/src/lcd/GC9503.h b/src/lcd/GC9503.h index a37b5f01..f1f9f49a 100644 --- a/src/lcd/GC9503.h +++ b/src/lcd/GC9503.h @@ -10,7 +10,7 @@ #if SOC_LCD_RGB_SUPPORTED #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_gc9503.h" /** diff --git a/src/lcd/GC9A01.cpp b/src/lcd/GC9A01.cpp index 34c938b3..775e46ea 100644 --- a/src/lcd/GC9A01.cpp +++ b/src/lcd/GC9A01.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_GC9A01::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9a01(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9a01(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/GC9A01.h b/src/lcd/GC9A01.h index e3dab146..6701706b 100644 --- a/src/lcd/GC9A01.h +++ b/src/lcd/GC9A01.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_gc9a01.h" /** diff --git a/src/lcd/GC9B71.cpp b/src/lcd/GC9B71.cpp index 1cdfdf63..61d5bab5 100644 --- a/src/lcd/GC9B71.cpp +++ b/src/lcd/GC9B71.cpp @@ -43,7 +43,7 @@ bool ESP_PanelLcd_GC9B71::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9b71(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9b71(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/GC9B71.h b/src/lcd/GC9B71.h index af2ac1db..12fd9767 100644 --- a/src/lcd/GC9B71.h +++ b/src/lcd/GC9B71.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_gc9b71.h" /** diff --git a/src/lcd/ILI9341.cpp b/src/lcd/ILI9341.cpp index c144581a..6a0294ef 100644 --- a/src/lcd/ILI9341.cpp +++ b/src/lcd/ILI9341.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_ILI9341::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_ili9341(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_ili9341(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/ILI9341.h b/src/lcd/ILI9341.h index 4dee383c..97557f35 100644 --- a/src/lcd/ILI9341.h +++ b/src/lcd/ILI9341.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_ili9341.h" /** diff --git a/src/lcd/NV3022B.cpp b/src/lcd/NV3022B.cpp index aa1d9741..54a95202 100644 --- a/src/lcd/NV3022B.cpp +++ b/src/lcd/NV3022B.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_NV3022B::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_nv3022b(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_nv3022b(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/NV3022B.h b/src/lcd/NV3022B.h index aa4f0a72..39dd3957 100644 --- a/src/lcd/NV3022B.h +++ b/src/lcd/NV3022B.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_nv3022b.h" /** diff --git a/src/lcd/SH8601.cpp b/src/lcd/SH8601.cpp index 7329fc1a..2c2f31c3 100644 --- a/src/lcd/SH8601.cpp +++ b/src/lcd/SH8601.cpp @@ -43,7 +43,7 @@ bool ESP_PanelLcd_SH8601::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_sh8601(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_sh8601(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/SH8601.h b/src/lcd/SH8601.h index 6d1c0a30..91090d7d 100644 --- a/src/lcd/SH8601.h +++ b/src/lcd/SH8601.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_sh8601.h" /** diff --git a/src/lcd/SPD2010.cpp b/src/lcd/SPD2010.cpp index 288dd006..133d3ddb 100644 --- a/src/lcd/SPD2010.cpp +++ b/src/lcd/SPD2010.cpp @@ -41,7 +41,7 @@ bool ESP_PanelLcd_SPD2010::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_spd2010(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_spd2010(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/SPD2010.h b/src/lcd/SPD2010.h index 9f1c6f72..94dd54a4 100644 --- a/src/lcd/SPD2010.h +++ b/src/lcd/SPD2010.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_spd2010.h" /** diff --git a/src/lcd/ST7701.cpp b/src/lcd/ST7701.cpp index c01e43c1..c279c1e8 100644 --- a/src/lcd/ST7701.cpp +++ b/src/lcd/ST7701.cpp @@ -42,7 +42,11 @@ bool ESP_PanelLcd_ST7701::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7701(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7701(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); + // Delete panel io if enable `auto_del_panel_io` or `enable_io_multiplex` flag + if (((esp_lcd_panel_vendor_config_t *)panel_config.vendor_config)->flags.auto_del_panel_io) { + ESP_PANEL_CHECK_FALSE_RET(bus->delSkipPanelIO(), false, "Delete panel io failed"); + } return true; } diff --git a/src/lcd/ST7701.h b/src/lcd/ST7701.h index 526c567c..b51fb025 100644 --- a/src/lcd/ST7701.h +++ b/src/lcd/ST7701.h @@ -9,7 +9,7 @@ #if SOC_LCD_RGB_SUPPORTED #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_st7701.h" /** diff --git a/src/lcd/ST7789.cpp b/src/lcd/ST7789.cpp index a28afecb..fd4980e1 100644 --- a/src/lcd/ST7789.cpp +++ b/src/lcd/ST7789.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_ST7789::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7789(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7789(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/ST7789.h b/src/lcd/ST7789.h index 78aadebd..7ca987ec 100644 --- a/src/lcd/ST7789.h +++ b/src/lcd/ST7789.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_st7789.h" /** diff --git a/src/lcd/ST77916.cpp b/src/lcd/ST77916.cpp index b2b88f72..7cfd2936 100644 --- a/src/lcd/ST77916.cpp +++ b/src/lcd/ST77916.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_ST77916::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st77916(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st77916(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/ST77916.h b/src/lcd/ST77916.h index 2149192b..906f1f09 100644 --- a/src/lcd/ST77916.h +++ b/src/lcd/ST77916.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_st77916.h" /** diff --git a/src/lcd/ST77922.cpp b/src/lcd/ST77922.cpp index e775ac62..a99a1140 100644 --- a/src/lcd/ST77922.cpp +++ b/src/lcd/ST77922.cpp @@ -41,7 +41,7 @@ bool ESP_PanelLcd_ST77922::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st77922(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st77922(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/ST77922.h b/src/lcd/ST77922.h index 893806bf..b839abec 100644 --- a/src/lcd/ST77922.h +++ b/src/lcd/ST77922.h @@ -7,7 +7,7 @@ #pragma once #include "ESP_PanelLcd.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" #include "base/esp_lcd_st77922.h" /** diff --git a/src/lcd/ST7796.cpp b/src/lcd/ST7796.cpp index abff96f6..67805379 100644 --- a/src/lcd/ST7796.cpp +++ b/src/lcd/ST7796.cpp @@ -39,7 +39,7 @@ bool ESP_PanelLcd_ST7796::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7796(bus->getHandle(), &panel_config, &handle), false, "Create panel failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7796(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); return true; } diff --git a/src/lcd/ST7796.h b/src/lcd/ST7796.h index d4341cb2..c6b54735 100644 --- a/src/lcd/ST7796.h +++ b/src/lcd/ST7796.h @@ -8,7 +8,7 @@ #include "ESP_PanelLcd.h" #include "base/esp_lcd_st7796.h" -#include "base/esp_lcd_custom_types.h" +#include "base/esp_lcd_vendor_types.h" /** * @brief ST7796 LCD device object class diff --git a/src/lcd/base/esp_lcd_gc9503.c b/src/lcd/base/esp_lcd_gc9503.c index ad2c23ae..25046ce4 100644 --- a/src/lcd/base/esp_lcd_gc9503.c +++ b/src/lcd/base/esp_lcd_gc9503.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_vendor.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_gc9503.h" #define GC9503_CMD_MADCTL (0xB1) // Memory data access control diff --git a/src/lcd/base/esp_lcd_gc9a01.c b/src/lcd/base/esp_lcd_gc9a01.c index 2d71b6f1..650985da 100644 --- a/src/lcd/base/esp_lcd_gc9a01.c +++ b/src/lcd/base/esp_lcd_gc9a01.c @@ -5,7 +5,7 @@ */ #include "ESP_PanelLog.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include #include diff --git a/src/lcd/base/esp_lcd_gc9b71.c b/src/lcd/base/esp_lcd_gc9b71.c index 4d2e8388..c7dcde4c 100644 --- a/src/lcd/base/esp_lcd_gc9b71.c +++ b/src/lcd/base/esp_lcd_gc9b71.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_commands.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_gc9b71.h" #define LCD_OPCODE_WRITE_CMD (0x02ULL) diff --git a/src/lcd/base/esp_lcd_ili9341.c b/src/lcd/base/esp_lcd_ili9341.c index 05e63c39..e7ff1cd1 100644 --- a/src/lcd/base/esp_lcd_ili9341.c +++ b/src/lcd/base/esp_lcd_ili9341.c @@ -18,7 +18,7 @@ #include "esp_log.h" #include "esp_check.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_ili9341.h" static const char *TAG = "ili9341"; diff --git a/src/lcd/base/esp_lcd_nv3022b.c b/src/lcd/base/esp_lcd_nv3022b.c index ba9ab985..00337aa0 100644 --- a/src/lcd/base/esp_lcd_nv3022b.c +++ b/src/lcd/base/esp_lcd_nv3022b.c @@ -18,7 +18,7 @@ #include "esp_log.h" #include "esp_check.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_nv3022b.h" static const char *TAG = "lcd_panel.nv3022b"; diff --git a/src/lcd/base/esp_lcd_sh8601.c b/src/lcd/base/esp_lcd_sh8601.c index 36bd73c9..1af95a8e 100644 --- a/src/lcd/base/esp_lcd_sh8601.c +++ b/src/lcd/base/esp_lcd_sh8601.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_commands.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_sh8601.h" #define LCD_OPCODE_WRITE_CMD (0x02ULL) diff --git a/src/lcd/base/esp_lcd_spd2010.c b/src/lcd/base/esp_lcd_spd2010.c index 1b3ccf28..979a67cc 100644 --- a/src/lcd/base/esp_lcd_spd2010.c +++ b/src/lcd/base/esp_lcd_spd2010.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_commands.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_spd2010.h" #define LCD_OPCODE_WRITE_CMD (0x02ULL) diff --git a/src/lcd/base/esp_lcd_st7701.c b/src/lcd/base/esp_lcd_st7701.c index 8f2c678a..8e921f89 100644 --- a/src/lcd/base/esp_lcd_st7701.c +++ b/src/lcd/base/esp_lcd_st7701.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_vendor.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_st7701.h" #define ST7701_CMD_SDIR (0xC7) diff --git a/src/lcd/base/esp_lcd_st7789.c b/src/lcd/base/esp_lcd_st7789.c index 84c787c5..c4eb8276 100644 --- a/src/lcd/base/esp_lcd_st7789.c +++ b/src/lcd/base/esp_lcd_st7789.c @@ -18,7 +18,7 @@ #include "esp_log.h" #include "esp_check.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_st7789.h" static const char *TAG = "st7789"; diff --git a/src/lcd/base/esp_lcd_st77916.c b/src/lcd/base/esp_lcd_st77916.c index e91cdf9f..d3082a36 100644 --- a/src/lcd/base/esp_lcd_st77916.c +++ b/src/lcd/base/esp_lcd_st77916.c @@ -19,7 +19,7 @@ #include "esp_lcd_panel_commands.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_st77916.h" #define LCD_OPCODE_WRITE_CMD (0x02ULL) diff --git a/src/lcd/base/esp_lcd_st77922.c b/src/lcd/base/esp_lcd_st77922.c index 3d9c49b9..a0d1089a 100644 --- a/src/lcd/base/esp_lcd_st77922.c +++ b/src/lcd/base/esp_lcd_st77922.c @@ -18,7 +18,7 @@ #include "esp_lcd_panel_commands.h" #include "esp_log.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_st77922.h" #define LCD_OPCODE_WRITE_CMD (0x02ULL) diff --git a/src/lcd/base/esp_lcd_st7796.c b/src/lcd/base/esp_lcd_st7796.c index 7ce75275..aac6e8f7 100644 --- a/src/lcd/base/esp_lcd_st7796.c +++ b/src/lcd/base/esp_lcd_st7796.c @@ -18,7 +18,7 @@ #include "esp_log.h" #include "esp_check.h" -#include "esp_lcd_custom_types.h" +#include "esp_lcd_vendor_types.h" #include "esp_lcd_st7796.h" static const char *TAG = "st7796"; diff --git a/src/lcd/base/esp_lcd_custom_types.h b/src/lcd/base/esp_lcd_vendor_types.h similarity index 59% rename from src/lcd/base/esp_lcd_custom_types.h rename to src/lcd/base/esp_lcd_vendor_types.h index 19342b70..78fc9047 100644 --- a/src/lcd/base/esp_lcd_custom_types.h +++ b/src/lcd/base/esp_lcd_vendor_types.h @@ -6,12 +6,14 @@ #pragma once +#include "sdkconfig.h" #include "soc/soc_caps.h" #if SOC_LCD_RGB_SUPPORTED #include "esp_lcd_panel_rgb.h" #endif -#include "soc/soc_caps.h" -#include "sdkconfig.h" +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_lcd_mipi_dsi.h" +#endif #ifdef __cplusplus extern "C" { @@ -42,19 +44,32 @@ typedef struct { unsigned int init_cmds_size; /*getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_cst816s(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/FT5x06.cpp b/src/touch/FT5x06.cpp index 08e5431d..d0fd6464 100644 --- a/src/touch/FT5x06.cpp +++ b/src/touch/FT5x06.cpp @@ -40,7 +40,7 @@ bool ESP_PanelTouch_FT5x06::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_ft5x06(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_ft5x06(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/GT1151.cpp b/src/touch/GT1151.cpp index 09134db0..cf468b46 100644 --- a/src/touch/GT1151.cpp +++ b/src/touch/GT1151.cpp @@ -40,7 +40,7 @@ bool ESP_PanelTouch_GT1151::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_gt1151(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_gt1151(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/GT911.cpp b/src/touch/GT911.cpp index cc3a06f8..f0c6e4fb 100644 --- a/src/touch/GT911.cpp +++ b/src/touch/GT911.cpp @@ -50,7 +50,7 @@ bool ESP_PanelTouch_GT911::begin(void) config.driver_data = (void *)&tp_gt911_config; } - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_gt911(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_gt911(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/ST1633.cpp b/src/touch/ST1633.cpp index 3b43989a..618ea213 100644 --- a/src/touch/ST1633.cpp +++ b/src/touch/ST1633.cpp @@ -40,7 +40,7 @@ bool ESP_PanelTouch_ST1633::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_st1633(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_st1633(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/ST7123.cpp b/src/touch/ST7123.cpp index f57df649..f64e8007 100644 --- a/src/touch/ST7123.cpp +++ b/src/touch/ST7123.cpp @@ -40,7 +40,7 @@ bool ESP_PanelTouch_ST7123::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_st7123(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_st7123(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/TT21100.cpp b/src/touch/TT21100.cpp index 78dc8582..4ca5e2e3 100644 --- a/src/touch/TT21100.cpp +++ b/src/touch/TT21100.cpp @@ -40,7 +40,7 @@ bool ESP_PanelTouch_TT21100::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_tt21100(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_i2c_tt21100(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/src/touch/XPT2046.cpp b/src/touch/XPT2046.cpp index e770e12b..fe2d80fd 100644 --- a/src/touch/XPT2046.cpp +++ b/src/touch/XPT2046.cpp @@ -39,7 +39,7 @@ bool ESP_PanelTouch_XPT2046::begin(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_spi_xpt2046(bus->getHandle(), &config, &handle), false, "New driver failed"); + ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_new_spi_xpt2046(bus->getPanelIO_Handle(), &config, &handle), false, "New driver failed"); return true; } diff --git a/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp b/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp index 550d5ffa..c409b390 100644 --- a/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp +++ b/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp @@ -17,15 +17,16 @@ using namespace std; // *INDENT-OFF* +/* The following default configurations are for the board 'jingcai: ESP32_4848S040C_I_Y_3, ST7701' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#define TEST_LCD_WIDTH (800) +#define TEST_LCD_WIDTH (480) #define TEST_LCD_HEIGHT (480) // | 8-bit RGB888 | 16-bit RGB565 | #define TEST_LCD_COLOR_BITS (18) // | 24 | 16/18/24 | #define TEST_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | -#define TEST_LCD_RGB_TIMING_FREQ_HZ (16 * 1000 * 1000) +#define TEST_LCD_RGB_TIMING_FREQ_HZ (26 * 1000 * 1000) #define TEST_LCD_RGB_TIMING_HPW (10) #define TEST_LCD_RGB_TIMING_HBP (10) #define TEST_LCD_RGB_TIMING_HFP (20) @@ -55,10 +56,39 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, // {0x29, (uint8_t []){0x00}, 0, 120}, // // or - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), - ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB0, {0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB0, {0x60}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {0x32}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB2, {0x07}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB3, {0x80}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB5, {0x49}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB7, {0x85}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB8, {0x21}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x78}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x78}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE0, {0x00, 0x1B, 0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE1, {0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE2, {0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE3, {0x00, 0x00, 0x11, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE4, {0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE5, {0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE6, {0x00, 0x00, 0x11, 0x11}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE7, {0x44, 0x44}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE8, {0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xEB, {0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xEC, {0x3C, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xED, {0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x13}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE5, {0xE4}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x00}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), }; #endif @@ -66,45 +96,44 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { //////////////////// Please update the following configuration according to your board spec //////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define TEST_LCD_PIN_NUM_RGB_DISP (-1) -#define TEST_LCD_PIN_NUM_RGB_VSYNC (3) -#define TEST_LCD_PIN_NUM_RGB_HSYNC (46) -#define TEST_LCD_PIN_NUM_RGB_DE (17) -#define TEST_LCD_PIN_NUM_RGB_PCLK (9) +#define TEST_LCD_PIN_NUM_RGB_VSYNC (17) +#define TEST_LCD_PIN_NUM_RGB_HSYNC (16) +#define TEST_LCD_PIN_NUM_RGB_DE (18) +#define TEST_LCD_PIN_NUM_RGB_PCLK (21) // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| -#define TEST_LCD_PIN_NUM_RGB_DATA0 (10) // | B0 | B0-1 | B0-3 | -#define TEST_LCD_PIN_NUM_RGB_DATA1 (11) // | B1 | B2 | B4 | -#define TEST_LCD_PIN_NUM_RGB_DATA2 (12) // | B2 | B3 | B5 | -#define TEST_LCD_PIN_NUM_RGB_DATA3 (13) // | B3 | B4 | B6 | -#define TEST_LCD_PIN_NUM_RGB_DATA4 (14) // | B4 | B5 | B7 | -#define TEST_LCD_PIN_NUM_RGB_DATA5 (21) // | G0 | G0 | G0-2 | -#define TEST_LCD_PIN_NUM_RGB_DATA6 (47) // | G1 | G1 | G3 | -#define TEST_LCD_PIN_NUM_RGB_DATA7 (48) // | G2 | G2 | G4 | +#define TEST_LCD_PIN_NUM_RGB_DATA0 (4) // | B0 | B0-1 | B0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA1 (5) // | B1 | B2 | B4 | +#define TEST_LCD_PIN_NUM_RGB_DATA2 (6) // | B2 | B3 | B5 | +#define TEST_LCD_PIN_NUM_RGB_DATA3 (7) // | B3 | B4 | B6 | +#define TEST_LCD_PIN_NUM_RGB_DATA4 (15) // | B4 | B5 | B7 | +#define TEST_LCD_PIN_NUM_RGB_DATA5 (8) // | G0 | G0 | G0-2 | +#define TEST_LCD_PIN_NUM_RGB_DATA6 (20) // | G1 | G1 | G3 | +#define TEST_LCD_PIN_NUM_RGB_DATA7 (3) // | G2 | G2 | G4 | #if TEST_LCD_RGB_DATA_WIDTH > 8 -#define TEST_LCD_PIN_NUM_RGB_DATA8 (45) // | G3 | G3 | G5 | -#define TEST_LCD_PIN_NUM_RGB_DATA9 (38) // | G4 | G4 | G6 | -#define TEST_LCD_PIN_NUM_RGB_DATA10 (39) // | G5 | G5 | G7 | -#define TEST_LCD_PIN_NUM_RGB_DATA11 (40) // | R0 | R0-1 | R0-3 | -#define TEST_LCD_PIN_NUM_RGB_DATA12 (41) // | R1 | R2 | R4 | -#define TEST_LCD_PIN_NUM_RGB_DATA13 (42) // | R2 | R3 | R5 | -#define TEST_LCD_PIN_NUM_RGB_DATA14 (2) // | R3 | R4 | R6 | -#define TEST_LCD_PIN_NUM_RGB_DATA15 (1) // | R4 | R5 | R7 | +#define TEST_LCD_PIN_NUM_RGB_DATA8 (46) // | G3 | G3 | G5 | +#define TEST_LCD_PIN_NUM_RGB_DATA9 (9) // | G4 | G4 | G6 | +#define TEST_LCD_PIN_NUM_RGB_DATA10 (10) // | G5 | G5 | G7 | +#define TEST_LCD_PIN_NUM_RGB_DATA11 (11) // | R0 | R0-1 | R0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA12 (12) // | R1 | R2 | R4 | +#define TEST_LCD_PIN_NUM_RGB_DATA13 (13) // | R2 | R3 | R5 | +#define TEST_LCD_PIN_NUM_RGB_DATA14 (14) // | R3 | R4 | R6 | +#define TEST_LCD_PIN_NUM_RGB_DATA15 (0) // | R4 | R5 | R7 | #endif #define TEST_LCD_PIN_NUM_SPI_CS (39) #define TEST_LCD_PIN_NUM_SPI_SCK (48) #define TEST_LCD_PIN_NUM_SPI_SDA (47) #define TEST_LCD_PIN_NUM_RST (-1) // Set to -1 if not used -#define TEST_LCD_PIN_NUM_BK_LIGHT (-1) // Set to -1 if not used +#define TEST_LCD_PIN_NUM_BK_LIGHT (38) // Set to -1 if not used #define TEST_LCD_BK_LIGHT_ON_LEVEL (1) #define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL // *INDENT-OFF* -/* Enable or disable printing RGB refresh rate */ +/* Enable or disable printing LCD refresh rate */ #define TEST_ENABLE_PRINT_LCD_FPS (1) -/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ -#define TEST_ENABLE_ATTACH_CALLBACK (1) -#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) +#define TEST_PRINT_LCD_FPS_PERIOD_MS (1000) +#define TEST_COLOR_BAR_SHOW_TIME_MS (5000) static const char *TAG = "test_3wire_spi_rgb_lcd"; @@ -162,9 +191,9 @@ static shared_ptr init_panel_bus(void) } #if TEST_ENABLE_PRINT_LCD_FPS -#define TEST_LCD_FPS_COUNT_MAX (100) +#define TEST_LCD_FPS_COUNT_MAX (100) #ifndef millis -#define millis() (esp_timer_get_time() / 1000) +#define millis() (esp_timer_get_time() / 1000) #endif DRAM_ATTR int frame_count = 0; @@ -193,6 +222,10 @@ IRAM_ATTR bool onVsyncEndCallback(void *user_data) static void run_test(shared_ptr lcd) { + frame_count = 0; + fps = 0; + start_time = 0; + #if TEST_LCD_USE_EXTERNAL_CMD // Configure external initialization commands, should called before `init()` lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); @@ -200,9 +233,7 @@ static void run_test(shared_ptr lcd) TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); -#if TEST_LCD_PIN_NUM_RGB_DISP >= 0 TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); -#endif #if TEST_ENABLE_PRINT_LCD_FPS TEST_ASSERT_TRUE_MESSAGE( lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time), "Attach refresh callback failed" @@ -211,6 +242,17 @@ static void run_test(shared_ptr lcd) ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); + + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_COLOR_BAR_SHOW_TIME_MS); +#if TEST_ENABLE_PRINT_LCD_FPS + int i = 0; + while (i++ < TEST_COLOR_BAR_SHOW_TIME_MS / TEST_PRINT_LCD_FPS_PERIOD_MS) { + ESP_LOGI(TAG, "FPS: %d", fps); + vTaskDelay(pdMS_TO_TICKS(TEST_PRINT_LCD_FPS_PERIOD_MS)); + } +#else + vTaskDelay(pdMS_TO_TICKS(TEST_COLOR_BAR_SHOW_TIME_MS)); +#endif } #define CREATE_LCD(name, panel_bus) \ diff --git a/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults index f627e6eb..c97863dd 100644 --- a/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults +++ b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults @@ -1,7 +1,2 @@ -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MODE_OCT=y -CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y -CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y CONFIG_ESP_TASK_WDT_INIT=n CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults.esp32s3 b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults.esp32s3 new file mode 100644 index 00000000..b7977b69 --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults.esp32s3 @@ -0,0 +1,10 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y + +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y diff --git a/test_apps/lcd/qspi/main/test_qspi_lcd.cpp b/test_apps/lcd/qspi/main/test_qspi_lcd.cpp index e69d530a..a3aa45de 100644 --- a/test_apps/lcd/qspi/main/test_qspi_lcd.cpp +++ b/test_apps/lcd/qspi/main/test_qspi_lcd.cpp @@ -14,6 +14,7 @@ using namespace std; +/* The following default configurations are for the board 'Espressif: Custom, ST77922' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -21,7 +22,7 @@ using namespace std; #define TEST_LCD_HEIGHT (300) #define TEST_LCD_COLOR_BITS (16) #define TEST_LCD_SPI_FREQ_HZ (40 * 1000 * 1000) -#define TEST_LCD_USE_EXTERNAL_CMD (1) +#define TEST_LCD_USE_EXTERNAL_CMD (0) #if TEST_LCD_USE_EXTERNAL_CMD /** * LCD initialization commands. @@ -125,12 +126,15 @@ static void run_test(shared_ptr lcd) TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); #if TEST_ENABLE_ATTACH_CALLBACK TEST_ASSERT_TRUE_MESSAGE( - lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" ); #endif ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); + + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_COLOR_BAR_SHOW_TIME_MS); + vTaskDelay(pdMS_TO_TICKS(TEST_COLOR_BAR_SHOW_TIME_MS)); } #define CREATE_LCD(name, panel_bus) \ diff --git a/test_apps/lcd/rgb/main/test_rgb_lcd.cpp b/test_apps/lcd/rgb/main/test_rgb_lcd.cpp index 5c837770..b34d926f 100644 --- a/test_apps/lcd/rgb/main/test_rgb_lcd.cpp +++ b/test_apps/lcd/rgb/main/test_rgb_lcd.cpp @@ -17,21 +17,22 @@ using namespace std; // *INDENT-OFF* +/* The following default configurations are for the board 'Espressif: ESP32_S3_LCD_EV_BOARD_2_V1_5, ST7262' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define TEST_LCD_WIDTH (800) #define TEST_LCD_HEIGHT (480) // | 8-bit RGB888 | 16-bit RGB565 | -#define TEST_LCD_COLOR_BITS (18) // | 24 | 16/18/24 | +#define TEST_LCD_COLOR_BITS (16) // | 24 | 16/18/24 | #define TEST_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define TEST_LCD_RGB_TIMING_FREQ_HZ (16 * 1000 * 1000) -#define TEST_LCD_RGB_TIMING_HPW (10) -#define TEST_LCD_RGB_TIMING_HBP (10) -#define TEST_LCD_RGB_TIMING_HFP (20) -#define TEST_LCD_RGB_TIMING_VPW (10) -#define TEST_LCD_RGB_TIMING_VBP (10) -#define TEST_LCD_RGB_TIMING_VFP (10) +#define TEST_LCD_RGB_TIMING_HPW (40) +#define TEST_LCD_RGB_TIMING_HBP (40) +#define TEST_LCD_RGB_TIMING_HFP (40) +#define TEST_LCD_RGB_TIMING_VPW (23) +#define TEST_LCD_RGB_TIMING_VBP (32) +#define TEST_LCD_RGB_TIMING_VFP (13) #define TEST_LCD_RGB_BOUNCE_BUFFER_SIZE (TEST_LCD_WIDTH * 10) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -50,8 +51,8 @@ using namespace std; #define TEST_LCD_PIN_NUM_RGB_DATA3 (13) // | B3 | B4 | B6 | #define TEST_LCD_PIN_NUM_RGB_DATA4 (14) // | B4 | B5 | B7 | #define TEST_LCD_PIN_NUM_RGB_DATA5 (21) // | G0 | G0 | G0-2 | -#define TEST_LCD_PIN_NUM_RGB_DATA6 (47) // | G1 | G1 | G3 | -#define TEST_LCD_PIN_NUM_RGB_DATA7 (48) // | G2 | G2 | G4 | +#define TEST_LCD_PIN_NUM_RGB_DATA6 (8) // | G1 | G1 | G3 | +#define TEST_LCD_PIN_NUM_RGB_DATA7 (18) // | G2 | G2 | G4 | #if TEST_LCD_RGB_DATA_WIDTH > 8 #define TEST_LCD_PIN_NUM_RGB_DATA8 (45) // | G3 | G3 | G5 | #define TEST_LCD_PIN_NUM_RGB_DATA9 (38) // | G4 | G4 | G6 | @@ -69,11 +70,12 @@ using namespace std; // *INDENT-OFF* -/* Enable or disable printing RGB refresh rate */ +/* Enable or disable printing LCD refresh rate */ #define TEST_ENABLE_PRINT_LCD_FPS (1) +#define TEST_PRINT_LCD_FPS_PERIOD_MS (1000) /* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ #define TEST_ENABLE_ATTACH_CALLBACK (1) -#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) +#define TEST_COLOR_BAR_SHOW_TIME_MS (5000) static const char *TAG = "test_rgb_lcd"; @@ -129,9 +131,9 @@ static shared_ptr init_panel_bus(void) } #if TEST_ENABLE_PRINT_LCD_FPS -#define TEST_LCD_FPS_COUNT_MAX (100) +#define TEST_LCD_FPS_COUNT_MAX (100) #ifndef millis -#define millis() (esp_timer_get_time() / 1000) +#define millis() (esp_timer_get_time() / 1000) #endif DRAM_ATTR int frame_count = 0; @@ -156,7 +158,7 @@ IRAM_ATTR bool onVsyncEndCallback(void *user_data) return false; } -#endif +#endif /* TEST_ENABLE_PRINT_LCD_FPS */ static void run_test(shared_ptr lcd) { @@ -167,9 +169,7 @@ static void run_test(shared_ptr lcd) TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); -#if TEST_LCD_PIN_NUM_RGB_DISP >= 0 TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); -#endif #if TEST_ENABLE_PRINT_LCD_FPS TEST_ASSERT_TRUE_MESSAGE( lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time), "Attach refresh callback failed" @@ -178,6 +178,17 @@ static void run_test(shared_ptr lcd) ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); + + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_COLOR_BAR_SHOW_TIME_MS); +#if TEST_ENABLE_PRINT_LCD_FPS + int i = 0; + while (i++ < TEST_COLOR_BAR_SHOW_TIME_MS / TEST_PRINT_LCD_FPS_PERIOD_MS) { + ESP_LOGI(TAG, "FPS: %d", fps); + vTaskDelay(pdMS_TO_TICKS(TEST_PRINT_LCD_FPS_PERIOD_MS)); + } +#else + vTaskDelay(pdMS_TO_TICKS(TEST_COLOR_BAR_SHOW_TIME_MS)); +#endif } #define CREATE_LCD(name, panel_bus) \ diff --git a/test_apps/lcd/rgb/sdkconfig.defaults b/test_apps/lcd/rgb/sdkconfig.defaults index f627e6eb..c97863dd 100644 --- a/test_apps/lcd/rgb/sdkconfig.defaults +++ b/test_apps/lcd/rgb/sdkconfig.defaults @@ -1,7 +1,2 @@ -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MODE_OCT=y -CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y -CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y CONFIG_ESP_TASK_WDT_INIT=n CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/rgb/sdkconfig.defaults.esp32s3 b/test_apps/lcd/rgb/sdkconfig.defaults.esp32s3 new file mode 100644 index 00000000..f71f272f --- /dev/null +++ b/test_apps/lcd/rgb/sdkconfig.defaults.esp32s3 @@ -0,0 +1,16 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lcd/spi/main/test_spi_lcd.cpp b/test_apps/lcd/spi/main/test_spi_lcd.cpp index 61cb6454..5dd4a348 100644 --- a/test_apps/lcd/spi/main/test_spi_lcd.cpp +++ b/test_apps/lcd/spi/main/test_spi_lcd.cpp @@ -14,6 +14,7 @@ using namespace std; +/* The following default configurations are for the board 'Espressif: ESP32_S3_BOX_3, ILI9341' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -43,10 +44,22 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, // {0x29, (uint8_t []){0x00}, 0, 120}, // // or - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), - ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC8, {0xFF, 0x93, 0x42}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x0E, 0x0E}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC5, {0xD0}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB4, {0x02}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE0, { + 0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, + 0x17, 0x17, 0x0F + }), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xE1, { + 0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, + 0x37, 0x38, 0x0F + }), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB1, {00, 0x1B}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xB7, {0x06}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(100, 0x11), }; #endif @@ -56,10 +69,10 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { #define TEST_LCD_PIN_NUM_SPI_CS (5) #define TEST_LCD_PIN_NUM_SPI_DC (4) #define TEST_LCD_PIN_NUM_SPI_SCK (7) -#define TEST_LCD_PIN_NUM_SPI_SDA (6) -#define TEST_LCD_PIN_NUM_SPI_SDO (-1) -#define TEST_LCD_PIN_NUM_RST (-1) // Set to -1 if not used -#define TEST_LCD_PIN_NUM_BK_LIGHT (45) // Set to -1 if not used +#define TEST_LCD_PIN_NUM_SPI_MOSI (6) +#define TEST_LCD_PIN_NUM_SPI_MISO (-1) +#define TEST_LCD_PIN_NUM_RST (48) // Set to -1 if not used +#define TEST_LCD_PIN_NUM_BK_LIGHT (47) // Set to -1 if not used #define TEST_LCD_BK_LIGHT_ON_LEVEL (1) #define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL @@ -92,7 +105,7 @@ static shared_ptr init_panel_bus(void) ESP_LOGI(TAG, "Create LCD bus"); shared_ptr panel_bus = make_shared( TEST_LCD_PIN_NUM_SPI_CS, TEST_LCD_PIN_NUM_SPI_DC, TEST_LCD_PIN_NUM_SPI_SCK, - TEST_LCD_PIN_NUM_SPI_SDA, TEST_LCD_PIN_NUM_SPI_SDO + TEST_LCD_PIN_NUM_SPI_MOSI, TEST_LCD_PIN_NUM_SPI_MISO ); TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); @@ -117,18 +130,25 @@ static void run_test(shared_ptr lcd) // Configure external initialization commands, should called before `init()` lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); #endif + lcd->configColorRgbOrder(true); + lcd->configResetActiveLevel(1); TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->mirrorX(true), "LCD mirror X failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->mirrorY(true), "LCD mirror Y failed"); TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); #if TEST_ENABLE_ATTACH_CALLBACK TEST_ASSERT_TRUE_MESSAGE( - lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" ); #endif ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); + + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_COLOR_BAR_SHOW_TIME_MS); + vTaskDelay(pdMS_TO_TICKS(TEST_COLOR_BAR_SHOW_TIME_MS)); } #define CREATE_LCD(name, panel_bus) \ @@ -153,6 +173,7 @@ static void run_test(shared_ptr lcd) */ CREATE_TEST_CASE(GC9A01) CREATE_TEST_CASE(GC9B71) +CREATE_TEST_CASE(ILI9341) CREATE_TEST_CASE(NV3022B) CREATE_TEST_CASE(SH8601) CREATE_TEST_CASE(SPD2010) diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.cpp b/test_apps/lvgl_port/main/lvgl_port_v8.cpp index 9478c387..91dcc27e 100644 --- a/test_apps/lvgl_port/main/lvgl_port_v8.cpp +++ b/test_apps/lvgl_port/main/lvgl_port_v8.cpp @@ -19,8 +19,8 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) static void *fbs[2] = { NULL }; if (next_fb == NULL) { - fbs[0] = lcd->getRgbBufferByIndex(0); - fbs[1] = lcd->getRgbBufferByIndex(1); + 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]; @@ -458,20 +458,20 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // 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 - lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); - buf[0] = lcd->getRgbBufferByIndex(1); - buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); + buf[0] = lcd->getFrameBufferByIndex(1); + buf[1] = lcd->getFrameBufferByIndex(2); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf[1]; #elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) - buf[0] = lcd->getRgbBufferByIndex(2); + 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++) { - buf[i] = lcd->getRgbBufferByIndex(i); + buf[i] = lcd->getFrameBufferByIndex(i); } #endif diff --git a/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 index adeda816..63b0828c 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 +++ b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -4,8 +4,15 @@ CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box index 6e15d03d..195ef439 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 index efa09e4b..412b6e76 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta index e859d720..0e17c514 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite index 5c3fe36e..39859b5a 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_LITE=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye index 236bdd7c..46308133 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_EYE=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 index ce44a69c..9006d223 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_KORVO_2=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board index 2f555ac0..cfaa1708 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -4,8 +4,15 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 index c2794b33..c446475c 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -4,8 +4,15 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 index 4eff920e..ed585da8 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -4,8 +4,15 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 index 057721b7..1c71be3a 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -4,8 +4,15 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 index 200614be..41a569bd 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -4,8 +4,15 @@ CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 index e8126d4b..0aaf8323 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 @@ -4,6 +4,13 @@ CONFIG_BOARD_M5STACK_M5CORES3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 index 2dc3c165..e643bfa1 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -4,8 +4,13 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y -CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 index 55839aba..7a15686b 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -4,6 +4,15 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 index 0b31c5f1..3d7859f0 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -4,8 +4,15 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/panel/main/test_app_main.c b/test_apps/panel/main/test_app_main.c index bf8ef112..cb221e0d 100644 --- a/test_apps/panel/main/test_app_main.c +++ b/test_apps/panel/main/test_app_main.c @@ -11,7 +11,7 @@ #include "unity_test_runner.h" // Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) +#define TEST_MEMORY_LEAK_THRESHOLD (-500) static size_t before_free_8bit; static size_t before_free_32bit; diff --git a/test_apps/panel/main/test_panel.cpp b/test_apps/panel/main/test_panel.cpp index f545cf13..8bc0c810 100644 --- a/test_apps/panel/main/test_panel.cpp +++ b/test_apps/panel/main/test_panel.cpp @@ -13,7 +13,7 @@ #include "ESP_Panel_Library.h" #define TEST_LCD_ENABLE_ATTACH_CALLBACK (0) -#define TEST_LCD_SHOW_TIME_MS (3000) +#define TEST_LCD_SHOW_TIME_MS (5000) #define TEST_TOUCH_ENABLE_ATTACH_CALLBACK (0) #define TEST_TOUCH_READ_POINTS_NUM (5) @@ -74,7 +74,6 @@ TEST_CASE("Test panel to draw color bar and read touch", "[panel]") TEST_ASSERT_TRUE_MESSAGE( lcd->colorBarTest(panel->getLcdWidth(), panel->getLcdHeight()), "LCD color bar test failed" ); - delay(TEST_LCD_SHOW_TIME_MS); } else { ESP_LOGI(TAG, "LCD is not available"); } @@ -84,6 +83,11 @@ TEST_CASE("Test panel to draw color bar and read touch", "[panel]") TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); } + if (lcd != nullptr) { + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_LCD_SHOW_TIME_MS); + vTaskDelay(pdMS_TO_TICKS(TEST_LCD_SHOW_TIME_MS)); + } + if (touch != nullptr) { #if TEST_LCD_ENABLE_ATTACH_CALLBACK && (ESP_PANEL_TOUCH_IO_INT >= 0) TEST_ASSERT_TRUE_MESSAGE( diff --git a/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 index 7d488c6c..bdd65a14 100644 --- a/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 +++ b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -4,6 +4,13 @@ CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box index 6e15d03d..195ef439 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 index efa09e4b..412b6e76 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta index e859d720..0e17c514 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite index 5c3fe36e..39859b5a 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_BOX_LITE=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye index 236bdd7c..46308133 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_EYE=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 index ce44a69c..9006d223 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_KORVO_2=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board index 0246bddb..89db6e7d 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 index 71375001..5b412571 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 index 605f2d29..3d206a48 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 index 3186a9a8..7acd274c 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 index 7904e91b..afa2870c 100644 --- a/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 +++ b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -4,6 +4,13 @@ CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5core3 b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 index e8126d4b..0aaf8323 100644 --- a/test_apps/panel/sdkconfig.ci.m5stack_m5core3 +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 @@ -4,6 +4,13 @@ CONFIG_BOARD_M5STACK_M5CORES3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5dial b/test_apps/panel/sdkconfig.ci.m5stack_m5dial index 89f97068..ac51725f 100644 --- a/test_apps/panel/sdkconfig.ci.m5stack_m5dial +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5dial @@ -4,6 +4,13 @@ CONFIG_BOARD_M5STACK_M5DIAL=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 index 7848f3e2..e643bfa1 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -4,6 +4,13 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 index 55839aba..a8c6105b 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -4,6 +4,13 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 index 9cdf5a34..b6f152fd 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -4,6 +4,13 @@ CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# Used in conjunction with "RGB Bounce Buffer" +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/test_apps/touch/i2c/main/test_i2c_touch.cpp b/test_apps/touch/i2c/main/test_i2c_touch.cpp index 741d7272..ea205fba 100644 --- a/test_apps/touch/i2c/main/test_i2c_touch.cpp +++ b/test_apps/touch/i2c/main/test_i2c_touch.cpp @@ -14,6 +14,7 @@ using namespace std; +/* The following default configurations are for the board 'Espressif: ESP32_S3_LCD_EV_BOARD_V1_5, GT1151' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your touch_device spec //////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -29,11 +30,11 @@ using namespace std; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your board spec //////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#define TEST_TOUCH_PIN_NUM_I2C_SCL (10) -#define TEST_TOUCH_PIN_NUM_I2C_SDA (9) -#define TEST_TOUCH_PIN_NUM_RST (13) // Set to `-1` if not used +#define TEST_TOUCH_PIN_NUM_I2C_SCL (48) +#define TEST_TOUCH_PIN_NUM_I2C_SDA (47) +#define TEST_TOUCH_PIN_NUM_RST (-1) // Set to `-1` if not used // For GT911, the RST pin is also used to configure the I2C address -#define TEST_TOUCH_PIN_NUM_INT (14) // Set to `-1` if not used +#define TEST_TOUCH_PIN_NUM_INT (-1) // Set to `-1` if not used // For GT911, the INT pin is also used to configure the I2C address #define TEST_READ_TOUCH_DELAY_MS (30) @@ -60,6 +61,8 @@ static void run_test(shared_ptr touch_device) touch_device->attachInterruptCallback(onTouchInterruptCallback, NULL); #endif + ESP_LOGI(TAG, "Reading touch_device point..."); + uint32_t t = 0; while (t++ < TEST_READ_TOUCH_TIME_MS / TEST_READ_TOUCH_DELAY_MS) { ESP_PanelTouchPoint point[TEST_TOUCH_READ_POINTS_NUM]; diff --git a/test_apps/touch/spi/main/test_spi_touch.cpp b/test_apps/touch/spi/main/test_spi_touch.cpp index 319b81da..3eef989c 100644 --- a/test_apps/touch/spi/main/test_spi_touch.cpp +++ b/test_apps/touch/spi/main/test_spi_touch.cpp @@ -14,6 +14,7 @@ using namespace std; +/* The following default configurations are for the board 'Espressif: Custom, XPT2046' */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your touch_device spec //////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -56,6 +57,8 @@ static void run_test(shared_ptr touch_device) touch_device->attachInterruptCallback(onTouchInterruptCallback, NULL); #endif + ESP_LOGI(TAG, "Reading touch_device point..."); + uint32_t t = 0; while (t++ < TEST_READ_TOUCH_TIME_MS / TEST_READ_TOUCH_DELAY_MS) { ESP_PanelTouchPoint point[TEST_TOUCH_READ_POINTS_NUM]; From af89b712350c91981500c53ff7391ed34776daef Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Thu, 7 Nov 2024 16:38:57 +0800 Subject: [PATCH 02/11] feat(lcd): add LCD controller EK79007 --- src/ESP_Panel_Library.h | 1 + src/lcd/EK79007.cpp | 58 +++++++ src/lcd/EK79007.h | 59 +++++++ src/lcd/base/esp_lcd_ek79007.c | 284 +++++++++++++++++++++++++++++++++ src/lcd/base/esp_lcd_ek79007.h | 93 +++++++++++ 5 files changed, 495 insertions(+) create mode 100644 src/lcd/EK79007.cpp create mode 100644 src/lcd/EK79007.h create mode 100644 src/lcd/base/esp_lcd_ek79007.c create mode 100644 src/lcd/base/esp_lcd_ek79007.h diff --git a/src/ESP_Panel_Library.h b/src/ESP_Panel_Library.h index 8180ba73..0799085a 100644 --- a/src/ESP_Panel_Library.h +++ b/src/ESP_Panel_Library.h @@ -25,6 +25,7 @@ /* LCD */ #include "lcd/ESP_PanelLcd.h" +#include "lcd/EK79007.h" #include "lcd/EK9716B.h" #include "lcd/GC9503.h" #include "lcd/GC9A01.h" diff --git a/src/lcd/EK79007.cpp b/src/lcd/EK79007.cpp new file mode 100644 index 00000000..0d94fe67 --- /dev/null +++ b/src/lcd/EK79007.cpp @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLog.h" +#include "EK79007.h" + +static const char *TAG = "EK79007_CPP"; + +ESP_PanelLcd_EK79007::ESP_PanelLcd_EK79007(ESP_PanelBus *bus, uint8_t color_bits, int rst_io): + ESP_PanelLcd(bus, color_bits, rst_io) +{ + disabled_functions.display_on_off = 1; + disabled_functions.set_gap = 1; + disabled_functions.swap_xy = 1; +} + +ESP_PanelLcd_EK79007::ESP_PanelLcd_EK79007(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config): + ESP_PanelLcd(bus, panel_config) +{ + disabled_functions.display_on_off = 1; + disabled_functions.set_gap = 1; + disabled_functions.swap_xy = 1; +} + +ESP_PanelLcd_EK79007::~ESP_PanelLcd_EK79007() +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + if (handle == NULL) { + goto end; + } + + if (!del()) { + ESP_LOGE(TAG, "Delete device failed"); + } + +end: + ESP_LOGD(TAG, "Destroyed"); +} + +bool ESP_PanelLcd_EK79007::init(void) +{ + ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_new_panel_ek79007(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed" + ); + + return true; +} + +#endif diff --git a/src/lcd/EK79007.h b/src/lcd/EK79007.h new file mode 100644 index 00000000..6a126465 --- /dev/null +++ b/src/lcd/EK79007.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLcd.h" +#include "base/esp_lcd_vendor_types.h" +#include "base/esp_lcd_ek79007.h" + +/** + * @brief EK79007 LCD device object class + * + * @note This class is a derived class of `ESP_PanelLcd`, user can use it directly + */ +class ESP_PanelLcd_EK79007: public ESP_PanelLcd { +public: + /** + * @brief Construct a new LCD device in a simple way, the `init()` function should be called after this function + * + * @note This function uses some default values to config the LCD device, please use `config*()` functions to + * change them + * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier + * for initialization sequence code, and use `configVendorCommands()` to configure + * + * @param bus Pointer of panel bus + * @param color_bits Bits per pixel (16/18/24) + * @param rst_io Reset pin, set to -1 if no use + */ + ESP_PanelLcd_EK79007(ESP_PanelBus *bus, uint8_t color_bits, int rst_io = -1); + + /** + * @brief Construct a new LCD in a complex way, the `init()` function should be called after this function + * + * @param bus Pointer of panel bus + * @param panel_config LCD device configuration + */ + ESP_PanelLcd_EK79007(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config); + + /** + * @brief Destroy the LCD device + * + */ + ~ESP_PanelLcd_EK79007() override; + + /** + * @brief Initialize the LCD device, the `begin()` function should be called after this function + * + * @note This function typically calls `esp_lcd_new_panel_*()` to create the LCD panel handle + * + * @return true if success, otherwise false + */ + bool init(void) override; +}; +#endif diff --git a/src/lcd/base/esp_lcd_ek79007.c b/src/lcd/base/esp_lcd_ek79007.c new file mode 100644 index 00000000..be96d391 --- /dev/null +++ b/src/lcd/base/esp_lcd_ek79007.c @@ -0,0 +1,284 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLog.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_lcd_panel_commands.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_log.h" +#include "esp_lcd_ek79007.h" + +#include "esp_lcd_vendor_types.h" + +#define EK79007_PAD_CONTROL (0xB2) +#define EK79007_DSI_2_LANE (0x10) +#define EK79007_DSI_4_LANE (0x00) + +#define EK79007_CMD_SHLR_BIT (1ULL << 0) +#define EK79007_CMD_UPDN_BIT (1ULL << 1) +#define EK79007_MDCTL_VALUE_DEFAULT (0x01) + +typedef struct { + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + const esp_lcd_panel_vendor_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + uint8_t lane_num; + struct { + unsigned int reset_level: 1; + } flags; + // To save the original functions of MIPI DPI panel + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*init)(esp_lcd_panel_t *panel); +} ek79007_panel_t; + +static const char *TAG = "ek79007"; + +static esp_err_t panel_ek79007_send_init_cmds(ek79007_panel_t *ek79007); + +static esp_err_t panel_ek79007_del(esp_lcd_panel_t *panel); +static esp_err_t panel_ek79007_init(esp_lcd_panel_t *panel); +static esp_err_t panel_ek79007_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_ek79007_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_ek79007_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); + +esp_err_t esp_lcd_new_panel_ek79007(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_EK79007_VER_MAJOR, ESP_LCD_EK79007_VER_MINOR, + ESP_LCD_EK79007_VER_PATCH); + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + esp_lcd_panel_vendor_config_t *vendor_config = (esp_lcd_panel_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG, + "invalid vendor config"); + + esp_err_t ret = ESP_OK; + ek79007_panel_t *ek79007 = (ek79007_panel_t *)calloc(1, sizeof(ek79007_panel_t)); + ESP_RETURN_ON_FALSE(ek79007, ESP_ERR_NO_MEM, TAG, "no mem for ek79007 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + ek79007->io = io; + ek79007->init_cmds = vendor_config->init_cmds; + ek79007->init_cmds_size = vendor_config->init_cmds_size; + ek79007->lane_num = vendor_config->mipi_config.lane_num; + ek79007->reset_gpio_num = panel_dev_config->reset_gpio_num; + ek79007->flags.reset_level = panel_dev_config->flags.reset_active_high; + ek79007->madctl_val = EK79007_MDCTL_VALUE_DEFAULT; + + // Create MIPI DPI panel + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG, + "create MIPI DPI panel failed"); + ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel); + + // Save the original functions of MIPI DPI panel + ek79007->del = (*ret_panel)->del; + ek79007->init = (*ret_panel)->init; + // Overwrite the functions of MIPI DPI panel + (*ret_panel)->del = panel_ek79007_del; + (*ret_panel)->init = panel_ek79007_init; + (*ret_panel)->reset = panel_ek79007_reset; + (*ret_panel)->mirror = panel_ek79007_mirror; + (*ret_panel)->invert_color = panel_ek79007_invert_color; + (*ret_panel)->user_data = ek79007; + ESP_LOGD(TAG, "new ek79007 panel @%p", ek79007); + + return ESP_OK; + +err: + if (ek79007) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(ek79007); + } + return ret; +} + +static const esp_lcd_panel_vendor_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0x80, (uint8_t []){0x8B}, 1, 0}, + {0x81, (uint8_t []){0x78}, 1, 0}, + {0x82, (uint8_t []){0x84}, 1, 0}, + {0x83, (uint8_t []){0x88}, 1, 0}, + {0x84, (uint8_t []){0xA8}, 1, 0}, + {0x85, (uint8_t []){0xE3}, 1, 0}, + {0x86, (uint8_t []){0x88}, 1, 0}, + {0x11, (uint8_t []){0x00}, 0, 120}, +}; + +static esp_err_t panel_ek79007_send_init_cmds(ek79007_panel_t *ek79007) +{ + esp_lcd_panel_io_handle_t io = ek79007->io; + const esp_lcd_panel_vendor_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + uint8_t lane_command = EK79007_DSI_2_LANE; + bool is_cmd_overwritten = false; + + switch (ek79007->lane_num) { + case 0: + case 2: + lane_command = EK79007_DSI_2_LANE; + break; + case 4: + lane_command = EK79007_DSI_4_LANE; + break; + default: + ESP_LOGE(TAG, "Invalid lane number %d", ek79007->lane_num); + return ESP_ERR_INVALID_ARG; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, EK79007_PAD_CONTROL, (uint8_t[]) { + lane_command, + }, 1), TAG, "send command failed"); + + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (ek79007->init_cmds) { + init_cmds = ek79007->init_cmds; + init_cmds_size = ek79007->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(esp_lcd_panel_vendor_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + if (init_cmds[i].data_bytes > 0) { + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + ek79007->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + is_cmd_overwritten = false; + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + } + + // Send command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_ek79007_del(esp_lcd_panel_t *panel) +{ + ek79007_panel_t *ek79007 = (ek79007_panel_t *)panel->user_data; + + if (ek79007->reset_gpio_num >= 0) { + gpio_reset_pin(ek79007->reset_gpio_num); + } + // Delete MIPI DPI panel + ek79007->del(panel); + ESP_LOGD(TAG, "del ek79007 panel @%p", ek79007); + free(ek79007); + + return ESP_OK; +} + +static esp_err_t panel_ek79007_init(esp_lcd_panel_t *panel) +{ + ek79007_panel_t *ek79007 = (ek79007_panel_t *)panel->user_data; + + ESP_RETURN_ON_ERROR(panel_ek79007_send_init_cmds(ek79007), TAG, "send init commands failed"); + ESP_RETURN_ON_ERROR(ek79007->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_ek79007_reset(esp_lcd_panel_t *panel) +{ + ek79007_panel_t *ek79007 = (ek79007_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ek79007->io; + + // Perform hardware reset + if (ek79007->reset_gpio_num >= 0) { + gpio_set_level(ek79007->reset_gpio_num, ek79007->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(ek79007->reset_gpio_num, !ek79007->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(20)); + } else if (io) { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); + } + + return ESP_OK; +} + +static esp_err_t panel_ek79007_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + ek79007_panel_t *ek79007 = (ek79007_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ek79007->io; + uint8_t madctl_val = ek79007->madctl_val; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + // Control mirror through LCD command + if (mirror_x) { + madctl_val |= EK79007_CMD_SHLR_BIT; + } else { + madctl_val &= ~EK79007_CMD_SHLR_BIT; + } + if (mirror_y) { + madctl_val |= EK79007_CMD_UPDN_BIT; + } else { + madctl_val &= ~EK79007_CMD_UPDN_BIT; + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) { + madctl_val + }, 1), TAG, "send command failed"); + ek79007->madctl_val = madctl_val; + + return ESP_OK; +} + +static esp_err_t panel_ek79007_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + ek79007_panel_t *ek79007 = (ek79007_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ek79007->io; + uint8_t command = 0; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + + return ESP_OK; +} +#endif /* SOC_MIPI_DSI_SUPPORTED */ diff --git a/src/lcd/base/esp_lcd_ek79007.h b/src/lcd/base/esp_lcd_ek79007.h new file mode 100644 index 00000000..b367b991 --- /dev/null +++ b/src/lcd/base/esp_lcd_ek79007.h @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_mipi_dsi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_LCD_EK79007_VER_MAJOR (1) +#define ESP_LCD_EK79007_VER_MINOR (0) +#define ESP_LCD_EK79007_VER_PATCH (0) + +/** + * @brief Create LCD panel for model EK79007 + * + * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code. + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config General panel device configuration + * @param[out] ret_panel Returned LCD panel handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + * - Otherwise on fail + */ +esp_err_t esp_lcd_new_panel_ek79007(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel); + +/** + * @brief MIPI DSI bus configuration structure + * + */ +#define EK79007_PANEL_BUS_DSI_2CH_CONFIG() \ + { \ + .bus_id = 0, \ + .num_data_lanes = 2, \ + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \ + .lane_bit_rate_mbps = 1000, \ + } + +/** + * @brief MIPI DBI panel IO configuration structure + * + */ +#define EK79007_PANEL_IO_DBI_CONFIG() \ + { \ + .virtual_channel = 0, \ + .lcd_cmd_bits = 8, \ + .lcd_param_bits = 8, \ + } + +/** + * @brief MIPI DPI configuration structure + * + * @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch) + * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch) + * + */ +#define EK79007_1024_600_PANEL_60HZ_CONFIG(px_format) \ + { \ + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \ + .dpi_clock_freq_mhz = 52, \ + .virtual_channel = 0, \ + .pixel_format = px_format, \ + .num_fbs = 1, \ + .video_timing = { \ + .h_size = 1024, \ + .v_size = 600, \ + .hsync_back_porch = 160, \ + .hsync_pulse_width = 10, \ + .hsync_front_porch = 160, \ + .vsync_back_porch = 23, \ + .vsync_pulse_width = 1, \ + .vsync_front_porch = 12, \ + }, \ + .flags.use_dma2d = true, \ + } +#endif /* SOC_MIPI_DSI_SUPPORTED */ + +#ifdef __cplusplus +} +#endif From ea0c944082662604c3a264b899ece8e8289f5181 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Thu, 7 Nov 2024 16:42:45 +0800 Subject: [PATCH 03/11] feat(test_apps): add MIPI-DSI LCD --- test_apps/lcd/mipi_dsi/CMakeLists.txt | 5 + test_apps/lcd/mipi_dsi/main/CMakeLists.txt | 5 + test_apps/lcd/mipi_dsi/main/idf_component.yml | 9 + test_apps/lcd/mipi_dsi/main/test_app_main.c | 63 +++++ .../lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp | 239 ++++++++++++++++++ test_apps/lcd/mipi_dsi/sdkconfig.defaults | 2 + .../lcd/mipi_dsi/sdkconfig.defaults.esp32p4 | 5 + 7 files changed, 328 insertions(+) create mode 100644 test_apps/lcd/mipi_dsi/CMakeLists.txt create mode 100644 test_apps/lcd/mipi_dsi/main/CMakeLists.txt create mode 100644 test_apps/lcd/mipi_dsi/main/idf_component.yml create mode 100644 test_apps/lcd/mipi_dsi/main/test_app_main.c create mode 100644 test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp create mode 100644 test_apps/lcd/mipi_dsi/sdkconfig.defaults create mode 100644 test_apps/lcd/mipi_dsi/sdkconfig.defaults.esp32p4 diff --git a/test_apps/lcd/mipi_dsi/CMakeLists.txt b/test_apps/lcd/mipi_dsi/CMakeLists.txt new file mode 100644 index 00000000..0821d1fb --- /dev/null +++ b/test_apps/lcd/mipi_dsi/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mipi_dsi_lcd_test) diff --git a/test_apps/lcd/mipi_dsi/main/CMakeLists.txt b/test_apps/lcd/mipi_dsi/main/CMakeLists.txt new file mode 100644 index 00000000..c6527dc6 --- /dev/null +++ b/test_apps/lcd/mipi_dsi/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_mipi_dsi_lcd.cpp" + PRIV_REQUIRES esp_lcd driver esp_timer + WHOLE_ARCHIVE +) diff --git a/test_apps/lcd/mipi_dsi/main/idf_component.yml b/test_apps/lcd/mipi_dsi/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/lcd/mipi_dsi/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/lcd/mipi_dsi/main/test_app_main.c b/test_apps/lcd/mipi_dsi/main/test_app_main.c new file mode 100644 index 00000000..5a550e08 --- /dev/null +++ b/test_apps/lcd/mipi_dsi/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + /** + * __ __ ______ _______ ______ _______ ______ ______ __ ______ _______ + * | \ / \| \| \| \ | \ / \ | \ | \ / \ | \ + * | $$\ / $$ \$$$$$$| $$$$$$$\\$$$$$$ | $$$$$$$\| $$$$$$\ \$$$$$$ | $$ | $$$$$$\| $$$$$$$\ + * | $$$\ / $$$ | $$ | $$__/ $$ | $$ ______ | $$ | $$| $$___\$$ | $$ | $$ | $$ \$$| $$ | $$ + * | $$$$\ $$$$ | $$ | $$ $$ | $$| \| $$ | $$ \$$ \ | $$ | $$ | $$ | $$ | $$ + * | $$\$$ $$ $$ | $$ | $$$$$$$ | $$ \$$$$$$| $$ | $$ _\$$$$$$\ | $$ | $$ | $$ __ | $$ | $$ + * | $$ \$$$| $$ _| $$_ | $$ _| $$_ | $$__/ $$| \__| $$ _| $$_ | $$_____| $$__/ \| $$__/ $$ + * | $$ \$ | $$| $$ \| $$ | $$ \ | $$ $$ \$$ $$| $$ \ | $$ \\$$ $$| $$ $$ + * \$$ \$$ \$$$$$$ \$$ \$$$$$$ \$$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$$$ \$$$$$$ \$$$$$$$ + */ + printf(" __ __ ______ _______ ______ _______ ______ ______ __ ______ _______\r\n"); + printf("| \\ / \\| \\| \\| \\ | \\ / \\ | \\ | \\ / \\ | \\\r\n"); + printf("| $$\\ / $$ \\$$$$$$| $$$$$$$\\\\$$$$$$ | $$$$$$$\\| $$$$$$\\ \\$$$$$$ | $$ | $$$$$$\\| $$$$$$$\\\r\n"); + printf("| $$$\\ / $$$ | $$ | $$__/ $$ | $$ ______ | $$ | $$| $$___\\$$ | $$ | $$ | $$ \\$$| $$ | $$\r\n"); + printf("| $$$$\\ $$$$ | $$ | $$ $$ | $$| \\| $$ | $$ \\$$ \\ | $$ | $$ | $$ | $$ | $$\r\n"); + printf("| $$\\$$ $$ $$ | $$ | $$$$$$$ | $$ \\$$$$$$| $$ | $$ _\\$$$$$$\\ | $$ | $$ | $$ __ | $$ | $$\r\n"); + printf("| $$ \\$$$| $$ _| $$_ | $$ _| $$_ | $$__/ $$| \\__| $$ _| $$_ | $$_____| $$__/ \\| $$__/ $$\r\n"); + printf("| $$ \\$ | $$| $$ \\| $$ | $$ \\ | $$ $$ \\$$ $$| $$ \\ | $$ \\\\$$ $$| $$ $$\r\n"); + printf(" \\$$ \\$$ \\$$$$$$ \\$$ \\$$$$$$ \\$$$$$$$ \\$$$$$$ \\$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp b/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp new file mode 100644 index 00000000..1fec2a1b --- /dev/null +++ b/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +/* The following default configurations are for the board 'Espressif: ESP32-P4-Function-EV-Board, EK79007' */ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_WIDTH (1024) +#define TEST_LCD_HEIGHT (600) +#define TEST_LCD_COLOR_BITS (ESP_PANEL_LCD_RGB888_COLOR_BITS_24) +#define TEST_LCD_DSI_PHY_LDO_ID (3) +#define TEST_LCD_DSI_LANE_NUM (2) +#define TEST_LCD_DSI_LANE_RATE_MBPS (1000) +#define TEST_LCD_DPI_CLK_MHZ (52) +#define TEST_LCD_DPI_COLOR_BITS (ESP_PANEL_LCD_RGB888_COLOR_BITS_24) +#define TEST_LCD_DPI_HPW (10) +#define TEST_LCD_DPI_HBP (160) +#define TEST_LCD_DPI_HFP (160) +#define TEST_LCD_DPI_VPW (1) +#define TEST_LCD_DPI_VBP (23) +#define TEST_LCD_DPI_VFP (12) +#define TEST_LCD_USE_EXTERNAL_CMD (0) +#if TEST_LCD_USE_EXTERNAL_CMD +/** + * LCD initialization commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. + * + * Please uncomment and change the following macro definitions, then use `configVendorCommands()` to pass them in the + * same format if needed. Otherwise, the LCD driver will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { + // {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + // {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, + // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, + // {0x29, (uint8_t []){0x00}, 0, 120}, + // // or + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_PIN_NUM_RST (27) // Set to -1 if not used +#define TEST_LCD_PIN_NUM_BK_LIGHT (26) // Set to -1 if not used +#define TEST_LCD_BK_LIGHT_ON_LEVEL (1) +#define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL + +/* Enable or disable pattern test */ +#define TEST_ENABLE_PATTERN_TEST (1) +/* Enable or disable printing LCD refresh rate */ +#define TEST_ENABLE_PRINT_LCD_FPS (1) +#define TEST_PRINT_LCD_FPS_PERIOD_MS (1000) +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define TEST_ENABLE_ATTACH_CALLBACK (1) +#define TEST_COLOR_BAR_SHOW_TIME_MS (5000) + +#define delay(ms) vTaskDelay(pdMS_TO_TICKS(ms)) + +static const char *TAG = "test_spi_lcd"; + +static shared_ptr init_backlight(void) +{ +#if TEST_LCD_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Initialize backlight control pin and turn it on"); + shared_ptr backlight = make_shared( + TEST_LCD_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL, true + ); + TEST_ASSERT_NOT_NULL_MESSAGE(backlight, "Create backlight object failed"); + + TEST_ASSERT_TRUE_MESSAGE(backlight->begin(), "Backlight begin failed"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + + return backlight; +#else + return nullptr; +#endif +} + +static shared_ptr init_panel_bus(void) +{ + ESP_LOGI(TAG, "Create LCD bus"); + shared_ptr panel_bus = make_shared( + TEST_LCD_DSI_LANE_NUM, TEST_LCD_DSI_LANE_RATE_MBPS, + TEST_LCD_DPI_CLK_MHZ, TEST_LCD_DPI_COLOR_BITS, TEST_LCD_WIDTH, TEST_LCD_HEIGHT, + TEST_LCD_DPI_HPW, TEST_LCD_DPI_HBP, TEST_LCD_DPI_HFP, + TEST_LCD_DPI_VPW, TEST_LCD_DPI_VBP, TEST_LCD_DPI_VFP, + TEST_LCD_DSI_PHY_LDO_ID + ); + TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); + + TEST_ASSERT_TRUE_MESSAGE(panel_bus->begin(), "Panel bus begin failed"); + + return panel_bus; +} + +#if TEST_ENABLE_ATTACH_CALLBACK +IRAM_ATTR static bool onDrawBitmapFinishCallback(void *user_data) +{ + esp_rom_printf("Draw bitmap finish callback\n"); + + return false; +} +#endif + +#if TEST_ENABLE_PRINT_LCD_FPS +#define TEST_LCD_FPS_COUNT_MAX (100) +#ifndef millis +#define millis() (esp_timer_get_time() / 1000) +#endif + +DRAM_ATTR int frame_count = 0; +DRAM_ATTR int fps = 0; +DRAM_ATTR long start_time = 0; + +IRAM_ATTR bool onVsyncEndCallback(void *user_data) +{ + long frame_start_time = *(long *)user_data; + if (frame_start_time == 0) { + (*(long *)user_data) = millis(); + + return false; + } + + frame_count++; + if (frame_count >= TEST_LCD_FPS_COUNT_MAX) { + fps = TEST_LCD_FPS_COUNT_MAX * 1000 / (millis() - frame_start_time); + frame_count = 0; + (*(long *)user_data) = millis(); + } + + return false; +} +#endif + +static void run_test(shared_ptr lcd) +{ + frame_count = 0; + fps = 0; + start_time = 0; + +#if TEST_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); +#endif + TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); + +#if TEST_ENABLE_PATTERN_TEST + ESP_LOGI(TAG, "Show MIPI-DSI patterns"); + TEST_ASSERT_TRUE_MESSAGE( + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BAR_HORIZONTAL), "MIPI DPI bar horizontal pattern test failed" + ); + delay(1000); + TEST_ASSERT_TRUE_MESSAGE( + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BAR_VERTICAL), "MIPI DPI bar vertical pattern test failed" + ); + delay(1000); + TEST_ASSERT_TRUE_MESSAGE( + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BER_VERTICAL), "MIPI DPI ber vertical pattern test failed" + ); + delay(1000); + TEST_ASSERT_TRUE_MESSAGE( + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::NONE), "MIPI DPI none pattern test failed" + ); +#endif +#if TEST_ENABLE_ATTACH_CALLBACK + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" + ); +#endif +#if TEST_ENABLE_PRINT_LCD_FPS + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time), "Attach refresh callback failed" + ); +#endif + + ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); + + ESP_LOGI(TAG, "Wait for %d ms to show the color bar", TEST_COLOR_BAR_SHOW_TIME_MS); +#if TEST_ENABLE_PRINT_LCD_FPS + int i = 0; + while (i++ < TEST_COLOR_BAR_SHOW_TIME_MS / TEST_PRINT_LCD_FPS_PERIOD_MS) { + ESP_LOGI(TAG, "FPS: %d", fps); + vTaskDelay(pdMS_TO_TICKS(TEST_PRINT_LCD_FPS_PERIOD_MS)); + } +#else + vTaskDelay(pdMS_TO_TICKS(TEST_COLOR_BAR_SHOW_TIME_MS)); +#endif +} + +#define CREATE_LCD(name, panel_bus) \ + ({ \ + ESP_LOGI(TAG, "Create LCD device: " #name); \ + shared_ptr lcd = make_shared(panel_bus, TEST_LCD_COLOR_BITS, TEST_LCD_PIN_NUM_RST); \ + TEST_ASSERT_NOT_NULL_MESSAGE(lcd, "Create LCD object failed"); \ + lcd; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test LCD (" #name ") to draw color bar", "[spi_lcd][" #name "]") \ + { \ + shared_ptr backlight = init_backlight(); \ + shared_ptr panel_bus = init_panel_bus(); \ + shared_ptr lcd = CREATE_LCD(name, panel_bus.get()); \ + run_test(lcd); \ + } + +/** + * Here to create test cases for different LCDs + * + */ +CREATE_TEST_CASE(EK79007) diff --git a/test_apps/lcd/mipi_dsi/sdkconfig.defaults b/test_apps/lcd/mipi_dsi/sdkconfig.defaults new file mode 100644 index 00000000..c97863dd --- /dev/null +++ b/test_apps/lcd/mipi_dsi/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/mipi_dsi/sdkconfig.defaults.esp32p4 b/test_apps/lcd/mipi_dsi/sdkconfig.defaults.esp32p4 new file mode 100644 index 00000000..85fd77f5 --- /dev/null +++ b/test_apps/lcd/mipi_dsi/sdkconfig.defaults.esp32p4 @@ -0,0 +1,5 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y From 2165b527099f771b8ebe272cc8bdadc5d95581af Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Thu, 7 Nov 2024 16:43:44 +0800 Subject: [PATCH 04/11] feat(examples): add MIPI-DSI LCD --- examples/LCD/MIPI_DSI/ESP_Panel_Conf.h | 77 ++++++++ examples/LCD/MIPI_DSI/MIPI_DSI.ino | 248 +++++++++++++++++++++++++ examples/LCD/MIPI_DSI/README.md | 62 +++++++ 3 files changed, 387 insertions(+) create mode 100644 examples/LCD/MIPI_DSI/ESP_Panel_Conf.h create mode 100644 examples/LCD/MIPI_DSI/MIPI_DSI.ino create mode 100644 examples/LCD/MIPI_DSI/README.md diff --git a/examples/LCD/MIPI_DSI/ESP_Panel_Conf.h b/examples/LCD/MIPI_DSI/ESP_Panel_Conf.h new file mode 100644 index 00000000..d860e8e1 --- /dev/null +++ b/examples/LCD/MIPI_DSI/ESP_Panel_Conf.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////// Debug Configurations ///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* Set to 1 if assert on error. Otherwise print error message */ +#define ESP_PANEL_CHECK_RESULT_ASSERT (0) // 0/1 + +/* Set to 1 if print log message for debug */ +#define ESP_PANEL_ENABLE_LOG (0) // 0/1 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////// Touch Driver Configurations ////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* Maximum point number */ +#define ESP_PANEL_TOUCH_MAX_POINTS (5) +/* Maximum button number */ +#define ESP_PANEL_TOUCH_MAX_BUTTONS (1) + +/** + * XPT2046 related + * + */ +#define ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD (400) // Minimum Z pressure threshold +/** + * Enable Interrupt (PENIRQ) output, also called Full Power Mode. + * Enable this to configure the XPT2046 to output low on the PENIRQ output if a touch is detected. + * This mode uses more power when enabled. Note that this signal goes low normally when a read is active. + */ +#define ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE (0) // 0/1 +/** + * Keep internal Vref enabled. + * Enable this to keep the internal Vref enabled between conversions. This uses slightly more power, + * but requires fewer transactions when reading the battery voltage, aux voltage and temperature. + * + */ +#define ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE (0) // 0/1 +/** + * Convert touch coordinates to screen coordinates. + * When this option is enabled the raw ADC values will be converted from 0-4096 to 0-{screen width} or 0-{screen height}. + * When this option is disabled the process_coordinates method will need to be used to convert the raw ADC values into a + * screen coordinate. + * + */ +#define ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS (1) // 0/1 +/** + * Enable data structure locking. + * By enabling this option the XPT2046 driver will lock the touch position data structures when reading values from the + * XPT2046 and when reading position data via API. + * WARNING: enabling this option may result in unintended crashes. + * + */ +#define ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING (0) // 0/1 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////// File Version /////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Do not change the following versions, they are used to check if the configurations in this file are compatible with + * the current version of `ESP_Panel_Conf.h` in the library. The detailed rules are as follows: + * + * 1. If the major version is not consistent, then the configurations in this file are incompatible with the library + * and must be replaced with the file from the library. + * 2. If the minor version is not consistent, this file might be missing some new configurations, which will be set to + * default values. It is recommended to replace it with the file from the library. + * 3. Even if the patch version is not consistent, it will not affect normal functionality. + * + */ +#define ESP_PANEL_CONF_FILE_VERSION_MAJOR 0 +#define ESP_PANEL_CONF_FILE_VERSION_MINOR 1 +#define ESP_PANEL_CONF_FILE_VERSION_PATCH 2 diff --git a/examples/LCD/MIPI_DSI/MIPI_DSI.ino b/examples/LCD/MIPI_DSI/MIPI_DSI.ino new file mode 100644 index 00000000..c019230e --- /dev/null +++ b/examples/LCD/MIPI_DSI/MIPI_DSI.ino @@ -0,0 +1,248 @@ +/** + * | Supported ESP SoCs | ESP32-P4 | + * | ------------------ | -------- | + * + * | Supported LCD Controllers | EK79007 | + * | ------------------------- | ------- | + * + * # MIPI-DSI LCD Example + * + * The example demonstrates how to develop different model LCDs with MIPI-DSI interface using standalone drivers and test them by displaying color bars. + * + * ## How to use + * + * 1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. + * 2. Modify the macros in the example to match the parameters according to your hardware. + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) + * 4. Verify and upload the example to your ESP board. + * + * ## Serial Output + * + * ``` + * ... + * MIPI-DSI LCD example start + * Initialize backlight control pin and turn it on + * Create MIPI-DSI LCD bus + * Create LCD device + * Show MIPI-DSI patterns + * Draw color bar from top left to bottom right, the order is B - G - R + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * Draw bitmap finish callback + * MIPI-DSI LCD example end + * MIPI-DSI refresh rate: 0 + * MIPI-DSI refresh rate: 69 + * MIPI-DSI refresh rate: 69 + * MIPI-DSI refresh rate: 69 + * ... + * ``` + * + * ## Troubleshooting + * + * Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * + */ + +#include +#include + +/* The following default configurations are for the board 'Espressif: ESP32-P4-Function-EV-Board, EK79007' */ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Currently, the library supports the following MIPI-DSI LCDs: + * - EK79007 + */ +#define EXAMPLE_LCD_NAME EK79007 +#define EXAMPLE_LCD_WIDTH (1024) +#define EXAMPLE_LCD_HEIGHT (600) +#define EXAMPLE_LCD_COLOR_BITS (ESP_PANEL_LCD_RGB888_COLOR_BITS_24) +#define EXAMPLE_LCD_DSI_PHY_LDO_ID (3) // -1 if not used +#define EXAMPLE_LCD_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes +#define EXAMPLE_LCD_DSI_LANE_RATE_MBPS (1000) /* Single lane bit rate, should consult the LCD supplier or check the + * LCD drive IC datasheet for the supported lane rate. + * ESP32-P4 supports max 1500Mbps + */ +#define EXAMPLE_LCD_DPI_CLK_MHZ (52) +#define EXAMPLE_LCD_DPI_COLOR_BITS (EXAMPLE_LCD_COLOR_BITS) +#define EXAMPLE_LCD_DPI_HPW (10) +#define EXAMPLE_LCD_DPI_HBP (160) +#define EXAMPLE_LCD_DPI_HFP (160) +#define EXAMPLE_LCD_DPI_VPW (1) +#define EXAMPLE_LCD_DPI_VBP (23) +#define EXAMPLE_LCD_DPI_VFP (12) +#define EXAMPLE_LCD_USE_EXTERNAL_CMD (0) +#if EXAMPLE_LCD_USE_EXTERNAL_CMD +/** + * LCD initialization commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. + * + * Please uncomment and change the following macro definitions, then use `configVendorCommands()` to pass them in the + * same format if needed. Otherwise, the LCD driver will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { + // {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + // {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, + // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, + // {0x29, (uint8_t []){0x00}, 0, 120}, + // // or + // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + // ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + // ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define EXAMPLE_LCD_PIN_NUM_RST (27) // Set to -1 if not used +#define EXAMPLE_LCD_PIN_NUM_BK_LIGHT (26) // Set to -1 if not used +#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL (1) +#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL + +/* Enable or disable pattern test */ +#define EXAMPLE_ENABLE_PATTERN_EXAMPLE (1) +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define EXAMPLE_ENABLE_ATTACH_CALLBACK (1) +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define EXAMPLE_ENABLE_PRINT_LCD_FPS (1) + +#define _EXAMPLE_LCD_CLASS(name, ...) ESP_PanelLcd_##name(__VA_ARGS__) +#define EXAMPLE_LCD_CLASS(name, ...) _EXAMPLE_LCD_CLASS(name, ##__VA_ARGS__) + +#if EXAMPLE_ENABLE_ATTACH_CALLBACK +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) +{ + esp_rom_printf("Draw bitmap finish callback\n"); + + return false; +} +#endif + +#if EXAMPLE_ENABLE_PRINT_LCD_FPS +#define EXAMPLE_LCD_FPS_COUNT_MAX (100) + +DRAM_ATTR int frame_count = 0; +DRAM_ATTR int fps = 0; +DRAM_ATTR long start_time = 0; + +IRAM_ATTR bool onVsyncEndCallback(void *user_data) +{ + long frame_start_time = *(long *)user_data; + if (frame_start_time == 0) { + (*(long *)user_data) = millis(); + + return false; + } + + frame_count++; + if (frame_count >= EXAMPLE_LCD_FPS_COUNT_MAX) { + fps = EXAMPLE_LCD_FPS_COUNT_MAX * 1000 / (millis() - frame_start_time); + frame_count = 0; + (*(long *)user_data) = millis(); + } + + return false; +} +#endif + +void setup() +{ + Serial.begin(115200); + Serial.println("MIPI-DSI LCD example start"); + +#if EXAMPLE_LCD_PIN_NUM_BK_LIGHT >= 0 + Serial.println("Initialize backlight control pin and turn it on"); + ESP_PanelBacklight *backlight = new ESP_PanelBacklight( + EXAMPLE_LCD_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL, true + ); + backlight->begin(); + backlight->on(); +#endif + + Serial.println("Create MIPI-DSI LCD bus"); + ESP_PanelBus_DSI *panel_bus = new ESP_PanelBus_DSI( + EXAMPLE_LCD_DSI_LANE_NUM, EXAMPLE_LCD_DSI_LANE_RATE_MBPS, + EXAMPLE_LCD_DPI_CLK_MHZ, EXAMPLE_LCD_DPI_COLOR_BITS, EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, + EXAMPLE_LCD_DPI_HPW, EXAMPLE_LCD_DPI_HBP, EXAMPLE_LCD_DPI_HFP, + EXAMPLE_LCD_DPI_VPW, EXAMPLE_LCD_DPI_VBP, EXAMPLE_LCD_DPI_VFP, + EXAMPLE_LCD_DSI_PHY_LDO_ID + ); + panel_bus->begin(); + + Serial.println("Create LCD device"); + ESP_PanelLcd *lcd = new EXAMPLE_LCD_CLASS(EXAMPLE_LCD_NAME, panel_bus, EXAMPLE_LCD_COLOR_BITS, EXAMPLE_LCD_PIN_NUM_RST); +#if EXAMPLE_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd)/sizeof(lcd_init_cmd[0])); +#endif + lcd->init(); + lcd->reset(); + lcd->begin(); + lcd->displayOn(); + +#if EXAMPLE_ENABLE_PATTERN_EXAMPLE + Serial.println("Show MIPI-DSI patterns"); + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BAR_HORIZONTAL); + delay(1000); + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BAR_VERTICAL); + delay(1000); + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::BER_VERTICAL); + delay(1000); + lcd->showDsiPattern(ESP_PanelLcd::DsiPatternType::NONE); +#endif +#if EXAMPLE_ENABLE_ATTACH_CALLBACK + /* Attach a callback function which will be called when every bitmap drawing is completed */ + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, NULL); +#endif +#if EXAMPLE_ENABLE_PRINT_LCD_FPS + /* Attach a callback function which will be called when the Vsync signal is detected */ + lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time); +#endif + + Serial.println("Draw color bar from top left to bottom right, the order is B - G - R"); + /* Users can refer to the implementation within `colorBardTest()` to draw patterns on the LCD */ + lcd->colorBarTest(EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT); + + Serial.println("MIPI-DSI LCD example end"); +} + +void loop() +{ + delay(1000); +#if EXAMPLE_ENABLE_PRINT_LCD_FPS + Serial.println("MIPI-DSI refresh rate: " + String(fps)); +#else + Serial.println("IDLE loop"); +#endif +} diff --git a/examples/LCD/MIPI_DSI/README.md b/examples/LCD/MIPI_DSI/README.md new file mode 100644 index 00000000..9a69fcea --- /dev/null +++ b/examples/LCD/MIPI_DSI/README.md @@ -0,0 +1,62 @@ +| Supported ESP SoCs | ESP32-P4 | +| ------------------ | -------- | + +| Supported LCD Controllers | EK79007 | +| ------------------------- | ------- | + +# MIPI-DSI LCD Example + +The example demonstrates how to develop different model LCDs with MIPI-DSI interface using standalone drivers and test them by displaying color bars. + +## How to use + +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. +2. Modify the macros in the example to match the parameters according to your hardware. +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) +4. Verify and upload the example to your ESP board. + +## Serial Output + +``` +... +MIPI-DSI LCD example start +Initialize backlight control pin and turn it on +Create MIPI-DSI LCD bus +Create LCD device +Show MIPI-DSI patterns +Draw color bar from top left to bottom right, the order is B - G - R +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +Draw bitmap finish callback +MIPI-DSI LCD example end +MIPI-DSI refresh rate: 0 +MIPI-DSI refresh rate: 69 +MIPI-DSI refresh rate: 69 +MIPI-DSI refresh rate: 69 +... +``` + +## Troubleshooting + +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. From f2b3257d5987f7e836e732ca5c4186e0b24c1939 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Thu, 7 Nov 2024 16:51:05 +0800 Subject: [PATCH 05/11] feat(ci): update for MIPI-DSI LCD --- .build-rules.yml | 4 ++++ .gitlab/ci/build.yml | 15 +++++++++++++ .gitlab/ci/rules.yml | 16 +++++++++++++ src/touch/base/esp_lcd_touch_xpt2046.h | 31 ++++++++++++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/.build-rules.yml b/.build-rules.yml index bee2b420..065f5662 100644 --- a/.build-rules.yml +++ b/.build-rules.yml @@ -15,6 +15,10 @@ test_apps/lcd/qspi: disable: - if: SOC_GPSPI_SUPPORTED != 1 +test_apps/lcd/mipi_dsi: + disable: + - if: SOC_MIPI_DSI_SUPPORTED != 1 + test_apps/lcd/rgb: disable: - if: SOC_LCD_RGB_SUPPORTED != 1 diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index a429ab4f..00a6c606 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -49,6 +49,13 @@ - IMAGE: espressif/idf:release-v5.1 - IMAGE: espressif/idf:release-v5.2 - IMAGE: espressif/idf:release-v5.3 + - IMAGE: espressif/idf:release-v5.4 + +.build_esp32_p4_idf_release_version: + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.3 + - IMAGE: espressif/idf:release-v5.4 # Test apps build_test_apps_lcd_3wire_spi_rgb: @@ -59,6 +66,14 @@ build_test_apps_lcd_3wire_spi_rgb: variables: EXAMPLE_DIR: test_apps/lcd/3wire_spi_rgb +build_test_apps_lcd_mipi_dsi: + extends: + - .build_examples_template + - .build_esp32_p4_idf_release_version + - .rules:build:test_apps_lcd_mipi_dsi + variables: + EXAMPLE_DIR: test_apps/lcd/mipi_dsi + build_test_apps_lcd_qspi: extends: - .build_examples_template diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 50720242..7a558e75 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -26,6 +26,9 @@ .patterns-test_apps_lcd_3wire_spi_rgb: &patterns-test_apps_lcd_3wire_spi_rgb - "test_apps/lcd/3wire_spi_rgb/**/*" +.patterns-test_apps_lcd_mipi_dsi: &patterns-test_apps_lcd_mipi_dsi + - "test_apps/lcd/mipi_dsi/**/*" + .patterns-test_apps_lcd_qspi: &patterns-test_apps_lcd_qspi - "test_apps/lcd/qspi/**/*" @@ -103,6 +106,19 @@ - <<: *if-dev-push changes: *patterns-test_apps_lcd_3wire_spi_rgb +.rules:build:test_apps_lcd_mipi_dsi: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lcd_mipi_dsi + .rules:build:test_apps_lcd_qspi: rules: - <<: *if-protected diff --git a/src/touch/base/esp_lcd_touch_xpt2046.h b/src/touch/base/esp_lcd_touch_xpt2046.h index 9539ce0a..c0e3c12f 100644 --- a/src/touch/base/esp_lcd_touch_xpt2046.h +++ b/src/touch/base/esp_lcd_touch_xpt2046.h @@ -135,6 +135,35 @@ extern "C" { .cs_high_active = 0 \ } \ } +#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,4,0) + +/** + * @brief Communication SPI device IO structure + * + */ +#define ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(touch_cs) \ + { \ + .cs_gpio_num = (gpio_num_t)touch_cs, \ + .dc_gpio_num = GPIO_NUM_NC, \ + .spi_mode = 0, \ + .pclk_hz = ESP_LCD_TOUCH_SPI_CLOCK_HZ, \ + .trans_queue_depth = 3, \ + .on_color_trans_done = NULL, \ + .user_ctx = NULL, \ + .lcd_cmd_bits = 8, \ + .lcd_param_bits = 8, \ + .flags = \ + { \ + .dc_high_on_cmd = 0, \ + .dc_low_on_data = 0, \ + .dc_low_on_param = 0, \ + .octal_mode = 0, \ + .quad_mode = 0, \ + .sio_mode = 0, \ + .lsb_first = 0, \ + .cs_high_active = 0 \ + } \ + } #else /** @@ -152,6 +181,8 @@ extern "C" { .user_ctx = NULL, \ .lcd_cmd_bits = 8, \ .lcd_param_bits = 8, \ + .cs_ena_pretrans = 0, \ + .cs_ena_posttrans = 0, \ .flags = \ { \ .dc_high_on_cmd = 0, \ From 42d1cf62faf03fae9bb17a9fc626e8b120945c1d Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Thu, 7 Nov 2024 17:25:28 +0800 Subject: [PATCH 06/11] feat(lcd): add LCD controller ILI9881C --- examples/LCD/MIPI_DSI/MIPI_DSI.ino | 6 +- examples/LCD/MIPI_DSI/README.md | 4 +- src/ESP_Panel_Library.h | 1 + src/lcd/ILI9881C.cpp | 56 ++ src/lcd/ILI9881C.h | 59 ++ src/lcd/base/esp_lcd_ili9881c.c | 583 ++++++++++++++++++ src/lcd/base/esp_lcd_ili9881c.h | 99 +++ .../lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp | 1 + 8 files changed, 804 insertions(+), 5 deletions(-) create mode 100644 src/lcd/ILI9881C.cpp create mode 100644 src/lcd/ILI9881C.h create mode 100644 src/lcd/base/esp_lcd_ili9881c.c create mode 100644 src/lcd/base/esp_lcd_ili9881c.h diff --git a/examples/LCD/MIPI_DSI/MIPI_DSI.ino b/examples/LCD/MIPI_DSI/MIPI_DSI.ino index c019230e..be1f767e 100644 --- a/examples/LCD/MIPI_DSI/MIPI_DSI.ino +++ b/examples/LCD/MIPI_DSI/MIPI_DSI.ino @@ -2,8 +2,8 @@ * | Supported ESP SoCs | ESP32-P4 | * | ------------------ | -------- | * - * | Supported LCD Controllers | EK79007 | - * | ------------------------- | ------- | + * | Supported LCD Controllers | EK79007 | ILI9881C | + * | ------------------------- | ------- | -------- | * * # MIPI-DSI LCD Example * @@ -73,7 +73,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Currently, the library supports the following MIPI-DSI LCDs: - * - EK79007 + * - EK79007, ILI9881C */ #define EXAMPLE_LCD_NAME EK79007 #define EXAMPLE_LCD_WIDTH (1024) diff --git a/examples/LCD/MIPI_DSI/README.md b/examples/LCD/MIPI_DSI/README.md index 9a69fcea..a8d2eb45 100644 --- a/examples/LCD/MIPI_DSI/README.md +++ b/examples/LCD/MIPI_DSI/README.md @@ -1,8 +1,8 @@ | Supported ESP SoCs | ESP32-P4 | | ------------------ | -------- | -| Supported LCD Controllers | EK79007 | -| ------------------------- | ------- | +| Supported LCD Controllers | EK79007 | ILI9881C | +| ------------------------- | ------- | -------- | # MIPI-DSI LCD Example diff --git a/src/ESP_Panel_Library.h b/src/ESP_Panel_Library.h index 0799085a..870a0620 100644 --- a/src/ESP_Panel_Library.h +++ b/src/ESP_Panel_Library.h @@ -31,6 +31,7 @@ #include "lcd/GC9A01.h" #include "lcd/GC9B71.h" #include "lcd/ILI9341.h" +#include "lcd/ILI9881C.h" #include "lcd/NV3022B.h" #include "lcd/SH8601.h" #include "lcd/SPD2010.h" diff --git a/src/lcd/ILI9881C.cpp b/src/lcd/ILI9881C.cpp new file mode 100644 index 00000000..59ab7aab --- /dev/null +++ b/src/lcd/ILI9881C.cpp @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLog.h" +#include "ILI9881C.h" + +static const char *TAG = "ILI9881C_CPP"; + +ESP_PanelLcd_ILI9881C::ESP_PanelLcd_ILI9881C(ESP_PanelBus *bus, uint8_t color_bits, int rst_io): + ESP_PanelLcd(bus, color_bits, rst_io) +{ + disabled_functions.set_gap = 1; + disabled_functions.swap_xy = 1; +} + +ESP_PanelLcd_ILI9881C::ESP_PanelLcd_ILI9881C(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config): + ESP_PanelLcd(bus, panel_config) +{ + disabled_functions.set_gap = 1; + disabled_functions.swap_xy = 1; +} + +ESP_PanelLcd_ILI9881C::~ESP_PanelLcd_ILI9881C() +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + if (handle == NULL) { + goto end; + } + + if (!del()) { + ESP_LOGE(TAG, "Delete device failed"); + } + +end: + ESP_LOGD(TAG, "Destroyed"); +} + +bool ESP_PanelLcd_ILI9881C::init(void) +{ + ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_new_panel_ili9881c(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed" + ); + + return true; +} + +#endif diff --git a/src/lcd/ILI9881C.h b/src/lcd/ILI9881C.h new file mode 100644 index 00000000..50457853 --- /dev/null +++ b/src/lcd/ILI9881C.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLcd.h" +#include "base/esp_lcd_vendor_types.h" +#include "base/esp_lcd_ili9881c.h" + +/** + * @brief ILI9881C LCD device object class + * + * @note This class is a derived class of `ESP_PanelLcd`, user can use it directly + */ +class ESP_PanelLcd_ILI9881C: public ESP_PanelLcd { +public: + /** + * @brief Construct a new LCD device in a simple way, the `init()` function should be called after this function + * + * @note This function uses some default values to config the LCD device, please use `config*()` functions to + * change them + * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier + * for initialization sequence code, and use `configVendorCommands()` to configure + * + * @param bus Pointer of panel bus + * @param color_bits Bits per pixel (16/18/24) + * @param rst_io Reset pin, set to -1 if no use + */ + ESP_PanelLcd_ILI9881C(ESP_PanelBus *bus, uint8_t color_bits, int rst_io = -1); + + /** + * @brief Construct a new LCD in a complex way, the `init()` function should be called after this function + * + * @param bus Pointer of panel bus + * @param panel_config LCD device configuration + */ + ESP_PanelLcd_ILI9881C(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config); + + /** + * @brief Destroy the LCD device + * + */ + ~ESP_PanelLcd_ILI9881C() override; + + /** + * @brief Initialize the LCD device, the `begin()` function should be called after this function + * + * @note This function typically calls `esp_lcd_new_panel_*()` to create the LCD panel handle + * + * @return true if success, otherwise false + */ + bool init(void) override; +}; +#endif diff --git a/src/lcd/base/esp_lcd_ili9881c.c b/src/lcd/base/esp_lcd_ili9881c.c new file mode 100644 index 00000000..bbcffb51 --- /dev/null +++ b/src/lcd/base/esp_lcd_ili9881c.c @@ -0,0 +1,583 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "ESP_PanelLog.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_lcd_panel_commands.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_vendor.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_lcd_ili9881c.h" + +#include "esp_lcd_vendor_types.h" + +#define ILI9881C_CMD_CNDBKxSEL (0xFF) +#define ILI9881C_CMD_BKxSEL_BYTE0 (0x98) +#define ILI9881C_CMD_BKxSEL_BYTE1 (0x81) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE0 (0x00) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 (0x01) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE2 (0x02) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE3 (0x03) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE4 (0x04) + +#define ILI9881C_PAD_CONTROL (0xB7) +#define ILI9881C_DSI_2_LANE (0x03) +#define ILI9881C_DSI_3_4_LANE (0x02) + +#define ILI9881C_CMD_GS_BIT (1 << 0) +#define ILI9881C_CMD_SS_BIT (1 << 1) + +typedef struct { + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register + const esp_lcd_panel_vendor_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + uint8_t lane_num; + struct { + unsigned int reset_level: 1; + } flags; + // To save the original functions of MIPI DPI panel + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*init)(esp_lcd_panel_t *panel); +} ili9881c_panel_t; + +static const char *TAG = "ili9881c"; + +static esp_err_t panel_ili9881c_del(esp_lcd_panel_t *panel); +static esp_err_t panel_ili9881c_init(esp_lcd_panel_t *panel); +static esp_err_t panel_ili9881c_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_ili9881c_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_ili9881c_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_ili9881c_disp_on_off(esp_lcd_panel_t *panel, bool on_off); +static esp_err_t panel_ili9881c_sleep(esp_lcd_panel_t *panel, bool sleep); + +esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + ESP_LOGI( + TAG, "version: %d.%d.%d", ESP_LCD_ILI9881C_VER_MAJOR, ESP_LCD_ILI9881C_VER_MINOR, ESP_LCD_ILI9881C_VER_PATCH + ); + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + esp_lcd_panel_vendor_config_t *vendor_config = (esp_lcd_panel_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE( + vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, + ESP_ERR_INVALID_ARG, TAG, "invalid vendor config" + ); + + esp_err_t ret = ESP_OK; + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)calloc(1, sizeof(ili9881c_panel_t)); + ESP_RETURN_ON_FALSE(ili9881c, ESP_ERR_NO_MEM, TAG, "no mem for ili9881c panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + ili9881c->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + ili9881c->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } + + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + ili9881c->colmod_val = 0x55; + break; + case 18: // RGB666 + ili9881c->colmod_val = 0x66; + break; + case 24: // RGB888 + ili9881c->colmod_val = 0x77; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + // The ID register is on the CMD_Page 1 + uint8_t ID1, ID2, ID3; + esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 + }, 3); + esp_lcd_panel_io_rx_param(io, 0x00, &ID1, 1); + esp_lcd_panel_io_rx_param(io, 0x01, &ID2, 1); + esp_lcd_panel_io_rx_param(io, 0x02, &ID3, 1); + ESP_LOGI(TAG, "ID1: 0x%x, ID2: 0x%x, ID3: 0x%x", ID1, ID2, ID3); + + ili9881c->io = io; + ili9881c->init_cmds = vendor_config->init_cmds; + ili9881c->init_cmds_size = vendor_config->init_cmds_size; + ili9881c->lane_num = vendor_config->mipi_config.lane_num; + ili9881c->reset_gpio_num = panel_dev_config->reset_gpio_num; + ili9881c->flags.reset_level = panel_dev_config->flags.reset_active_high; + + // Create MIPI DPI panel + ESP_GOTO_ON_ERROR( + esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), + err, TAG, "create MIPI DPI panel failed" + ); + ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel); + + // Save the original functions of MIPI DPI panel + ili9881c->del = (*ret_panel)->del; + ili9881c->init = (*ret_panel)->init; + // Overwrite the functions of MIPI DPI panel + (*ret_panel)->del = panel_ili9881c_del; + (*ret_panel)->init = panel_ili9881c_init; + (*ret_panel)->reset = panel_ili9881c_reset; + (*ret_panel)->mirror = panel_ili9881c_mirror; + (*ret_panel)->invert_color = panel_ili9881c_invert_color; + (*ret_panel)->disp_on_off = panel_ili9881c_disp_on_off; + (*ret_panel)->disp_sleep = panel_ili9881c_sleep; + (*ret_panel)->user_data = ili9881c; + ESP_LOGD(TAG, "new ili9881c panel @%p", ili9881c); + + return ESP_OK; + +err: + if (ili9881c) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(ili9881c); + } + return ret; +} + +static const esp_lcd_panel_vendor_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + /**** CMD_Page 3 ****/ + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE3}, 3, 0}, + {0x01, (uint8_t []){0x00}, 1, 0}, + {0x02, (uint8_t []){0x00}, 1, 0}, + {0x03, (uint8_t []){0x53}, 1, 0}, + {0x04, (uint8_t []){0x53}, 1, 0}, + {0x05, (uint8_t []){0x13}, 1, 0}, + {0x06, (uint8_t []){0x04}, 1, 0}, + {0x07, (uint8_t []){0x02}, 1, 0}, + {0x08, (uint8_t []){0x02}, 1, 0}, + {0x09, (uint8_t []){0x00}, 1, 0}, + {0x0a, (uint8_t []){0x00}, 1, 0}, + {0x0b, (uint8_t []){0x00}, 1, 0}, + {0x0c, (uint8_t []){0x00}, 1, 0}, + {0x0d, (uint8_t []){0x00}, 1, 0}, + {0x0e, (uint8_t []){0x00}, 1, 0}, + {0x0f, (uint8_t []){0x00}, 1, 0}, + {0x10, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 1, 0}, + {0x12, (uint8_t []){0x00}, 1, 0}, + {0x13, (uint8_t []){0x00}, 1, 0}, + {0x14, (uint8_t []){0x00}, 1, 0}, + {0x15, (uint8_t []){0x00}, 1, 0}, + {0x16, (uint8_t []){0x00}, 1, 0}, + {0x17, (uint8_t []){0x00}, 1, 0}, + {0x18, (uint8_t []){0x00}, 1, 0}, + {0x19, (uint8_t []){0x00}, 1, 0}, + {0x1a, (uint8_t []){0x00}, 1, 0}, + {0x1b, (uint8_t []){0x00}, 1, 0}, + {0x1c, (uint8_t []){0x00}, 1, 0}, + {0x1d, (uint8_t []){0x00}, 1, 0}, + {0x1e, (uint8_t []){0xc0}, 1, 0}, + {0x1f, (uint8_t []){0x80}, 1, 0}, + {0x20, (uint8_t []){0x02}, 1, 0}, + {0x21, (uint8_t []){0x09}, 1, 0}, + {0x22, (uint8_t []){0x00}, 1, 0}, + {0x23, (uint8_t []){0x00}, 1, 0}, + {0x24, (uint8_t []){0x00}, 1, 0}, + {0x25, (uint8_t []){0x00}, 1, 0}, + {0x26, (uint8_t []){0x00}, 1, 0}, + {0x27, (uint8_t []){0x00}, 1, 0}, + {0x28, (uint8_t []){0x55}, 1, 0}, + {0x29, (uint8_t []){0x03}, 1, 0}, + {0x2a, (uint8_t []){0x00}, 1, 0}, + {0x2b, (uint8_t []){0x00}, 1, 0}, + {0x2c, (uint8_t []){0x00}, 1, 0}, + {0x2d, (uint8_t []){0x00}, 1, 0}, + {0x2e, (uint8_t []){0x00}, 1, 0}, + {0x2f, (uint8_t []){0x00}, 1, 0}, + {0x30, (uint8_t []){0x00}, 1, 0}, + {0x31, (uint8_t []){0x00}, 1, 0}, + {0x32, (uint8_t []){0x00}, 1, 0}, + {0x33, (uint8_t []){0x00}, 1, 0}, + {0x34, (uint8_t []){0x00}, 1, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x36, (uint8_t []){0x00}, 1, 0}, + {0x37, (uint8_t []){0x00}, 1, 0}, + {0x38, (uint8_t []){0x3C}, 1, 0}, + {0x39, (uint8_t []){0x00}, 1, 0}, + {0x3a, (uint8_t []){0x00}, 1, 0}, + {0x3b, (uint8_t []){0x00}, 1, 0}, + {0x3c, (uint8_t []){0x00}, 1, 0}, + {0x3d, (uint8_t []){0x00}, 1, 0}, + {0x3e, (uint8_t []){0x00}, 1, 0}, + {0x3f, (uint8_t []){0x00}, 1, 0}, + {0x40, (uint8_t []){0x00}, 1, 0}, + {0x41, (uint8_t []){0x00}, 1, 0}, + {0x42, (uint8_t []){0x00}, 1, 0}, + {0x43, (uint8_t []){0x00}, 1, 0}, + {0x44, (uint8_t []){0x00}, 1, 0}, + {0x50, (uint8_t []){0x01}, 1, 0}, + {0x51, (uint8_t []){0x23}, 1, 0}, + {0x52, (uint8_t []){0x45}, 1, 0}, + {0x53, (uint8_t []){0x67}, 1, 0}, + {0x54, (uint8_t []){0x89}, 1, 0}, + {0x55, (uint8_t []){0xab}, 1, 0}, + {0x56, (uint8_t []){0x01}, 1, 0}, + {0x57, (uint8_t []){0x23}, 1, 0}, + {0x58, (uint8_t []){0x45}, 1, 0}, + {0x59, (uint8_t []){0x67}, 1, 0}, + {0x5a, (uint8_t []){0x89}, 1, 0}, + {0x5b, (uint8_t []){0xab}, 1, 0}, + {0x5c, (uint8_t []){0xcd}, 1, 0}, + {0x5d, (uint8_t []){0xef}, 1, 0}, + {0x5e, (uint8_t []){0x01}, 1, 0}, + {0x5f, (uint8_t []){0x08}, 1, 0}, + {0x60, (uint8_t []){0x02}, 1, 0}, + {0x61, (uint8_t []){0x02}, 1, 0}, + {0x62, (uint8_t []){0x0A}, 1, 0}, + {0x63, (uint8_t []){0x15}, 1, 0}, + {0x64, (uint8_t []){0x14}, 1, 0}, + {0x65, (uint8_t []){0x02}, 1, 0}, + {0x66, (uint8_t []){0x11}, 1, 0}, + {0x67, (uint8_t []){0x10}, 1, 0}, + {0x68, (uint8_t []){0x02}, 1, 0}, + {0x69, (uint8_t []){0x0F}, 1, 0}, + {0x6a, (uint8_t []){0x0E}, 1, 0}, + {0x6b, (uint8_t []){0x02}, 1, 0}, + {0x6c, (uint8_t []){0x0D}, 1, 0}, + {0x6d, (uint8_t []){0x0C}, 1, 0}, + {0x6e, (uint8_t []){0x06}, 1, 0}, + {0x6f, (uint8_t []){0x02}, 1, 0}, + {0x70, (uint8_t []){0x02}, 1, 0}, + {0x71, (uint8_t []){0x02}, 1, 0}, + {0x72, (uint8_t []){0x02}, 1, 0}, + {0x73, (uint8_t []){0x02}, 1, 0}, + {0x74, (uint8_t []){0x02}, 1, 0}, + {0x75, (uint8_t []){0x06}, 1, 0}, + {0x76, (uint8_t []){0x02}, 1, 0}, + {0x77, (uint8_t []){0x02}, 1, 0}, + {0x78, (uint8_t []){0x0A}, 1, 0}, + {0x79, (uint8_t []){0x15}, 1, 0}, + {0x7a, (uint8_t []){0x14}, 1, 0}, + {0x7b, (uint8_t []){0x02}, 1, 0}, + {0x7c, (uint8_t []){0x10}, 1, 0}, + {0x7d, (uint8_t []){0x11}, 1, 0}, + {0x7e, (uint8_t []){0x02}, 1, 0}, + {0x7f, (uint8_t []){0x0C}, 1, 0}, + {0x80, (uint8_t []){0x0D}, 1, 0}, + {0x81, (uint8_t []){0x02}, 1, 0}, + {0x82, (uint8_t []){0x0E}, 1, 0}, + {0x83, (uint8_t []){0x0F}, 1, 0}, + {0x84, (uint8_t []){0x08}, 1, 0}, + {0x85, (uint8_t []){0x02}, 1, 0}, + {0x86, (uint8_t []){0x02}, 1, 0}, + {0x87, (uint8_t []){0x02}, 1, 0}, + {0x88, (uint8_t []){0x02}, 1, 0}, + {0x89, (uint8_t []){0x02}, 1, 0}, + {0x8A, (uint8_t []){0x02}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE4}, 3, 0}, + {0x6C, (uint8_t []){0x15}, 1, 0}, + {0x6E, (uint8_t []){0x30}, 1, 0}, + {0x6F, (uint8_t []){0x33}, 1, 0}, + {0x8D, (uint8_t []){0x1F}, 1, 0}, + {0x87, (uint8_t []){0xBA}, 1, 0}, + {0x26, (uint8_t []){0x76}, 1, 0}, + {0xB2, (uint8_t []){0xD1}, 1, 0}, + {0x35, (uint8_t []){0x1F}, 1, 0}, + {0x33, (uint8_t []){0x14}, 1, 0}, + {0x3A, (uint8_t []){0xA9}, 1, 0}, + {0x3B, (uint8_t []){0x3D}, 1, 0}, + {0x38, (uint8_t []){0x01}, 1, 0}, + {0x39, (uint8_t []){0x00}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1}, 3, 0}, + {0x22, (uint8_t []){0x09}, 1, 0}, + {0x31, (uint8_t []){0x00}, 1, 0}, + {0x40, (uint8_t []){0x53}, 1, 0}, + {0x50, (uint8_t []){0xC0}, 1, 0}, + {0x51, (uint8_t []){0xC0}, 1, 0}, + {0x53, (uint8_t []){0x47}, 1, 0}, + {0x55, (uint8_t []){0x46}, 1, 0}, + {0x60, (uint8_t []){0x28}, 1, 0}, + {0x2E, (uint8_t []){0xC8}, 1, 0}, + {0xA0, (uint8_t []){0x01}, 1, 0}, + {0xA1, (uint8_t []){0x10}, 1, 0}, + {0xA2, (uint8_t []){0x1B}, 1, 0}, + {0xA3, (uint8_t []){0x0C}, 1, 0}, + {0xA4, (uint8_t []){0x14}, 1, 0}, + {0xA5, (uint8_t []){0x25}, 1, 0}, + {0xA6, (uint8_t []){0x1A}, 1, 0}, + {0xA7, (uint8_t []){0x1D}, 1, 0}, + {0xA8, (uint8_t []){0x68}, 1, 0}, + {0xA9, (uint8_t []){0x1B}, 1, 0}, + {0xAA, (uint8_t []){0x26}, 1, 0}, + {0xAB, (uint8_t []){0x5B}, 1, 0}, + {0xAC, (uint8_t []){0x1B}, 1, 0}, + {0xAD, (uint8_t []){0x17}, 1, 0}, + {0xAE, (uint8_t []){0x4F}, 1, 0}, + {0xAF, (uint8_t []){0x24}, 1, 0}, + {0xB0, (uint8_t []){0x2A}, 1, 0}, + {0xB1, (uint8_t []){0x4E}, 1, 0}, + {0xB2, (uint8_t []){0x5F}, 1, 0}, + {0xB3, (uint8_t []){0x39}, 1, 0}, + {0xC0, (uint8_t []){0x0F}, 1, 0}, + {0xC1, (uint8_t []){0x1B}, 1, 0}, + {0xC2, (uint8_t []){0x27}, 1, 0}, + {0xC3, (uint8_t []){0x16}, 1, 0}, + {0xC4, (uint8_t []){0x14}, 1, 0}, + {0xC5, (uint8_t []){0x28}, 1, 0}, + {0xC6, (uint8_t []){0x1D}, 1, 0}, + {0xC7, (uint8_t []){0x21}, 1, 0}, + {0xC8, (uint8_t []){0x6C}, 1, 0}, + {0xC9, (uint8_t []){0x1B}, 1, 0}, + {0xCA, (uint8_t []){0x26}, 1, 0}, + {0xCB, (uint8_t []){0x5B}, 1, 0}, + {0xCC, (uint8_t []){0x1B}, 1, 0}, + {0xCD, (uint8_t []){0x1B}, 1, 0}, + {0xCE, (uint8_t []){0x4F}, 1, 0}, + {0xCF, (uint8_t []){0x24}, 1, 0}, + {0xD0, (uint8_t []){0x2A}, 1, 0}, + {0xD1, (uint8_t []){0x4E}, 1, 0}, + {0xD2, (uint8_t []){0x5F}, 1, 0}, + {0xD3, (uint8_t []){0x39}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE0}, 3, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x29, (uint8_t []){0x00}, 0, 0}, + + //============ Gamma END=========== +}; + +static esp_err_t panel_ili9881c_del(esp_lcd_panel_t *panel) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + + if (ili9881c->reset_gpio_num >= 0) { + gpio_reset_pin(ili9881c->reset_gpio_num); + } + // Delete MIPI DPI panel + ili9881c->del(panel); + free(ili9881c); + ESP_LOGD(TAG, "del ili9881c panel @%p", ili9881c); + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_init(esp_lcd_panel_t *panel) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + const esp_lcd_panel_vendor_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + uint8_t lane_command = ILI9881C_DSI_2_LANE; + bool is_command0_enable = false; + bool is_cmd_overwritten = false; + + switch (ili9881c->lane_num) { + case 0: + case 2: + lane_command = ILI9881C_DSI_2_LANE; + break; + case 3: + case 4: + lane_command = ILI9881C_DSI_3_4_LANE; + break; + default: + ESP_LOGE(TAG, "Invalid lane number %d", ili9881c->lane_num); + return ESP_ERR_INVALID_ARG; + } + + // back to CMD_Page 1 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 + }, 3), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_PAD_CONTROL, (uint8_t[]) { + lane_command, + }, 1), TAG, "send command failed"); + + // back to CMD_Page 0 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE0 + }, 3), TAG, "send command failed"); + // exit sleep mode + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, + "io tx param failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + ili9881c->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { + ili9881c->colmod_val, + }, 1), TAG, "send command failed"); + + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (ili9881c->init_cmds) { + init_cmds = ili9881c->init_cmds; + init_cmds_size = ili9881c->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(esp_lcd_panel_vendor_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + if (is_command0_enable && init_cmds[i].data_bytes > 0) { + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + ili9881c->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + ili9881c->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + is_cmd_overwritten = false; + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + } + + // Send command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + + if ((init_cmds[i].cmd == ILI9881C_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] == ILI9881C_CMD_BKxSEL_BYTE2_PAGE0)) { + is_command0_enable = true; + } else if ((init_cmds[i].cmd == ILI9881C_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] != ILI9881C_CMD_BKxSEL_BYTE2_PAGE0)) { + is_command0_enable = false; + } + } + ESP_LOGD(TAG, "send init commands success"); + + ESP_RETURN_ON_ERROR(ili9881c->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_reset(esp_lcd_panel_t *panel) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + + // Perform hardware reset + if (ili9881c->reset_gpio_num >= 0) { + gpio_set_level(ili9881c->reset_gpio_num, ili9881c->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(ili9881c->reset_gpio_num, !ili9881c->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } else if (io) { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); + } + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + uint8_t command = 0; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + uint8_t madctl_val = ili9881c->madctl_val; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + // Control mirror through LCD command + if (mirror_x) { + madctl_val |= ILI9881C_CMD_GS_BIT; + } else { + madctl_val &= ~ILI9881C_CMD_GS_BIT; + } + if (mirror_y) { + madctl_val |= ILI9881C_CMD_SS_BIT; + } else { + madctl_val &= ~ILI9881C_CMD_SS_BIT; + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) { + madctl_val + }, 1), TAG, "send command failed"); + ili9881c->madctl_val = madctl_val; + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + int command = 0; + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_ili9881c_sleep(esp_lcd_panel_t *panel, bool sleep) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + int command = 0; + + if (sleep) { + command = LCD_CMD_SLPIN; + } else { + command = LCD_CMD_SLPOUT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + + return ESP_OK; +} +#endif // SOC_MIPI_DSI_SUPPORTED diff --git a/src/lcd/base/esp_lcd_ili9881c.h b/src/lcd/base/esp_lcd_ili9881c.h new file mode 100644 index 00000000..f4d9ceba --- /dev/null +++ b/src/lcd/base/esp_lcd_ili9881c.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/** + * @file + * @brief ESP LCD: ILI9881C + */ + +#pragma once + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_mipi_dsi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_LCD_ILI9881C_VER_MAJOR (1) +#define ESP_LCD_ILI9881C_VER_MINOR (0) +#define ESP_LCD_ILI9881C_VER_PATCH (0) + +/** + * @brief Create LCD panel for model ILI9881C + * + * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code. + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config General panel device configuration + * @param[out] ret_panel Returned LCD panel handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + * - Otherwise on fail + */ +esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel); + +/** + * @brief MIPI-DSI bus configuration structure + * + */ +#define ILI9881C_PANEL_BUS_DSI_2CH_CONFIG() \ + { \ + .bus_id = 0, \ + .num_data_lanes = 2, \ + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \ + .lane_bit_rate_mbps = 1000, \ + } + +/** + * @brief MIPI-DBI panel IO configuration structure + * + */ +#define ILI9881C_PANEL_IO_DBI_CONFIG() \ + { \ + .virtual_channel = 0, \ + .lcd_cmd_bits = 8, \ + .lcd_param_bits = 8, \ + } + +/** + * @brief MIPI DPI configuration structure + * + * @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch) + * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch) + * + * @param[in] px_format Pixel format of the panel + * + */ +#define ILI9881C_800_1280_PANEL_60HZ_DPI_CONFIG(px_format) \ + { \ + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \ + .dpi_clock_freq_mhz = 80, \ + .virtual_channel = 0, \ + .pixel_format = px_format, \ + .num_fbs = 1, \ + .video_timing = { \ + .h_size = 800, \ + .v_size = 1280, \ + .hsync_back_porch = 140, \ + .hsync_pulse_width = 40, \ + .hsync_front_porch = 40, \ + .vsync_back_porch = 16, \ + .vsync_pulse_width = 4, \ + .vsync_front_porch = 16, \ + }, \ + .flags.use_dma2d = true, \ + } +#endif + +#ifdef __cplusplus +} +#endif diff --git a/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp b/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp index 1fec2a1b..6dec2604 100644 --- a/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp +++ b/test_apps/lcd/mipi_dsi/main/test_mipi_dsi_lcd.cpp @@ -237,3 +237,4 @@ static void run_test(shared_ptr lcd) * */ CREATE_TEST_CASE(EK79007) +CREATE_TEST_CASE(ILI9881C) From 2f10dca9b493e26088953242190c2116d56cfb84 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Fri, 8 Nov 2024 14:54:17 +0800 Subject: [PATCH 07/11] feat(panel): add support for MIPI-DSI LCD --- ESP_Panel_Board_Custom.h | 23 +++- .../LVGL/v8/Porting/ESP_Panel_Board_Custom.h | 23 +++- examples/LVGL/v8/Porting/Porting.ino | 12 +- .../LVGL/v8/Rotation/ESP_Panel_Board_Custom.h | 23 +++- .../Panel/PanelTest/ESP_Panel_Board_Custom.h | 23 +++- .../PlatformIO/src/ESP_Panel_Board_Custom.h | 23 +++- .../v8/Porting/ESP_Panel_Board_Custom.h | 23 +++- .../v8/WiFiClock/ESP_Panel_Board_Custom.h | 23 +++- src/ESP_Panel.cpp | 50 ++++---- src/ESP_PanelVersions.h | 98 +++++++------- src/ESP_Panel_Board_Internal.h | 10 +- src/board/ESP_PanelBoard.h | 76 +++++++---- src/board/elecrow/CROWPANEL_7_0.h | 2 +- src/board/espressif/ESP32_S3_LCD_EV_BOARD.h | 2 +- src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h | 2 +- .../espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h | 2 +- .../espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h | 2 +- src/board/jingcai/ESP32_4848S040C_I_Y_3.h | 2 +- src/board/m5stack/M5DIAL.h | 2 +- src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h | 2 +- src/bus/DSI.cpp | 5 + src/bus/DSI.h | 6 +- src/lcd/EK79007.cpp | 4 + src/lcd/EK9716B.cpp | 6 + src/lcd/ESP_PanelLcd.cpp | 17 ++- src/lcd/ESP_PanelLcd.h | 3 +- src/lcd/GC9503.cpp | 9 +- src/lcd/ILI9881C.cpp | 4 + src/lcd/ST7262.cpp | 6 + src/lcd/ST7701.cpp | 5 + test_apps/lvgl_port/main/CMakeLists.txt | 2 +- test_apps/lvgl_port/main/Kconfig.projbuild | 4 +- test_apps/lvgl_port/main/lvgl_port_v8.cpp | 121 ++++++++++++------ test_apps/lvgl_port/main/lvgl_port_v8.h | 6 +- .../{test_app_main.c => test_app_main.cpp} | 8 +- test_apps/lvgl_port/main/test_lvgl_port.cpp | 12 +- .../sdkconfig.ci.elecrow_crowpanel_7_0 | 1 + .../sdkconfig.ci.espressif_esp32_c3_lcdkit | 5 + .../sdkconfig.ci.espressif_esp32_s3_box | 5 + .../sdkconfig.ci.espressif_esp32_s3_box_3 | 5 + ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 5 + .../sdkconfig.ci.espressif_esp32_s3_box_lite | 5 + .../sdkconfig.ci.espressif_esp32_s3_eye | 5 + .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 4 + ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 1 + ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 1 + ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 1 + ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 1 + .../sdkconfig.ci.espressif_esp32_s3_usb_otg | 4 + ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 1 + .../lvgl_port/sdkconfig.ci.m5stack_m5core2 | 4 + .../lvgl_port/sdkconfig.ci.m5stack_m5core3 | 5 + .../lvgl_port/sdkconfig.ci.m5stack_m5dial | 10 +- ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 5 + ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 2 + ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 1 + test_apps/panel/main/CMakeLists.txt | 2 +- .../{test_app_main.c => test_app_main.cpp} | 9 +- .../panel/sdkconfig.ci.elecrow_crowpanel_7_0 | 1 + .../sdkconfig.ci.espressif_esp32_c3_lcdkit | 1 + .../panel/sdkconfig.ci.espressif_esp32_s3_box | 2 + .../sdkconfig.ci.espressif_esp32_s3_box_3 | 2 + ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 2 + .../sdkconfig.ci.espressif_esp32_s3_box_lite | 2 + .../panel/sdkconfig.ci.espressif_esp32_s3_eye | 2 + .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 1 + ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 1 + ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 1 + ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 1 + ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 1 + .../sdkconfig.ci.espressif_esp32_s3_usb_otg | 1 + ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 1 + test_apps/panel/sdkconfig.ci.m5stack_m5core2 | 1 + test_apps/panel/sdkconfig.ci.m5stack_m5core3 | 2 + test_apps/panel/sdkconfig.ci.m5stack_m5dial | 1 + ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 2 + ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 2 + ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 1 + 78 files changed, 534 insertions(+), 217 deletions(-) rename test_apps/lvgl_port/main/{test_app_main.c => test_app_main.cpp} (90%) rename test_apps/panel/main/{test_app_main.c => test_app_main.cpp} (90%) diff --git a/ESP_Panel_Board_Custom.h b/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/ESP_Panel_Board_Custom.h +++ b/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h b/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h +++ b/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/LVGL/v8/Porting/Porting.ino b/examples/LVGL/v8/Porting/Porting.ino index 0bc750e9..4b38a10d 100644 --- a/examples/LVGL/v8/Porting/Porting.ino +++ b/examples/LVGL/v8/Porting/Porting.ino @@ -71,10 +71,14 @@ void setup() ESP_Panel *panel = new ESP_Panel(); panel->init(); #if LVGL_PORT_AVOID_TEAR - // When avoid tearing function is enabled, configure the RGB bus according to the LVGL configuration - ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); - rgb_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); - rgb_bus->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE); + // When avoid tearing function is enabled, configure the bus according to the LVGL configuration + ESP_PanelBus *lcd_bus = panel->getLcd()->getBus(); +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB + static_cast(lcd_bus)->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE); + static_cast(lcd_bus)->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + static_cast(lcd_bus)->configDpiFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); +#endif #endif panel->begin(); diff --git a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h +++ b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h b/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h +++ b/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/PlatformIO/src/ESP_Panel_Board_Custom.h b/examples/PlatformIO/src/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/PlatformIO/src/ESP_Panel_Board_Custom.h +++ b/examples/PlatformIO/src/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h +++ b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h index 3ae31b50..dfd5e727 100644 --- a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h +++ b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h @@ -110,8 +110,6 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, @@ -121,7 +119,6 @@ #define ESP_PANEL_LCD_RGB_IO_DE (17) // -1 if not used #define ESP_PANEL_LCD_RGB_IO_PCLK (9) #define ESP_PANEL_LCD_RGB_IO_DISP (-1) // -1 if not used - // | RGB565 | RGB666 | RGB888 | // |--------|--------|--------| #define ESP_PANEL_LCD_RGB_IO_DATA0 (10) // | B0 | B0-1 | B0-3 | @@ -158,6 +155,22 @@ // The `mirror()` function will be implemented by LCD command if set to 1. #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + #else #error "The function is not ready and will be implemented in the future." @@ -380,8 +393,8 @@ * */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/src/ESP_Panel.cpp b/src/ESP_Panel.cpp index 46403472..4b02877c 100644 --- a/src/ESP_Panel.cpp +++ b/src/ESP_Panel.cpp @@ -229,7 +229,7 @@ bool ESP_Panel::init(void) }, .data_width = ESP_PANEL_LCD_RGB_DATA_WIDTH, .bits_per_pixel = ESP_PANEL_LCD_RGB_PIXEL_BITS, - .num_fbs = ESP_PANEL_LCD_RGB_FRAME_BUF_NUM, + .num_fbs = 1, .bounce_buffer_size_px = ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE, .sram_trans_align = 4, .psram_trans_align = 64, @@ -261,6 +261,21 @@ bool ESP_Panel::init(void) }, }; +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + ESP_LOGD(TAG, "Use MIPI-DSI bus"); + // MIPI-DSI bus + esp_lcd_dsi_bus_config_t dsi_bus_config = ESP_PANEL_HOST_DSI_CONFIG_DEFAULT( + ESP_PANEL_LCD_MIPI_DSI_LANE_NUM, ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS + ); + // MIPI-DPI panel + esp_lcd_dpi_panel_config_t dpi_panel_config = ESP_PANEL_DPI_CONFIG_DEFAULT( + ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ, ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS, + ESP_PANEL_LCD_WIDTH, ESP_PANEL_LCD_HEIGHT, + ESP_PANEL_LCD_MIPI_DSI_HPW, ESP_PANEL_LCD_MIPI_DSI_HBP, ESP_PANEL_LCD_MIPI_DSI_HFP, + ESP_PANEL_LCD_MIPI_DSI_VPW, ESP_PANEL_LCD_MIPI_DSI_VBP, ESP_PANEL_LCD_MIPI_DSI_VFP + ); + #else #error "This function is not ready and will be implemented in the future." @@ -273,19 +288,12 @@ bool ESP_Panel::init(void) .init_cmds = lcd_init_cmds, .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), #endif -#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB - .rgb_config = &rgb_panel_config, .flags = { -#if !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST +#if (ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB) && !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST .mirror_by_cmd = ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD, .auto_del_panel_io = ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO, #endif }, -#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_QSPI - .flags = { - .use_qspi_interface = true, - }, -#endif }; // LCD device configuration @@ -308,14 +316,18 @@ bool ESP_Panel::init(void) #else lcd_bus_ptr = CREATE_BUS_SKIP_HOST(ESP_PANEL_LCD_BUS_NAME, rgb_panel_config, ESP_PANEL_LCD_BUS_HOST); #endif +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + lcd_bus_ptr = CREATE_BUS_INIT_HOST( + ESP_PANEL_LCD_BUS_NAME, dsi_bus_config, dpi_panel_config, ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID + ); #else - /* For non-RGB LCD, should use `ADD_HOST()` to init host when `ESP_PANEL_LCD_BUS_SKIP_INIT_HOST` enabled */ + /* For other LCDs, should use `ADD_HOST()` to init host when `ESP_PANEL_LCD_BUS_SKIP_INIT_HOST` enabled */ #if !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(ESP_PANEL_LCD_BUS_NAME, host_ptr, lcd_bus_host_config, ESP_PANEL_LCD_BUS_HOST), false, "Add host failed"); #endif lcd_bus_ptr = CREATE_BUS_SKIP_HOST(ESP_PANEL_LCD_BUS_NAME, lcd_panel_io_config, ESP_PANEL_LCD_BUS_HOST); -#endif +#endif /* ESP_PANEL_LCD_BUS_TYPE */ ESP_PANEL_CHECK_NULL_RET(lcd_bus_ptr, false, "Create LCD bus failed"); @@ -630,37 +642,21 @@ bool ESP_Panel::del(void) ESP_PanelLcd *ESP_Panel::getLcd(void) { - if (_lcd_ptr == nullptr) { - ESP_LOGD(TAG, "Get invalid LCD pointer"); - } - return _lcd_ptr.get(); } ESP_PanelTouch *ESP_Panel::getTouch(void) { - if (_touch_ptr == nullptr) { - ESP_LOGD(TAG, "Get invalid touch pointer"); - } - return _touch_ptr.get(); } ESP_PanelBacklight *ESP_Panel::getBacklight(void) { - if (_backlight_ptr == nullptr) { - ESP_LOGD(TAG, "Get invalid backlight pointer"); - } - return _backlight_ptr.get(); } ESP_IOExpander *ESP_Panel::getExpander(void) { - if (_expander_ptr == nullptr) { - ESP_LOGD(TAG, "Get invalid expander pointer"); - } - return _expander_ptr.get(); } diff --git a/src/ESP_PanelVersions.h b/src/ESP_PanelVersions.h index f8338218..2902587f 100644 --- a/src/ESP_PanelVersions.h +++ b/src/ESP_PanelVersions.h @@ -20,8 +20,8 @@ /* File `ESP_Panel_Board_Custom.h` */ #define ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH 3 +#define ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH 0 /* File `ESP_Panel_Board_Supported.h` */ #define ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR 0 @@ -30,9 +30,12 @@ // *INDENT-OFF* +/** + * Check if the current configuration file version is compatible with the library version + * + */ +/* File `ESP_Panel_Conf.h` */ #ifndef ESP_PANEL_CONF_FILE_SKIP - /* Check if the current configuration file version is compatible with the library version */ - // File `ESP_Panel_Conf.h` // If the version is not defined, set it to `0.1.0` #if !defined(ESP_PANEL_CONF_FILE_VERSION_MAJOR) && \ !defined(ESP_PANEL_CONF_FILE_VERSION_MINOR) && \ @@ -51,46 +54,51 @@ #endif /* ESP_PANEL_CONF_INCLUDE_INSIDE */ #endif /* ESP_PANEL_CONF_FILE_SKIP */ -#ifndef ESP_PANEL_BOARD_FILE_SKIP - // File `ESP_Panel_Board_Custom.h` & `ESP_Panel_Board_Supported.h` - #ifdef ESP_PANEL_USE_BOARD - /* For using a supported board */ - #if ESP_PANEL_USE_SUPPORTED_BOARD - // If the version is not defined, set it to `0.1.0` - #if !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR) && \ - !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR) && \ - !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH) - #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 - #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 1 - #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 - #endif - // Check if the current configuration file version is compatible with the library version - #if ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR - #error "The file `ESP_Panel_Board_Supported.h` version is not compatible. Please update it with the file from the library" - #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR < ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR - #warning "The file `ESP_Panel_Board_Supported.h` version is outdated. Some new configurations are missing" - #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR > ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR - #warning "The file `ESP_Panel_Board_Supported.h` version is newer than the library. Some new configurations are not supported" - #endif - #else /* For using a custom board */ - // If the version is not defined, set it to `0.1.0` - #if !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR) && \ - !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR) && \ - !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH) - #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 - #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 1 - #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 - #endif - // Check if the current configuration file version is compatible with the library version - #if ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR - #error "The file `ESP_Panel_Board_Custom.h` version is not compatible. Please update it with the file from the library" - #elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR < ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR - #warning "The file `ESP_Panel_Board_Custom.h` version is outdated. Some new configurations are missing" - #elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR > ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH - #warning "The file `ESP_Panel_Board_Custom.h` version is newer than the library. Some new configurations are not supported" - #endif - #endif /* CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD */ - #endif /* ESP_PANEL_USE_BOARD */ -#endif /* ESP_PANEL_BOARD_FILE_SKIP */ +/* File `ESP_Panel_Board_Custom.h` & `ESP_Panel_Board_Supported.h` */ +#ifdef ESP_PANEL_USE_BOARD + /* File `ESP_Panel_Board_Supported.h` */ + // Only check this file versions if use a supported board and not skip the file + #if ESP_PANEL_USE_SUPPORTED_BOARD && !defined(ESP_PANEL_BOARD_FILE_SKIP) + // If the version is not defined, set it to `0.1.0` + #if !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR) && \ + !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR) && \ + !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH) + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 1 + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 + #endif + // Check if the current configuration file version is compatible with the library version + #if ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR + #error "The file `ESP_Panel_Board_Supported.h` version is not compatible. Please update it with the file from the library" + #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR < ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Supported.h` version is outdated. Some new configurations are missing" + #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR > ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Supported.h` version is newer than the library. Some new configurations are not supported" + #endif + #endif + + /* File `ESP_Panel_Board_Custom.h` */ + // If the version is not defined, set it to `0.1.0` + #if !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR) && \ + !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR) && \ + !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH) + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 1 + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 + #endif + // Check if the current configuration file version is compatible with the library version + // Must check the major version + #if ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR + #error "The file `ESP_Panel_Board_Custom.h` version is not compatible. Please update it with the file from the library" + #endif + // Only check the other versions if not skip the file + #if !defined(ESP_PANEL_BOARD_FILE_SKIP) + #if ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR < ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Custom.h` version is outdated. Some new configurations are missing" + #elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR > ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH + #warning "The file `ESP_Panel_Board_Custom.h` version is newer than the library. Some new configurations are not supported" + #endif + #endif +#endif /* ESP_PANEL_USE_BOARD */ // *INDENT-OFF* diff --git a/src/ESP_Panel_Board_Internal.h b/src/ESP_Panel_Board_Internal.h index 91527867..d221135e 100644 --- a/src/ESP_Panel_Board_Internal.h +++ b/src/ESP_Panel_Board_Internal.h @@ -119,12 +119,20 @@ #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB - #ifndef SOC_LCD_RGB_SUPPORTED + #if !SOC_LCD_RGB_SUPPORTED #error "RGB is not supported for current SoC, please select the correct board." #endif #define ESP_PANEL_LCD_BUS_NAME RGB #define ESP_PANEL_LCD_BUS_HOST (-1) + #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #if !SOC_MIPI_DSI_SUPPORTED + #error "MIPI-DSI is not supported for current SoC, please select the correct board." + #endif + #define ESP_PANEL_LCD_BUS_NAME DSI + #define ESP_PANEL_LCD_BUS_HOST (-1) + #else #error "Unknown LCD panel bus type selected, please refer to the README for supported bus types" diff --git a/src/board/ESP_PanelBoard.h b/src/board/ESP_PanelBoard.h index 92d083bf..cab561c8 100644 --- a/src/board/ESP_PanelBoard.h +++ b/src/board/ESP_PanelBoard.h @@ -9,63 +9,83 @@ // *INDENT-OFF* // Check if multiple boards are enabled -#if defined(BOARD_ESP32_C3_LCDKIT) + defined(BOARD_ESP32_S3_BOX) + defined(BOARD_ESP32_S3_BOX_3) + \ - defined(BOARD_ESP32_S3_BOX_3_BETA) + defined(BOARD_ESP32_S3_BOX_LITE) + defined(BOARD_ESP32_S3_EYE) + \ - defined(BOARD_ESP32_S3_KORVO_2) + defined(BOARD_ESP32_S3_LCD_EV_BOARD) + \ - defined(BOARD_ESP32_S3_LCD_EV_BOARD_V1_5) + defined(BOARD_ESP32_S3_LCD_EV_BOARD_2) + \ - defined(BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5) + defined(BOARD_ESP32_S3_USB_OTG) + defined(BOARD_ELECROW_CROWPANEL_7_0) + \ - defined(BOARD_M5STACK_M5CORE2) + defined(BOARD_M5STACK_M5DIAL) + defined(BOARD_M5STACK_M5CORES3) + \ - defined(BOARD_ESP32_4848S040C_I_Y_3) + defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3) + defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85) + \ - defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1) \ +#if \ + /* Espressif */ \ + defined(BOARD_ESP32_C3_LCDKIT) \ + + defined(BOARD_ESP32_S3_BOX) \ + + defined(BOARD_ESP32_S3_BOX_3) \ + + defined(BOARD_ESP32_S3_BOX_3_BETA) \ + + defined(BOARD_ESP32_S3_BOX_LITE) \ + + defined(BOARD_ESP32_S3_EYE) \ + + defined(BOARD_ESP32_S3_KORVO_2) \ + + defined(BOARD_ESP32_S3_LCD_EV_BOARD) \ + + defined(BOARD_ESP32_S3_LCD_EV_BOARD_V1_5) \ + + defined(BOARD_ESP32_S3_LCD_EV_BOARD_2) \ + + defined(BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5) \ + + defined(BOARD_ESP32_S3_USB_OTG) \ + /* Elecrow */ \ + + defined(BOARD_ELECROW_CROWPANEL_7_0) \ + /* M5Stack */ \ + + defined(BOARD_M5STACK_M5CORE2) \ + + defined(BOARD_M5STACK_M5DIAL) \ + + defined(BOARD_M5STACK_M5CORES3) \ + /* JingCai */ \ + + defined(BOARD_ESP32_4848S040C_I_Y_3) \ + /* Waveshare */ \ + + defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3) \ + + defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85) \ + + defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1) \ > 1 #error "Multiple boards enabled! Please check file `ESP_Panel_Board_Supported.h` and make sure only one board is enabled." #endif // Include board specific header file /* Espressif */ -#if defined(BOARD_ESP32_C3_LCDKIT) || CONFIG_BOARD_ESP32_C3_LCDKIT +#if defined(BOARD_ESP32_C3_LCDKIT) #include "board/espressif/ESP32_C3_LCDKIT.h" -#elif defined(BOARD_ESP32_S3_BOX) || CONFIG_BOARD_ESP32_S3_BOX +#elif defined(BOARD_ESP32_S3_BOX) #include "board/espressif/ESP32_S3_BOX.h" -#elif defined(BOARD_ESP32_S3_BOX_3) || CONFIG_BOARD_ESP32_S3_BOX_3 +#elif defined(BOARD_ESP32_S3_BOX_3) #include "board/espressif/ESP32_S3_BOX_3.h" -#elif defined(BOARD_ESP32_S3_BOX_3_BETA) || CONFIG_BOARD_ESP32_S3_BOX_3_BETA +#elif defined(BOARD_ESP32_S3_BOX_3_BETA) #include "board/espressif/ESP32_S3_BOX_3_BETA.h" -#elif defined(BOARD_ESP32_S3_BOX_LITE) || CONFIG_BOARD_ESP32_S3_BOX_LITE +#elif defined(BOARD_ESP32_S3_BOX_LITE) #include "board/espressif/ESP32_S3_BOX_LITE.h" -#elif defined(BOARD_ESP32_S3_EYE) || CONFIG_BOARD_ESP32_S3_EYE +#elif defined(BOARD_ESP32_S3_EYE) #include "board/espressif/ESP32_S3_EYE.h" -#elif defined(BOARD_ESP32_S3_KORVO_2) || CONFIG_BOARD_ESP32_S3_KORVO_2 +#elif defined(BOARD_ESP32_S3_KORVO_2) #include "board/espressif/ESP32_S3_KORVO_2.h" -#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD) || CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD +#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD) #include "board/espressif/ESP32_S3_LCD_EV_BOARD.h" -#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_V1_5) || CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 +#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_V1_5) #include "board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h" -#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_2) || CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2 +#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_2) #include "board/espressif/ESP32_S3_LCD_EV_BOARD_2.h" -#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5) || CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 +#elif defined(BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5) #include "board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h" -#elif defined(BOARD_ESP32_S3_USB_OTG) || CONFIG_BOARD_ESP32_S3_USB_OTG +#elif defined(BOARD_ESP32_S3_USB_OTG) #include "board/espressif/ESP32_S3_USB_OTG.h" +#elif defined(BOARD_ESP32_P4_FUNCTION_EV_BOARD) + #include "board/espressif/ESP32_P4_FUNCTION_EV_BOARD.h" /* Elecrow */ -#elif defined(BOARD_ELECROW_CROWPANEL_7_0) || CONFIG_BOARD_ELECROW_CROWPANEL_7_0 +#elif defined(BOARD_ELECROW_CROWPANEL_7_0) #include "board/elecrow/CROWPANEL_7_0.h" /* M5Stack */ -#elif defined(BOARD_M5STACK_M5CORE2) || CONFIG_BOARD_M5STACK_M5CORE2 +#elif defined(BOARD_M5STACK_M5CORE2) #include "board/m5stack/M5CORE2.h" -#elif defined(BOARD_M5STACK_M5DIAL) || CONFIG_BOARD_M5STACK_M5DIAL +#elif defined(BOARD_M5STACK_M5DIAL) #include "board/m5stack/M5DIAL.h" -#elif defined(BOARD_M5STACK_M5CORES3) || CONFIG_BOARD_M5STACK_M5CORES3 +#elif defined(BOARD_M5STACK_M5CORES3) #include "board/m5stack/M5CORES3.h" /* Jingcai */ -#elif defined(BOARD_ESP32_4848S040C_I_Y_3) || CONFIG_BOARD_ESP32_4848S040C_I_Y_3 +#elif defined(BOARD_ESP32_4848S040C_I_Y_3) #include "board/jingcai/ESP32_4848S040C_I_Y_3.h" /* Waveshare */ -#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3) || CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 +#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3) #include "board/waveshare/ESP32_S3_Touch_LCD_4_3.h" -#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85) || CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 +#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85) #include "board/waveshare/ESP32_S3_Touch_LCD_1_85.h" -#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1) || CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 +#elif defined(BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1) #include "board/waveshare/ESP32_S3_Touch_LCD_2_1.h" #else #error "Unknown board selected! Please check file `ESP_Panel_Board_Supported.h` and make sure only one board is enabled." diff --git a/src/board/elecrow/CROWPANEL_7_0.h b/src/board/elecrow/CROWPANEL_7_0.h index 46ba70a0..bb9a7890 100644 --- a/src/board/elecrow/CROWPANEL_7_0.h +++ b/src/board/elecrow/CROWPANEL_7_0.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (1) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h index 7fd47cc4..b50b5065 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (0) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h index e909972c..813a4104 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (1) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h index cefbcb55..6b50bf81 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (1) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h index a4626e5d..f09a51f2 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (0) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/jingcai/ESP32_4848S040C_I_Y_3.h b/src/board/jingcai/ESP32_4848S040C_I_Y_3.h index 3c23176c..2a696f51 100644 --- a/src/board/jingcai/ESP32_4848S040C_I_Y_3.h +++ b/src/board/jingcai/ESP32_4848S040C_I_Y_3.h @@ -53,7 +53,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (0) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. diff --git a/src/board/m5stack/M5DIAL.h b/src/board/m5stack/M5DIAL.h index 58afaa2e..dc5e5acb 100644 --- a/src/board/m5stack/M5DIAL.h +++ b/src/board/m5stack/M5DIAL.h @@ -101,7 +101,7 @@ #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG (0) // 0: rising edge, 1: falling edge #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // 8 | 16 #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // 24 | 16 - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (0) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, diff --git a/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h b/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h index 1155896d..ecea5576 100644 --- a/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h +++ b/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h @@ -56,7 +56,7 @@ // |--------------|---------------| #define ESP_PANEL_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | #define ESP_PANEL_LCD_RGB_PIXEL_BITS (16) // | 24 | 16 | - #define ESP_PANEL_LCD_RGB_FRAME_BUF_NUM (1) // 1/2/3 + #define ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE (ESP_PANEL_LCD_WIDTH * 10) // Bounce buffer size in bytes. This function is used to avoid screen drift. // To enable the bounce buffer, set it to a non-zero value. Typically set to `ESP_PANEL_LCD_WIDTH * 10` // The size of the Bounce Buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, diff --git a/src/bus/DSI.cpp b/src/bus/DSI.cpp index 5c1ba5cb..a20b0db2 100644 --- a/src/bus/DSI.cpp +++ b/src/bus/DSI.cpp @@ -101,6 +101,11 @@ bool ESP_PanelBus_DSI::del(void) return true; } +void ESP_PanelBus_DSI::configDpiFrameBufferNumber(uint8_t num) +{ + _dpi_config.num_fbs = num; +} + bool ESP_PanelBus_DSI::begin(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); diff --git a/src/bus/DSI.h b/src/bus/DSI.h index ed504dee..bd9ee416 100644 --- a/src/bus/DSI.h +++ b/src/bus/DSI.h @@ -20,10 +20,10 @@ #define ESP_PANEL_HOST_DSI_ID_DEFAULT (0) #define ESP_PANEL_HOST_DSI_CONFIG_DEFAULT(lane_num, lane_rate_mbps) \ { \ - .bus_id = 0, \ + .bus_id = ESP_PANEL_HOST_DSI_ID_DEFAULT, \ .num_data_lanes = lane_num, \ .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \ - .lane_bit_rate_mbps = lane_rate_mbps, \ + .lane_bit_rate_mbps = lane_rate_mbps, \ } /** @@ -134,7 +134,7 @@ class ESP_PanelBus_DSI: public ESP_PanelBus { * @brief Here are some functions to configure the MIPI-DSI bus object. These functions should be called before `begin()` * */ - // void configSpiMode(uint8_t mode); + void configDpiFrameBufferNumber(uint8_t num); /** * @brief Startup the bus diff --git a/src/lcd/EK79007.cpp b/src/lcd/EK79007.cpp index 0d94fe67..30cab660 100644 --- a/src/lcd/EK79007.cpp +++ b/src/lcd/EK79007.cpp @@ -48,6 +48,10 @@ bool ESP_PanelLcd_EK79007::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + /* Load MIPI-DSI configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ ESP_PANEL_CHECK_ERR_RET( esp_lcd_new_panel_ek79007(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed" ); diff --git a/src/lcd/EK9716B.cpp b/src/lcd/EK9716B.cpp index 0495c7b3..727c7852 100644 --- a/src/lcd/EK9716B.cpp +++ b/src/lcd/EK9716B.cpp @@ -52,6 +52,7 @@ bool ESP_PanelLcd_EK9716B::init(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + /* Initialize RST pin */ if (panel_config.reset_gpio_num >= 0) { gpio_config_t gpio_conf = { .pin_bit_mask = BIT64(panel_config.reset_gpio_num), @@ -62,6 +63,11 @@ bool ESP_PanelLcd_EK9716B::init(void) }; ESP_PANEL_CHECK_ERR_RET(gpio_config(&gpio_conf), false, "`Config RST gpio failed"); } + + /* Load RGB configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_rgb_panel(vendor_config.rgb_config, &handle), false, "Create panel failed"); ESP_LOGD(TAG, "LCD panel @%p created", handle); diff --git a/src/lcd/ESP_PanelLcd.cpp b/src/lcd/ESP_PanelLcd.cpp index 93298468..4976e5c5 100644 --- a/src/lcd/ESP_PanelLcd.cpp +++ b/src/lcd/ESP_PanelLcd.cpp @@ -59,8 +59,6 @@ ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, uint8_t color_bits, int rst_io): _draw_bitmap_finish_sem(NULL), _callback_data(CALLBACK_DATA_DEFAULT()) { - /* Construct vendor configuration */ - constructVendorConfig(bus); } ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t &panel_config): @@ -84,9 +82,6 @@ ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, const esp_lcd_panel_dev_config_t & vendor_config = *(esp_lcd_panel_vendor_config_t *)panel_config.vendor_config; } this->panel_config.vendor_config = &vendor_config; - - /* Construct vendor configuration */ - constructVendorConfig(bus); } bool ESP_PanelLcd::configVendorCommands(const esp_lcd_panel_vendor_init_cmd_t init_cmd[], uint32_t init_cmd_size) @@ -125,6 +120,7 @@ bool ESP_PanelLcd::configResetActiveLevel(int level) bool ESP_PanelLcd::configMirrorByCommand(bool en) { ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); ESP_PANEL_CHECK_FALSE_RET(bus->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "This function is only for RGB interface"); vendor_config.flags.mirror_by_cmd = en; @@ -135,6 +131,7 @@ bool ESP_PanelLcd::configMirrorByCommand(bool en) bool ESP_PanelLcd::configEnableIO_Multiplex(bool en) { ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); ESP_PANEL_CHECK_FALSE_RET(bus->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "This function is only for RGB interface"); vendor_config.flags.enable_io_multiplex = en; @@ -612,11 +609,10 @@ void *ESP_PanelLcd::getFrameBufferByIndex(uint8_t index) return buffer[index]; } -void ESP_PanelLcd::constructVendorConfig(ESP_PanelBus *bus) +bool ESP_PanelLcd::loadVendorConfigFromBus(void) { - if (bus == NULL) { - return; - } + ESP_PANEL_CHECK_FALSE_RET(!checkIsInit(), false, "This function should be called before `init()`"); + ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); switch (bus->getType()) { case ESP_PANEL_BUS_TYPE_SPI: @@ -644,8 +640,11 @@ void ESP_PanelLcd::constructVendorConfig(ESP_PanelBus *bus) break; #endif default: + ESP_LOGE(TAG, "Unsupported bus type"); break; } + + return true; } IRAM_ATTR bool ESP_PanelLcd::onDrawBitmapFinish(void *panel_io, void *edata, void *user_ctx) diff --git a/src/lcd/ESP_PanelLcd.h b/src/lcd/ESP_PanelLcd.h index c30d3343..e4da6a85 100644 --- a/src/lcd/ESP_PanelLcd.h +++ b/src/lcd/ESP_PanelLcd.h @@ -484,6 +484,8 @@ class ESP_PanelLcd { } protected: + bool loadVendorConfigFromBus(void); + bool checkIsInit(void) { return (handle != NULL) && (bus != NULL); @@ -508,7 +510,6 @@ class ESP_PanelLcd { esp_lcd_panel_handle_t handle; private: - void constructVendorConfig(ESP_PanelBus *bus); IRAM_ATTR static bool onDrawBitmapFinish(void *panel_io, void *edata, void *user_ctx); IRAM_ATTR static bool onRefreshFinish(void *panel_io, void *edata, void *user_ctx); diff --git a/src/lcd/GC9503.cpp b/src/lcd/GC9503.cpp index 3abb8db3..8c96b8fe 100644 --- a/src/lcd/GC9503.cpp +++ b/src/lcd/GC9503.cpp @@ -42,7 +42,14 @@ bool ESP_PanelLcd_GC9503::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); - ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_gc9503(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); + /* Load RGB configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ + ESP_PANEL_CHECK_ERR_RET( + esp_lcd_new_panel_gc9503(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed" + ); + // Delete panel io if enable `auto_del_panel_io` or `enable_io_multiplex` flag if (((esp_lcd_panel_vendor_config_t *)panel_config.vendor_config)->flags.auto_del_panel_io) { ESP_PANEL_CHECK_FALSE_RET(bus->delSkipPanelIO(), false, "Delete panel io failed"); diff --git a/src/lcd/ILI9881C.cpp b/src/lcd/ILI9881C.cpp index 59ab7aab..1a3a498c 100644 --- a/src/lcd/ILI9881C.cpp +++ b/src/lcd/ILI9881C.cpp @@ -46,6 +46,10 @@ bool ESP_PanelLcd_ILI9881C::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + /* Load MIPI-DSI configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ ESP_PANEL_CHECK_ERR_RET( esp_lcd_new_panel_ili9881c(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed" ); diff --git a/src/lcd/ST7262.cpp b/src/lcd/ST7262.cpp index c0b7e071..766a32d0 100644 --- a/src/lcd/ST7262.cpp +++ b/src/lcd/ST7262.cpp @@ -52,6 +52,7 @@ bool ESP_PanelLcd_ST7262::init(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + /* Initialize RST pin */ if (panel_config.reset_gpio_num >= 0) { gpio_config_t gpio_conf = { .pin_bit_mask = BIT64(panel_config.reset_gpio_num), @@ -62,6 +63,11 @@ bool ESP_PanelLcd_ST7262::init(void) }; ESP_PANEL_CHECK_ERR_RET(gpio_config(&gpio_conf), false, "`Config RST gpio failed"); } + + /* Load RGB configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_rgb_panel(vendor_config.rgb_config, &handle), false, "Create panel failed"); ESP_LOGD(TAG, "LCD panel @%p created", handle); diff --git a/src/lcd/ST7701.cpp b/src/lcd/ST7701.cpp index c279c1e8..a75d4adc 100644 --- a/src/lcd/ST7701.cpp +++ b/src/lcd/ST7701.cpp @@ -42,7 +42,12 @@ bool ESP_PanelLcd_ST7701::init(void) { ESP_PANEL_CHECK_NULL_RET(bus, false, "Invalid bus"); + /* Load RGB configurations from bus to vendor configurations */ + ESP_PANEL_CHECK_FALSE_RET(loadVendorConfigFromBus(), false, "Load vendor config from bus failed"); + + /* Create panel handle */ ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_st7701(bus->getPanelIO_Handle(), &panel_config, &handle), false, "Create panel failed"); + // Delete panel io if enable `auto_del_panel_io` or `enable_io_multiplex` flag if (((esp_lcd_panel_vendor_config_t *)panel_config.vendor_config)->flags.auto_del_panel_io) { ESP_PANEL_CHECK_FALSE_RET(bus->delSkipPanelIO(), false, "Delete panel io failed"); diff --git a/test_apps/lvgl_port/main/CMakeLists.txt b/test_apps/lvgl_port/main/CMakeLists.txt index 43ee3b82..341b8146 100644 --- a/test_apps/lvgl_port/main/CMakeLists.txt +++ b/test_apps/lvgl_port/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRCS "test_app_main.c" "test_lvgl_port.cpp" "lvgl_port_v8.cpp" + SRCS "test_app_main.cpp" "test_lvgl_port.cpp" "lvgl_port_v8.cpp" WHOLE_ARCHIVE ) diff --git a/test_apps/lvgl_port/main/Kconfig.projbuild b/test_apps/lvgl_port/main/Kconfig.projbuild index 7446973a..da91251b 100644 --- a/test_apps/lvgl_port/main/Kconfig.projbuild +++ b/test_apps/lvgl_port/main/Kconfig.projbuild @@ -1,8 +1,8 @@ menu "Test Configurations" choice LVGL_PORT_AVOID_TEARING_MODE_CHOICE prompt "Avoid Tearing Mode" - depends on SOC_LCD_RGB_SUPPORTED - default LVGL_PORT_AVOID_TEARING_MODE_3 + depends on SOC_LCD_RGB_SUPPORTED || SOC_MIPI_DSI_SUPPORTED + default LVGL_PORT_AVOID_TEARING_MODE_NONE config LVGL_PORT_AVOID_TEARING_MODE_NONE bool "None" diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.cpp b/test_apps/lvgl_port/main/lvgl_port_v8.cpp index 91dcc27e..5e776c49 100644 --- a/test_apps/lvgl_port/main/lvgl_port_v8.cpp +++ b/test_apps/lvgl_port/main/lvgl_port_v8.cpp @@ -11,6 +11,8 @@ static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -201,7 +203,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -232,7 +234,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -264,7 +266,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -286,7 +288,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -299,8 +301,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -315,38 +317,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -439,7 +441,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -447,38 +448,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -548,16 +549,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -580,7 +598,7 @@ static void lvgl_port_task(void *arg) } } -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) { lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; @@ -592,9 +610,14 @@ IRAM_ATTR bool onRefreshFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -602,7 +625,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -612,9 +635,9 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } if (tp != nullptr) { @@ -645,7 +668,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -670,14 +693,28 @@ bool lvgl_port_unlock(void) bool lvgl_port_deinit(void) { - lvgl_port_lock(-1); +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); if (lvgl_task_handle != nullptr) { vTaskDelete(lvgl_task_handle); lvgl_task_handle = nullptr; } - lvgl_port_unlock(); + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); +#if LV_ENABLE_GC || !LV_MEM_CUSTOM lv_deinit(); +#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; diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.h b/test_apps/lvgl_port/main/lvgl_port_v8.h index 67948749..1f39050c 100644 --- a/test_apps/lvgl_port/main/lvgl_port_v8.h +++ b/test_apps/lvgl_port/main/lvgl_port_v8.h @@ -73,6 +73,7 @@ #define LVGL_PORT_AVOID_TEARING_MODE (CONFIG_LVGL_PORT_AVOID_TEARING_MODE) #if LVGL_PORT_AVOID_TEARING_MODE != 0 +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB /** * 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. @@ -81,10 +82,11 @@ * */ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) +#endif + /** * 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 extremely reduce FPS. - * So it is recommended to be used when using a low resolution display. + * 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 diff --git a/test_apps/lvgl_port/main/test_app_main.c b/test_apps/lvgl_port/main/test_app_main.cpp similarity index 90% rename from test_apps/lvgl_port/main/test_app_main.c rename to test_apps/lvgl_port/main/test_app_main.cpp index bf8ef112..50372fd5 100644 --- a/test_apps/lvgl_port/main/test_app_main.c +++ b/test_apps/lvgl_port/main/test_app_main.cpp @@ -11,7 +11,13 @@ #include "unity_test_runner.h" // Some resources are lazy allocated in the LCD driver, the threadhold is left for that case +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI +#define TEST_MEMORY_LEAK_THRESHOLD (-800) +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB +#define TEST_MEMORY_LEAK_THRESHOLD (-500) +#else #define TEST_MEMORY_LEAK_THRESHOLD (-300) +#endif static size_t before_free_8bit; static size_t before_free_32bit; @@ -37,7 +43,7 @@ void tearDown(void) check_leak(before_free_32bit, after_free_32bit, "32BIT"); } -void app_main(void) +extern "C" void app_main(void) { /** * _______ ______ __ __ ________ __ diff --git a/test_apps/lvgl_port/main/test_lvgl_port.cpp b/test_apps/lvgl_port/main/test_lvgl_port.cpp index 64931df2..71caa3bc 100644 --- a/test_apps/lvgl_port/main/test_lvgl_port.cpp +++ b/test_apps/lvgl_port/main/test_lvgl_port.cpp @@ -31,10 +31,14 @@ TEST_CASE("Test panel lvgl port to show demo", "[panel][lvgl]") ESP_LOGI(TAG, "Initialize display panel"); TEST_ASSERT_TRUE_MESSAGE(panel->init(), "Panel init failed"); #if LVGL_PORT_AVOID_TEAR - // When avoid tearing function is enabled, configure the RGB bus according to the LVGL configuration - ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); - rgb_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); - rgb_bus->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE); + // When avoid tearing function is enabled, configure the bus according to the LVGL configuration + ESP_PanelBus *lcd_bus = panel->getLcd()->getBus(); +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB + static_cast(lcd_bus)->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE); + static_cast(lcd_bus)->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + static_cast(lcd_bus)->configDpiFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); +#endif #endif TEST_ASSERT_TRUE_MESSAGE(panel->begin(), "Panel begin failed"); diff --git a/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 index 63b0828c..677bab1a 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 +++ b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit index e81429f1..59c9fe7a 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit @@ -1,3 +1,8 @@ CONFIG_IDF_TARGET="esp32c3" CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_C3_LCDKIT=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box index 195ef439..4d6a2b7b 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 index 412b6e76..05319b36 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_3=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta index 0e17c514..e32d3c5f 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite index 39859b5a..df03890d 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_LITE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye index 46308133..598da3d3 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_EYE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 index 9006d223..05824538 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_KORVO_2=y @@ -14,3 +15,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board index cfaa1708..85cb761b 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 index c446475c..9496f02e 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 index ed585da8..62011779 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 index 1c71be3a..5371fbc5 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg index 266fd377..ec33415a 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg @@ -1,3 +1,7 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_USB_OTG=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 index 41a569bd..4ea4f368 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 index ef2f58da..8f056a5d 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 @@ -1,3 +1,7 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5CORE2=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 index 0aaf8323..e34a32a9 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5CORES3=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial index 89f97068..280f68c7 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial @@ -1,9 +1,17 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5DIAL=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash +# For v5.2 and below CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_SPEED_80M=y +# For v5.3 and above +CONFIG_SPIRAM_XIP_FROM_PSRAM=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 index e643bfa1..bc98d906 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -14,3 +16,6 @@ CONFIG_SPIRAM_XIP_FROM_PSRAM=y # Used in conjunction with "RGB Bounce Buffer" CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# lvgl +CONFIG_LV_COLOR_16_SWAP=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 index 7a15686b..41eb76ba 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 index 3d7859f0..d2a84d8c 100644 --- a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y diff --git a/test_apps/panel/main/CMakeLists.txt b/test_apps/panel/main/CMakeLists.txt index 6489599d..ffc7a5dc 100644 --- a/test_apps/panel/main/CMakeLists.txt +++ b/test_apps/panel/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( - SRCS "test_app_main.c" "test_panel.cpp" + SRCS "test_app_main.cpp" "test_panel.cpp" WHOLE_ARCHIVE ) diff --git a/test_apps/panel/main/test_app_main.c b/test_apps/panel/main/test_app_main.cpp similarity index 90% rename from test_apps/panel/main/test_app_main.c rename to test_apps/panel/main/test_app_main.cpp index cb221e0d..9b5a6232 100644 --- a/test_apps/panel/main/test_app_main.c +++ b/test_apps/panel/main/test_app_main.cpp @@ -9,9 +9,16 @@ #include "esp_heap_caps.h" #include "unity.h" #include "unity_test_runner.h" +#include "ESP_Panel.h" // Some resources are lazy allocated in the LCD driver, the threadhold is left for that case +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI +#define TEST_MEMORY_LEAK_THRESHOLD (-800) +#elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB #define TEST_MEMORY_LEAK_THRESHOLD (-500) +#else +#define TEST_MEMORY_LEAK_THRESHOLD (-300) +#endif static size_t before_free_8bit; static size_t before_free_32bit; @@ -37,7 +44,7 @@ void tearDown(void) check_leak(before_free_32bit, after_free_32bit, "32BIT"); } -void app_main(void) +extern "C" void app_main(void) { /** * _______ ______ __ __ ________ __ diff --git a/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 index bdd65a14..0437db6f 100644 --- a/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 +++ b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit b/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit index e81429f1..e887afa1 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit @@ -1,3 +1,4 @@ CONFIG_IDF_TARGET="esp32c3" CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_C3_LCDKIT=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box index 195ef439..696c6d13 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 index 412b6e76..7a0dd29e 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_3=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta index 0e17c514..66661b39 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite index 39859b5a..882bada8 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_BOX_LITE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye index 46308133..86f11b14 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_EYE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 index 9006d223..1a5ad395 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_KORVO_2=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board index 89db6e7d..40c62318 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 index 5b412571..5ff41467 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 index 3d206a48..4a519889 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 index 7acd274c..91766341 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg index 266fd377..9b984dca 100644 --- a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg @@ -1,3 +1,4 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_S3_USB_OTG=y diff --git a/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 index afa2870c..58cab0c7 100644 --- a/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 +++ b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5core2 b/test_apps/panel/sdkconfig.ci.m5stack_m5core2 index ef2f58da..2ea44f44 100644 --- a/test_apps/panel/sdkconfig.ci.m5stack_m5core2 +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5core2 @@ -1,3 +1,4 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5CORE2=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5core3 b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 index 0aaf8323..b19879d1 100644 --- a/test_apps/panel/sdkconfig.ci.m5stack_m5core3 +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5CORES3=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5dial b/test_apps/panel/sdkconfig.ci.m5stack_m5dial index ac51725f..f01a02e9 100644 --- a/test_apps/panel/sdkconfig.ci.m5stack_m5dial +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5dial @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_M5STACK_M5DIAL=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 index e643bfa1..5c69a91c 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 index a8c6105b..67dcc67b 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -1,6 +1,8 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 index b6f152fd..faac3260 100644 --- a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -1,4 +1,5 @@ CONFIG_IDF_TARGET="esp32s3" +CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_BOARD_MANUFACTURER_ALL=y CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y From 6cb98662d0184e0712d989476f5182ab461cc09e Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Fri, 8 Nov 2024 16:19:42 +0800 Subject: [PATCH 08/11] feat(examples): optimize anti-tear rotation in lvgl_port_v8 --- examples/LVGL/v8/Porting/lvgl_port_v8.cpp | 326 +++++++++++++----- examples/LVGL/v8/Rotation/lvgl_port_v8.cpp | 326 +++++++++++++----- examples/PlatformIO/src/lvgl_port_v8.cpp | 326 +++++++++++++----- .../SquareLine/v8/Porting/lvgl_port_v8.cpp | 326 +++++++++++++----- .../SquareLine/v8/WiFiClock/lvgl_port_v8.cpp | 326 +++++++++++++----- test_apps/lvgl_port/main/lvgl_port_v8.cpp | 191 ++++++++-- 6 files changed, 1400 insertions(+), 421 deletions(-) diff --git a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp index 8a2ca67c..7173f33e 100644 --- a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp @@ -3,16 +3,17 @@ * * SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include +#include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,10 +317,12 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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 RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -234,7 +353,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -266,7 +385,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -288,7 +407,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -301,8 +420,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -317,38 +436,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -441,7 +560,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -449,38 +567,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -550,16 +668,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -594,9 +729,14 @@ IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -604,7 +744,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -614,7 +754,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } @@ -647,7 +787,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -669,3 +809,35 @@ bool lvgl_port_unlock(void) return true; } + +bool lvgl_port_deinit(void) +{ +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); + +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#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; +} diff --git a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp index 8a2ca67c..7173f33e 100644 --- a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp @@ -3,16 +3,17 @@ * * SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include +#include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,10 +317,12 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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 RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -234,7 +353,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -266,7 +385,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -288,7 +407,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -301,8 +420,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -317,38 +436,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -441,7 +560,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -449,38 +567,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -550,16 +668,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -594,9 +729,14 @@ IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -604,7 +744,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -614,7 +754,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } @@ -647,7 +787,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -669,3 +809,35 @@ bool lvgl_port_unlock(void) return true; } + +bool lvgl_port_deinit(void) +{ +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); + +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#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; +} diff --git a/examples/PlatformIO/src/lvgl_port_v8.cpp b/examples/PlatformIO/src/lvgl_port_v8.cpp index 8a2ca67c..7173f33e 100644 --- a/examples/PlatformIO/src/lvgl_port_v8.cpp +++ b/examples/PlatformIO/src/lvgl_port_v8.cpp @@ -3,16 +3,17 @@ * * SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include +#include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,10 +317,12 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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 RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -234,7 +353,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -266,7 +385,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -288,7 +407,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -301,8 +420,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -317,38 +436,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -441,7 +560,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -449,38 +567,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -550,16 +668,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -594,9 +729,14 @@ IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -604,7 +744,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -614,7 +754,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } @@ -647,7 +787,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -669,3 +809,35 @@ bool lvgl_port_unlock(void) return true; } + +bool lvgl_port_deinit(void) +{ +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); + +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#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; +} diff --git a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp index 8a2ca67c..7173f33e 100644 --- a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp @@ -3,16 +3,17 @@ * * SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include +#include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,10 +317,12 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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 RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -234,7 +353,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -266,7 +385,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -288,7 +407,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -301,8 +420,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -317,38 +436,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -441,7 +560,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -449,38 +567,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -550,16 +668,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -594,9 +729,14 @@ IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -604,7 +744,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -614,7 +754,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } @@ -647,7 +787,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -669,3 +809,35 @@ bool lvgl_port_unlock(void) return true; } + +bool lvgl_port_deinit(void) +{ +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); + +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#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; +} diff --git a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp index 8a2ca67c..7173f33e 100644 --- a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp @@ -3,16 +3,17 @@ * * SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include +#include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; 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(ESP_PanelLcd *lcd) @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,10 +317,12 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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 RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -234,7 +353,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); /* Waiting for the current frame buffer to complete transmission */ @@ -266,7 +385,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Action after last area refresh */ if (lv_disp_flush_is_last(drv)) { - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -288,7 +407,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t const int offsety1 = area->y1; const int offsety2 = area->y2; - /* Switch the current RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); /* Waiting for the last frame buffer to complete transmission */ @@ -301,8 +420,8 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 #if 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_lcd_last_buf = NULL; +static void *lvgl_port_lcd_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif @@ -317,38 +436,38 @@ void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color #if LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(lcd); - /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + /* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */ rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); - /* Switch the current RGB frame buffer to `next_fb` */ + /* Switch the current LCD frame buffer to `next_fb` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)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 RGB frame buffer to `color_map` */ + /* Switch the current LCD frame buffer to `color_map` */ lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); - lvgl_port_rgb_next_buf = color_map; + lvgl_port_lcd_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif -IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +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_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; + 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 RGB frame buffer has been transmitted + // Notify that the current LCD frame buffer has been transmitted xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); @@ -441,7 +560,6 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) static lv_disp_drv_t disp_drv; // Alloc draw buffers used by LVGL - void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; int buffer_size = 0; ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); @@ -449,38 +567,38 @@ static lv_disp_t *display_init(ESP_PanelLcd *lcd) // Avoid tearing function is disabled buffer_size = LVGL_PORT_BUFFER_SIZE; for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { - buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); - assert(buf[i]); - ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + lvgl_buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(lvgl_buf[i]); + ESP_LOGD(TAG, "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 RGB output + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for LCD refresh buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_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 RGB's sync signal - lvgl_port_rgb_last_buf = lcd->getFrameBufferByIndex(0); - buf[0] = lcd->getFrameBufferByIndex(1); - buf[1] = lcd->getFrameBufferByIndex(2); - lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; - lvgl_port_flush_next_buf = buf[1]; + // 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) - buf[0] = lcd->getFrameBufferByIndex(2); + 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++) { - buf[i] = lcd->getFrameBufferByIndex(i); + lvgl_buf[i] = lcd->getFrameBufferByIndex(i); } #endif #endif /* LVGL_PORT_AVOID_TEAR */ // initialize LVGL draw buffers - lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + lv_disp_draw_buf_init(&disp_buf, lvgl_buf[0], lvgl_buf[1], buffer_size); ESP_LOGD(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); @@ -550,16 +668,33 @@ static void tick_increment(void *arg) lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } -static esp_err_t tick_init(void) +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_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); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer), false, "Create LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + 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_PANEL_CHECK_ERR_RET( + esp_timer_stop(lvgl_tick_timer), false, "Stop LVGL tick timer failed" + ); + ESP_PANEL_CHECK_ERR_RET( + esp_timer_delete(lvgl_tick_timer), false, "Delete LVGL tick timer failed" + ); + return true; } #endif @@ -594,9 +729,14 @@ IRAM_ATTR bool onDrawBitmapFinishCallback(void *user_data) bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) { ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); + + auto bus_type = lcd->getBus()->getType(); #if LVGL_PORT_AVOID_TEAR - ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); - ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); + ESP_PANEL_CHECK_FALSE_RET( + (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_LOGI(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); #endif lv_disp_t *disp = nullptr; @@ -604,7 +744,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) lv_init(); #if !LV_TICK_CUSTOM - ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); + ESP_PANEL_CHECK_FALSE_RET(tick_init(), false, "Initialize LVGL tick failed"); #endif ESP_LOGD(TAG, "Initialize LVGL display driver"); @@ -614,7 +754,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) 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 (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + if (bus_type != ESP_PANEL_BUS_TYPE_RGB) { ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); lcd->attachDrawBitmapFinishCallback(onDrawBitmapFinishCallback, (void *)disp->driver); } @@ -647,7 +787,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); #if LVGL_PORT_AVOID_TEAR - lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)lvgl_task_handle); + lcd->attachRefreshFinishCallback(onLcdVsyncCallback, (void *)lvgl_task_handle); #endif return true; @@ -669,3 +809,35 @@ bool lvgl_port_unlock(void) return true; } + +bool lvgl_port_deinit(void) +{ +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_FALSE_RET(tick_deinit(), false, "Deinitialize LVGL tick failed"); +#endif + + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_lock(-1), false, "Lock LVGL failed"); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + ESP_PANEL_CHECK_FALSE_RET(lvgl_port_unlock(), false, "Unlock LVGL failed"); + +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#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; +} diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.cpp b/test_apps/lvgl_port/main/lvgl_port_v8.cpp index 5e776c49..c9f0d4a2 100644 --- a/test_apps/lvgl_port/main/lvgl_port_v8.cpp +++ b/test_apps/lvgl_port/main/lvgl_port_v8.cpp @@ -6,6 +6,7 @@ #include "esp_timer.h" #include "lvgl_port_v8.h" +#define LVGL_PORT_ENABLE_ROTATION_OPTIMIZED (1) #define LVGL_PORT_BUFFER_NUM_MAX (2) static const char *TAG = "lvgl_port"; @@ -31,53 +32,167 @@ static void *get_next_frame_buffer(ESP_PanelLcd *lcd) return next_fb; } -IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_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) +__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 from_index_const = 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: - to_index_const = (w - x_start - 1) * h; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const + from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= h; - } - } +#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: - to_index_const = h * w - x_start - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y * w; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index -= 1; - } - } + ROTATE_180_ALL_BPP(); break; case 270: - to_index_const = (x_start + 1) * h - 1; - for (int from_y = y_start; from_y < y_end + 1; from_y++) { - from_index = from_y * w + x_start; - to_index = to_index_const - from_y; - for (int from_x = x_start; from_x < x_end + 1; from_x++) { - *(to + to_index) = *(from + from_index); - from_index += 1; - to_index += h; - } - } +#if (LV_COLOR_DEPTH == 16) && LVGL_PORT_ENABLE_ROTATION_OPTIMIZED + ROTATE_270_OPTIMIZED_16BPP(32, 256); +#else + 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 */ @@ -174,8 +289,10 @@ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_a y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; - rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, - LVGL_PORT_ROTATION_DEGREE); + 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 + ); } } } @@ -200,8 +317,10 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t // 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((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, - LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + 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->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); From f341c7bae69f8e8c0720156bec6258d41ccf405a Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Fri, 8 Nov 2024 17:08:41 +0800 Subject: [PATCH 09/11] fix(touch): release ISR semaphore when delete --- src/touch/ESP_PanelTouch.cpp | 11 +++++++++++ src/touch/ESP_PanelTouch.h | 8 ++++++++ test_apps/touch/i2c/main/test_i2c_touch.cpp | 1 + 3 files changed, 20 insertions(+) diff --git a/src/touch/ESP_PanelTouch.cpp b/src/touch/ESP_PanelTouch.cpp index 64e93065..c80ea441 100644 --- a/src/touch/ESP_PanelTouch.cpp +++ b/src/touch/ESP_PanelTouch.cpp @@ -78,6 +78,12 @@ ESP_PanelTouch::ESP_PanelTouch(ESP_PanelBus *bus, const esp_lcd_touch_config_t & } } +void ESP_PanelTouch::configLevels(int reset_level, int interrupt_level) +{ + config.levels.reset = reset_level; + config.levels.interrupt = interrupt_level; +} + bool ESP_PanelTouch::attachInterruptCallback(std::function callback, void *user_data) { ESP_PANEL_CHECK_FALSE_RET((config.interrupt_callback == onTouchInterrupt), false, "Interruption is not enabled"); @@ -109,6 +115,11 @@ bool ESP_PanelTouch::del(void) ESP_PANEL_CHECK_NULL_RET(handle, false, "Invalid handle"); ESP_PANEL_CHECK_ERR_RET(esp_lcd_touch_del(handle), false, "Delete touch panel failed"); + if (_isr_sem != NULL) { + vSemaphoreDelete(_isr_sem); + _isr_sem = NULL; + } + ESP_LOGD(TAG, "Touch panel @%p deleted", handle); handle = NULL; diff --git a/src/touch/ESP_PanelTouch.h b/src/touch/ESP_PanelTouch.h index 1a8e4d7f..342eada0 100644 --- a/src/touch/ESP_PanelTouch.h +++ b/src/touch/ESP_PanelTouch.h @@ -84,6 +84,14 @@ class ESP_PanelTouch { */ virtual ~ESP_PanelTouch() = default; + /** + * @brief Configure the levels of the reset and interrupt signals + * + * @param reset_level The level of the reset signal + * @param interrupt_level The level of the interrupt signal + */ + void configLevels(int reset_level, int interrupt_level); + /** * @brief Attach a callback function, which will be called when the refreshing is finished * diff --git a/test_apps/touch/i2c/main/test_i2c_touch.cpp b/test_apps/touch/i2c/main/test_i2c_touch.cpp index ea205fba..067f0543 100644 --- a/test_apps/touch/i2c/main/test_i2c_touch.cpp +++ b/test_apps/touch/i2c/main/test_i2c_touch.cpp @@ -117,6 +117,7 @@ static void run_test(shared_ptr touch_device) CREATE_TEST_CASE(CST816S) CREATE_TEST_CASE(FT5x06) CREATE_TEST_CASE(GT1151) +CREATE_TEST_CASE(GT911) CREATE_TEST_CASE(TT21100) CREATE_TEST_CASE(ST1633) CREATE_TEST_CASE(ST7123) From a787d61259bd5c6479b4e8ba8162e96a8e2b5289 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Fri, 8 Nov 2024 17:25:20 +0800 Subject: [PATCH 10/11] feat(board): add support for Espressif ESP32-P4-Function-EV-Board --- ESP_Panel_Board_Supported.h | 6 +- README.md | 2 +- README_CN.md | 2 +- docs/Board_Instructions.md | 2 + .../v8/Porting/ESP_Panel_Board_Supported.h | 6 +- .../v8/Rotation/ESP_Panel_Board_Supported.h | 6 +- .../PanelTest/ESP_Panel_Board_Supported.h | 6 +- .../src/ESP_Panel_Board_Supported.h | 6 +- .../v8/Porting/ESP_Panel_Board_Supported.h | 6 +- .../v8/WiFiClock/ESP_Panel_Board_Supported.h | 6 +- library.properties | 2 +- src/ESP_PanelVersions.h | 4 +- src/ESP_Panel_Board_Kconfig.h | 10 + src/board/ESP_PanelBoard.h | 1 + .../espressif/ESP32_P4_FUNCTION_EV_BOARD.h | 225 ++++++++++++++++++ src/board/espressif/Kconfig.espressif | 5 + ...ig.ci.espressif_esp32_p4_function_ev_board | 15 ++ ...ig.ci.espressif_esp32_p4_function_ev_board | 10 + 18 files changed, 301 insertions(+), 19 deletions(-) create mode 100644 src/board/espressif/ESP32_P4_FUNCTION_EV_BOARD.h create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_p4_function_ev_board create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_p4_function_ev_board diff --git a/ESP_Panel_Board_Supported.h b/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/ESP_Panel_Board_Supported.h +++ b/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/README.md b/README.md index 6cce25a8..51f7addd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Below is the list of [supported development boards](docs/Board_Instructions.md): | **Manufacturer** | **Board Model** | | ---------------- | --------------- | -| [Espressif](docs/Board_Instructions.md#espressif) | ESP32-C3-LCDkit, ESP32-S3-BOX, ESP32-S3-BOX-3, ESP32-S3-BOX-3B, ESP32-S3-BOX-3(beta), ESP32-S3-BOX-Lite, ESP32-S3-EYE, ESP32-S3-Korvo-2, ESP32-S3-LCD-EV-Board, ESP32-S3-LCD-EV-Board-2, ESP32-S3-USB-OTG | +| [Espressif](docs/Board_Instructions.md#espressif) | ESP32-C3-LCDkit, ESP32-S3-BOX, ESP32-S3-BOX-3, ESP32-S3-BOX-3B, ESP32-S3-BOX-3(beta), ESP32-S3-BOX-Lite, ESP32-S3-EYE, ESP32-S3-Korvo-2, ESP32-S3-LCD-EV-Board, ESP32-S3-LCD-EV-Board-2, ESP32-S3-USB-OTG, ESP32-P4-Function-EV-Board | | [Elecrow](docs/Board_Instructions.md#elecrow) | CrowPanel 7.0" | | [M5Stack](docs/Board_Instructions.md#m5stack) | M5STACK-M5CORE2, M5STACK-M5DIAL, M5STACK-M5CORES3 | | [Jingcai](docs/Board_Instructions.md#shenzhen-jingcai-intelligent) | ESP32-4848S040C_I_Y_3 | diff --git a/README_CN.md b/README_CN.md index 7e2a6e02..9cfd4395 100644 --- a/README_CN.md +++ b/README_CN.md @@ -36,7 +36,7 @@ ESP32_Display_Panel 的功能框图如下所示,主要包含以下特性: | **厂商** | **开发板型号** | | -------- | -------------- | -| [Espressif](docs/Board_Instructions.md#espressif) | ESP32-C3-LCDkit, ESP32-S3-BOX, ESP32-S3-BOX-3, ESP32-S3-BOX-3B, ESP32-S3-BOX-3(beta), ESP32-S3-BOX-Lite, ESP32-S3-EYE, ESP32-S3-Korvo-2, ESP32-S3-LCD-EV-Board, ESP32-S3-LCD-EV-Board-2, ESP32-S3-USB-OTG | +| [Espressif](docs/Board_Instructions.md#espressif) | ESP32-C3-LCDkit, ESP32-S3-BOX, ESP32-S3-BOX-3, ESP32-S3-BOX-3B, ESP32-S3-BOX-3(beta), ESP32-S3-BOX-Lite, ESP32-S3-EYE, ESP32-S3-Korvo-2, ESP32-S3-LCD-EV-Board, ESP32-S3-LCD-EV-Board-2, ESP32-S3-USB-OTG, ESP32-P4-Function-EV-Board | | [M5Stack](docs/Board_Instructions.md#m5stack) | M5STACK-M5CORE2, M5STACK-M5DIAL, M5STACK-M5CORES3 | | [Elecrow](docs/Board_Instructions.md#elecrow) | CrowPanel 7.0" | | [Jingcai](docs/Board_Instructions.md#shenzhen-jingcai-intelligent) | ESP32-4848S040C_I_Y_3 | diff --git a/docs/Board_Instructions.md b/docs/Board_Instructions.md index 46014230..7650cb87 100644 --- a/docs/Board_Instructions.md +++ b/docs/Board_Instructions.md @@ -16,6 +16,7 @@ | | [ESP32-S3-LCD-EV-Board](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/index.html) | 3-wire SPI + RGB | GC9503 | 480x480 | I2C | FT5x06 | | | [ESP32-S3-LCD-EV-Board-2](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/index.html) | RGB | ST7262E43 | 800x480 | I2C | GT1151 | | | [ESP32-S3-USB-OTG](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html) | SPI | ST7789 | 240x240 | - | - | +| | [ESP32-P4-Function-EV-Board](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html) | MIPI-DSI | EK79007 | 1024x600 | I2C | GT911 | ### [Elecrow](https://www.elecrow.com/) @@ -62,6 +63,7 @@ Below are recommended configurations for developing GUI applications on differen | ESP32-S3-LCD-EV-Board | ESP32S3 Dev Module | OPI | QIO 80MHz | 16MB | **See Note 1** | 16M Flash (3MB) | | ESP32-S3-LCD-EV-Board-2 | ESP32S3 Dev Module | OPI | QIO 80MHz | 16MB | **See Note 1** | 16M Flash (3MB) | | ESP32-S3-USB-OTG | ESP32-S3-USB-OTG | - | - | - | - | 8M with spiffs | +| ESP32-P4-Function-EV-Board | ESP32P4 Dev Module | Enabled | QIO | 16MB | Disabled | 16M Flash (3MB) | | M5STACK-M5CORE2 | M5Stack-Core2 | Enabled | - | - | - | Default | | M5STACK-M5DIAL | ESP32S3 Dev Module | OPI | QIO 80MHz | 8MB | Disabled | Default | | M5STACK-M5CORES3 | ESP32S3 Dev Module | OPI | QIO 80MHz | 16MB | Enabled | Default 4MB with spiffs | diff --git a/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h b/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h +++ b/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h +++ b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h b/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h +++ b/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/examples/PlatformIO/src/ESP_Panel_Board_Supported.h b/examples/PlatformIO/src/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/PlatformIO/src/ESP_Panel_Board_Supported.h +++ b/examples/PlatformIO/src/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h +++ b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h index 603e59bd..75d5c84a 100644 --- a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h +++ b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h @@ -31,6 +31,7 @@ * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + * - BOARD_ESP32_P4_FUNCTION_EV_BOARD (ESP32-P4-Function-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html * */ // #define BOARD_ESP32_C3_LCDKIT @@ -45,6 +46,7 @@ // #define BOARD_ESP32_S3_LCD_EV_BOARD_2 // #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 // #define BOARD_ESP32_S3_USB_OTG +// #define BOARD_ESP32_P4_FUNCTION_EV_BOARD /* * Elecrow (https://www.elecrow.com): @@ -101,7 +103,7 @@ * */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 #endif diff --git a/library.properties b/library.properties index 97ee5217..c8b92e57 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=0.2.0 author=espressif maintainer=espressif sentence=ESP32_Display_Panel is an library designed for ESP SoCs to drive display panels and facilitate rapid GUI development. -paragraph=Currently supported boards:ESP32-C3-LCDkit,ESP32-S3-BOX,ESP32-S3-BOX-3,ESP32-S3-BOX-3B,ESP32-S3-BOX-3(beta),ESP32-S3-BOX-Lite,ESP32-S3-EYE,ESP32-S3-Korvo-2,ESP32-S3-LCD-EV-Board,ESP32-S3-LCD-EV-Board-2,ESP32-S3-USB-OTG,M5STACK-M5CORE2,M5STACK-M5DIAL,M5STACK-M5CORES3,ESP32-4848S040C_I_Y_3,ESP32-S3-Touch-LCD-4.3,ESP32-S3-Touch-LCD-1.85,ESP32-S3-Touch-LCD-2.1. Currently supported devices: Bus,LCD,Touch,Backlight,IO expander. Currently supported Bus: I2C,SPI,QSPI,3-wire SPI + RGB. Currently supported LCD controllers: EK9716B,GC9A01,GC9B71,GC9503,ILI9341,NV3022B,ST7262,ST7701,ST7789,ST7796,ST77916,ST77922. Currently supported Touch controllers: CST816S,FT5x06,GT1151,GT911,ST7123,TT21100,XPT2046. +paragraph=Currently supported boards:ESP32-C3-LCDkit,ESP32-S3-BOX,ESP32-S3-BOX-3,ESP32-S3-BOX-3B,ESP32-S3-BOX-3(beta),ESP32-S3-BOX-Lite,ESP32-S3-EYE,ESP32-S3-Korvo-2,ESP32-S3-LCD-EV-Board,ESP32-S3-LCD-EV-Board-2,ESP32-S3-USB-OTG,ESP32-P4-Function-EV-Board,M5STACK-M5CORE2,M5STACK-M5DIAL,M5STACK-M5CORES3,ESP32-4848S040C_I_Y_3,ESP32-S3-Touch-LCD-4.3,ESP32-S3-Touch-LCD-1.85,ESP32-S3-Touch-LCD-2.1. Currently supported devices: Bus,LCD,Touch,Backlight,IO expander. Currently supported Bus: I2C,SPI,QSPI,3-wire SPI + RGB. Currently supported LCD controllers: EK9716B,GC9A01,GC9B71,GC9503,ILI9341,NV3022B,ST7262,ST7701,ST7789,ST7796,ST77916,ST77922. Currently supported Touch controllers: CST816S,FT5x06,GT1151,GT911,ST7123,TT21100,XPT2046. category=Other architectures=esp32 url=https://github.com/esp-arduino-libs/ESP32_Display_Panel diff --git a/src/ESP_PanelVersions.h b/src/ESP_PanelVersions.h index 2902587f..db246b84 100644 --- a/src/ESP_PanelVersions.h +++ b/src/ESP_PanelVersions.h @@ -25,8 +25,8 @@ /* File `ESP_Panel_Board_Supported.h` */ #define ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH 1 +#define ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR 6 +#define ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH 0 // *INDENT-OFF* diff --git a/src/ESP_Panel_Board_Kconfig.h b/src/ESP_Panel_Board_Kconfig.h index 34054e9d..ebe9fb6e 100644 --- a/src/ESP_Panel_Board_Kconfig.h +++ b/src/ESP_Panel_Board_Kconfig.h @@ -91,6 +91,16 @@ #define BOARD_ESP32_S3_USB_OTG CONFIG_BOARD_ESP32_S3_USB_OTG #endif #endif + #ifndef BOARD_ESP32_P4_FUNCTION_EV_BOARD + #ifdef CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD + #define BOARD_ESP32_P4_FUNCTION_EV_BOARD CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD + #endif + #endif + #ifndef BOARD_ESP32_P4_FUNCTION_EV_BOARD_800_1280 + #ifdef CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD_800_1280 + #define BOARD_ESP32_P4_FUNCTION_EV_BOARD_800_1280 CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD_800_1280 + #endif + #endif // Elecrow #ifndef BOARD_ELECROW_CROWPANEL_7_0 #ifdef CONFIG_BOARD_ELECROW_CROWPANEL_7_0 diff --git a/src/board/ESP_PanelBoard.h b/src/board/ESP_PanelBoard.h index cab561c8..50816c8e 100644 --- a/src/board/ESP_PanelBoard.h +++ b/src/board/ESP_PanelBoard.h @@ -23,6 +23,7 @@ + defined(BOARD_ESP32_S3_LCD_EV_BOARD_2) \ + defined(BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5) \ + defined(BOARD_ESP32_S3_USB_OTG) \ + + defined(BOARD_ESP32_P4_FUNCTION_EV_BOARD) \ /* Elecrow */ \ + defined(BOARD_ELECROW_CROWPANEL_7_0) \ /* M5Stack */ \ diff --git a/src/board/espressif/ESP32_P4_FUNCTION_EV_BOARD.h b/src/board/espressif/ESP32_P4_FUNCTION_EV_BOARD.h new file mode 100644 index 00000000..ca731432 --- /dev/null +++ b/src/board/espressif/ESP32_P4_FUNCTION_EV_BOARD.h @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +// *INDENT-OFF* + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////// Please update the following macros to configure the LCD panel ///////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* Set to 1 when using an LCD panel */ +#define ESP_PANEL_USE_LCD (1) // 0/1 + +#if ESP_PANEL_USE_LCD +/** + * LCD Controller Name. + */ +#define ESP_PANEL_LCD_NAME EK79007 + +/* LCD resolution in pixels */ +#define ESP_PANEL_LCD_WIDTH (1024) +#define ESP_PANEL_LCD_HEIGHT (600) + +/* LCD Bus Settings */ +/** + * If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance. + * It is useful if other devices use the same host. Please ensure that the host is initialized only once. + * + * Note: This macro is not useful for the MIPI-DSI bus. + * + */ +#define ESP_PANEL_LCD_BUS_SKIP_INIT_HOST (0) // 0/1 +/** + * LCD Bus Type. + */ +#define ESP_PANEL_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_MIPI_DSI) +/** + * LCD Bus Parameters. + * + * Please refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd.html and + * https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html for more details. + * + */ +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_MIPI_DSI + + #define ESP_PANEL_LCD_MIPI_DSI_LANE_NUM (2) // ESP32-P4 supports 1 or 2 lanes + #define ESP_PANEL_LCD_MIPI_DSI_LANE_RATE_MBPS (1000) // Single lane bit rate, should consult the LCD supplier or check the + // LCD drive IC datasheet for the supported lane rate. + // ESP32-P4 supports max 1500Mbps + #define ESP_PANEL_LCD_MIPI_DSI_PHY_LDO_ID (3) // -1 if not used + #define ESP_PANEL_LCD_MIPI_DPI_CLK_MHZ (52) + #define ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS (ESP_PANEL_LCD_RGB565_COLOR_BITS_16) + #define ESP_PANEL_LCD_MIPI_DSI_HPW (10) + #define ESP_PANEL_LCD_MIPI_DSI_HBP (160) + #define ESP_PANEL_LCD_MIPI_DSI_HFP (160) + #define ESP_PANEL_LCD_MIPI_DSI_VPW (1) + #define ESP_PANEL_LCD_MIPI_DSI_VBP (23) + #define ESP_PANEL_LCD_MIPI_DSI_VFP (12) + +#endif /* ESP_PANEL_LCD_BUS_TYPE */ + +/** + * LCD Vendor Initialization Commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. Please uncomment and change the following macro definitions. Otherwise, the LCD driver + * will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ + +/* LCD Color Settings */ +/* LCD color depth in bits */ +#define ESP_PANEL_LCD_COLOR_BITS (ESP_PANEL_LCD_MIPI_DPI_PIXEL_BITS) // 8/16/18/24, typically same as the pixel bits +/* + * LCD RGB Element Order. Choose one of the following: + * - 0: RGB + * - 1: BGR + */ +#define ESP_PANEL_LCD_BGR_ORDER (0) // 0/1 +#define ESP_PANEL_LCD_INEVRT_COLOR (0) // 0/1 + +/* LCD Transformation Flags */ +// #define ESP_PANEL_LCD_SWAP_XY (0) // 0/1 +// #define ESP_PANEL_LCD_MIRROR_X (0) // 0/1 +// #define ESP_PANEL_LCD_MIRROR_Y (0) // 0/1 + +/* LCD Other Settings */ +/* IO num of RESET pin, set to -1 if not use */ +#define ESP_PANEL_LCD_IO_RST (27) +#define ESP_PANEL_LCD_RST_LEVEL (0) // 0: low level, 1: high level + +#endif /* ESP_PANEL_USE_LCD */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////// Please update the following macros to configure the touch panel /////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* Set to 1 when using an touch panel */ +#define ESP_PANEL_USE_TOUCH (1) // 0/1 +#if ESP_PANEL_USE_TOUCH +/** + * Touch controller name. + */ +#define ESP_PANEL_TOUCH_NAME GT911 + +/* Touch resolution in pixels */ +#define ESP_PANEL_TOUCH_H_RES (ESP_PANEL_LCD_WIDTH) // Typically set to the same value as the width of LCD +#define ESP_PANEL_TOUCH_V_RES (ESP_PANEL_LCD_HEIGHT) // Typically set to the same value as the height of LCD + +/* Touch Panel Bus Settings */ +/** + * If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance. + * It is useful if other devices use the same host. Please ensure that the host is initialized only once. + */ +#define ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST (0) // 0/1 +/** + * Touch panel bus type. + */ +#define ESP_PANEL_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C) +/* Touch panel bus parameters */ +#if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C + + #define ESP_PANEL_TOUCH_BUS_HOST_ID (0) // Typically set to 0 + #define ESP_PANEL_TOUCH_I2C_ADDRESS (0) // Typically set to 0 to use default address +#if !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + #define ESP_PANEL_TOUCH_I2C_CLK_HZ (400 * 1000) + // Typically set to 400K + #define ESP_PANEL_TOUCH_I2C_SCL_PULLUP (0) // 0/1 + #define ESP_PANEL_TOUCH_I2C_SDA_PULLUP (0) // 0/1 + #define ESP_PANEL_TOUCH_I2C_IO_SCL (8) + #define ESP_PANEL_TOUCH_I2C_IO_SDA (7) +#endif + +#endif + +/* Touch Transformation Flags */ +#define ESP_PANEL_TOUCH_SWAP_XY (0) // 0/1 +#define ESP_PANEL_TOUCH_MIRROR_X (1) // 0/1 +#define ESP_PANEL_TOUCH_MIRROR_Y (1) // 0/1 + +/* Touch Other Settings */ +/* IO num of RESET pin, set to -1 if not use */ +#define ESP_PANEL_TOUCH_IO_RST (-1) +#define ESP_PANEL_TOUCH_RST_LEVEL (0) // 0: low level, 1: high level +/* IO num of INT pin, set to -1 if not use */ +#define ESP_PANEL_TOUCH_IO_INT (-1) +#define ESP_PANEL_TOUCH_INT_LEVEL (0) // 0: low level, 1: high level + +#endif /* ESP_PANEL_USE_TOUCH */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// Please update the following macros to configure the backlight //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define ESP_PANEL_USE_BACKLIGHT (1) // 0/1 +#if ESP_PANEL_USE_BACKLIGHT +/* Backlight pin */ +#define ESP_PANEL_BACKLIGHT_IO (26) // IO num of backlight pin +#define ESP_PANEL_BACKLIGHT_ON_LEVEL (1) // 0: low level, 1: high level + +/* Set to 1 if you want to turn off the backlight after initializing the panel; otherwise, set it to turn on */ +#define ESP_PANEL_BACKLIGHT_IDLE_OFF (0) // 0: on, 1: off + +/* Set to 1 if use PWM for brightness control */ +#define ESP_PANEL_LCD_BL_USE_PWM (1) // 0/1 +#endif /* ESP_PANEL_USE_BACKLIGHT */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// Please update the following macros to configure the IO expander ////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* Set to 0 if not using IO Expander */ +#define ESP_PANEL_USE_EXPANDER (0) // 0/1 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// Please utilize the following macros to execute any additional code if required. ////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// #define ESP_PANEL_BEGIN_START_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_EXPANDER_START_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_EXPANDER_END_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_LCD_START_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_LCD_END_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_TOUCH_START_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_TOUCH_END_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_BACKLIGHT_START_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_BACKLIGHT_END_FUNCTION( panel ) +// #define ESP_PANEL_BEGIN_END_FUNCTION( panel ) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////// File Version /////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Do not change the following versions, they are used to check if the configurations in this file are compatible with + * the current version of `ESP_Panel_Board_Custom.h` in the library. The detailed rules are as follows: + * + * 1. If the major version is not consistent, then the configurations in this file are incompatible with the library + * and must be replaced with the file from the library. + * 2. If the minor version is not consistent, this file might be missing some new configurations, which will be set to + * default values. It is recommended to replace it with the file from the library. + * 3. Even if the patch version is not consistent, it will not affect normal functionality. + * + */ +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 3 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 + +// *INDENT-OFF* diff --git a/src/board/espressif/Kconfig.espressif b/src/board/espressif/Kconfig.espressif index 57d934ef..1aff0a17 100644 --- a/src/board/espressif/Kconfig.espressif +++ b/src/board/espressif/Kconfig.espressif @@ -57,3 +57,8 @@ config BOARD_ESP32_S3_USB_OTG bool "ESP32-S3-USB-OTG" help https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html + +config BOARD_ESP32_P4_FUNCTION_EV_BOARD + bool "ESP32-P4-Function-EV-Board (with 7-inch 1024x600 LCD)" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_p4_function_ev_board b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_p4_function_ev_board new file mode 100644 index 00000000..a5815a9f --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_p4_function_ev_board @@ -0,0 +1,15 @@ +CONFIG_IDF_TARGET="esp32p4" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# Improve FPS +CONFIG_CACHE_L2_CACHE_256KB=y +CONFIG_CACHE_L2_CACHE_LINE_128B=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_p4_function_ev_board b/test_apps/panel/sdkconfig.ci.espressif_esp32_p4_function_ev_board new file mode 100644 index 00000000..c2a39505 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_p4_function_ev_board @@ -0,0 +1,10 @@ +CONFIG_IDF_TARGET="esp32p4" +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_P4_FUNCTION_EV_BOARD=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y From ab9ad35b7110314db3e284bec3b8fff97cf16c96 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Fri, 8 Nov 2024 19:31:46 +0800 Subject: [PATCH 11/11] feat(docs): update README and fix broken links --- CHANGELOG.md | 27 ++- docs/Board_Contribution_Guide.md | 10 + docs/Board_Contribution_Guide_CN.md | 10 + docs/FAQ.md | 12 + docs/FAQ_CN.md | 12 + docs/_static/block_diagram.drawio | 222 +++++++++--------- docs/_static/block_diagram.png | Bin 152308 -> 152647 bytes examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino | 6 +- examples/LCD/MIPI_DSI/MIPI_DSI.ino | 6 +- examples/LCD/QSPI/QSPI.ino | 6 +- examples/LCD/RGB/RGB.ino | 6 +- examples/LCD/SPI/SPI.ino | 6 +- examples/LVGL/v8/Porting/Porting.ino | 12 +- examples/LVGL/v8/Rotation/Rotation.ino | 12 +- examples/Panel/PanelTest/PanelTest.ino | 10 +- examples/SquareLine/v8/Porting/Porting.ino | 12 +- .../SquareLine/v8/WiFiClock/WiFiClock.ino | 12 +- examples/Touch/I2C/I2C.ino | 6 +- examples/Touch/SPI/SPI.ino | 6 +- 19 files changed, 226 insertions(+), 167 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f93459f..e404f1ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,36 @@ # ChangeLog -## v0.1.9 - 2024-10-29 +## v0.2.0 - 2024-11-08 -* feat(board): add support for Waveshare ESP32-S3-Touch-LCD-2.1 +### Enhancements: -## v0.1.8 - 2024-10-25 +* feat(repo): support build on the esp-idf +* feat(bus & lcd): support MIPI-DSI LCD +* feat(lcd): add LCD controller EK79007 +* feat(lcd): add LCD controller ILI9881C +* feat(panel): add support for MIPI-DSI LCD +* feat(board): add support for Waveshare ESP32-S3-Touch-LCD-2.1 @martinroger (#117) +* feat(board): add support for Espressif ESP32-P4-Function-EV-Board +* feat(examples): add MIPI-DSI LCD +* feat(examples): optimize anti-tear rotation in lvgl_port_v8 +* feat(ci): update for MIPI-DSI LCD +* feat(test_apps): add MIPI-DSI LCD -* feat(board): add support for Waveshare ESP32-S3-Touch-LCD-1.85 +### Bugfixes: -### Enhancements: +* fix(touch): release ISR semaphore when delete -## v0.1.7 - 2024-08-22 +## v0.1.8 - 2024-10-25 ### Enhancements: +* feat(board): add support for Waveshare ESP32-S3-Touch-LCD-1.85 @martinroger (#115) * feat(docs): add additional information about screen drift issue +### Bugfixes: + +* fix(examples): correct readme broken links + ## v0.1.6 - 2024-07-30 ### Enhancements: diff --git a/docs/Board_Contribution_Guide.md b/docs/Board_Contribution_Guide.md index fdbe11bc..4334662c 100644 --- a/docs/Board_Contribution_Guide.md +++ b/docs/Board_Contribution_Guide.md @@ -1,5 +1,15 @@ # Board Contribution Guide +* [中文版本](./Board_Contribution_Guide_CN.md) + +## Table of Contents + +- [Board Contribution Guide](#board-contribution-guide) + - [Table of Contents](#table-of-contents) + - [Contribution Guidelines](#contribution-guidelines) + - [File Modifications](#file-modifications) + - [Adaptation Process](#adaptation-process) + ## Contribution Guidelines 1. The development board must at least ensure its hardware schematic is open-source, providing a link or file for reference. diff --git a/docs/Board_Contribution_Guide_CN.md b/docs/Board_Contribution_Guide_CN.md index 06941c74..5aaade42 100644 --- a/docs/Board_Contribution_Guide_CN.md +++ b/docs/Board_Contribution_Guide_CN.md @@ -1,5 +1,15 @@ # 开发板贡献指南 +* [English Version](./Board_Contribution_Guide.md) + +## 目录 + +- [开发板贡献指南](#开发板贡献指南) + - [目录](#目录) + - [贡献说明](#贡献说明) + - [文件修改](#文件修改) + - [适配流程](#适配流程) + ## 贡献说明 1. 开发板至少需要确保其硬件原理图开源,并提供链接或文件。 diff --git a/docs/FAQ.md b/docs/FAQ.md index 5c6d2f53..726f8507 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,5 +1,17 @@ # FAQ +* [中文版本](./FAQ_CN.md) + +## Table of Contents + +- [FAQ](#faq) + - [Table of Contents](#table-of-contents) + - [Where is the directory for Arduino libraries?](#where-is-the-directory-for-arduino-libraries) + - [How to Install ESP32\_Display\_Panel in Arduino IDE?](#how-to-install-esp32_display_panel-in-arduino-ide) + - [Where are the installation directory for arduino-esp32 and the SDK located?](#where-are-the-installation-directory-for-arduino-esp32-and-the-sdk-located) + - [How to fix screen drift issue when driving RGB LCD with ESP32-S3?](#how-to-fix-screen-drift-issue-when-driving-rgb-lcd-with-esp32-s3) + - [How to Use ESP32\_Display\_Panel on PlatformIO?](#how-to-use-esp32_display_panel-on-platformio) + ## Where is the directory for Arduino libraries? You can find and modify the directory path for Arduino libraries by selecting `File` > `Preferences` > `Settings` > `Sketchbook location` from the menu bar in the Arduino IDE. diff --git a/docs/FAQ_CN.md b/docs/FAQ_CN.md index cd2d5174..d0070f13 100644 --- a/docs/FAQ_CN.md +++ b/docs/FAQ_CN.md @@ -1,5 +1,17 @@ # 常见问题解答 +* [English Version](./FAQ.md) + +## 目录 + +- [常见问题解答](#常见问题解答) + - [目录](#目录) + - [Arduino 库的目录在哪儿?](#arduino-库的目录在哪儿) + - [如何在 Arduino IDE 中安装 ESP32\_Display\_Panel?](#如何在-arduino-ide-中安装-esp32_display_panel) + - [arduino-eps32 的安装目录以及 SDK 的目录在哪儿?](#arduino-eps32-的安装目录以及-sdk-的目录在哪儿) + - [使用 ESP32-S3 驱动 RGB LCD 时出现画面漂移问题的解决方案](#使用-esp32-s3-驱动-rgb-lcd-时出现画面漂移问题的解决方案) + - [如何在 PlatformIO 上使用 ESP32\_Display\_Panel?](#如何在-platformio-上使用-esp32_display_panel) + ## Arduino 库的目录在哪儿? 您可以在 Arduino IDE 的菜单栏中选择 `File` > `Preferences` > `Settings` > `Sketchbook location` 来查找和修改 Arduino 库的目录路径。 diff --git a/docs/_static/block_diagram.drawio b/docs/_static/block_diagram.drawio index 5bec07dd..e40ed152 100644 --- a/docs/_static/block_diagram.drawio +++ b/docs/_static/block_diagram.drawio @@ -1,112 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/block_diagram.png b/docs/_static/block_diagram.png index 845df05e7c072276a6f1fe376a21de580a0d8281..ee2659099a0ec064285bcc7899b5edcc4f0f9a3d 100644 GIT binary patch delta 65465 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 j1SoJDL7C0s@74rbeafB{4qK^g0