From 6de0f442fbe44ed26ace8aadbe4e242178e22186 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 15 Jun 2025 13:49:00 +0200 Subject: [PATCH] Updated screenshots and .gitignore of HelloWorld (#291) * T-Deck Pro work in progress * Add .gitignore to HelloWorld --- .github/workflows/build-firmware.yml | 18 ++ App/Kconfig | 4 + App/Source/Boards.h | 14 +- Boards/LilygoTdeck/Source/hal/TdeckDisplay.h | 35 --- Boards/LilygoTdeckPro/CMakeLists.txt | 7 + Boards/LilygoTdeckPro/Source/Init.cpp | 63 ++++ Boards/LilygoTdeckPro/Source/LilygoTdeck.cpp | 108 +++++++ Boards/LilygoTdeckPro/Source/LilygoTdeck.h | 5 + Boards/LilygoTdeckPro/Source/hal/GxEPD2.h | 114 +++++++ .../Source/hal/GxEPD2_310_GDEQ031T10.cpp | 297 ++++++++++++++++++ .../Source/hal/GxEPD2_310_GDEQ031T10.h | 100 ++++++ .../LilygoTdeckPro/Source/hal/GxEPD2_EPD.cpp | 161 ++++++++++ Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.h | 143 +++++++++ .../Source/hal/TdeckDisplay.cpp | 45 +++ .../LilygoTdeckPro/Source/hal/TdeckDisplay.h | 40 +++ .../Source/hal/TdeckDisplayConstants.h | 8 + .../Source/hal/TdeckKeyboard.cpp | 67 ++++ .../LilygoTdeckPro/Source/hal/TdeckKeyboard.h | 25 ++ .../LilygoTdeckPro/Source/hal/TdeckPower.cpp | 130 ++++++++ Boards/LilygoTdeckPro/Source/hal/TdeckPower.h | 30 ++ .../LilygoTdeckPro/Source/hal/TdeckSdCard.cpp | 33 ++ .../LilygoTdeckPro/Source/hal/TdeckSdCard.h | 7 + Buildscripts/board.cmake | 4 + ExternalApps/HelloWorld/.gitignore | 2 +- sdkconfig.board.lilygo-tdeck-plus | 53 ++++ sdkconfig.board.lilygo-tdeck-pro | 57 ++++ 26 files changed, 1527 insertions(+), 43 deletions(-) create mode 100644 Boards/LilygoTdeckPro/CMakeLists.txt create mode 100644 Boards/LilygoTdeckPro/Source/Init.cpp create mode 100644 Boards/LilygoTdeckPro/Source/LilygoTdeck.cpp create mode 100644 Boards/LilygoTdeckPro/Source/LilygoTdeck.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/GxEPD2.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckDisplayConstants.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckPower.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckPower.h create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.cpp create mode 100644 Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.h create mode 100644 sdkconfig.board.lilygo-tdeck-plus create mode 100644 sdkconfig.board.lilygo-tdeck-pro diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 2e8f4434..47d58ab5 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -126,6 +126,24 @@ jobs: with: board_id: lilygo-tdeck arch: esp32s3 + lilygo-tdeck-plus: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: lilygo-tdeck-plus + arch: esp32s3 + lilygo-tdeck-pro: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: lilygo-tdeck-pro + arch: esp32s3 m5stack-core2: runs-on: ubuntu-latest steps: diff --git a/App/Kconfig b/App/Kconfig index 302912a1..73f961c2 100644 --- a/App/Kconfig +++ b/App/Kconfig @@ -37,6 +37,10 @@ menu "Tactility App" bool "Elecrow CrowPanel Basic 5.0" config TT_BOARD_LILYGO_TDECK bool "LilyGo T-Deck" + config TT_BOARD_LILYGO_TDECK_PLUS + bool "LilyGo T-Deck Plus" + config TT_BOARD_LILYGO_TDECK_PRO + bool "LilyGo T-Deck Pro" config TT_BOARD_M5STACK_CORE2 bool "M5Stack Core2" config TT_BOARD_M5STACK_CORES3 diff --git a/App/Source/Boards.h b/App/Source/Boards.h index 982b0cee..f42203e1 100644 --- a/App/Source/Boards.h +++ b/App/Source/Boards.h @@ -5,7 +5,7 @@ #include // Supported hardware: -#if defined(CONFIG_TT_BOARD_LILYGO_TDECK) +#if defined(CONFIG_TT_BOARD_LILYGO_TDECK) || defined(CONFIG_TT_BOARD_LILYGO_TDECK_PLUS) || defined(CONFIG_TT_BOARD_LILYGO_TDECK_PRO) #include "LilygoTdeck.h" #define TT_BOARD_HARDWARE &lilygo_tdeck #elif defined(CONFIG_TT_BOARD_CYD_2432S024C) @@ -14,22 +14,22 @@ #elif defined(CONFIG_TT_BOARD_CYD_2432S032C) #include "CYD2432S032C.h" #define TT_BOARD_HARDWARE &cyd_2432S032c_config -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_28)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_28) #define TT_BOARD_HARDWARE &crowpanel_advance_28 #include "CrowPanelAdvance28.h" -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_35)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_35) #define TT_BOARD_HARDWARE &crowpanel_advance_35 #include "CrowPanelAdvance35.h" -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_50)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_50) #define TT_BOARD_HARDWARE &crowpanel_advance_50 #include "CrowPanelAdvance50.h" -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_28)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_28) #define TT_BOARD_HARDWARE &crowpanel_basic_28 #include "CrowPanelBasic28.h" -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_35)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_35) #define TT_BOARD_HARDWARE &crowpanel_basic_35 #include "CrowPanelBasic35.h" -#elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_50)) +#elif defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_50) #define TT_BOARD_HARDWARE &crowpanel_basic_50 #include "CrowPanelBasic50.h" #elif defined(CONFIG_TT_BOARD_M5STACK_CORE2) diff --git a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h index e9a26ec4..5a0d81b3 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h +++ b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h @@ -1,40 +1,5 @@ #pragma once #include "Tactility/hal/display/DisplayDevice.h" -#include -#include - -class TdeckDisplay : public tt::hal::display::DisplayDevice { - -private: - - esp_lcd_panel_io_handle_t ioHandle = nullptr; - esp_lcd_panel_handle_t panelHandle = nullptr; - lv_display_t* displayHandle = nullptr; - bool poweredOn = false; - -public: - - std::string getName() const final { return "ST7789"; } - std::string getDescription() const final { return "SPI display"; } - - bool start() override; - - bool stop() override; - - void setPowerOn(bool turnOn) override; - bool isPoweredOn() const override { return poweredOn; }; - bool supportsPowerControl() const override { return true; } - - std::shared_ptr _Nullable createTouch() override; - - void setBacklightDuty(uint8_t backlightDuty) override; - bool supportsBacklightDuty() const override { return true; } - - void setGammaCurve(uint8_t index) override; - uint8_t getGammaCurveCount() const override { return 4; }; - - lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } -}; std::shared_ptr createDisplay(); diff --git a/Boards/LilygoTdeckPro/CMakeLists.txt b/Boards/LilygoTdeckPro/CMakeLists.txt new file mode 100644 index 00000000..d1096554 --- /dev/null +++ b/Boards/LilygoTdeckPro/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 GT911 PwmBacklight driver esp_adc +) diff --git a/Boards/LilygoTdeckPro/Source/Init.cpp b/Boards/LilygoTdeckPro/Source/Init.cpp new file mode 100644 index 00000000..c8c4b6df --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/Init.cpp @@ -0,0 +1,63 @@ +#include "PwmBacklight.h" +#include "Tactility/kernel/SystemEvents.h" +#include "Tactility/service/gps/GpsService.h" + +#include +#include + +#define TAG "tdeck" + +// Power on +#define TDECK_POWERON_GPIO GPIO_NUM_10 + +static bool powerOn() { + gpio_config_t device_power_signal_config = { + .pin_bit_mask = BIT64(TDECK_POWERON_GPIO), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + if (gpio_config(&device_power_signal_config) != ESP_OK) { + return false; + } + + if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) { + return false; + } + + return true; +} + +bool tdeckInit() { + ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); + if (!powerOn()) { + TT_LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); + return false; + } + + /* 32 Khz and higher gives an issue where the screen starts dimming again above 80% brightness + * when moving the brightness slider rapidly from a lower setting to 100%. + * This is not a slider bug (data was debug-traced) */ + if (!driver::pwmbacklight::init(GPIO_NUM_42, 30000)) { + TT_LOG_E(TAG, "Backlight init failed"); + return false; + } + + tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { + auto gps_service = tt::service::gps::findGpsService(); + if (gps_service != nullptr) { + std::vector gps_configurations; + gps_service->getGpsConfigurations(gps_configurations); + if (gps_configurations.empty()) { + if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "Grove", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) { + TT_LOG_I(TAG, "Configured internal GPS"); + } else { + TT_LOG_E(TAG, "Failed to configure internal GPS"); + } + } + } + }); + return true; +} diff --git a/Boards/LilygoTdeckPro/Source/LilygoTdeck.cpp b/Boards/LilygoTdeckPro/Source/LilygoTdeck.cpp new file mode 100644 index 00000000..d88e4054 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/LilygoTdeck.cpp @@ -0,0 +1,108 @@ +#include "Tactility/lvgl/LvglSync.h" +#include "hal/TdeckDisplay.h" +#include "hal/TdeckDisplayConstants.h" +#include "hal/TdeckKeyboard.h" +#include "hal/TdeckPower.h" +#include "hal/TdeckSdCard.h" + +#include + +#define TDECK_SPI_TRANSFER_SIZE_LIMIT (TDECK_LCD_HORIZONTAL_RESOLUTION * TDECK_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8)) + +bool tdeckInit(); + +using namespace tt::hal; + +extern const Configuration lilygo_tdeck = { + .initBoot = tdeckInit, + .createDisplay = createDisplay, + .createKeyboard = createKeyboard, + .sdcard = createTdeckSdCard(), + .power = tdeck_get_power, + .i2c = { + i2c::Configuration { + .name = "Internal", + .port = I2C_NUM_0, + .initMode = i2c::InitMode::ByTactility, + .isMutable = false, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_18, + .scl_io_num = GPIO_NUM_8, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + }, + i2c::Configuration { + .name = "External", + .port = I2C_NUM_1, + .initMode = i2c::InitMode::Disabled, + .isMutable = true, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_43, + .scl_io_num = GPIO_NUM_44, + .sda_pullup_en = false, + .scl_pullup_en = false, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + }, + .spi { + spi::Configuration { + .device = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_41, + .miso_io_num = GPIO_NUM_38, + .sclk_io_num = GPIO_NUM_40, + .quadwp_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .quadhd_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .data4_io_num = GPIO_NUM_NC, + .data5_io_num = GPIO_NUM_NC, + .data6_io_num = GPIO_NUM_NC, + .data7_io_num = GPIO_NUM_NC, + .data_io_default_level = false, + .max_transfer_sz = TDECK_SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 + }, + .initMode = spi::InitMode::ByTactility, + .isMutable = false, + .lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display + } + }, + .uart { + uart::Configuration { + .name = "Grove", + .port = UART_NUM_1, + .rxPin = GPIO_NUM_44, + .txPin = GPIO_NUM_43, + .rtsPin = GPIO_NUM_NC, + .ctsPin = GPIO_NUM_NC, + .rxBufferSize = 1024, + .txBufferSize = 1024, + .config = { + .baud_rate = 38400, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0, + .source_clk = UART_SCLK_DEFAULT, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + } + } + } + } +}; diff --git a/Boards/LilygoTdeckPro/Source/LilygoTdeck.h b/Boards/LilygoTdeckPro/Source/LilygoTdeck.h new file mode 100644 index 00000000..5edc402d --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/LilygoTdeck.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration lilygo_tdeck; diff --git a/Boards/LilygoTdeckPro/Source/hal/GxEPD2.h b/Boards/LilygoTdeckPro/Source/hal/GxEPD2.h new file mode 100644 index 00000000..3c18dec0 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/GxEPD2.h @@ -0,0 +1,114 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/ +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#pragma once + +// color definitions for GxEPD, GxEPD2 and GxEPD_HD, values correspond to RGB565 values for TFTs +#define GxEPD_BLACK 0x0000 +#define GxEPD_WHITE 0xFFFF +// some controllers for b/w EPDs support grey levels +#define GxEPD_DARKGREY 0x7BEF // 128, 128, 128 +#define GxEPD_LIGHTGREY 0xC618 // 192, 192, 192 +// values for 3-color or 7-color EPDs +#define GxEPD_RED 0xF800 // 255, 0, 0 +#define GxEPD_YELLOW 0xFFE0 // 255, 255, 0 !!no longer same as GxEPD_RED!! +#define GxEPD_COLORED GxEPD_RED +// values for 7-color EPDs only +#define GxEPD_BLUE 0x001F // 0, 0, 255 +#define GxEPD_GREEN 0x07E0 // 0, 255, 0 +#define GxEPD_ORANGE 0xFC00 // 255, 128, 0 + +class GxEPD2 +{ + public: + enum Panel + { + GDEW0102T4, Waveshare_1_02_bw = GDEW0102T4, + GDEP015OC1, Waveshare_1_54_bw = GDEP015OC1, + DEPG0150BN, + GDEH0154D67, Waveshare_1_54_bw_D67 = GDEH0154D67, + GDEW0154T8, + GDEW0154M09, + GDEW0154M10, + GDEY0154D67, + GDE0213B1, Waveshare_2_13_bw = GDE0213B1, + GDEH0213B72, Waveshare_2_13_bw_B72 = GDEH0213B72, + GDEH0213B73, Waveshare_2_13_bw_B73 = GDEH0213B73, + GDEM0213B74, + GDEW0213I5F, Waveshare_2_13_flex = GDEW0213I5F, + GDEW0213M21, + GDEW0213T5D, + DEPG0213BN, + GDEY0213B74, + GDEW026T0, Waveshare_2_6_bw = GDEW026T0, + GDEW026M01, + DEPG0266BN, + GDEY0266T90, + GDEH029A1, Waveshare_2_9_bw = GDEH029A1, + GDEW029T5, Waveshare_2_9_bw_T5 = GDEW029T5, + GDEW029T5D, + GDEW029I6FD, + GDEW029M06, + GDEM029T94, + GDEY029T94, + DEPG0290BS, + GDEW027W3, Waveshare_2_7_bw = GDEW027W3, + GDEY027T91, + GDEQ031T10, + ED037TC1, + GDEW0371W7, Waveshare_3_7_bw = GDEW0371W7, + GDEW042T2, Waveshare_4_2_bw = GDEW042T2, + GDEW042M01, + GDEY042T81, + GDEQ0426T82, + GDEW0583T7, Waveshare_5_83_bw = GDEW0583T7, + GDEW0583T8, + GDEQ0583T31, + GDEW075T8, Waveshare_7_5_bw = GDEW075T8, + GDEW075T7, Waveshare_7_5_bw_T7 = GDEW075T7, + GDEY075T7, + GDEH116T91, + GDEW1248T3, Waveshare_12_24_bw = GDEW1248T3, + ED060SCT, // on Waveshare IT8951 Driver HAT + ED060KC1, // on Waveshare IT8951 Driver HAT 1448x1072 + ED078KC2, // on Waveshare IT8951 Driver HAT 1872x1404 + ES103TC1, // on Waveshare IT8951 Driver HAT 1872x1404 + // 3-color + GDEW0154Z04, Waveshare_1_54_bwr = GDEW0154Z04, + GDEH0154Z90, Waveshare_1_54_bwr_Z90 = GDEH0154Z90, + GDEW0213Z16, Waveshare_2_13_bwr = GDEW0213Z16, + GDEW0213Z19, + GDEY0213Z98, + GDEW029Z10, Waveshare_2_9_bwr = GDEW029Z10, + GDEH029Z13, + GDEM029C90, + GDEY0266Z90, Waveshare_2_66_bwr = GDEY0266Z90, + GDEW027C44, Waveshare_2_7_bwr = GDEW027C44, + GDEW042Z15, Waveshare_4_2_bwr = GDEW042Z15, + GDEQ042Z21, Waveshare_4_2_V2_bwr = GDEQ042Z21, + GDEW0583Z21, Waveshare_5_83_bwr = GDEW0583Z21, + GDEW0583Z83, + GDEW075Z09, Waveshare_7_5_bwr = GDEW075Z09, + GDEW075Z08, Waveshare_7_5_bwr_Z08 = GDEW075Z08, + GDEH075Z90, Waveshare_7_5_bwr_Z90 = GDEH075Z90, + GDEY1248Z51, + // 4-color + GDEY0266F51H, + GDEY029F51H, + Waveshare3inch4color, + GDEY0420F51, + Waveshare437inch4color, + // 7-color + ACeP565, Waveshare_5_65_7c = ACeP565, + GDEY073D46, + ACeP730, Waveshare_7_30_7c = ACeP730 + }; +}; diff --git a/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.cpp b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.cpp new file mode 100644 index 00000000..84d6f60c --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.cpp @@ -0,0 +1,297 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// based on Demo Example from Good Display: https://www.good-display.com/product/426.html +// Panel: GDEQ031T10 : https://www.good-display.com/product/426.html +// Controller: UC8253 : https://v4.cecdn.yun300.cn/100001_1909185148/UC8253.pdf +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#include "GxEPD2_310_GDEQ031T10.h" +#include + +constexpr uint32_t LOW = 0; +constexpr uint32_t HIGH = 1; + +GxEPD2_310_GDEQ031T10::GxEPD2_310_GDEQ031T10(int16_t cs, int16_t dc, int16_t rst, int16_t busy) : + GxEPD2_EPD(cs, dc, rst, busy, LOW, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate) {} + +void GxEPD2_310_GDEQ031T10::clearScreen(uint8_t value) { + // full refresh needed for all cases (previous != screen) + _writeScreenBuffer(0x10, value); // set previous + _writeScreenBuffer(0x13, value); // set current + refresh(false); // full refresh + _initial_write = false; +} + +void GxEPD2_310_GDEQ031T10::writeScreenBuffer(uint8_t value) { + if (_initial_write) return clearScreen(value); + _writeScreenBuffer(0x13, value); // set current +} + +void GxEPD2_310_GDEQ031T10::writeScreenBufferAgain(uint8_t value) { + _writeScreenBuffer(0x10, value); // set previous + //_writeScreenBuffer(0x13, value); // set current, not needed +} + +void GxEPD2_310_GDEQ031T10::_writeScreenBuffer(uint8_t command, uint8_t value) { + if (!_init_display_done) _InitDisplay(); + _writeCommand(command); + _startTransfer(); + for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) { _transfer(value); } + _endTransfer(); +} + +void GxEPD2_310_GDEQ031T10::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); } + +void GxEPD2_310_GDEQ031T10::writeImageForFullRefresh(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + _writeImage(0x10, bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); // set current +} + +void GxEPD2_310_GDEQ031T10::writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + _writeImage(0x10, bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + //_writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); // set current, not needed +} + +void GxEPD2_310_GDEQ031T10::_writeImage(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + tt::kernel::delayMillis(1); // WDT hack + uint16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit + int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit + int16_t dx = x1 - x; + int16_t dy = y1 - y; + w1 -= dx; + h1 -= dy; + if ((w1 <= 0) || (h1 <= 0)) return; + if (!_init_display_done) _InitDisplay(); + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean + _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _writeCommand(command); + _startTransfer(); + for (int16_t i = 0; i < h1; i++) { + for (int16_t j = 0; j < w1 / 8; j++) { + uint8_t data; + // use wb, h of bitmap for index! + uint16_t idx = mirror_y ? j + dx / 8 + uint16_t((h - 1 - (i + dy))) * wb : j + dx / 8 + uint16_t(i + dy) * wb; + if (pgm) { +#if defined(__AVR) || defined(ESP8266) || defined(ESP32) + data = pgm_read_byte(&bitmap[idx]); +#else + data = bitmap[idx]; +#endif + } else { data = bitmap[idx]; } + if (invert) data = ~data; + _transfer(data); + } + } + _endTransfer(); + _writeCommand(0x92); // partial out + tt::kernel::delayMillis(1); // WDT hack +} + +void GxEPD2_310_GDEQ031T10::writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { _writeImagePart(0x13, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); } + +void GxEPD2_310_GDEQ031T10::writeImagePartAgain(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + _writeImagePart(0x10, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + //_writeImagePart(0x13, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); // set current, not needed +} + +void GxEPD2_310_GDEQ031T10::_writeImagePart(uint8_t command, const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + tt::kernel::delayMillis(1); // WDT hack + if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return; + if ((x_part < 0) || (x_part >= w_bitmap)) return; + if ((y_part < 0) || (y_part >= h_bitmap)) return; + uint16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded + x_part -= x_part % 8; // byte boundary + w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit + h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit + x -= x % 8; // byte boundary + w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit + int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit + int16_t dx = x1 - x; + int16_t dy = y1 - y; + w1 -= dx; + h1 -= dy; + if ((w1 <= 0) || (h1 <= 0)) return; + if (!_init_display_done) _InitDisplay(); + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean + _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _writeCommand(command); + _startTransfer(); + for (int16_t i = 0; i < h1; i++) { + for (int16_t j = 0; j < w1 / 8; j++) { + uint8_t data; + // use wb_bitmap, h_bitmap of bitmap for index! + uint16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + uint16_t((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + uint16_t(y_part + i + dy) * wb_bitmap; + if (pgm) { +#if defined(__AVR) || defined(ESP8266) || defined(ESP32) + data = pgm_read_byte(&bitmap[idx]); +#else + data = bitmap[idx]; +#endif + } else { data = bitmap[idx]; } + if (invert) data = ~data; + _transfer(data); + } + } + _endTransfer(); + _writeCommand(0x92); // partial out + tt::kernel::delayMillis(1); // WDT hack +} + +void GxEPD2_310_GDEQ031T10::writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (black) { writeImage(black, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (black) { writeImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (data1) { writeImage(data1, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + refresh(x, y, w, h); + writeImageAgain(bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_310_GDEQ031T10::drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + refresh(x, y, w, h); + writeImagePartAgain(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_310_GDEQ031T10::drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (black) { drawImage(black, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (black) { drawImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { if (data1) { drawImage(data1, x, y, w, h, invert, mirror_y, pgm); } } + +void GxEPD2_310_GDEQ031T10::refresh(bool partial_update_mode) { + if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT); + else { + _Update_Full(); + _initial_refresh = false; // initial full update done + } +} + +void GxEPD2_310_GDEQ031T10::refresh(int16_t x, int16_t y, int16_t w, int16_t h) { + if (_initial_refresh) return refresh(false); // initial update needs be full update + // intersection with screen + int16_t w1 = x < 0 ? w + x : w; // reduce + int16_t h1 = y < 0 ? h + y : h; // reduce + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit + h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit + if ((w1 <= 0) || (h1 <= 0)) return; + // make x1, w1 multiple of 8 + w1 += x1 % 8; + if (w1 % 8 > 0) w1 += 8 - w1 % 8; + x1 -= x1 % 8; + if (usePartialUpdateWindow) _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _Update_Part(); + if (usePartialUpdateWindow) _writeCommand(0x92); // partial out +} + +void GxEPD2_310_GDEQ031T10::powerOff(void) { _PowerOff(); } + +void GxEPD2_310_GDEQ031T10::hibernate() { + _PowerOff(); + if (_rst >= 0) { + _writeCommand(0x07); // deep sleep + _writeData(0xA5); // check code + _hibernating = true; + _init_display_done = false; + } +} + +void GxEPD2_310_GDEQ031T10::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + uint16_t xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte) + uint16_t ye = y + h - 1; + x &= 0xFFF8; // byte boundary + _writeCommand(0x90); // partial window + _writeData(x); + _writeData(xe); + _writeData(y / 256); + _writeData(y % 256); + _writeData(ye / 256); + _writeData(ye % 256); + _writeData(0x01); +} + +void GxEPD2_310_GDEQ031T10::_PowerOn() { + if (!_power_is_on) { + _writeCommand(0x04); + _waitWhileBusy("_PowerOn", power_on_time); + } + _power_is_on = true; +} + +void GxEPD2_310_GDEQ031T10::_PowerOff() { + if (_power_is_on) { + _writeCommand(0x02); // power off + _waitWhileBusy("_PowerOff", power_off_time); + } + _power_is_on = false; +} + +void GxEPD2_310_GDEQ031T10::_InitDisplay() { + _writeCommand(0x00); // PANEL SETTING + _writeData(0x1e); // soft reset + _writeData(0x0d); + tt::kernel::delayMillis(1); + _power_is_on = false; + _writeCommand(0x00); // PANEL SETTING + _writeData(0x1f); // KW: 3f, KWR: 2F, BWROTP: 0f, BWOTP: 1f + _writeData(0x0d); + _init_display_done = true; +} + +void GxEPD2_310_GDEQ031T10::_Update_Full() { + if (useFastFullUpdate) { + _writeCommand(0xE0); // Cascade Setting (CCSET) + _writeData(0x02); // TSFIX + _writeCommand(0xE5); // Force Temperature (TSSET) + _writeData(0x5A); // 90, 1015000us + //_writeData(0x6E); // 110, 1542001 + } + _writeCommand(0x50); + _writeData(0x97); + _PowerOn(); + _writeCommand(0x12); //display refresh + _waitWhileBusy("_Update_Full", full_refresh_time); + _init_display_done = false; // needed, reason unknown +} + +void GxEPD2_310_GDEQ031T10::_Update_Part() { + if (hasFastPartialUpdate) { + _writeCommand(0xE0); // Cascade Setting (CCSET) + _writeData(0x02); // TSFIX + _writeCommand(0xE5); // Force Temperature (TSSET) + _writeData(0x79); // 121 + } + _writeCommand(0x50); + _writeData(0xD7); + _PowerOn(); + _writeCommand(0x12); //display refresh + _waitWhileBusy("_Update_Part", partial_refresh_time); + _init_display_done = false; // needed, reason unknown +} diff --git a/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.h b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.h new file mode 100644 index 00000000..438f9460 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_310_GDEQ031T10.h @@ -0,0 +1,100 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// based on Demo Example from Good Display: https://www.good-display.com/product/426.html +// Panel: GDEQ031T10 : https://www.good-display.com/product/426.html +// Controller: UC8253 : https://v4.cecdn.yun300.cn/100001_1909185148/UC8253.pdf +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#pragma once + +#include "GxEPD2_EPD.h" + +class GxEPD2_310_GDEQ031T10 : public GxEPD2_EPD +{ +public: + // attributes + static const uint16_t WIDTH = 240; + static const uint16_t WIDTH_VISIBLE = WIDTH; + static const uint16_t HEIGHT = 320; + static const GxEPD2::Panel panel = GxEPD2::GDEQ031T10; + static const bool hasColor = false; + static const bool hasPartialUpdate = true; + static const bool usePartialUpdateWindow = true; // set false for better image + static const bool hasFastPartialUpdate = true; // set this false to force full refresh always + static const bool useFastFullUpdate = true; + // set false for extended (low) temperature range, 1015000us vs 3082001us + static const uint16_t power_on_time = 50; // ms, e.g. 45000us + static const uint16_t power_off_time = 50; // ms, e.g. 45000us + static const uint16_t full_refresh_time = 1100; // ms, e.g. 1015000us + static const uint16_t partial_refresh_time = 700; // ms, e.g. 650000us + // constructor + GxEPD2_310_GDEQ031T10(int16_t cs, int16_t dc, int16_t rst, int16_t busy); + // methods (virtual) + // Support for Bitmaps (Sprites) to Controller Buffer and to Screen + void clearScreen(uint8_t value = 0xFF); // init controller memory and screen (default white) + void writeScreenBuffer(uint8_t value = 0xFF); // init controller memory (default white) + void writeScreenBufferAgain(uint8_t value = 0xFF); // init previous buffer controller memory (default white) + // write to controller memory, without screen refresh; x and w should be multiple of 8 + void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, + bool mirror_y = false, bool pgm = false); + void writeImageForFullRefresh(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, + int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + // for differential update: set current and previous buffers equal (for fast partial update to work correctly) + void writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, + bool mirror_y = false, bool pgm = false); + void writeImagePartAgain(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + // write sprite of native data to controller memory, without screen refresh; x and w should be multiple of 8 + void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + // write to controller memory, with screen refresh; x and w should be multiple of 8 + void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, + bool mirror_y = false, bool pgm = false); + void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, + int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + // write sprite of native data to controller memory, with screen refresh; x and w should be multiple of 8 + void drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + void refresh(bool partial_update_mode = false); // screen refresh from controller memory to full screen + void refresh(int16_t x, int16_t y, int16_t w, int16_t h); // screen refresh from controller memory, partial screen + void powerOff(); // turns off generation of panel driving voltages, avoids screen fading over time + void hibernate(); + // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0) +private: + void _writeScreenBuffer(uint8_t command, uint8_t value); + void _writeImage(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false); + void _writeImagePart(uint8_t command, const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, + int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false); + void _setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void _PowerOn(); + void _PowerOff(); + void _InitDisplay(); + void _Update_Full(); + void _Update_Part(); +}; diff --git a/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.cpp b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.cpp new file mode 100644 index 00000000..cf155c4d --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.cpp @@ -0,0 +1,161 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/ +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#include "GxEPD2_EPD.h" + +#include +#include + +constexpr auto* TAG = "GxEPD2"; + +GxEPD2_EPD::GxEPD2_EPD(int16_t cs, int16_t dc, int16_t rst, int16_t busy, int16_t busy_level, uint32_t busy_timeout, + uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu) : + WIDTH(w), HEIGHT(h), panel(p), hasColor(c), hasPartialUpdate(pu), hasFastPartialUpdate(fpu), + _cs(cs), _dc(dc), _rst(rst), _busy(busy), _busy_level(busy_level), _busy_timeout(busy_timeout) { + _initial_write = true; + _initial_refresh = true; + _power_is_on = false; + _using_partial_mode = false; + _hibernating = false; + _init_display_done = false; + _busy_callback = 0; + _busy_callback_parameter = 0; +} + +void GxEPD2_EPD::init() { init(true, 10, false); } + +void GxEPD2_EPD::init(bool initial, uint16_t reset_duration, bool pulldown_rst_mode) { + _initial_write = initial; + _initial_refresh = initial; + _pulldown_rst_mode = pulldown_rst_mode; + _power_is_on = false; + _using_partial_mode = false; + _hibernating = false; + _init_display_done = false; +} + +void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* busy_callback_parameter) { + _busy_callback = busyCallback; + _busy_callback_parameter = busy_callback_parameter; +} + +void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) { + if (_busy >= 0) { + tt::kernel::delayMillis(1); // add some margin to become active + unsigned long start = tt::kernel::getMicros(); + while (1) { + if (digitalRead(_busy) != _busy_level) break; + if (_busy_callback) _busy_callback(_busy_callback_parameter); + else tt::kernel::delayMillis(1); + if (digitalRead(_busy) != _busy_level) break; + if (tt::kernel::getMicros() - start > _busy_timeout) { + TT_LOG_W(TAG, "Busy timeout"); + break; + } + vPortYield(); // avoid wdt + } + (void)start; + } else tt::kernel::delayMillis(busy_time); +} + +void GxEPD2_EPD::_writeCommand(uint8_t c) { + _pSPIx->beginTransaction(_spi_settings); + if (_dc >= 0) digitalWrite(_dc, LOW); + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(c); + if (_cs >= 0) digitalWrite(_cs, HIGH); + if (_dc >= 0) digitalWrite(_dc, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeData(uint8_t d) { + _pSPIx->beginTransaction(_spi_settings); + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(d); + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeData(const uint8_t* data, uint16_t n) { + _pSPIx->beginTransaction(_spi_settings); + if (_cs >= 0) digitalWrite(_cs, LOW); + for (uint16_t i = 0; i < n; i++) { _pSPIx->transfer(*data++); } + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeDataPGM(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes) { + _pSPIx->beginTransaction(_spi_settings); + if (_cs >= 0) digitalWrite(_cs, LOW); + for (uint16_t i = 0; i < n; i++) { _pSPIx->transfer(pgm_read_byte(&*data++)); } + while (fill_with_zeroes > 0) { + _pSPIx->transfer(0x00); + fill_with_zeroes--; + } + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeDataPGM_sCS(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes) { + _pSPIx->beginTransaction(_spi_settings); + for (uint8_t i = 0; i < n; i++) { + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(pgm_read_byte(&*data++)); + if (_cs >= 0) digitalWrite(_cs, HIGH); + } + while (fill_with_zeroes > 0) { + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(0x00); + fill_with_zeroes--; + if (_cs >= 0) digitalWrite(_cs, HIGH); + } + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeCommandData(const uint8_t* pCommandData, uint8_t datalen) { + _pSPIx->beginTransaction(_spi_settings); + if (_dc >= 0) digitalWrite(_dc, LOW); + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(*pCommandData++); + if (_dc >= 0) digitalWrite(_dc, HIGH); + for (uint8_t i = 0; i < datalen - 1; i++) // sub the command + { + _pSPIx->transfer(*pCommandData++); + } + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_writeCommandDataPGM(const uint8_t* pCommandData, uint8_t datalen) { + _pSPIx->beginTransaction(_spi_settings); + if (_dc >= 0) digitalWrite(_dc, LOW); + if (_cs >= 0) digitalWrite(_cs, LOW); + _pSPIx->transfer(pgm_read_byte(&*pCommandData++)); + if (_dc >= 0) digitalWrite(_dc, HIGH); + for (uint8_t i = 0; i < datalen - 1; i++) // sub the command + { + _pSPIx->transfer(pgm_read_byte(&*pCommandData++)); + } + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} + +void GxEPD2_EPD::_startTransfer() { + _pSPIx->beginTransaction(_spi_settings); + if (_cs >= 0) digitalWrite(_cs, LOW); +} + +void GxEPD2_EPD::_transfer(uint8_t value) { _pSPIx->transfer(value); } + +void GxEPD2_EPD::_endTransfer() { + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} \ No newline at end of file diff --git a/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.h b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.h new file mode 100644 index 00000000..fc759954 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/GxEPD2_EPD.h @@ -0,0 +1,143 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/ +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#pragma once + +#include "GxEPD2.h" +#include + +#pragma GCC diagnostic ignored "-Wunused-parameter" + +class GxEPD2_EPD +{ +public: + // attributes + const uint16_t WIDTH; + const uint16_t HEIGHT; + const GxEPD2::Panel panel; + const bool hasColor; + const bool hasPartialUpdate; + const bool hasFastPartialUpdate; + // constructor + GxEPD2_EPD(int16_t cs, int16_t dc, int16_t rst, int16_t busy, int16_t busy_level, uint32_t busy_timeout, + uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu); + virtual void init(); + virtual void init(bool initial, uint16_t reset_duration = 10, bool pulldown_rst_mode = false); + // Support for Bitmaps (Sprites) to Controller Buffer and to Screen + virtual void clearScreen(uint8_t value) = 0; // init controller memory and screen (default white) + virtual void writeScreenBuffer(uint8_t value) = 0; // init controller memory (default white) + // write to controller memory, without screen refresh; x and w should be multiple of 8 + virtual void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, + bool mirror_y = false, bool pgm = false) = 0; + + virtual void writeImageForFullRefresh(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false) + { + // writeImage is independent from refresh mode for most controllers, exception e.g. SSD1681 + writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + } + + virtual void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, + int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, + bool pgm = false) = 0; + // virtual void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // virtual void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + // int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // write sprite of native data to controller memory, without screen refresh; x and w should be multiple of 8 + // virtual void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // for differential update: set current and previous buffers equal (for fast partial update to work correctly) + virtual void writeScreenBufferAgain(uint8_t value = 0xFF) // init controller memory (default white) + { + // most controllers with differential update do switch buffers on refresh, can use: + writeScreenBuffer(value); + } + + virtual void writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, + bool invert = false, bool mirror_y = false, bool pgm = false) + { + // most controllers with differential update do switch buffers on refresh, can use: + writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + } + + virtual void writeImagePartAgain(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, + int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, + bool mirror_y = false, bool pgm = false) + { + // most controllers with differential update do switch buffers on refresh, can use: + writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } + + // write to controller memory, with screen refresh; x and w should be multiple of 8 + // virtual void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // virtual void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + // int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // virtual void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // virtual void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + // int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // write sprite of native data to controller memory, with screen refresh; x and w should be multiple of 8 + // virtual void drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0; + // a demo bitmap can use yet another bitmap format, e.g. 7-color bitmap from Good Display for GDEY073D46 + virtual void writeDemoBitmap(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, + int16_t mode = 0, bool mirror_y = false, bool pgm = false) + { + }; + + virtual void drawDemoBitmap(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, + int16_t mode = 0, bool mirror_y = false, bool pgm = false) + { + }; + virtual void refresh(bool partial_update_mode = false) = 0; // screen refresh from controller memory to full screen + virtual void refresh(int16_t x, int16_t y, int16_t w, int16_t h) = 0; + // screen refresh from controller memory, partial screen + virtual void powerOff() = 0; // turns off generation of panel driving voltages, avoids screen fading over time + virtual void hibernate() = 0; + // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0) + virtual void setPaged() + { + }; // for GxEPD2_154c paged workaround + // register a callback function to be called during _waitWhileBusy continuously. + void setBusyCallback(void (*busyCallback)(const void*), const void* busy_callback_parameter = 0); + + static inline uint16_t gx_uint16_min(uint16_t a, uint16_t b) + { + return (a < b ? a : b); + }; + + static inline uint16_t gx_uint16_max(uint16_t a, uint16_t b) + { + return (a > b ? a : b); + }; + +protected: + void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000); + void _writeCommand(uint8_t c); + void _writeData(uint8_t d); + void _writeData(const uint8_t* data, uint16_t n); + void _writeDataPGM(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes = 0); + void _writeDataPGM_sCS(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes = 0); + void _writeCommandData(const uint8_t* pCommandData, uint8_t datalen); + void _writeCommandDataPGM(const uint8_t* pCommandData, uint8_t datalen); + void _startTransfer(); + void _transfer(uint8_t value); + void _endTransfer(); + +protected: + int16_t _cs, _dc, _rst, _busy, _busy_level; + uint32_t _busy_timeout; + bool _pulldown_rst_mode; + bool _initial_write, _initial_refresh; + bool _power_is_on, _using_partial_mode, _hibernating; + bool _init_display_done; + void (*_busy_callback)(const void*); + const void* _busy_callback_parameter; +}; diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.cpp b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.cpp new file mode 100644 index 00000000..01175a21 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.cpp @@ -0,0 +1,45 @@ +#include "TdeckDisplay.h" +#include "TdeckDisplayConstants.h" + +#include +#include +#include + +#include + +#define TAG "tdeck_display" + +static std::shared_ptr createTouch() { + // Note for future changes: Reset pin is 48 and interrupt pin is 47 + auto configuration = std::make_unique( + I2C_NUM_0, + 240, + 320, + true, + true, + false + ); + + return std::make_shared(std::move(configuration)); +} + +std::shared_ptr createDisplay() { + auto touch = createTouch(); + + auto configuration = std::make_unique( + TDECK_LCD_SPI_HOST, + TDECK_LCD_PIN_CS, + TDECK_LCD_PIN_DC, + 320, + 240, + touch, + true, + true, + false, + true + ); + + configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; + + return std::make_shared(std::move(configuration)); +} diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.h b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.h new file mode 100644 index 00000000..e9a26ec4 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplay.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" +#include +#include + +class TdeckDisplay : public tt::hal::display::DisplayDevice { + +private: + + esp_lcd_panel_io_handle_t ioHandle = nullptr; + esp_lcd_panel_handle_t panelHandle = nullptr; + lv_display_t* displayHandle = nullptr; + bool poweredOn = false; + +public: + + std::string getName() const final { return "ST7789"; } + std::string getDescription() const final { return "SPI display"; } + + bool start() override; + + bool stop() override; + + void setPowerOn(bool turnOn) override; + bool isPoweredOn() const override { return poweredOn; }; + bool supportsPowerControl() const override { return true; } + + std::shared_ptr _Nullable createTouch() override; + + void setBacklightDuty(uint8_t backlightDuty) override; + bool supportsBacklightDuty() const override { return true; } + + void setGammaCurve(uint8_t index) override; + uint8_t getGammaCurveCount() const override { return 4; }; + + lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } +}; + +std::shared_ptr createDisplay(); diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckDisplayConstants.h b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplayConstants.h new file mode 100644 index 00000000..f0dbb645 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckDisplayConstants.h @@ -0,0 +1,8 @@ +#pragma once + +#define TDECK_LCD_SPI_HOST SPI2_HOST +#define TDECK_LCD_PIN_CS GPIO_NUM_12 +#define TDECK_LCD_PIN_DC GPIO_NUM_11 // RS +#define TDECK_LCD_HORIZONTAL_RESOLUTION 320 +#define TDECK_LCD_VERTICAL_RESOLUTION 240 +#define TDECK_LCD_SPI_TRANSFER_HEIGHT (TDECK_LCD_VERTICAL_RESOLUTION / 10) diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.cpp b/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.cpp new file mode 100644 index 00000000..47c8590d --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.cpp @@ -0,0 +1,67 @@ +#include "TdeckKeyboard.h" +#include +#include + +#define TAG "tdeck_keyboard" + +#define TDECK_KEYBOARD_I2C_BUS_HANDLE I2C_NUM_0 +#define TDECK_KEYBOARD_SLAVE_ADDRESS 0x55 + +static inline bool keyboard_i2c_read(uint8_t* output) { + return tt::hal::i2c::masterRead(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, output, 1, 100 / portTICK_PERIOD_MS); +} + +/** + * The callback simulates press and release events, because the T-Deck + * keyboard only publishes press events on I2C. + * LVGL currently works without those extra release events, but they + * are implemented for correctness and future compatibility. + * + * @param indev_drv + * @param data + */ +static void keyboard_read_callback(TT_UNUSED lv_indev_t* indev, lv_indev_data_t* data) { + static uint8_t last_buffer = 0x00; + uint8_t read_buffer = 0x00; + + // Defaults + data->key = 0; + data->state = LV_INDEV_STATE_RELEASED; + + if (keyboard_i2c_read(&read_buffer)) { + if (read_buffer == 0 && read_buffer != last_buffer) { + TT_LOG_D(TAG, "Released %d", last_buffer); + data->key = last_buffer; + data->state = LV_INDEV_STATE_RELEASED; + } else if (read_buffer != 0) { + TT_LOG_D(TAG, "Pressed %d", read_buffer); + data->key = read_buffer; + data->state = LV_INDEV_STATE_PRESSED; + } + } + + last_buffer = read_buffer; +} + +bool TdeckKeyboard::start(lv_display_t* display) { + deviceHandle = lv_indev_create(); + lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(deviceHandle, &keyboard_read_callback); + lv_indev_set_display(deviceHandle, display); + lv_indev_set_user_data(deviceHandle, this); + return true; +} + +bool TdeckKeyboard::stop() { + lv_indev_delete(deviceHandle); + deviceHandle = nullptr; + return true; +} + +bool TdeckKeyboard::isAttached() const { + return tt::hal::i2c::masterHasDeviceAtAddress(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, 100); +} + +std::shared_ptr createKeyboard() { + return std::make_shared(); +} diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.h b/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.h new file mode 100644 index 00000000..26eea592 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckKeyboard.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +class TdeckKeyboard : public tt::hal::keyboard::KeyboardDevice { + +private: + + lv_indev_t* _Nullable deviceHandle = nullptr; + +public: + + std::string getName() const final { return "T-Deck Keyboard"; } + std::string getDescription() const final { return "I2C keyboard"; } + + bool start(lv_display_t* display) override; + bool stop() override; + bool isAttached() const override; + lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } +}; + +std::shared_ptr createKeyboard(); diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckPower.cpp b/Boards/LilygoTdeckPro/Source/hal/TdeckPower.cpp new file mode 100644 index 00000000..008198db --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckPower.cpp @@ -0,0 +1,130 @@ +#include "TdeckPower.h" + +#include + +#define TAG "power" + +/** + * 2.0 ratio, but +.11 added as display voltage sag compensation. + */ +#define ADC_MULTIPLIER 2.11 + +#define ADC_REF_VOLTAGE 3.3f +#define BATTERY_VOLTAGE_MIN 3.2f +#define BATTERY_VOLTAGE_MAX 4.2f + +static adc_oneshot_unit_init_cfg_t adcConfig = { + .unit_id = ADC_UNIT_1, + .clk_src = ADC_RTC_CLK_SRC_DEFAULT, + .ulp_mode = ADC_ULP_MODE_DISABLE, +}; + +static adc_oneshot_chan_cfg_t adcChannelConfig = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, +}; + +static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) { + float volts = std::min((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); + float voltage_percentage = (volts - BATTERY_VOLTAGE_MIN) / (BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN); + float voltage_factor = std::min(1.0f, voltage_percentage); + auto charge_level = (uint8_t) (voltage_factor * 100.f); + TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); + return charge_level; +} + +TdeckPower::TdeckPower() { + if (adc_oneshot_new_unit(&adcConfig, &adcHandle) != ESP_OK) { + TT_LOG_E(TAG, "ADC config failed"); + return; + } + + if (adc_oneshot_config_channel(adcHandle, ADC_CHANNEL_3, &adcChannelConfig) != ESP_OK) { + TT_LOG_E(TAG, "ADC channel config failed"); + + adc_oneshot_del_unit(adcHandle); + return; + } +} + +TdeckPower::~TdeckPower() { + if (adcHandle) { + adc_oneshot_del_unit(adcHandle); + } +} + +bool TdeckPower::supportsMetric(MetricType type) const { + switch (type) { + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + return true; + default: + return false; + } + + return false; // Safety guard for when new enum values are introduced +} + +bool TdeckPower::getMetric(MetricType type, MetricData& data) { + switch (type) { + using enum MetricType; + case BatteryVoltage: + return readBatteryVoltageSampled(data.valueAsUint32); + case ChargeLevel: + if (readBatteryVoltageSampled(data.valueAsUint32)) { + data.valueAsUint32 = estimateChargeLevelFromVoltage(data.valueAsUint32); + return true; + } else { + return false; + } + default: + return false; + } + + return false; // Safety guard for when new enum values are introduced +} + +bool TdeckPower::readBatteryVoltageOnce(uint32_t& output) { + int raw; + if (adc_oneshot_read(adcHandle, ADC_CHANNEL_3, &raw) == ESP_OK) { + output = ADC_MULTIPLIER * ((1000.f * ADC_REF_VOLTAGE) / 4096.f) * (float)raw; + TT_LOG_V(TAG, "Raw = %d, voltage = %lu", raw, output); + return true; + } else { + TT_LOG_E(TAG, "Read failed"); + return false; + } +} + +#define MAX_VOLTAGE_SAMPLES 15 + +bool TdeckPower::readBatteryVoltageSampled(uint32_t& output) { + size_t samples_read = 0; + uint32_t sample_accumulator = 0; + uint32_t sample_read_buffer; + + for (size_t i = 0; i < MAX_VOLTAGE_SAMPLES; ++i) { + if (readBatteryVoltageOnce(sample_read_buffer)) { + sample_accumulator += sample_read_buffer; + samples_read++; + } + } + + if (samples_read > 0) { + output = sample_accumulator / samples_read; + return true; + } else { + return false; + } +} + +static std::shared_ptr power; + +std::shared_ptr tdeck_get_power() { + if (power == nullptr) { + power = std::make_shared(); + } + return power; +} + diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckPower.h b/Boards/LilygoTdeckPro/Source/hal/TdeckPower.h new file mode 100644 index 00000000..4dc4d75c --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckPower.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Tactility/hal/power/PowerDevice.h" +#include +#include + +using tt::hal::power::PowerDevice; + +class TdeckPower : public PowerDevice { + + adc_oneshot_unit_handle_t adcHandle = nullptr; + +public: + + TdeckPower(); + ~TdeckPower(); + + std::string getName() const final { return "ADC Power Measurement"; } + std::string getDescription() const final { return "Power measurement interface via ADC pin"; } + + bool supportsMetric(MetricType type) const override; + bool getMetric(MetricType type, MetricData& data) override; + +private: + + bool readBatteryVoltageSampled(uint32_t& output); + bool readBatteryVoltageOnce(uint32_t& output); +}; + +std::shared_ptr tdeck_get_power(); diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.cpp b/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.cpp new file mode 100644 index 00000000..486d3d13 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.cpp @@ -0,0 +1,33 @@ +#include "TdeckSdCard.h" + +#include +#include + +#include + +using tt::hal::sdcard::SpiSdCardDevice; + +#define TDECK_SDCARD_PIN_CS GPIO_NUM_39 +#define TDECK_LCD_PIN_CS GPIO_NUM_12 +#define TDECK_RADIO_PIN_CS GPIO_NUM_9 + +std::shared_ptr createTdeckSdCard() { + auto* configuration = new SpiSdCardDevice::Config( + TDECK_SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCardDevice::MountBehaviour::AtBoot, + tt::lvgl::getSyncLock(), + { + TDECK_RADIO_PIN_CS, + TDECK_LCD_PIN_CS + } + ); + + auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( + std::unique_ptr(configuration) + ); + + return std::shared_ptr(sdcard); +} diff --git a/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.h b/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.h new file mode 100644 index 00000000..e12eb6d0 --- /dev/null +++ b/Boards/LilygoTdeckPro/Source/hal/TdeckSdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Tactility/hal/sdcard/SdCardDevice.h" + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createTdeckSdCard(); diff --git a/Buildscripts/board.cmake b/Buildscripts/board.cmake index 29afb107..ada9a64f 100644 --- a/Buildscripts/board.cmake +++ b/Buildscripts/board.cmake @@ -47,6 +47,10 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) set(TACTILITY_BOARD_PROJECT ElecrowCrowpanelBasic50) elseif (board_id STREQUAL "lilygo-tdeck") set(TACTILITY_BOARD_PROJECT LilygoTdeck) + elseif (board_id STREQUAL "lilygo-tdeck-plus") + set(TACTILITY_BOARD_PROJECT LilygoTdeck) + elseif (board_id STREQUAL "lilygo-tdeck-pro") + set(TACTILITY_BOARD_PROJECT LilygoTdeckPro) elseif (board_id STREQUAL "m5stack-core2") set(TACTILITY_BOARD_PROJECT M5stackCore2) elseif (board_id STREQUAL "m5stack-cores3") diff --git a/ExternalApps/HelloWorld/.gitignore b/ExternalApps/HelloWorld/.gitignore index 89baa26e..31095bb4 100644 --- a/ExternalApps/HelloWorld/.gitignore +++ b/ExternalApps/HelloWorld/.gitignore @@ -1,2 +1,2 @@ build*/ -.tactility/ +.tactility/ \ No newline at end of file diff --git a/sdkconfig.board.lilygo-tdeck-plus b/sdkconfig.board.lilygo-tdeck-plus new file mode 100644 index 00000000..0b3fc464 --- /dev/null +++ b/sdkconfig.board.lilygo-tdeck-plus @@ -0,0 +1,53 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_USE_FS_STDIO=y +CONFIG_LV_FS_STDIO_LETTER=65 +CONFIG_LV_FS_STDIO_PATH="" +CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_BUILTIN_MALLOC=n +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_MSGBOX=n +CONFIG_LV_USE_SPINNER=n +CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_FREERTOS_SMP=n +CONFIG_FREERTOS_UNICORE=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 + +# Hardware: Main +CONFIG_TT_BOARD_LILYGO_TDECK_PLUS=y +CONFIG_TT_BOARD_NAME="LilyGo T-Deck Plus" +CONFIG_TT_BOARD_ID="lilygo-tdeck-plus" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved) +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +# LVGL +CONFIG_LV_DPI_DEF=139 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +# USB +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard" \ No newline at end of file diff --git a/sdkconfig.board.lilygo-tdeck-pro b/sdkconfig.board.lilygo-tdeck-pro new file mode 100644 index 00000000..ac0b17c7 --- /dev/null +++ b/sdkconfig.board.lilygo-tdeck-pro @@ -0,0 +1,57 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_USE_FS_STDIO=y +CONFIG_LV_FS_STDIO_LETTER=65 +CONFIG_LV_FS_STDIO_PATH="" +CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_BUILTIN_MALLOC=n +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_MSGBOX=n +CONFIG_LV_USE_SPINNER=n +CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_FREERTOS_SMP=n +CONFIG_FREERTOS_UNICORE=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 + +# Hardware: Main +CONFIG_TT_BOARD_LILYGO_TDECK_PRO=y +CONFIG_TT_BOARD_NAME="LilyGo T-Deck Pro" +CONFIG_TT_BOARD_ID="lilygo-tdeck-pro" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved) +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +# LVGL +CONFIG_LV_DPI_DEF=139 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_USE_THEME_DEFAULT=n +CONFIG_LV_USE_THEME_SIMPLE=n +CONFIG_LV_USE_THEME_MONO=y +CONFIG_LV_COLOR_DEPTH=1 +# USB +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"