diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 964d8b4f..bbb153a7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -323,6 +323,11 @@ elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA) set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) set(BUILTIN_ICON_FONT font_awesome_20_4) set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_P4) + set(BOARD_TYPE "lilygo-t-display-p4") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3) set(BOARD_TYPE "movecall-moji-esp32s3") set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index c78c0727..919b2140 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -273,6 +273,9 @@ choice BOARD_TYPE config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY bool "LILYGO T-Display-S3-Pro-MVSRLora_No_Battery" depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_DISPLAY_P4 + bool "LILYGO T-Display-P4" + depends on IDF_TARGET_ESP32P4 config BOARD_TYPE_MOVECALL_MOJI_ESP32S3 bool "Movecall Moji 小智AI衍生版" depends on IDF_TARGET_ESP32S3 @@ -385,6 +388,26 @@ choice BOARD_TYPE depends on IDF_TARGET_ESP32S3 endchoice +choice + depends on BOARD_TYPE_LILYGO_T_DISPLAY_P4 + prompt "Select the screen type" + default SCREEN_TYPE_HI8561 + config SCREEN_TYPE_HI8561 + bool "HI8561" + config SCREEN_TYPE_RM69A10 + bool "RM69A10" +endchoice + +choice + depends on BOARD_TYPE_LILYGO_T_DISPLAY_P4 + prompt "Select the color format of the screen" + default SCREEN_PIXEL_FORMAT_RGB565 + config SCREEN_PIXEL_FORMAT_RGB565 + bool "RGB565" + config SCREEN_PIXEL_FORMAT_RGB888 + bool "RGB888" +endchoice + choice ESP_S3_LCD_EV_Board_Version_TYPE depends on BOARD_TYPE_ESP_S3_LCD_EV_Board prompt "EV_BOARD Type" diff --git a/main/boards/lilygo-t-display-p4/README.md b/main/boards/lilygo-t-display-p4/README.md new file mode 100644 index 00000000..9a236d8d --- /dev/null +++ b/main/boards/lilygo-t-display-p4/README.md @@ -0,0 +1,42 @@ +## LILYGO T-Display-P4 + +The T-Display-P4 is a versatile development board based on the ESP32-P4 core. Its features include: + +1. **High Processing Power**: Equipped with the high-performance ESP32-P4 core processor, it can handle more complex graphics and video tasks, delivering smoother display performance. +2. **Low Power Design**: Offers multiple selectable power modes to effectively reduce energy consumption and extend battery life. +3. **High-Resolution Display**: Supports high resolution (default with a large MIPI interface screen at 540x1168px), providing sharp and clear visuals. +4. **Rich Peripheral Support**: Onboard peripherals include an HD MIPI touchscreen, ESP32-C6 module, speaker, microphone, LoRa module, GPS module, Ethernet, a linear vibration motor, an independent battery gauge for monitoring battery health and percentage, and an MIPI camera. Multiple GPIOs of both the ESP32-P4 and ESP32-C6 are exposed, enhancing the device's expandability. + +Official github: [T-Display-P4](https://github.com/Xinyuan-LilyGO/T-Display-P4) + +## Configuration + +### ESP32P4 Configuration + +* Set the compilation target to ESP32P4 + + idf.py set-target esp32p4 + +* Open menuconfig + + idf.py menuconfig + +* Select the board + + Xiaozhi Assistant -> Board Type -> LILYGO T-Display-P4 + +* Select the screen type + + Xiaozhi Assistant -> Select the screen type -> (Select the screen type you need to configure) + +* Screen the screen pixel format + + Xiaozhi Assistant -> Select the color format of the screen -> (Select the screen pixel format you need to configure) + +* Build + + idf.py build + +### ESP32C6 Configuration + +* Flash the slave example from the esp-hosted-mcu library for the target chip ESP32C6. The esp-hosted-mcu version must match the one used in the xiaozhi-esp32 library. \ No newline at end of file diff --git a/main/boards/lilygo-t-display-p4/config.h b/main/boards/lilygo-t-display-p4/config.h new file mode 100644 index 00000000..3e6b0887 --- /dev/null +++ b/main/boards/lilygo-t-display-p4/config.h @@ -0,0 +1,76 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#include "t_display_p4_config.h" + +#if defined CONFIG_SCREEN_PIXEL_FORMAT_RGB565 +#define SCREEN_BITS_PER_PIXEL 16 +#define SCREEN_COLOR_RGB_PIXEL_FORMAT LCD_COLOR_PIXEL_FORMAT_RGB565 +#define LVGL_COLOR_FORMAT LV_COLOR_FORMAT_RGB565 +#elif defined CONFIG_SCREEN_PIXEL_FORMAT_RGB888 +#define SCREEN_BITS_PER_PIXEL 24 +#define SCREEN_COLOR_RGB_PIXEL_FORMAT LCD_COLOR_PIXEL_FORMAT_RGB888 +#define LVGL_COLOR_FORMAT LV_COLOR_FORMAT_RGB888 +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + +// SCREEN +#if defined CONFIG_SCREEN_TYPE_HI8561 +#define SCREEN_WIDTH HI8561_SCREEN_WIDTH +#define SCREEN_HEIGHT HI8561_SCREEN_HEIGHT +#define SCREEN_MIPI_DSI_DPI_CLK_MHZ HI8561_SCREEN_MIPI_DSI_DPI_CLK_MHZ +#define SCREEN_MIPI_DSI_HSYNC HI8561_SCREEN_MIPI_DSI_HSYNC +#define SCREEN_MIPI_DSI_HBP HI8561_SCREEN_MIPI_DSI_HBP +#define SCREEN_MIPI_DSI_HFP HI8561_SCREEN_MIPI_DSI_HFP +#define SCREEN_MIPI_DSI_VSYNC HI8561_SCREEN_MIPI_DSI_VSYNC +#define SCREEN_MIPI_DSI_VBP HI8561_SCREEN_MIPI_DSI_VBP +#define SCREEN_MIPI_DSI_VFP HI8561_SCREEN_MIPI_DSI_VFP +#define SCREEN_DATA_LANE_NUM HI8561_SCREEN_DATA_LANE_NUM +#define SCREEN_LANE_BIT_RATE_MBPS HI8561_SCREEN_LANE_BIT_RATE_MBPS + +#elif defined CONFIG_SCREEN_TYPE_RM69A10 +#define SCREEN_WIDTH RM69A10_SCREEN_WIDTH +#define SCREEN_HEIGHT RM69A10_SCREEN_HEIGHT +#define SCREEN_MIPI_DSI_DPI_CLK_MHZ RM69A10_SCREEN_MIPI_DSI_DPI_CLK_MHZ +#define SCREEN_MIPI_DSI_HSYNC RM69A10_SCREEN_MIPI_DSI_HSYNC +#define SCREEN_MIPI_DSI_HBP RM69A10_SCREEN_MIPI_DSI_HBP +#define SCREEN_MIPI_DSI_HFP RM69A10_SCREEN_MIPI_DSI_HFP +#define SCREEN_MIPI_DSI_VSYNC RM69A10_SCREEN_MIPI_DSI_VSYNC +#define SCREEN_MIPI_DSI_VBP RM69A10_SCREEN_MIPI_DSI_VBP +#define SCREEN_MIPI_DSI_VFP RM69A10_SCREEN_MIPI_DSI_VFP +#define SCREEN_DATA_LANE_NUM RM69A10_SCREEN_DATA_LANE_NUM +#define SCREEN_LANE_BIT_RATE_MBPS RM69A10_SCREEN_LANE_BIT_RATE_MBPS + +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK static_cast(ES8311_MCLK) +#define AUDIO_I2S_GPIO_WS static_cast(ES8311_WS_LRCK) +#define AUDIO_I2S_GPIO_BCLK static_cast(ES8311_BCLK) +#define AUDIO_I2S_GPIO_DIN static_cast(ES8311_ADC_DATA) +#define AUDIO_I2S_GPIO_DOUT static_cast(ES8311_DAC_DATA) + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN static_cast(ES8311_SDA) +#define AUDIO_CODEC_I2C_SCL_PIN static_cast(ES8311_SCL) +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_35 + +#define SCREEN_SWAP_XY false +#define SCREEN_MIRROR_X false +#define SCREEN_MIRROR_Y false + +#define SCREEN_OFFSET_X 0 +#define SCREEN_OFFSET_Y 0 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-display-p4/hi8561_driver.cc b/main/boards/lilygo-t-display-p4/hi8561_driver.cc new file mode 100644 index 00000000..2d222b24 --- /dev/null +++ b/main/boards/lilygo-t-display-p4/hi8561_driver.cc @@ -0,0 +1,404 @@ +/* + * 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 "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 "hi8561_driver.h" + +#define HI8561_PAD_CONTROL (0xB2) +#define HI8561_DSI_2_LANE (0x10) +#define HI8561_DSI_4_LANE (0x00) + +#define HI8561_CMD_SHLR_BIT (1ULL << 0) +#define HI8561_CMD_UPDN_BIT (1ULL << 1) +#define HI8561_MDCTL_VALUE_DEFAULT (0x01) + +static const hi8561_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + /**** CMD_Page 3 ****/ + {0xDF, (uint8_t[]){0x90, 0x69, 0xF9}, 3, 0}, + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0xBB, (uint8_t[]){0x0F, 0x10, 0x43, 0x50, 0x32, 0x44, 0x44}, 7, 0}, + {0xBF, (uint8_t[]){0x46, 0x32}, 2, 0}, + {0xC0, (uint8_t[]){0x01, 0xAD, 0x01, 0xAD}, 4, 0}, + {0xBD, (uint8_t[]){0x00, 0xB4}, 2, 0}, + {0xC6, (uint8_t[]){0x00, 0x7D, 0x00, 0xC8, 0x00, 0x17, 0x1A, 0x82, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01}, 23, 0}, + {0xC8, (uint8_t[]){0x23, 0x48, 0x87}, 3, 0}, + // {0xCC, (uint8_t[]){0x33}, 1, 0},//4lane + {0xCC, (uint8_t[]){0x31}, 1, 0}, // 2lane + // {0xCC, (uint8_t[]){0x30}, 1, 0}, // 1lane + {0xBC, (uint8_t[]){0x2E, 0x80, 0x84}, 3, 0}, + {0xC3, (uint8_t[]){0x3B, 0x01, 0x02, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 25, 0}, + {0xC4, (uint8_t[]){0x01, 0x02, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 24, 0}, + {0xC5, (uint8_t[]){0x03, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 23, 0}, + {0xD7, (uint8_t[]){0x00, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63}, 17, 0}, + {0xCB, (uint8_t[]){0x7F, 0x78, 0x71, 0x64, 0x5A, 0x58, 0x4B, 0x51, 0x3A, 0x53, 0x51, 0x4F, 0x6A, 0x54, 0x57, 0x46, 0x3F, 0x2F, 0x1B, 0x0F, 0x08, 0x7F, 0x78, 0x71, 0x64, 0x5A, 0x58, 0x4B, 0x51, 0x3A, 0x53, 0x51, 0x4F, 0x6A, 0x54, 0x57, 0x46, 0x3F, 0x2F, 0x1B, 0x0F, 0x08, 0x00}, 43, 0}, + {0xCE, (uint8_t[]){0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C}, 23, 0}, + {0xCF, (uint8_t[]){0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 45, 0}, + {0xD0, (uint8_t[]){0x00, 0x1F, 0x1F, 0x11, 0x1E, 0x1F, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x07, 0x07, 0x05, 0x05, 0x01, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0}, + {0xD1, (uint8_t[]){0x00, 0x1F, 0x1F, 0x10, 0x1E, 0x1F, 0x0E, 0x0E, 0x0C, 0x0C, 0x0A, 0x0A, 0x08, 0x08, 0x06, 0x06, 0x04, 0x04, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0}, + {0xD2, (uint8_t[]){0x00, 0x5F, 0x1F, 0x10, 0x1F, 0x1E, 0x08, 0x08, 0x4A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x04, 0x04, 0x06, 0x06, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0}, + {0xD3, (uint8_t[]){0x00, 0x1F, 0x1F, 0x11, 0x1F, 0x1E, 0x09, 0x09, 0x0B, 0x0B, 0x0D, 0x0D, 0x0F, 0x0F, 0x05, 0x05, 0x07, 0x07, 0x01, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0}, + {0xD4, (uint8_t[]){0x00, 0x20, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x81, 0x04, 0xAE, 0x04, 0xB0, 0x04, 0xB2, 0x04, 0xB4, 0x04, 0xB6, 0x04, 0xB8, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x06, 0x44, 0x06, 0x46, 0x03, 0x03, 0x00, 0x00, 0x07, 0x00, 0x06, 0x04, 0xA7, 0x04, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00}, 87, 0}, + {0xD5, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x32, 0x5A, 0x00, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x1E, 0xB3, 0x00, 0x0F, 0x06, 0x0C, 0x00, 0x71, 0x20, 0x04, 0x10, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 61, 0}, + {0xCD, (uint8_t[]){0x00, 0x00}, 2, 0}, + + {0xDE, (uint8_t[]){0x01}, 1, 0}, + {0xB9, (uint8_t[]){0x00, 0xFF, 0xFF, 0x04}, 4, 0}, + {0xC7, (uint8_t[]){0x1F, 0x14, 0x0E}, 3, 0}, + + {0xDE, (uint8_t[]){0x02}, 1, 0}, + {0xE5, (uint8_t[]){0x00, 0x60, 0x60, 0x02, 0x18, 0x60, 0x18, 0x60, 0x09, 0x04, 0x00, 0xC5, 0x01, 0x2C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04}, 24, 0}, + {0xE6, (uint8_t[]){0x10, 0x10, 0x82}, 3, 0}, + {0xC4, (uint8_t[]){0x00, 0x11, 0x07, 0x00, 0x11, 0x01, 0x08}, 7, 0}, + {0xC3, (uint8_t[]){0x20, 0xFF}, 2, 0}, + {0xBD, (uint8_t[]){0x1B}, 1, 0}, + {0xC6, (uint8_t[]){0x4A, 0x00}, 2, 0}, + {0xCD, (uint8_t[]){0x14, 0x64, 0x11, 0x40}, 4, 0}, + {0xC1, (uint8_t[]){0x00, 0x40, 0x00, 0x02, 0x02, 0x02, 0x02, 0x7F, 0x00, 0x00}, 10, 0}, + {0xB3, (uint8_t[]){0x00, 0xA8}, 2, 0}, + {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x40, 0x43, 0x04}, 11, 0}, + {0xC2, (uint8_t[]){0x02, 0x42, 0x50, 0x00, 0x02, 0xE4, 0x61, 0x73, 0xF9, 0x08}, 10, 0}, + {0xEC, (uint8_t[]){0x07, 0x07, 0x40, 0x00, 0x22, 0x02, 0x00, 0xFF, 0x08, 0x7C, 0x00, 0x00, 0x00, 0x00}, 14, 0}, + + {0xDE, (uint8_t[]){0x03}, 1, 0}, + {0xD1, (uint8_t[]){0x00, 0x00, 0x21, 0xFF, 0x00}, 5, 0}, + + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 0, 30}, + + {0x11, (uint8_t[]){0x00}, 0, 120}, + + {0x29, (uint8_t[]){0x00}, 0, 50}, + + //============ Gamma END=========== +}; + +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 hi8561_lcd_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); +} hi8561_panel_t; + +static const char *TAG = "hi8561"; + +static esp_err_t panel_hi8561_send_init_cmds(hi8561_panel_t *hi8561); + +static esp_err_t panel_hi8561_del(esp_lcd_panel_t *panel); +static esp_err_t panel_hi8561_init(esp_lcd_panel_t *panel); +static esp_err_t panel_hi8561_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_hi8561_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_hi8561_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_hi8561_sleep(esp_lcd_panel_t *panel, bool sleep); +static esp_err_t panel_hi8561_on_off(esp_lcd_panel_t *panel, bool on_off); + +esp_err_t esp_lcd_new_panel_hi8561(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_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_hi8561_VER_MAJOR, ESP_LCD_hi8561_VER_MINOR, + // ESP_LCD_hi8561_VER_PATCH); + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + hi8561_vendor_config_t *vendor_config = (hi8561_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; + hi8561_panel_t *hi8561 = (hi8561_panel_t *)calloc(1, sizeof(hi8561_panel_t)); + ESP_RETURN_ON_FALSE(hi8561, ESP_ERR_NO_MEM, TAG, "no mem for hi8561 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_config_t io_conf = { + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + .mode = GPIO_MODE_OUTPUT, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + hi8561->io = io; + hi8561->init_cmds = vendor_config->init_cmds; + hi8561->init_cmds_size = vendor_config->init_cmds_size; + hi8561->lane_num = vendor_config->mipi_config.lane_num; + hi8561->reset_gpio_num = panel_dev_config->reset_gpio_num; + hi8561->flags.reset_level = panel_dev_config->flags.reset_active_high; + hi8561->madctl_val = HI8561_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 + hi8561->del = (*ret_panel)->del; + hi8561->init = (*ret_panel)->init; + // Overwrite the functions of MIPI DPI panel + (*ret_panel)->del = panel_hi8561_del; + (*ret_panel)->init = panel_hi8561_init; + (*ret_panel)->reset = panel_hi8561_reset; + (*ret_panel)->mirror = panel_hi8561_mirror; + (*ret_panel)->invert_color = panel_hi8561_invert_color; + (*ret_panel)->disp_sleep = panel_hi8561_sleep; + (*ret_panel)->disp_on_off = panel_hi8561_on_off; + (*ret_panel)->user_data = hi8561; + + ESP_LOGD(TAG, "new hi8561 panel @%p", hi8561); + + return ESP_OK; + +err: + if (hi8561) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(static_cast(panel_dev_config->reset_gpio_num)); + } + free(hi8561); + } + return ret; +} + +static esp_err_t panel_hi8561_send_init_cmds(hi8561_panel_t *hi8561) +{ + esp_lcd_panel_io_handle_t io = hi8561->io; + const hi8561_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + // uint8_t lane_command = HI8561_DSI_2_LANE; + // bool is_cmd_overwritten = false; + + // switch (hi8561->lane_num) + // { + // case 0: + // case 2: + // lane_command = HI8561_DSI_2_LANE; + // break; + // case 4: + // lane_command = HI8561_DSI_4_LANE; + // break; + // default: + // ESP_LOGE(TAG, "Invalid lane number %d", hi8561->lane_num); + // return ESP_ERR_INVALID_ARG; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, HI8561_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 (hi8561->init_cmds) + // { + // init_cmds = hi8561->init_cmds; + // init_cmds_size = hi8561->init_cmds_size; + // } + // else + // { + // init_cmds = vendor_specific_init_default; + // init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(hi8561_lcd_init_cmd_t); + // } + + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(hi8561_lcd_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; + // hi8561->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); + // } + // } + + esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + // printf("Ciallo\n"); + } + + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_hi8561_del(esp_lcd_panel_t *panel) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + + if (hi8561->reset_gpio_num >= 0) + { + gpio_reset_pin(static_cast(hi8561->reset_gpio_num)); + } + // Delete MIPI DPI panel + hi8561->del(panel); + ESP_LOGD(TAG, "del hi8561 panel @%p", hi8561); + free(hi8561); + + return ESP_OK; +} + +static esp_err_t panel_hi8561_init(esp_lcd_panel_t *panel) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + + ESP_RETURN_ON_ERROR(panel_hi8561_send_init_cmds(hi8561), TAG, "send init commands failed"); + ESP_RETURN_ON_ERROR(hi8561->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_hi8561_reset(esp_lcd_panel_t *panel) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = hi8561->io; + + // Perform hardware reset + if (hi8561->reset_gpio_num >= 0) + { + gpio_set_level(static_cast(hi8561->reset_gpio_num), hi8561->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(static_cast(hi8561->reset_gpio_num), !hi8561->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_hi8561_sleep(esp_lcd_panel_t *panel, bool sleep) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = hi8561->io; + + if (sleep == true) + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x10, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_hi8561 sleep on"); + } + else + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_hi8561 sleep off"); + } + + vTaskDelay(pdMS_TO_TICKS(120)); + + return ESP_OK; +} + +static esp_err_t panel_hi8561_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = hi8561->io; + + if (on_off == true) + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_hi8561 display on"); + } + else + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x28, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_hi8561 display off"); + } + + vTaskDelay(pdMS_TO_TICKS(120)); + + return ESP_OK; +} + +static esp_err_t panel_hi8561_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = hi8561->io; + uint8_t madctl_val = hi8561->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 |= HI8561_CMD_SHLR_BIT; + } + else + { + madctl_val &= ~HI8561_CMD_SHLR_BIT; + } + if (mirror_y) + { + madctl_val |= HI8561_CMD_UPDN_BIT; + } + else + { + madctl_val &= ~HI8561_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"); + hi8561->madctl_val = madctl_val; + + return ESP_OK; +} + +static esp_err_t panel_hi8561_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = hi8561->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 diff --git a/main/boards/lilygo-t-display-p4/hi8561_driver.h b/main/boards/lilygo-t-display-p4/hi8561_driver.h new file mode 100644 index 00000000..58429b8d --- /dev/null +++ b/main/boards/lilygo-t-display-p4/hi8561_driver.h @@ -0,0 +1,66 @@ +/* + * @Description: hi8561_driver + * @Author: LILYGO_L + * @Date: 2025-06-13 11:02:44 + * @LastEditTime: 2025-10-09 10:36:27 + * @License: GPL 3.0 + */ +#pragma once + +#include +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_mipi_dsi.h" + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct +{ + int cmd; /* +#include +#include +#include + +#include "cpp_bus_driver_library.h" +#include "hi8561_driver.h" +#include "rm69a10_driver.h" + +#define TAG "LilygoTDisplayP4Board" + +void TouchTask(void *arg); + +#if defined CONFIG_SCREEN_TYPE_HI8561 +class CustomBacklight : public Backlight { +private: + std::unique_ptr tool_; + +public: + CustomBacklight(std::unique_ptr tool) : tool_(std::move(tool)) {} + + void SetBrightnessImpl(uint8_t brightness) override{ + tool_->set_pwm_duty(brightness); + }; +}; +#elif defined CONFIG_SCREEN_TYPE_RM69A10 +class CustomBacklight : public Backlight { +private: + esp_lcd_panel_handle_t mipi_dpi_panel_; + +public: + CustomBacklight(esp_lcd_panel_handle_t panel) : mipi_dpi_panel_(panel) {} + + void SetBrightnessImpl(uint8_t brightness) override{ + set_rm69a10_brightness(mipi_dpi_panel_,static_cast(static_cast(brightness) * 2.55)); + }; +}; + +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + +class LilygoTDisplayP4Board : public WifiBoard { +public: + i2c_master_bus_handle_t audio_codec_i2c_bus_; + Button boot_button_; + LcdDisplay *display_; + esp_lcd_panel_handle_t mipi_dpi_panel_; + esp_timer_handle_t touchpad_timer_; + uint8_t brightness_; + + std::shared_ptr xl9535_iic_bus_ = std::make_shared(XL9535_SDA, XL9535_SCL, I2C_NUM_1); + std::unique_ptr xl9535_ = std::make_unique(xl9535_iic_bus_, XL9535_IIC_ADDRESS, DEFAULT_CPP_BUS_DRIVER_VALUE); + +#if defined CONFIG_SCREEN_TYPE_HI8561 + std::shared_ptr hi8561_t_iic_bus_ = std::make_shared(HI8561_TOUCH_SDA, HI8561_TOUCH_SCL, I2C_NUM_1); + std::unique_ptr hi8561_t_ = std::make_unique(hi8561_t_iic_bus_, HI8561_TOUCH_IIC_ADDRESS, DEFAULT_CPP_BUS_DRIVER_VALUE); + + std::unique_ptr hi8561_backlight_ = std::make_unique(); +#elif defined CONFIG_SCREEN_TYPE_RM69A10 + std::shared_ptr gt9895_iic_bus_ = std::make_shared(GT9895_TOUCH_SDA, GT9895_TOUCH_SCL, I2C_NUM_1); + std::unique_ptr gt9895_ = std::make_unique(gt9895_iic_bus_, GT9895_IIC_ADDRESS, GT9895_X_SCALE_FACTOR, GT9895_Y_SCALE_FACTOR, + DEFAULT_CPP_BUS_DRIVER_VALUE); +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + + std::unique_ptr esp32p4_ = std::make_unique(); + + bool Init_Ldo_Channel_Power(uint8_t chan_id, uint32_t voltage_mv){ + esp_ldo_channel_handle_t ldo_channel_handle = NULL; + esp_ldo_channel_config_t ldo_channel_config ={ + .chan_id = static_cast(chan_id), + .voltage_mv = static_cast(voltage_mv), + }; + if (esp_ldo_acquire_channel(&ldo_channel_config, &ldo_channel_handle) != ESP_OK){ + printf("esp_ldo_acquire_channel %d fail\n", chan_id); + return false; + } + + return true; + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &audio_codec_i2c_bus_)); + } + + void InitializeXl9535() { + xl9535_->begin(500000); + xl9535_->pin_mode(XL9535_ESP32P4_VCCA_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_mode(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_mode(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + // 开关3.3v电压时候必须先将GPS断电 + xl9535_->pin_mode(XL9535_GPS_WAKE_UP, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_write(XL9535_GPS_WAKE_UP, Cpp_Bus_Driver::Xl95x5::Value::LOW); + // 开关3.3v电压时候必须先将ESP32C6断电 + xl9535_->pin_mode(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + + xl9535_->pin_write(XL9535_ESP32P4_VCCA_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + + xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + + xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(10)); + + // ESP32C6复位 + xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(100)); + xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(100)); + xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(100)); + + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + void InitializeLCD() { + xl9535_->pin_mode(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + + Init_Ldo_Channel_Power(3, 1800); + + esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = SCREEN_DATA_LANE_NUM, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = SCREEN_LANE_BIT_RATE_MBPS, + }; + esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + // we use DBI interface to send LCD commands and parameters + esp_lcd_dbi_io_config_t dbi_io_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, // according to the LCD spec + .lcd_param_bits = 8, // according to the LCD spec + }; + esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_io_config, &mipi_dbi_io); + + esp_lcd_dpi_panel_config_t dpi_config = { + .virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = SCREEN_MIPI_DSI_DPI_CLK_MHZ, + .pixel_format = SCREEN_COLOR_RGB_PIXEL_FORMAT, + .num_fbs = 0, + .video_timing = { + .h_size = SCREEN_WIDTH, + .v_size = SCREEN_HEIGHT, + .hsync_pulse_width = SCREEN_MIPI_DSI_HSYNC, + .hsync_back_porch = SCREEN_MIPI_DSI_HBP, + .hsync_front_porch = SCREEN_MIPI_DSI_HFP, + .vsync_pulse_width = SCREEN_MIPI_DSI_VSYNC, + .vsync_back_porch = SCREEN_MIPI_DSI_VBP, + .vsync_front_porch = SCREEN_MIPI_DSI_VFP, + }, + .flags = { + .use_dma2d = true, // use DMA2D to copy draw buffer into frame buffer + } + }; + +#if defined CONFIG_SCREEN_TYPE_HI8561 + hi8561_vendor_config_t vendor_config = { + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + }, + }; + esp_lcd_panel_dev_config_t dev_config = { + .reset_gpio_num = -1, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = SCREEN_BITS_PER_PIXEL, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_hi8561(mipi_dbi_io, &dev_config, &mipi_dpi_panel_); + #elif defined CONFIG_SCREEN_TYPE_RM69A10 + rm69a10_vendor_config_t vendor_config = { + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + }, + }; + esp_lcd_panel_dev_config_t dev_config = { + .reset_gpio_num = -1, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = SCREEN_BITS_PER_PIXEL, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_rm69a10(mipi_dbi_io, &dev_config, &mipi_dpi_panel_); +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + + esp_lcd_panel_init(mipi_dpi_panel_); + +#if defined CONFIG_SCREEN_TYPE_HI8561 + hi8561_backlight_->create_pwm(HI8561_SCREEN_BL, ledc_channel_t::LEDC_CHANNEL_0, 2000); +#elif defined CONFIG_SCREEN_TYPE_RM69A10 +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + + + display_ = new MipiLcdDisplay(mipi_dbi_io, mipi_dpi_panel_, SCREEN_WIDTH, SCREEN_HEIGHT, + SCREEN_OFFSET_X, SCREEN_OFFSET_Y, SCREEN_MIRROR_X, SCREEN_MIRROR_Y, SCREEN_SWAP_XY); + } + + void InitializeTouch(){ + xl9535_->pin_mode(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT); + xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::LOW); + vTaskDelay(pdMS_TO_TICKS(10)); + xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH); + vTaskDelay(pdMS_TO_TICKS(10)); + +#if defined CONFIG_SCREEN_TYPE_HI8561 + hi8561_t_iic_bus_->set_bus_handle(xl9535_iic_bus_->get_bus_handle()); + hi8561_t_->begin(); +#elif defined CONFIG_SCREEN_TYPE_RM69A10 + gt9895_iic_bus_->set_bus_handle(xl9535_iic_bus_->get_bus_handle()); + gt9895_->begin(); +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + + xTaskCreate(TouchTask, "tp", 2 * 1024, this, 5, NULL); + } + + void AppToggleChatState(void){ + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + AppToggleChatState(); + }); + } + + LilygoTDisplayP4Board() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeXl9535(); + InitializeLCD(); + InitializeTouch(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + } + + virtual AudioCodec *GetAudioCodec() override { + static Es8311AudioCodec audio_codec(audio_codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + +#if defined CONFIG_SCREEN_TYPE_HI8561 + virtual Backlight* GetBacklight() override { + static CustomBacklight backlight(std::move(hi8561_backlight_)); + return &backlight; + } +#elif defined CONFIG_SCREEN_TYPE_RM69A10 + virtual Backlight* GetBacklight() override { + static CustomBacklight backlight(std::move(mipi_dpi_panel_)); + return &backlight; + } +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + +}; + +void TouchTask(void *arg) { + LilygoTDisplayP4Board* board = (LilygoTDisplayP4Board*)arg; + + static bool touch_flag = false; + static bool touch_lock_flag = true; + static size_t first_touch_time = 0; + static bool waiting_for_second_tap = false; + + while (1){ +#if defined CONFIG_SCREEN_TYPE_HI8561 + if (board->hi8561_t_->get_finger_count() > 0){ + if(touch_flag == false){ + touch_lock_flag = false; + } + touch_flag = true; + } else { + touch_flag = false; + } +#elif defined CONFIG_SCREEN_TYPE_RM69A10 + if (board->gt9895_->get_finger_count() > 0){ + if(touch_flag == false){ + touch_lock_flag = false; + } + touch_flag = true; + } else { + touch_flag = false; + } +#else +#error "unknown macro definition, please select the correct macro definition." +#endif + + if(touch_lock_flag == false){ + size_t current_time = board->esp32p4_->get_system_time_ms(); + + if (!waiting_for_second_tap) { + // 第一次点击 + first_touch_time = current_time; + waiting_for_second_tap = true; + printf("first touch detected, waiting for second...\n"); + } else { + // 第二次点击,检查时间间隔 + // 500ms内完成双击 + if ((current_time - first_touch_time) <= 500) { + printf("double touch trigger\n"); + + board->AppToggleChatState(); + + // 重置状态 + waiting_for_second_tap = false; + first_touch_time = 0; + } else { + // 超时,重新开始 + first_touch_time = current_time; + printf("first touch timeout, restart...\n"); + } + } + + touch_lock_flag = true; + } + + // 处理双击超时 + if (waiting_for_second_tap) { + size_t current_time = board->esp32p4_->get_system_time_ms(); + + if ((current_time - first_touch_time) > 500) { + waiting_for_second_tap = false; + first_touch_time = 0; + printf("double touch timeout\n"); + } + } + + vTaskDelay(pdMS_TO_TICKS(50)); + } +} + +DECLARE_BOARD(LilygoTDisplayP4Board); diff --git a/main/boards/lilygo-t-display-p4/rm69a10_driver.cc b/main/boards/lilygo-t-display-p4/rm69a10_driver.cc new file mode 100644 index 00000000..cc6b3290 --- /dev/null +++ b/main/boards/lilygo-t-display-p4/rm69a10_driver.cc @@ -0,0 +1,377 @@ +/* + * 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 "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 "rm69a10_driver.h" + +#define RM69A10_PAD_CONTROL (0xB2) +#define RM69A10_DSI_2_LANE (0x10) +#define RM69A10_DSI_4_LANE (0x00) + +#define RM69A10_CMD_SHLR_BIT (1ULL << 0) +#define RM69A10_CMD_UPDN_BIT (1ULL << 1) +#define RM69A10_MDCTL_VALUE_DEFAULT (0x01) + +static const rm69a10_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + /**** CMD_Page 3 ****/ + {0xFE, (uint8_t[]){0xFD}, 1, 0}, + {0x80, (uint8_t[]){0xFC}, 1, 0}, + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x02, 0x37}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x04, 0xCF}, 4, 0}, + {0x31, (uint8_t[]){0x00, 0x03, 0x02, 0x34}, 4, 0}, + {0x30, (uint8_t[]){0x00, 0x00, 0x04, 0xCF}, 4, 0}, + {0x12, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, +#if CONFIG_SCREEN_PIXEL_FORMAT_RGB565 + {0x3A, (uint8_t[]){0x75}, 1, 0}, // interface pixel format 16bit/pixel +#elif CONFIG_SCREEN_PIXEL_FORMAT_RGB888 + {0x3A, (uint8_t[]){0x77}, 1, 0}, // interface pixel format 24bit/pixel +#endif + // {0x51, (uint8_t[]){0xFE}, 1, 0}, + {0x51, (uint8_t[]){0x00}, 1, 0}, // 设置屏幕亮度为0 + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + //============ Gamma END=========== +}; + +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 rm69a10_lcd_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); +} rm69a10_panel_t; + +static const char *TAG = "rm69a10"; + +static esp_err_t panel_rm69a10_send_init_cmds(rm69a10_panel_t *rm69a10); + +static esp_err_t panel_rm69a10_del(esp_lcd_panel_t *panel); +static esp_err_t panel_rm69a10_init(esp_lcd_panel_t *panel); +static esp_err_t panel_rm69a10_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_rm69a10_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_rm69a10_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_rm69a10_sleep(esp_lcd_panel_t *panel, bool sleep); +static esp_err_t panel_rm69a10_on_off(esp_lcd_panel_t *panel, bool on_off); + +esp_err_t esp_lcd_new_panel_rm69a10(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_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_RM69A10_VER_MAJOR, ESP_LCD_RM69A10_VER_MINOR, + // ESP_LCD_RM69A10_VER_PATCH); + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + rm69a10_vendor_config_t *vendor_config = (rm69a10_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; + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)calloc(1, sizeof(rm69a10_panel_t)); + ESP_RETURN_ON_FALSE(rm69a10, ESP_ERR_NO_MEM, TAG, "no mem for rm69a10 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_config_t io_conf = { + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + .mode = GPIO_MODE_OUTPUT, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + rm69a10->io = io; + rm69a10->init_cmds = vendor_config->init_cmds; + rm69a10->init_cmds_size = vendor_config->init_cmds_size; + rm69a10->lane_num = vendor_config->mipi_config.lane_num; + rm69a10->reset_gpio_num = panel_dev_config->reset_gpio_num; + rm69a10->flags.reset_level = panel_dev_config->flags.reset_active_high; + rm69a10->madctl_val = RM69A10_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 + rm69a10->del = (*ret_panel)->del; + rm69a10->init = (*ret_panel)->init; + // Overwrite the functions of MIPI DPI panel + (*ret_panel)->del = panel_rm69a10_del; + (*ret_panel)->init = panel_rm69a10_init; + (*ret_panel)->reset = panel_rm69a10_reset; + (*ret_panel)->mirror = panel_rm69a10_mirror; + (*ret_panel)->invert_color = panel_rm69a10_invert_color; + (*ret_panel)->disp_sleep = panel_rm69a10_sleep; + (*ret_panel)->disp_on_off = panel_rm69a10_on_off; + (*ret_panel)->user_data = rm69a10; + ESP_LOGD(TAG, "new rm69a10 panel @%p", rm69a10); + + return ESP_OK; + +err: + if (rm69a10) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(static_cast(panel_dev_config->reset_gpio_num)); + } + free(rm69a10); + } + return ret; +} + +static esp_err_t panel_rm69a10_send_init_cmds(rm69a10_panel_t *rm69a10) +{ + esp_lcd_panel_io_handle_t io = rm69a10->io; + const rm69a10_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + // uint8_t lane_command = RM69A10_DSI_2_LANE; + // bool is_cmd_overwritten = false; + + // switch (rm69a10->lane_num) + // { + // case 0: + // case 2: + // lane_command = RM69A10_DSI_2_LANE; + // break; + // case 4: + // lane_command = RM69A10_DSI_4_LANE; + // break; + // default: + // ESP_LOGE(TAG, "Invalid lane number %d", rm69a10->lane_num); + // return ESP_ERR_INVALID_ARG; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, RM69A10_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 (rm69a10->init_cmds) + // { + // init_cmds = rm69a10->init_cmds; + // init_cmds_size = rm69a10->init_cmds_size; + // } + // else + // { + // init_cmds = vendor_specific_init_default; + // init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(rm69a10_lcd_init_cmd_t); + // } + + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(rm69a10_lcd_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; + // rm69a10->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)); + // printf("Ciallo\n"); + } + + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_del(esp_lcd_panel_t *panel) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + + if (rm69a10->reset_gpio_num >= 0) + { + gpio_reset_pin(static_cast(rm69a10->reset_gpio_num)); + } + // Delete MIPI DPI panel + rm69a10->del(panel); + ESP_LOGD(TAG, "del rm69a10 panel @%p", rm69a10); + free(rm69a10); + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_init(esp_lcd_panel_t *panel) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + + ESP_RETURN_ON_ERROR(panel_rm69a10_send_init_cmds(rm69a10), TAG, "send init commands failed"); + ESP_RETURN_ON_ERROR(rm69a10->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_reset(esp_lcd_panel_t *panel) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->io; + + // Perform hardware reset + if (rm69a10->reset_gpio_num >= 0) + { + gpio_set_level(static_cast(rm69a10->reset_gpio_num), rm69a10->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(static_cast(rm69a10->reset_gpio_num), !rm69a10->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_rm69a10_sleep(esp_lcd_panel_t *panel, bool sleep) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->io; + + if (sleep == true) + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x10, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_rm69a10 sleep on"); + } + else + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_rm69a10 sleep off"); + } + + vTaskDelay(pdMS_TO_TICKS(120)); + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->io; + + if (on_off == true) + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_rm69a10 display on"); + } + else + { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x28, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail"); + ESP_LOGI(TAG, "panel_rm69a10 display off"); + } + + vTaskDelay(pdMS_TO_TICKS(120)); + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->io; + uint8_t madctl_val = rm69a10->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 |= RM69A10_CMD_SHLR_BIT; + } + else + { + madctl_val &= ~RM69A10_CMD_SHLR_BIT; + } + if (mirror_y) + { + madctl_val |= RM69A10_CMD_UPDN_BIT; + } + else + { + madctl_val &= ~RM69A10_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"); + rm69a10->madctl_val = madctl_val; + + return ESP_OK; +} + +static esp_err_t panel_rm69a10_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->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; +} + +esp_err_t set_rm69a10_brightness(esp_lcd_panel_t *panel, uint8_t brightness) +{ + rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = rm69a10->io; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x51, &brightness, 1), TAG, "esp_lcd_panel_io_tx_param fail"); + + return ESP_OK; +} + +#endif diff --git a/main/boards/lilygo-t-display-p4/rm69a10_driver.h b/main/boards/lilygo-t-display-p4/rm69a10_driver.h new file mode 100644 index 00000000..e525bd45 --- /dev/null +++ b/main/boards/lilygo-t-display-p4/rm69a10_driver.h @@ -0,0 +1,68 @@ +/* + * @Description: rm69a10_driver + * @Author: LILYGO_L + * @Date: 2025-07-07 14:23:16 + * @LastEditTime: 2025-10-09 10:36:33 + * @License: GPL 3.0 + */ +#pragma once + +#include +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_mipi_dsi.h" + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct +{ + int cmd; /*(RM69A10_SCREEN_WIDTH) / static_cast(GT9895_MAX_X_SIZE) +#define GT9895_Y_SCALE_FACTOR static_cast(RM69A10_SCREEN_HEIGHT) / static_cast(GT9895_MAX_Y_SIZE) + +// CAMERA +#define CAMERA_WIDTH 1920 +#define CAMERA_HEIGHT 1080 +// #define CAMERA_WIDTH 1280 +// #define CAMERA_HEIGHT 720 +// #define CAMERA_WIDTH 800 +// #define CAMERA_HEIGHT 800 +// #define CAMERA_WIDTH 640 +// #define CAMERA_HEIGHT 480 + +#define CAMERA_DATA_LANE_NUM 2 +#define CAMERA_LANE_BIT_RATE_MBPS 1000 +#define CAMERA_MIPI_DSI_DPI_CLK_MHZ 60 + +// SD +#define SD_BASE_PATH "/sdcard" + +////////////////////////////////////////////////// other define config ////////////////////////////////////////////////// \ No newline at end of file diff --git a/main/idf_component.yml b/main/idf_component.yml index d79f58f3..1addac76 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -82,6 +82,11 @@ dependencies: rules: - if: target in [esp32c3] + llgok/cpp_bus_driver: + version: 1.1.0 + rules: + - if: target in [esp32p4] + ## Required IDF version idf: version: '>=5.4.0'