From 638198b86ed840a7578336982cc60e5b19b5e3c4 Mon Sep 17 00:00:00 2001 From: losh11 Date: Tue, 3 Mar 2026 19:43:20 +0000 Subject: [PATCH] diy: support for Waveshare AMOLED 1.64 --- .gitignore | 1 + components/esp_lcd_sh8601/CMakeLists.txt | 5 + components/esp_lcd_sh8601/esp_lcd_sh8601.c | 346 ++++++++++++++++++ .../esp_lcd_sh8601/include/esp_lcd_sh8601.h | 115 ++++++ ...splay_waveshares3_touch_amoled164.defaults | 78 ++++ main/CMakeLists.txt | 2 +- main/Kconfig.projbuild | 39 +- main/display.c | 6 +- main/display_hw.c | 115 +++++- main/display_hw.h | 3 + main/input/touchscreen.inc | 18 +- main/power.c | 2 + main/power/wsamoled164.inc | 170 +++++++++ main/process/dashboard.c | 6 +- main/process/ota_defines.h | 2 + main/ui/dashboard.c | 3 +- main/ui/dialogs.c | 2 +- 17 files changed, 889 insertions(+), 24 deletions(-) create mode 100644 components/esp_lcd_sh8601/CMakeLists.txt create mode 100644 components/esp_lcd_sh8601/esp_lcd_sh8601.c create mode 100644 components/esp_lcd_sh8601/include/esp_lcd_sh8601.h create mode 100644 configs/sdkconfig_display_waveshares3_touch_amoled164.defaults create mode 100644 main/power/wsamoled164.inc diff --git a/.gitignore b/.gitignore index d5ca9f11..c4945036 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ managed_components/ dist/ jade_client.egg-info/ docs/_build/ +.cache diff --git a/components/esp_lcd_sh8601/CMakeLists.txt b/components/esp_lcd_sh8601/CMakeLists.txt new file mode 100644 index 00000000..51d7e1f6 --- /dev/null +++ b/components/esp_lcd_sh8601/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "esp_lcd_sh8601.c" + INCLUDE_DIRS "include" + REQUIRES driver esp_lcd +) diff --git a/components/esp_lcd_sh8601/esp_lcd_sh8601.c b/components/esp_lcd_sh8601/esp_lcd_sh8601.c new file mode 100644 index 00000000..149ebdb0 --- /dev/null +++ b/components/esp_lcd_sh8601/esp_lcd_sh8601.c @@ -0,0 +1,346 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 +*/ +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "esp_log.h" + +#include "esp_lcd_sh8601.h" + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const char *TAG = "sh8601"; + +static esp_err_t panel_sh8601_del(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_init(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_sh8601_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_sh8601_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_sh8601_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_sh8601_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_sh8601_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + 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 sh8601_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct { + unsigned int use_qspi_interface: 1; + unsigned int reset_level: 1; + } flags; +} sh8601_panel_t; + +esp_err_t esp_lcd_new_panel_sh8601(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_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + esp_err_t ret = ESP_OK; + sh8601_panel_t *sh8601 = NULL; + sh8601 = calloc(1, sizeof(sh8601_panel_t)); + ESP_GOTO_ON_FALSE(sh8601, ESP_ERR_NO_MEM, err, TAG, "no mem for sh8601 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: + sh8601->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + sh8601->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + uint8_t fb_bits_per_pixel = 0; + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + sh8601->colmod_val = 0x55; + fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + sh8601->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + fb_bits_per_pixel = 18; + break; + case 24: // RGB888 + sh8601->colmod_val = 0x77; + fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + sh8601->io = io; + sh8601->reset_gpio_num = panel_dev_config->reset_gpio_num; + sh8601->fb_bits_per_pixel = fb_bits_per_pixel; + sh8601_vendor_config_t *vendor_config = (sh8601_vendor_config_t *)panel_dev_config->vendor_config; + if (vendor_config) { + sh8601->init_cmds = vendor_config->init_cmds; + sh8601->init_cmds_size = vendor_config->init_cmds_size; + sh8601->flags.use_qspi_interface = vendor_config->flags.use_qspi_interface; + } + sh8601->flags.reset_level = panel_dev_config->flags.reset_active_high; + sh8601->base.del = panel_sh8601_del; + sh8601->base.reset = panel_sh8601_reset; + sh8601->base.init = panel_sh8601_init; + sh8601->base.draw_bitmap = panel_sh8601_draw_bitmap; + sh8601->base.invert_color = panel_sh8601_invert_color; + sh8601->base.set_gap = panel_sh8601_set_gap; + sh8601->base.mirror = panel_sh8601_mirror; + sh8601->base.swap_xy = panel_sh8601_swap_xy; + sh8601->base.disp_on_off = panel_sh8601_disp_on_off; + *ret_panel = &(sh8601->base); + ESP_LOGD(TAG, "new sh8601 panel @%p", sh8601); + + //ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_SH8601_VER_MAJOR, ESP_LCD_SH8601_VER_MINOR, + //ESP_LCD_SH8601_VER_PATCH); + + return ESP_OK; + +err: + if (sh8601) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(sh8601); + } + return ret; +} + +static esp_err_t tx_param(sh8601_panel_t *sh8601, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (sh8601->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + } + return esp_lcd_panel_io_tx_param(io, lcd_cmd, param, param_size); +} + +static esp_err_t tx_color(sh8601_panel_t *sh8601, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (sh8601->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_COLOR << 24; + } + return esp_lcd_panel_io_tx_color(io, lcd_cmd, param, param_size); +} + +static esp_err_t panel_sh8601_del(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + + if (sh8601->reset_gpio_num >= 0) { + gpio_reset_pin(sh8601->reset_gpio_num); + } + ESP_LOGD(TAG, "del sh8601 panel @%p", sh8601); + free(sh8601); + return ESP_OK; +} + +static esp_err_t panel_sh8601_reset(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + + // Perform hardware reset + if (sh8601->reset_gpio_num >= 0) { + gpio_set_level(sh8601->reset_gpio_num, sh8601->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(sh8601->reset_gpio_num, !sh8601->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(150)); + } else { // Perform software reset + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(80)); + } + + return ESP_OK; +} + +static const sh8601_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0x44, (uint8_t []){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t []){0x00}, 0, 0}, + {0x53, (uint8_t []){0x20}, 1, 25}, +}; + +static esp_err_t panel_sh8601_init(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + const sh8601_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + bool is_cmd_overwritten = false; + + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_MADCTL, (uint8_t[]) { + sh8601->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_COLMOD, (uint8_t[]) { + sh8601->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 (sh8601->init_cmds) { + init_cmds = sh8601->init_cmds; + init_cmds_size = sh8601->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(sh8601_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 + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + sh8601->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + sh8601->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(tx_param(sh8601, 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_sh8601_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = sh8601->io; + + x_start += sh8601->x_gap ; + x_end += sh8601->x_gap ; + y_start += sh8601->y_gap; + y_end += sh8601->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * sh8601->fb_bits_per_pixel / 8; + tx_color(sh8601, io, LCD_CMD_RAMWR, color_data, len); + + return ESP_OK; +} + +static esp_err_t panel_sh8601_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_sh8601_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + esp_err_t ret = ESP_OK; + + if (mirror_x) { + sh8601->madctl_val |= BIT(6); + } else { + sh8601->madctl_val &= ~BIT(6); + } + if (mirror_y) { + ESP_LOGE(TAG, "mirror_y is not supported by this panel"); + ret = ESP_ERR_NOT_SUPPORTED; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_MADCTL, (uint8_t[]) { + sh8601->madctl_val + }, 1), TAG, "send command failed"); + return ret; +} + +static esp_err_t panel_sh8601_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + ESP_LOGE(TAG, "swap_xy is not supported by this panel"); + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_sh8601_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + sh8601->x_gap = x_gap; + sh8601->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_sh8601_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + int command = 0; + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h b/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h new file mode 100644 index 00000000..c4de803d --- /dev/null +++ b/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* #include #include +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +#include +#endif #endif // ndef CONFIG_LIBJADE #include "freertos/semphr.h" @@ -37,6 +40,10 @@ typedef void* esp_lcd_panel_handle_t; static TaskHandle_t* gui_h = NULL; static SemaphoreHandle_t init_done = NULL; +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +static esp_lcd_panel_io_handle_t global_io_handle = NULL; +#endif + #ifdef CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3 static void set_gpio_high(gpio_num_t num) @@ -50,6 +57,14 @@ static void set_gpio_high(gpio_num_t num) #include #endif +/* For touchscreen boards with QSPI AMOLED, include the virtual button area (40px) + * in the frame buffer so it gets flushed via DMA along with the content area. */ +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +#define DISPLAY_FLUSH_HEIGHT (CONFIG_DISPLAY_HEIGHT + 40) +#else +#define DISPLAY_FLUSH_HEIGHT CONFIG_DISPLAY_HEIGHT +#endif + #ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER #ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE static color_t** _disp_buf = NULL; @@ -58,6 +73,8 @@ static uint8_t buffer_selected = 0; static color_t* disp_buf = NULL; #if CONFIG_PIN_NUM_DATA0 != -1 #define TRANSFER_BUFFER_LINES CONFIG_DISPLAY_HEIGHT +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) +#define TRANSFER_BUFFER_LINES 8 #else // not i80, thus SPI - use fewer lines, save dram #define TRANSFER_BUFFER_LINES 8 #endif // CONFIG_PIN_NUM_DATA0 @@ -86,13 +103,70 @@ static void esp_lcd_init(void* _ignored) #ifdef ESP_PLATFORM esp_lcd_panel_io_handle_t io_handle = NULL; -#if CONFIG_DISPLAY_PIN_BL != -1 && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) +#if CONFIG_DISPLAY_PIN_BL != -1 && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) \ + && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << CONFIG_DISPLAY_PIN_BL }; ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); ESP_ERROR_CHECK(gpio_set_level(CONFIG_DISPLAY_PIN_BL, 0)); #endif -#if CONFIG_PIN_NUM_DATA0 != -1 +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 + // QSPI AMOLED initialization using SH8601 + static const uint8_t cmd_00[] = {0x00}; + static const uint8_t cmd_80[] = {0x80}; + static const uint8_t cmd_20[] = {0x20}; + static const uint8_t cmd_ff[] = {0xFF}; + static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = { + {0x11, cmd_00, 0, 80}, + {0xC4, cmd_80, 1, 0}, + {0x35, cmd_00, 1, 0}, + {0x53, cmd_20, 1, 1}, + {0x63, cmd_ff, 1, 1}, + {0x51, cmd_00, 1, 1}, + {0x29, cmd_00, 0, 10}, + {0x51, cmd_ff, 1, 0}, + }; + + const spi_bus_config_t buscfg = SH8601_PANEL_BUS_QSPI_CONFIG( + CONFIG_DISPLAY_QSPI_CLK, + CONFIG_DISPLAY_QSPI_DATA0, + CONFIG_DISPLAY_QSPI_DATA1, + CONFIG_DISPLAY_QSPI_DATA2, + CONFIG_DISPLAY_QSPI_DATA3, + CONFIG_DISPLAY_WIDTH * TRANSFER_BUFFER_LINES * sizeof(uint16_t) + 8); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + + const esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( + CONFIG_DISPLAY_QSPI_CS, +#if defined(CONFIG_DISPLAY_FULL_FRAME_BUFFER) && !defined(CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE) + color_trans_done, +#else + NULL, +#endif + NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &io_handle)); + + sh8601_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), + .flags = { + .use_qspi_interface = 1, + }, + }; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = CONFIG_DISPLAY_PIN_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &ph)); + global_io_handle = io_handle; + + ESP_ERROR_CHECK(esp_lcd_panel_reset(ph)); + ESP_ERROR_CHECK(esp_lcd_panel_init(ph)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(ph, CONFIG_DISPLAY_OFFSET_X, CONFIG_DISPLAY_OFFSET_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(ph, true)); +#elif CONFIG_PIN_NUM_DATA0 != -1 set_gpio_high(CONFIG_LCD_POWER_PIN_NUM); set_gpio_high(CONFIG_LCD_RD_PIN_NUM); set_gpio_high(CONFIG_LCD_BACKLIGHT_PIN_NUM); @@ -176,8 +250,9 @@ static void esp_lcd_init(void* _ignored) #endif .bits_per_pixel = 16, }; -#endif // else CONFIG_PIN_NUM_DATA0 +#endif // CONFIG_PIN_NUM_DATA0 / SPI +#ifndef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &ph)); #if CONFIG_DISPLAY_PIN_BL != -1 @@ -211,6 +286,10 @@ static void esp_lcd_init(void* _ignored) ESP_ERROR_CHECK(esp_lcd_panel_set_gap(ph, CONFIG_DISPLAY_OFFSET_X, CONFIG_DISPLAY_OFFSET_Y)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(ph, true)); +#else // CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +#define X_FLIPPED false +#define Y_FLIPPED false +#endif // !CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 #else #define X_FLIPPED false #define Y_FLIPPED false @@ -245,14 +324,14 @@ void display_hw_init(TaskHandle_t* gui_handle) #ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE _disp_buf = JADE_MALLOC_PREFER_SPIRAM(2 * sizeof(color_t*)); _disp_buf[0] - = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * CONFIG_DISPLAY_HEIGHT * sizeof(color_t), 16); + = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * DISPLAY_FLUSH_HEIGHT * sizeof(color_t), 16); _disp_buf[1] - = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * CONFIG_DISPLAY_HEIGHT * sizeof(color_t), 16); + = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * DISPLAY_FLUSH_HEIGHT * sizeof(color_t), 16); disp_buf = _disp_buf[0]; #endif #ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER #ifndef CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE - disp_buf = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * CONFIG_DISPLAY_HEIGHT * sizeof(color_t), 16); + disp_buf = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * DISPLAY_FLUSH_HEIGHT * sizeof(color_t), 16); #endif #endif #ifndef CONFIG_LIBJADE @@ -276,13 +355,12 @@ inline void display_hw_draw_bitmap(int x, int y, int w, int h, const uint16_t* c const int calculatedx = x - CONFIG_DISPLAY_OFFSET_X; const int calculatedy = y - CONFIG_DISPLAY_OFFSET_Y; #if (defined(CONFIG_BOARD_TYPE_M5_CORES3) || defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2)) \ + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164)) \ && defined(CONFIG_DISPLAY_FULL_FRAME_BUFFER) /* this is required for the virtual buttons */ - if (calculatedy >= CONFIG_DISPLAY_HEIGHT) { + if (calculatedy >= DISPLAY_FLUSH_HEIGHT) { ESP_ERROR_CHECK( esp_lcd_panel_draw_bitmap(ph, calculatedx, calculatedy, calculatedx + w, calculatedy + h, color_data)); - ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ph, calculatedx, calculatedy, x + w, calculatedy + h, color_data)); return; } #endif @@ -385,7 +463,7 @@ static inline void switch_buffer(void) disp_buf = _disp_buf[buffer_selected]; /* it is necessary to copy the old buffer over the new one as writes can be partial */ /* FIXME: 5.2+ idf seems to support dma memcpy for ESP32 S2/S3 */ - jmemcpy(disp_buf, _disp_buf[1 - buffer_selected], CONFIG_DISPLAY_WIDTH * CONFIG_DISPLAY_HEIGHT * sizeof(color_t)); + jmemcpy(disp_buf, _disp_buf[1 - buffer_selected], CONFIG_DISPLAY_WIDTH * DISPLAY_FLUSH_HEIGHT * sizeof(color_t)); } #endif @@ -395,7 +473,7 @@ static inline void switch_buffer(void) void display_hw_flush(void) { #ifndef CONFIG_LIBJADE - ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ph, 0, 0, CONFIG_DISPLAY_WIDTH, CONFIG_DISPLAY_HEIGHT, disp_buf)); + ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ph, 0, 0, CONFIG_DISPLAY_WIDTH, DISPLAY_FLUSH_HEIGHT, disp_buf)); #endif // CONFIG_LIBJADE #ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE /* we only need to switch buffer if we have more than one and we don't bother waiting for writes */ @@ -406,4 +484,19 @@ void display_hw_flush(void) #endif } #endif // FRAME BUFFER + +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +void display_hw_set_brightness(uint8_t brightness) +{ + if (!global_io_handle) { + return; + } + uint32_t cmd = 0x51; + cmd &= 0xff; + cmd <<= 8; + cmd |= 0x02 << 24; + esp_lcd_panel_io_tx_param(global_io_handle, cmd, &brightness, 1); +} +#endif + #endif // AMALGAMATED_BUILD diff --git a/main/display_hw.h b/main/display_hw.h index 55d0d8a0..4127a5f5 100644 --- a/main/display_hw.h +++ b/main/display_hw.h @@ -10,4 +10,7 @@ void display_hw_flush(void); void display_hw_draw_rect(int x, int y, int w, int h, const uint16_t color_data); uint16_t* display_hw_get_buffer(void); #endif +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 +void display_hw_set_brightness(uint8_t brightness); +#endif #endif /* DISPLAY_HW_H_ */ diff --git a/main/input/touchscreen.inc b/main/input/touchscreen.inc index 7607db01..31d3ec4d 100644 --- a/main/input/touchscreen.inc +++ b/main/input/touchscreen.inc @@ -2,7 +2,7 @@ #include "jade_assert.h" #include "jade_tasks.h" -#if defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) +#if defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) // No PMU, so relevant power files will not have been included #include "power/i2c.inc" #endif @@ -31,7 +31,12 @@ static void touchscreen_task(void* ignored) // FIXME: check mirror flags? const esp_lcd_touch_config_t tp_cfg = { .x_max = CONFIG_DISPLAY_WIDTH + CONFIG_DISPLAY_OFFSET_X, +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 + // Full 456px panel height (416 content + 40 virtual buttons) + .y_max = CONFIG_DISPLAY_HEIGHT + 40, +#else .y_max = CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y, +#endif .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = GPIO_NUM_NC, .levels = { @@ -60,6 +65,15 @@ static void touchscreen_task(void* ignored) ESP_ERROR_CHECK(ESP_LCD_TOUCH_NEW_I2C(tp_io_handle, &tp_cfg, &ret_touch)); JADE_ASSERT(ret_touch); +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164 + /* FT3168: Extend the monitor-mode timeout to prevent the touch controller + * from entering low-power mode and becoming unresponsive. + * Register 0x87 = TIME_ENTER_MONITOR: seconds before entering monitor mode (default 2s, set to max 255s) + * Register 0x86 = ID_G_CTRL: 0 = keep active mode */ + esp_lcd_panel_io_tx_param(tp_io_handle, 0x87, (uint8_t[]){0xFF}, 1); + esp_lcd_panel_io_tx_param(tp_io_handle, 0x86, (uint8_t[]){0x00}, 1); +#endif + uint16_t touch_x[1]; uint16_t touch_y[1]; uint16_t touch_strength[1]; @@ -75,7 +89,7 @@ static void touchscreen_task(void* ignored) if (touchpad_pressed) { const uint16_t first_third_end = CONFIG_DISPLAY_WIDTH / 3; const uint16_t middle_thirds_end = (CONFIG_DISPLAY_WIDTH * 2) / 3; - if (touch_y[0] > 200) { + if (touch_y[0] > (CONFIG_DISPLAY_HEIGHT - 40)) { if (touch_x[0] <= first_third_end) { gui_prev(); } else if (touch_x[0] > first_third_end && touch_x[0] < middle_thirds_end) { diff --git a/main/power.c b/main/power.c index 1dd7624b..8b8a3b55 100644 --- a/main/power.c +++ b/main/power.c @@ -25,6 +25,8 @@ #include "power/twatchs3.inc" #elif defined(CONFIG_HAS_IP5306) #include "power/ip5306.inc" +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) +#include "power/wsamoled164.inc" #elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) #include "power/wslcdtouch2.inc" #elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3) diff --git a/main/power/wsamoled164.inc b/main/power/wsamoled164.inc new file mode 100644 index 00000000..f660ca64 --- /dev/null +++ b/main/power/wsamoled164.inc @@ -0,0 +1,170 @@ +// Waveshare S3 Touch AMOLED 1.64 implementation +// +#include +#include +#include +#include +#include +#include + +#include "button_gpio.h" +#include "display_hw.h" +#include "hal/adc_types.h" +#include "hal/gpio_types.h" +#include "iot_button.h" +#include "power.h" +#include "soc/gpio_num.h" +#include "tusb.h" + +#define BATTERY_ADC_CHANNEL ADC_CHANNEL_3 +#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12 +#define BATTERY_EMA_ALPHA 0.3f // estimated moving average smoothing factor + +static adc_oneshot_unit_handle_t adc1_handle = NULL; +static adc_cali_handle_t adc1_cali_chan0_handle = NULL; + +static float ema_voltage = 0.0f; + +static void button_shutdown(void* arg, void* ctx) +{ + power_backlight_off(); + while (gpio_get_level(GPIO_NUM_0) == 0) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + power_shutdown(); +} + +static esp_err_t boot_button_init(void) +{ + const button_config_t btn_cfg = { + .long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS, + .short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS, + }; + const button_gpio_config_t btn_gpio_cfg = { + .gpio_num = 0, + .active_level = 0, + }; + button_handle_t btn_handle = NULL; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn_handle)); + ESP_ERROR_CHECK(iot_button_register_cb(btn_handle, BUTTON_LONG_PRESS_HOLD, NULL, button_shutdown, NULL)); + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + gpio_hold_en(GPIO_NUM_0); + gpio_deep_sleep_hold_en(); + esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); + return ESP_OK; +} + +esp_err_t power_init(void) +{ + // Use the BOOT button as a sleep/wakeup button + ESP_ERROR_CHECK(boot_button_init()); + // Initialise the ADC to measure battery level + adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT_1, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle)); + JADE_ASSERT(adc1_handle); + // ADC Config + adc_oneshot_chan_cfg_t config = { + .atten = BATTERY_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config)); + // Use voltage calibration + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = BATTERY_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_chan0_handle) != ESP_OK) { + JADE_LOGW("ADC calibration not available, continuing without it"); + adc1_cali_chan0_handle = NULL; + } + return ESP_OK; +} + +esp_err_t power_shutdown(void) { esp_deep_sleep_start(); } +esp_err_t power_screen_on(void) { return ESP_OK; } +esp_err_t power_screen_off(void) { return ESP_OK; } +esp_err_t power_backlight_on(uint8_t brightness) +{ + if (brightness < BACKLIGHT_MIN) { + brightness = BACKLIGHT_MIN; + } else if (brightness > BACKLIGHT_MAX) { + brightness = BACKLIGHT_MAX; + } + // Map brightness levels 1-5 to AMOLED brightness values + const uint8_t amoled_brightness = (brightness * 255) / 5; + display_hw_set_brightness(amoled_brightness); + return ESP_OK; +} +esp_err_t power_backlight_off(void) +{ + display_hw_set_brightness(0); + return ESP_OK; +} +esp_err_t power_camera_on(void) { return ESP_OK; } +esp_err_t power_camera_off(void) { return ESP_OK; } + +uint16_t power_get_vbat(void) +{ + JADE_ASSERT(adc1_handle); + int raw_adc, voltage; + ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &raw_adc)); + if (adc1_cali_chan0_handle) { + adc_cali_raw_to_voltage(adc1_cali_chan0_handle, raw_adc, &voltage); + } else { + voltage = (raw_adc * 3300) / 4095; + } + // Apply Estimated Moving Average (EMA) + const float ema_v = (ema_voltage > 0) ? ema_voltage : (float)voltage; + ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1.0f - BATTERY_EMA_ALPHA) * ema_v); + return (uint16_t)ema_voltage; +} + +uint8_t power_get_battery_status(void) +{ + const uint16_t vbat = power_get_vbat() * 3; // apply voltage divider + if (vbat > 3800) { + return 5; + } else if (vbat > 3600) { + return 4; + } else if (vbat > 3400) { + return 3; + } else if (vbat > 3200) { + return 2; + } else if (vbat > 3000) { + return 1; + } + return 0; +} + +bool power_get_battery_charging(void) +{ + // The charging IC STAT pin is not connected to GPIO. + // Return USB status as a proxy for charging (Dummy status) + return usb_is_powered(); +} + +uint16_t power_get_ibat_charge(void) { return 0; } +uint16_t power_get_ibat_discharge(void) { return 0; } +uint16_t power_get_vusb(void) { return 0; } +uint16_t power_get_iusb(void) { return 0; } +uint16_t power_get_temp(void) { return 0; } + +void disable_usb_host(void) {} +void enable_usb_host(void) {} + +bool usb_is_powered(void) +{ + // Check if USB is mounted and not suspended (active connection) + if (tud_mounted() && !tud_suspended()) { + return true; + } + // Fallback: If voltage is near zero (<1V) but chip is on, + // we must be running on USB power without a battery. + if (power_get_vbat() < 1000) { + return true; + } + return false; +} diff --git a/main/process/dashboard.c b/main/process/dashboard.c index 2d97a966..157c65b6 100644 --- a/main/process/dashboard.c +++ b/main/process/dashboard.c @@ -1687,7 +1687,7 @@ static void handle_view_otps(void) // NOTE: Only Jade v1.1's and v2's have brightness controls #if defined(CONFIG_BOARD_TYPE_JADE_V1_1) || defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) static void handle_screen_brightness(void) { static const char* LABELS[] = { "Min(1)", "Low(2)", "Medium(3)", "High(4)", "Max(5)" }; @@ -2032,7 +2032,7 @@ static void handle_display_battery_volts(void) const int ret = snprintf(power_status, sizeof(power_status), "%umv", vbat); JADE_ASSERT(ret > 0 && ret < sizeof(power_status)); } -#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) const uint16_t vbat = power_get_vbat() * 3; // applying voltage divider if (vbat > 0) { const int ret = snprintf(power_status, sizeof(power_status), "%umv", vbat); @@ -2267,7 +2267,7 @@ static void handle_settings(const bool startup_menu) // NOTE: Only Jade v1.1's and v2's have brightness controls #if defined(CONFIG_BOARD_TYPE_JADE_V1_1) || defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) case BTN_SETTINGS_DISPLAY_BRIGHTNESS: handle_screen_brightness(); break; diff --git a/main/process/ota_defines.h b/main/process/ota_defines.h index 57c33829..0e69319c 100644 --- a/main/process/ota_defines.h +++ b/main/process/ota_defines.h @@ -45,6 +45,8 @@ #define JADE_OTA_BOARD_TYPE "TTGO_TDISPLAYS3PROCAMERA" #elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) #define JADE_OTA_BOARD_TYPE "WAVESHARE_TOUCH_LCD2" +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) +#define JADE_OTA_BOARD_TYPE "WAVESHARE_TOUCH_AMOLED164" #elif defined(CONFIG_BOARD_TYPE_QEMU) || defined(CONFIG_BOARD_TYPE_QEMU_LARGER) #define JADE_OTA_BOARD_TYPE "QEMU" #else diff --git a/main/ui/dashboard.c b/main/ui/dashboard.c index 182ca2ef..e2c5c57a 100644 --- a/main/ui/dashboard.c +++ b/main/ui/dashboard.c @@ -396,7 +396,8 @@ gui_activity_t* make_display_settings_activity(void) // NOTE: Only Jade v1.1's and v2's have brightness controls // NOTE: Jade v1.1's do not support Flip Orientation because of issues with screen offsets -#if defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) +#if defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) \ + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_AMOLED164) btn_data_t menubtns[] = { { .txt = "Display Brightness", .font = GUI_DEFAULT_FONT, .ev_id = BTN_SETTINGS_DISPLAY_BRIGHTNESS }, { .txt = "Flip Orientation", .font = GUI_DEFAULT_FONT, .ev_id = BTN_SETTINGS_DISPLAY_ORIENTATION }, diff --git a/main/ui/dialogs.c b/main/ui/dialogs.c index 1261374b..ca057493 100644 --- a/main/ui/dialogs.c +++ b/main/ui/dialogs.c @@ -332,7 +332,7 @@ gui_activity_t* make_show_message_activity(const char* message[], const size_t m const size_t msgextent = message_size * h; toppad = msgextent < yextent ? (yextent - msgextent) / 2 : 0; // top padding to centre message JADE_LOGD("ypct, yextent, msgextent, toppad: %u, %u, %u, %u", ypct, yextent, msgextent, toppad); - JADE_ASSERT(toppad < 100); // sanity check + JADE_ASSERT(toppad < CONFIG_DISPLAY_HEIGHT / 2); // sanity check switch (message_size) { case 2: