From 281744f2d9ba1cae57cd0bdb00b1cd393fdf8c62 Mon Sep 17 00:00:00 2001 From: mverch67 Date: Fri, 18 Apr 2025 19:23:51 +0200 Subject: [PATCH 01/10] preliminary io pin definitions --- boards/t5-epaper-s3.json | 38 ++++++++ src/graphics/niche/InkHUD/Applet.h | 1 + variants/t5s3_epaper/nicheGraphics.h | 135 +++++++++++++++++++++++++++ variants/t5s3_epaper/pins_arduino.h | 27 ++++++ variants/t5s3_epaper/platformio.ini | 29 ++++++ variants/t5s3_epaper/variant.h | 49 ++++++++++ 6 files changed, 279 insertions(+) create mode 100644 boards/t5-epaper-s3.json create mode 100644 variants/t5s3_epaper/nicheGraphics.h create mode 100644 variants/t5s3_epaper/pins_arduino.h create mode 100644 variants/t5s3_epaper/platformio.ini create mode 100644 variants/t5s3_epaper/variant.h diff --git a/boards/t5-epaper-s3.json b/boards/t5-epaper-s3.json new file mode 100644 index 000000000..628715af7 --- /dev/null +++ b/boards/t5-epaper-s3.json @@ -0,0 +1,38 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_RUNNING_CORE=0", + "-DARDUINO_EVENT_RUNNING_CORE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "LilyGo T5-ePaper-S3", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.lilygo.cc/products/t5-4-7-inch-e-paper-v2-3", + "vendor": "LILYGO" +} diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index 802186e6e..798cc88cb 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -15,6 +15,7 @@ #include // GFXRoot drawing lib +#include "mesh/MeshModule.h" #include "mesh/MeshTypes.h" #include "./AppletFont.h" diff --git a/variants/t5s3_epaper/nicheGraphics.h b/variants/t5s3_epaper/nicheGraphics.h new file mode 100644 index 000000000..baadc3d28 --- /dev/null +++ b/variants/t5s3_epaper/nicheGraphics.h @@ -0,0 +1,135 @@ +/* + +Most of the Meshtastic firmware uses preprocessor macros throughout the code to support different hardware variants. +NicheGraphics attempts a different approach: + +Per-device config takes place in this setupNicheGraphics() method +(And a small amount in platformio.ini) + +This file sets up InkHUD for Heltec VM-E290. +Different NicheGraphics UIs and different hardware variants will each have their own setup procedure. + +*/ + +#pragma once + +#include "configuration.h" +#include "mesh/MeshModule.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +// InkHUD-specific components +// --------------------------- +// #include "graphics/niche/InkHUD/InkHUD.h" +#include "graphics/niche/InkHUD/WindowManager.h" + +// Applets +#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" +#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" +#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" +#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" +#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h" + +// #include "graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h" +// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h" + +// Shared NicheGraphics components +// -------------------------------- +#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" +#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h" +#include "graphics/niche/Inputs/TwoButton.h" + +#include "graphics/niche/Fonts/FreeSans6pt7b.h" +#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h" +#include + +void setupNicheGraphics() +{ + using namespace NicheGraphics; + + // SPI + // ----------------------------- + + // Display is connected to HSPI + SPIClass *hspi = new SPIClass(HSPI); + hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); + + // E-Ink Driver + // ----------------------------- + + // Use E-Ink driver + Drivers::EInk *driver = new Drivers::DEPG0290BNS800; + driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY); + + // InkHUD + // ---------------------------- + + InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance(); + + // Set the driver + inkhud->setDriver(driver); + + // Set how many FAST updates per FULL update + // Set how unhealthy additional FAST updates beyond this number are + inkhud->setDisplayResilience(7, 1.5); + + // Prepare fonts + InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b); + InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b); + /* + // Font localization demo: Cyrillic + InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic); + InkHUD::Applet::fontSmall.addSubstitutionsWin1251(); + */ + + // Init settings, and customize defaults + inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle? + inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise + inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users + inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead + inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery + + // Setup backlight + // Note: AUX button behavior configured further down + Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance(); + backlight->setPin(PIN_EINK_EN); + + // Pick applets + // Note: order of applets determines priority of "auto-show" feature + // Optional arguments for defaults: + // - is activated? + // - is autoshown? + // - is foreground on a specific tile (index)? + inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown + inkhud->addApplet("DMs", new InkHUD::DMApplet); + inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); + inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); + inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 + // inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet); + // inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet); + + // Start running InkHUD + inkhud->begin(); + + // Buttons + // -------------------------- + + Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component + + // Setup the main user button (0) + buttons->setWiring(0, BUTTON_PIN); + buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); }); + buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); }); + + // Setup the aux button (1) + // Bonus feature of VME290 + buttons->setWiring(1, BUTTON_PIN_SECONDARY); + buttons->setHandlerShortPress(1, []() { InkHUD::InkHUD::getInstance()->nextTile(); }); + + buttons->start(); +} + +#endif \ No newline at end of file diff --git a/variants/t5s3_epaper/pins_arduino.h b/variants/t5s3_epaper/pins_arduino.h new file mode 100644 index 000000000..ccde8a5f9 --- /dev/null +++ b/variants/t5s3_epaper/pins_arduino.h @@ -0,0 +1,27 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to RTC and Touch +static const uint8_t SDA = 39; +static const uint8_t SCL = 40; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 46; +static const uint8_t MOSI = 17; +static const uint8_t MISO = 8; +static const uint8_t SCK = 18; + +// Default SPI1 will be mapped to SD Card +#define SPI_MOSI (13) +#define SPI_SCK (14) +#define SPI_MISO (21) +#define SPI_CS (16) + +#define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/t5s3_epaper/platformio.ini b/variants/t5s3_epaper/platformio.ini new file mode 100644 index 000000000..59b1db657 --- /dev/null +++ b/variants/t5s3_epaper/platformio.ini @@ -0,0 +1,29 @@ +[env:t5s3-epaper-inkhud] +extends = esp32s3_base, inkhud +board = t5-epaper-s3 +board_build.partition = default_16MB.csv +board_check = false +upload_protocol = esptool + +build_flags = + ${esp32_base.build_flags} + ${inkhud.build_flags} + -I variants/t5s3_epaper + -D PRIVATE_HW + -D MESHTASTIC_EXCLUDE_I2C=1 + -D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 +; -D GPS_POWER_TOGGLE + -D HAS_SDCARD + -D SDCARD_USE_SPI1 + -D PCF8563_RTC=0x51 + +build_src_filter = + ${esp32s3_base.build_src_filter} + ${inkhud.build_src_filter} + +lib_deps = + ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX + ${esp32s3_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 + lewisxhe/XPowersLib@^0.2.3 + lewisxhe/SensorLib@^0.2.2 \ No newline at end of file diff --git a/variants/t5s3_epaper/variant.h b/variants/t5s3_epaper/variant.h new file mode 100644 index 000000000..dcf438973 --- /dev/null +++ b/variants/t5s3_epaper/variant.h @@ -0,0 +1,49 @@ + +// TODO: Display (E-Ink) +#define PIN_EINK_EN 11 // BL +#define PIN_EINK_CS 11 +#define PIN_EINK_BUSY -1 +#define PIN_EINK_DC 21 +#define PIN_EINK_RES -1 +#define PIN_EINK_SCLK 14 +#define PIN_EINK_MOSI 13 // SDI + +#define EPD_WIDTH 960 +#define EPD_HEIGHT 540 + +// TODO: battery voltage measurement (I2C) +// BQ25896 +// BQ27220 + +// #define I2C_SDA SDA +// #define I2C_SCL SCL + +// optional GPS +#define GPS_DEFAULT_NOT_PRESENT 1 +// #define GPS_RX_PIN 43 +// #define GPS_TX_PIN 44 + +#define BUTTON_PIN 48 +#define BUTTON_PIN_SECONDARY 0 + +#define USE_SX1262 +#define LORA_SCK 18 +#define LORA_MISO 8 +#define LORA_MOSI 17 +#define LORA_CS 46 +#define LORA_RESET 1 + +#define LORA_DIO0 +#define LORA_DIO1 10 +#define LORA_DIO2 +#define LORA_RXEN NC +#define LORA_TXEN NC + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 47 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 2.4 +#endif From bfcc6f5bd742af4495830f9af2c10e1e414f6176 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:28:18 +0200 Subject: [PATCH 02/10] Update product link --- boards/t5-epaper-s3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/t5-epaper-s3.json b/boards/t5-epaper-s3.json index 628715af7..8d47b1866 100644 --- a/boards/t5-epaper-s3.json +++ b/boards/t5-epaper-s3.json @@ -33,6 +33,6 @@ "require_upload_port": true, "speed": 921600 }, - "url": "https://www.lilygo.cc/products/t5-4-7-inch-e-paper-v2-3", + "url": "https://lilygo.cc/products/t5-e-paper-s3-pro", "vendor": "LILYGO" } From 0c8e7481f470ab6dc52cf37f44d7cd034cd25ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 7 Aug 2025 14:03:14 +0200 Subject: [PATCH 03/10] Move to new variant structure and refactor inkHUD - Display does not work and SX1262 init fails on HT752-02 --- .../{ => esp32s3}/t5s3_epaper/nicheGraphics.h | 16 ++-------------- .../{ => esp32s3}/t5s3_epaper/pins_arduino.h | 0 .../{ => esp32s3}/t5s3_epaper/platformio.ini | 2 +- variants/{ => esp32s3}/t5s3_epaper/variant.h | 0 4 files changed, 3 insertions(+), 15 deletions(-) rename variants/{ => esp32s3}/t5s3_epaper/nicheGraphics.h (87%) rename variants/{ => esp32s3}/t5s3_epaper/pins_arduino.h (100%) rename variants/{ => esp32s3}/t5s3_epaper/platformio.ini (95%) rename variants/{ => esp32s3}/t5s3_epaper/variant.h (100%) diff --git a/variants/t5s3_epaper/nicheGraphics.h b/variants/esp32s3/t5s3_epaper/nicheGraphics.h similarity index 87% rename from variants/t5s3_epaper/nicheGraphics.h rename to variants/esp32s3/t5s3_epaper/nicheGraphics.h index baadc3d28..699a82de0 100644 --- a/variants/t5s3_epaper/nicheGraphics.h +++ b/variants/esp32s3/t5s3_epaper/nicheGraphics.h @@ -31,19 +31,12 @@ Different NicheGraphics UIs and different hardware variants will each have their #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" #include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h" -// #include "graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h" -// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h" - // Shared NicheGraphics components // -------------------------------- #include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" #include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h" #include "graphics/niche/Inputs/TwoButton.h" -#include "graphics/niche/Fonts/FreeSans6pt7b.h" -#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h" -#include - void setupNicheGraphics() { using namespace NicheGraphics; @@ -75,13 +68,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(7, 1.5); // Prepare fonts - InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b); - InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b); - /* - // Font localization demo: Cyrillic - InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic); - InkHUD::Applet::fontSmall.addSubstitutionsWin1251(); - */ + InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Init settings, and customize defaults inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle? diff --git a/variants/t5s3_epaper/pins_arduino.h b/variants/esp32s3/t5s3_epaper/pins_arduino.h similarity index 100% rename from variants/t5s3_epaper/pins_arduino.h rename to variants/esp32s3/t5s3_epaper/pins_arduino.h diff --git a/variants/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini similarity index 95% rename from variants/t5s3_epaper/platformio.ini rename to variants/esp32s3/t5s3_epaper/platformio.ini index 59b1db657..93ae5f685 100644 --- a/variants/t5s3_epaper/platformio.ini +++ b/variants/esp32s3/t5s3_epaper/platformio.ini @@ -8,7 +8,7 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} ${inkhud.build_flags} - -I variants/t5s3_epaper + -I variants/esp32s3/t5s3_epaper -D PRIVATE_HW -D MESHTASTIC_EXCLUDE_I2C=1 -D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 diff --git a/variants/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h similarity index 100% rename from variants/t5s3_epaper/variant.h rename to variants/esp32s3/t5s3_epaper/variant.h From 84654a09cb379553108ad16a49234f86d1b7d2cd Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:51:49 +0200 Subject: [PATCH 04/10] update variant definitions --- .../t5_s3_epaper_pro/variant.cpp | 33 +++++++++++ variants/esp32s3/t5s3_epaper/pins_arduino.h | 5 +- variants/esp32s3/t5s3_epaper/platformio.ini | 16 +++-- variants/esp32s3/t5s3_epaper/variant.h | 58 ++++++++++++++----- 4 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 src/platform/extra_variants/t5_s3_epaper_pro/variant.cpp diff --git a/src/platform/extra_variants/t5_s3_epaper_pro/variant.cpp b/src/platform/extra_variants/t5_s3_epaper_pro/variant.cpp new file mode 100644 index 000000000..943b8510d --- /dev/null +++ b/src/platform/extra_variants/t5_s3_epaper_pro/variant.cpp @@ -0,0 +1,33 @@ +#include "configuration.h" + +#ifdef T5_S3_EPAPER_PRO + +#include "input/TouchScreenImpl1.h" +#include +#include + +BBCapTouch bbct; + +bool readTouch(int16_t *x, int16_t *y) +{ + TOUCHINFO ti; + if (!digitalRead(GT911_PIN_INT)) { + if (bbct.getSamples(&ti)) { + *x = ti.x[0]; + *y = ti.y[0]; + return true; + } + } + return false; +} + +// T5-S3-ePaper Pro specific (late-) init +void lateInitVariant() +{ + bbct.init(GT911_PIN_SDA, GT911_PIN_SCL, GT911_PIN_RST, GT911_PIN_INT); + bbct.setOrientation(90, EPD_WIDTH, EPD_HEIGHT); + // FIXME: crashes! + // touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch); + // touchScreenImpl1->init(); +} +#endif \ No newline at end of file diff --git a/variants/esp32s3/t5s3_epaper/pins_arduino.h b/variants/esp32s3/t5s3_epaper/pins_arduino.h index ccde8a5f9..1949124a6 100644 --- a/variants/esp32s3/t5s3_epaper/pins_arduino.h +++ b/variants/esp32s3/t5s3_epaper/pins_arduino.h @@ -16,12 +16,9 @@ static const uint8_t MOSI = 17; static const uint8_t MISO = 8; static const uint8_t SCK = 18; -// Default SPI1 will be mapped to SD Card #define SPI_MOSI (13) #define SPI_SCK (14) #define SPI_MISO (21) -#define SPI_CS (16) - -#define SDCARD_CS SPI_CS +#define SPI_CS (12) #endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini index 93ae5f685..1eeb7f27d 100644 --- a/variants/esp32s3/t5s3_epaper/platformio.ini +++ b/variants/esp32s3/t5s3_epaper/platformio.ini @@ -9,13 +9,9 @@ build_flags = ${esp32_base.build_flags} ${inkhud.build_flags} -I variants/esp32s3/t5s3_epaper + -D T5_S3_EPAPER_PRO -D PRIVATE_HW - -D MESHTASTIC_EXCLUDE_I2C=1 - -D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -; -D GPS_POWER_TOGGLE - -D HAS_SDCARD - -D SDCARD_USE_SPI1 - -D PCF8563_RTC=0x51 + -D GPS_POWER_TOGGLE build_src_filter = ${esp32s3_base.build_src_filter} @@ -24,6 +20,8 @@ build_src_filter = lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} - lewisxhe/PCF8563_Library@^1.0.1 - lewisxhe/XPowersLib@^0.2.3 - lewisxhe/SensorLib@^0.2.2 \ No newline at end of file + https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 + https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip + https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip + lewisxhe/XPowersLib@0.3.1 + lewisxhe/SensorLib@0.3.1 \ No newline at end of file diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index dcf438973..e5f3fa127 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -11,39 +11,65 @@ #define EPD_WIDTH 960 #define EPD_HEIGHT 540 -// TODO: battery voltage measurement (I2C) -// BQ25896 -// BQ27220 +#define I2C_SDA SDA +#define I2C_SCL SCL -// #define I2C_SDA SDA -// #define I2C_SCL SCL +#define HAS_TOUCHSCREEN 1 +#define GT911_PIN_SDA 39 +#define GT911_PIN_SCL 40 +#define GT911_PIN_INT 15 +#define GT911_PIN_RST 41 + +#define PCF85063_RTC 0x51 +#define HAS_RTC 1 + +#define USE_POWERSAVE +#define SLEEP_TIME 120 // optional GPS #define GPS_DEFAULT_NOT_PRESENT 1 -// #define GPS_RX_PIN 43 -// #define GPS_TX_PIN 44 +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 #define BUTTON_PIN 48 #define BUTTON_PIN_SECONDARY 0 +// SD card +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +#define SDCARD_CS SPI_CS +#define SD_SPI_FREQUENCY 75000000U + +// battery charger BQ25896 +#define HAS_PPM 1 +#define XPOWERS_CHIP_BQ25896 + +// battery quality management BQ27220 +#define HAS_BQ27220 1 +#define BQ27220_I2C_SDA SDA +#define BQ27220_I2C_SCL SCL +#define BQ27220_DESIGN_CAPACITY 1500 + +// TPS651851 + +// LoRa #define USE_SX1262 +#define USE_SX1268 + #define LORA_SCK 18 #define LORA_MISO 8 #define LORA_MOSI 17 #define LORA_CS 46 + +#define LORA_DIO0 -1 #define LORA_RESET 1 +#define LORA_DIO1 10 // SX1262 IRQ +#define LORA_DIO2 47 // SX1262 BUSY +#define LORA_DIO3 -#define LORA_DIO0 -#define LORA_DIO1 10 -#define LORA_DIO2 -#define LORA_RXEN NC -#define LORA_TXEN NC - -#ifdef USE_SX1262 #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY 47 +#define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 2.4 -#endif From 92988e32b9f599f352aff1da62619e89119a316f Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:52:04 +0200 Subject: [PATCH 05/10] add EPD driver --- src/graphics/ed047tc1.c | 264 +++++++++++ src/graphics/ed047tc1.h | 117 +++++ src/graphics/epd_driver.c | 889 ++++++++++++++++++++++++++++++++++++++ src/graphics/epd_driver.h | 386 +++++++++++++++++ 4 files changed, 1656 insertions(+) create mode 100644 src/graphics/ed047tc1.c create mode 100644 src/graphics/ed047tc1.h create mode 100644 src/graphics/epd_driver.c create mode 100644 src/graphics/epd_driver.h diff --git a/src/graphics/ed047tc1.c b/src/graphics/ed047tc1.c new file mode 100644 index 000000000..d254f3196 --- /dev/null +++ b/src/graphics/ed047tc1.c @@ -0,0 +1,264 @@ +/******************************************************************************/ +/*** include files ***/ +/******************************************************************************/ + +#if defined(LILYGO_T5_EPD47_S3) + +#include "ed047tc1.h" +#include "i2s_data_bus.h" +#include "rmt_pulse.h" + +#include + +#include + +/******************************************************************************/ +/*** macro definitions ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** type definitions ***/ +/******************************************************************************/ + +typedef struct { + bool ep_latch_enable : 1; + bool power_disable : 1; + bool pos_power_enable : 1; + bool neg_power_enable : 1; + bool ep_stv : 1; + bool ep_scan_direction : 1; + bool ep_mode : 1; + bool ep_output_enable : 1; +} epd_config_register_t; + +/******************************************************************************/ +/*** local function prototypes ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** exported variables ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** local variables ***/ +/******************************************************************************/ + +static epd_config_register_t config_reg; + +/******************************************************************************/ +/*** exported functions ***/ +/******************************************************************************/ + +/* + * Write bits directly using the registers. + * Won't work for some pins (>= 32). + */ +inline static void fast_gpio_set_hi(gpio_num_t gpio_num) +{ + gpio_set_level(gpio_num, 1); +} + +inline static void fast_gpio_set_lo(gpio_num_t gpio_num) +{ + gpio_set_level(gpio_num, 0); +} + +inline static void IRAM_ATTR push_cfg_bit(bool bit) +{ + fast_gpio_set_lo(CFG_CLK); + if (bit) { + fast_gpio_set_hi(CFG_DATA); + } else { + fast_gpio_set_lo(CFG_DATA); + } + fast_gpio_set_hi(CFG_CLK); +} + +static void IRAM_ATTR push_cfg(epd_config_register_t *cfg) +{ + fast_gpio_set_lo(CFG_STR); + + // push config bits in reverse order + push_cfg_bit(cfg->ep_output_enable); + push_cfg_bit(cfg->ep_mode); + push_cfg_bit(cfg->ep_scan_direction); + push_cfg_bit(cfg->ep_stv); + + push_cfg_bit(cfg->neg_power_enable); + push_cfg_bit(cfg->pos_power_enable); + push_cfg_bit(cfg->power_disable); + push_cfg_bit(cfg->ep_latch_enable); + + fast_gpio_set_hi(CFG_STR); +} + +void IRAM_ATTR busy_delay(uint32_t cycles) +{ + volatile uint64_t counts = XTHAL_GET_CCOUNT() + cycles; + while (XTHAL_GET_CCOUNT() < counts) + ; +} + +void epd_base_init(uint32_t epd_row_width) +{ + config_reg.ep_latch_enable = false; + config_reg.power_disable = true; + config_reg.pos_power_enable = false; + config_reg.neg_power_enable = false; + config_reg.ep_stv = true; + config_reg.ep_scan_direction = true; + config_reg.ep_mode = false; + config_reg.ep_output_enable = false; + + /* Power Control Output/Off */ + gpio_reset_pin(CFG_CLK); + gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT); + gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT); + gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT); + fast_gpio_set_lo(CFG_STR); + + push_cfg(&config_reg); + + // Setup I2S + i2s_bus_config i2s_config; + // add an offset off dummy bytes to allow for enough timing headroom + i2s_config.epd_row_width = epd_row_width + 32; + i2s_config.clock = CKH; + i2s_config.start_pulse = STH; + i2s_config.data_0 = D0; + i2s_config.data_1 = D1; + i2s_config.data_2 = D2; + i2s_config.data_3 = D3; + i2s_config.data_4 = D4; + i2s_config.data_5 = D5; + i2s_config.data_6 = D6; + i2s_config.data_7 = D7; + + i2s_bus_init(&i2s_config); + + rmt_pulse_init(CKV); +} + +void epd_poweron() +{ + config_reg.ep_scan_direction = true; + config_reg.power_disable = false; + push_cfg(&config_reg); + busy_delay(100 * 240); + config_reg.neg_power_enable = true; + push_cfg(&config_reg); + busy_delay(500 * 240); + config_reg.pos_power_enable = true; + push_cfg(&config_reg); + busy_delay(100 * 240); + config_reg.ep_stv = true; + push_cfg(&config_reg); + fast_gpio_set_hi(STH); +} + +void epd_poweroff() +{ + config_reg.pos_power_enable = false; + push_cfg(&config_reg); + busy_delay(10 * 240); + config_reg.neg_power_enable = false; + push_cfg(&config_reg); + busy_delay(100 * 240); + config_reg.power_disable = true; + push_cfg(&config_reg); + + config_reg.ep_stv = false; + push_cfg(&config_reg); +} + +void epd_poweroff_all() +{ + memset(&config_reg, 0, sizeof(config_reg)); + push_cfg(&config_reg); +} + +void epd_start_frame() +{ + while (i2s_is_busy()) + ; + + config_reg.ep_mode = true; + push_cfg(&config_reg); + + pulse_ckv_us(1, 1, true); + + // This is very timing-sensitive! + config_reg.ep_stv = false; + push_cfg(&config_reg); + busy_delay(240); + pulse_ckv_us(10, 10, false); + config_reg.ep_stv = true; + push_cfg(&config_reg); + pulse_ckv_us(0, 10, true); + + config_reg.ep_output_enable = true; + push_cfg(&config_reg); + + pulse_ckv_us(1, 1, true); +} + +static inline void latch_row() +{ + config_reg.ep_latch_enable = true; + push_cfg(&config_reg); + + config_reg.ep_latch_enable = false; + push_cfg(&config_reg); +} + +void IRAM_ATTR epd_skip() +{ +#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2) + pulse_ckv_ticks(2, 2, false); +#else + // According to the spec, the OC4 maximum CKV frequency is 200kHz. + pulse_ckv_ticks(45, 5, false); +#endif +} + +void IRAM_ATTR epd_output_row(uint32_t output_time_dus) +{ + while (i2s_is_busy()) + ; + + latch_row(); + + pulse_ckv_ticks(output_time_dus, 50, false); + + i2s_start_line_output(); + i2s_switch_buffer(); +} + +void epd_end_frame() +{ + config_reg.ep_output_enable = false; + push_cfg(&config_reg); + config_reg.ep_mode = false; + push_cfg(&config_reg); + pulse_ckv_us(1, 1, true); + pulse_ckv_us(1, 1, true); +} + +void IRAM_ATTR epd_switch_buffer() +{ + i2s_switch_buffer(); +} + +uint8_t *IRAM_ATTR epd_get_current_buffer() +{ + return (uint8_t *)i2s_get_current_buffer(); +} + +/******************************************************************************/ +/*** local functions ***/ +/******************************************************************************/ + +#endif +/******************************************************************************/ +/*** END OF FILE ***/ +/******************************************************************************/ \ No newline at end of file diff --git a/src/graphics/ed047tc1.h b/src/graphics/ed047tc1.h new file mode 100644 index 000000000..0e1f8eaa3 --- /dev/null +++ b/src/graphics/ed047tc1.h @@ -0,0 +1,117 @@ +#ifndef _ED047TC1_H_ +#define _ED047TC1_H_ + +#if defined(T5_S3_EPAPER_PRO) + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/*** include files ***/ +/******************************************************************************/ + +#include + +#include + +/******************************************************************************/ +/*** macro definitions ***/ +/******************************************************************************/ + +/* Config Reggister Control */ +#define CFG_DATA GPIO_NUM_2 +#define CFG_CLK GPIO_NUM_42 +#define CFG_STR GPIO_NUM_1 + +/* Control Lines */ +#define CKV GPIO_NUM_39 +#define STH GPIO_NUM_9 + +/* Edges */ +#define CKH GPIO_NUM_10 + +/* Data Lines */ +#define D7 GPIO_NUM_38 +#define D6 GPIO_NUM_45 +#define D5 GPIO_NUM_47 +#define D4 GPIO_NUM_21 +#define D3 GPIO_NUM_14 +#define D2 GPIO_NUM_13 +#define D1 GPIO_NUM_12 +#define D0 GPIO_NUM_11 + +#else +#error "Unknown SOC" +#endif + +/******************************************************************************/ +/*** type definitions ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** exported variables ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** exported functions ***/ +/******************************************************************************/ + +void epd_base_init(uint32_t epd_row_width); +void epd_poweron(); +void epd_poweroff(); + +/** + * @brief Start a draw cycle. + */ +void epd_start_frame(); + +/** + * @brief End a draw cycle. + */ +void epd_end_frame(); + +/** + * @brief output row data + * + * @note Waits until all previously submitted data has been written. + * Then, the following operations are initiated: + * + * 1. Previously submitted data is latched to the output register. + * 2. The RMT peripheral is set up to pulse the vertical (gate) driver + * for `output_time_dus` / 10 microseconds. + * 3. The I2S peripheral starts transmission of the current buffer to + * the source driver. + * 4. The line buffers are switched. + * + * This sequence of operations allows for pipelining data preparation and + * transfer, reducing total refresh times. + */ +void IRAM_ATTR epd_output_row(uint32_t output_time_dus); + +/** + * @brief Skip a row without writing to it. + */ +void IRAM_ATTR epd_skip(); + +/** + * @brief Get the currently writable line buffer. + */ +uint8_t *IRAM_ATTR epd_get_current_buffer(); + +/** + * @brief Switches front and back line buffer. + * + * @note If the switched-to line buffer is currently in use, this function + * blocks until transmission is done. + */ +void IRAM_ATTR epd_switch_buffer(); + +#ifdef __cplusplus +} +#endif + +#endif +/******************************************************************************/ +/*** END OF FILE ***/ +/******************************************************************************/ \ No newline at end of file diff --git a/src/graphics/epd_driver.c b/src/graphics/epd_driver.c new file mode 100644 index 000000000..96e831e5f --- /dev/null +++ b/src/graphics/epd_driver.c @@ -0,0 +1,889 @@ +/******************************************************************************/ +/*** include files ***/ +/******************************************************************************/ + +#include "epd_driver.h" +#include "ed047tc1.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/******************************************************************************/ +/*** macro definitions ***/ +/******************************************************************************/ + +/** + * @brief number of bytes needed for one line of EPD pixel data. + */ +#define EPD_LINE_BYTES EPD_WIDTH / 4 + +#define CLEAR_BYTE 0B10101010 +#define DARK_BYTE 0B01010101 + +#ifndef _swap_int +#define _swap_int(a, b) \ + { \ + int32_t t = a; \ + a = b; \ + b = t; \ + } +#endif + +/******************************************************************************/ +/*** type definitions ***/ +/******************************************************************************/ + +typedef struct { + uint8_t *data_ptr; + SemaphoreHandle_t done_smphr; + Rect_t area; + int32_t frame; + DrawMode_t mode; +} OutputParams; + +/******************************************************************************/ +/*** local function prototypes ***/ +/******************************************************************************/ + +/** + * @brief Reorder the output buffer to account for I2S FIFO order. + */ +static void reorder_line_buffer(uint32_t *line_data); + +/** + * @brief output a row to the display. + */ +static void write_row(uint32_t output_time_dus); + +/** + * @brief skip a display row + */ +static void skip_row(uint8_t pipeline_finish_time); + +static void IRAM_ATTR reset_lut(uint8_t *lut_mem, DrawMode_t mode); + +static void IRAM_ATTR update_LUT(uint8_t *lut_mem, uint8_t k, DrawMode_t mode); + +/** + * @brief bit-shift a buffer `shift` <= 7 bits to the right. + */ +static void IRAM_ATTR bit_shift_buffer_right(uint8_t *buf, uint32_t len, int32_t shift); + +static void IRAM_ATTR nibble_shift_buffer_right(uint8_t *buf, uint32_t len); + +static void IRAM_ATTR provide_out(OutputParams *params); + +static void IRAM_ATTR feed_display(OutputParams *params); + +static void epd_fill_circle_helper(int32_t x0, int32_t y0, int32_t r, int32_t corners, int32_t delta, uint8_t color, + uint8_t *framebuffer); + +/******************************************************************************/ +/*** exported variables ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** local variables ***/ +/******************************************************************************/ + +/** + * @brief status tracker for row skipping + */ +static uint32_t skipping; + +/* 4bpp Contrast cycles in order of contrast (Darkest first). */ +static const int32_t contrast_cycles_4[15] = {30, 30, 20, 20, 30, 30, 30, 40, 40, 50, 50, 50, 100, 200, 300}; + +static const int32_t contrast_cycles_4_white[15] = {10, 10, 8, 8, 8, 8, 8, 10, 10, 15, 15, 20, 20, 100, 300}; + +// Heap space to use for the EPD output lookup table, which +// is calculated for each cycle. +static uint8_t *conversion_lut; +static QueueHandle_t output_queue; + +static const DRAM_ATTR uint32_t lut_1bpp[256] = { + 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, + 0x0055, 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, + 0x0154, 0x0155, 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, + 0x0451, 0x0454, 0x0455, 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, 0x0540, 0x0541, 0x0544, 0x0545, + 0x0550, 0x0551, 0x0554, 0x0555, 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 0x1040, 0x1041, 0x1044, + 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141, + 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, + 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, + 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, + 0x4015, 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, + 0x4114, 0x4115, 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, + 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504, 0x4505, + 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, + 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, 0x5100, 0x5101, + 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400, + 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, + 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, + 0x5555}; + +/******************************************************************************/ +/*** exported functions ***/ +/******************************************************************************/ + +void epd_init() +{ + skipping = 0; + epd_base_init(EPD_WIDTH); + + conversion_lut = (uint8_t *)heap_caps_malloc(1 << 16, MALLOC_CAP_8BIT); + assert(conversion_lut != NULL); + output_queue = xQueueCreate(64, EPD_WIDTH / 2); +} + +void epd_push_pixels(Rect_t area, int16_t time, int32_t color) +{ + uint8_t row[EPD_LINE_BYTES] = {0}; + + for (uint32_t i = 0; i < area.width; i++) { + uint32_t position = i + area.x % 4; + uint8_t mask = (color ? CLEAR_BYTE : DARK_BYTE) & (0b00000011 << (2 * (position % 4))); + row[area.x / 4 + position / 4] |= mask; + } + reorder_line_buffer((uint32_t *)row); + + epd_start_frame(); + + for (int32_t i = 0; i < EPD_HEIGHT; i++) { + // before are of interest: skip + if (i < area.y) { + skip_row(time); + // start area of interest: set row data + } else if (i == area.y) { + epd_switch_buffer(); + memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES); + epd_switch_buffer(); + memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES); + + write_row(time * 10); + // load nop row if done with area + } else if (i >= area.y + area.height) { + skip_row(time); + // output the same as before + } else { + write_row(time * 10); + } + } + // Since we "pipeline" row output, we still have to latch out the last row. + write_row(time * 10); + + epd_end_frame(); +} + +void epd_clear_area(Rect_t area) +{ + epd_clear_area_cycles(area, 4, 50); +} + +void epd_clear_area_cycles(Rect_t area, int32_t cycles, int32_t cycle_time) +{ + const int16_t white_time = cycle_time; + const int16_t dark_time = cycle_time; + + for (int32_t c = 0; c < cycles; c++) { + for (int32_t i = 0; i < 4; i++) { + epd_push_pixels(area, dark_time, 0); + } + for (int32_t i = 0; i < 4; i++) { + epd_push_pixels(area, white_time, 1); + } + } +} + +Rect_t epd_full_screen() +{ + Rect_t area = {.x = 0, .y = 0, .width = EPD_WIDTH, .height = EPD_HEIGHT}; + return area; +} + +void epd_clear() +{ + epd_clear_area(epd_full_screen()); +} + +void IRAM_ATTR calc_epd_input_4bpp(uint32_t *line_data, uint8_t *epd_input, uint8_t k, uint8_t *conversion_lut) +{ + uint32_t *wide_epd_input = (uint32_t *)epd_input; + uint16_t *line_data_16 = (uint16_t *)line_data; + + // this is reversed for little-endian, but this is later compensated + // through the output peripheral. + for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) { + uint16_t v1 = *(line_data_16++); + uint16_t v2 = *(line_data_16++); + uint16_t v3 = *(line_data_16++); + uint16_t v4 = *(line_data_16++); +#if USER_I2S_REG + uint32_t pixel = conversion_lut[v1] << 16 | conversion_lut[v2] << 24 | conversion_lut[v3] | conversion_lut[v4] << 8; +#else + uint32_t pixel = + (conversion_lut[v1]) << 0 | (conversion_lut[v2]) << 8 | (conversion_lut[v3]) << 16 | (conversion_lut[v4]) << 24; +#endif + wide_epd_input[j] = pixel; + } +} + +void IRAM_ATTR calc_epd_input_1bpp(uint8_t *line_data, uint8_t *epd_input, DrawMode_t mode) +{ + uint32_t *wide_epd_input = (uint32_t *)epd_input; + + // this is reversed for little-endian, but this is later compensated + // through the output peripheral. + for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) { + uint8_t v1 = *(line_data++); + uint8_t v2 = *(line_data++); + wide_epd_input[j] = (lut_1bpp[v1] << 16) | lut_1bpp[v2]; + } +} + +inline uint32_t min(uint32_t x, uint32_t y) +{ + return x < y ? x : y; +} + +void epd_draw_hline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer) +{ + for (int32_t i = 0; i < length; i++) { + int32_t xx = x + i; + epd_draw_pixel(xx, y, color, framebuffer); + } +} + +void epd_draw_vline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer) +{ + for (int32_t i = 0; i < length; i++) { + int32_t yy = y + i; + epd_draw_pixel(x, yy, color, framebuffer); + } +} + +void epd_draw_pixel(int32_t x, int32_t y, uint8_t color, uint8_t *framebuffer) +{ + if (x < 0 || x >= EPD_WIDTH) { + return; + } + if (y < 0 || y >= EPD_HEIGHT) { + return; + } + uint8_t *buf_ptr = &framebuffer[y * EPD_WIDTH / 2 + x / 2]; + if (x % 2) { + *buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0); + } else { + *buf_ptr = (*buf_ptr & 0xF0) | (color >> 4); + } +} + +void epd_draw_circle(int32_t x0, int32_t y0, int32_t r, uint8_t color, uint8_t *framebuffer) +{ + int32_t f = 1 - r; + int32_t ddF_x = 1; + int32_t ddF_y = -2 * r; + int32_t x = 0; + int32_t y = r; + + epd_draw_pixel(x0, y0 + r, color, framebuffer); + epd_draw_pixel(x0, y0 - r, color, framebuffer); + epd_draw_pixel(x0 + r, y0, color, framebuffer); + epd_draw_pixel(x0 - r, y0, color, framebuffer); + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + epd_draw_pixel(x0 + x, y0 + y, color, framebuffer); + epd_draw_pixel(x0 - x, y0 + y, color, framebuffer); + epd_draw_pixel(x0 + x, y0 - y, color, framebuffer); + epd_draw_pixel(x0 - x, y0 - y, color, framebuffer); + epd_draw_pixel(x0 + y, y0 + x, color, framebuffer); + epd_draw_pixel(x0 - y, y0 + x, color, framebuffer); + epd_draw_pixel(x0 + y, y0 - x, color, framebuffer); + epd_draw_pixel(x0 - y, y0 - x, color, framebuffer); + } +} + +void epd_fill_circle(int32_t x0, int32_t y0, int32_t r, uint8_t color, uint8_t *framebuffer) +{ + epd_draw_vline(x0, y0 - r, 2 * r + 1, color, framebuffer); + epd_fill_circle_helper(x0, y0, r, 3, 0, color, framebuffer); +} + +static void epd_fill_circle_helper(int32_t x0, int32_t y0, int32_t r, int32_t corners, int32_t delta, uint8_t color, + uint8_t *framebuffer) +{ + int32_t f = 1 - r; + int32_t ddF_x = 1; + int32_t ddF_y = -2 * r; + int32_t x = 0; + int32_t y = r; + int32_t px = x; + int32_t py = y; + + delta++; // Avoid some +1's in the loop + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + // These checks avoid double-drawing certain lines, important + // for the SSD1306 library which has an INVERT drawing mode. + if (x < (y + 1)) { + if (corners & 1) + epd_draw_vline(x0 + x, y0 - y, 2 * y + delta, color, framebuffer); + if (corners & 2) + epd_draw_vline(x0 - x, y0 - y, 2 * y + delta, color, framebuffer); + } + if (y != py) { + if (corners & 1) + epd_draw_vline(x0 + py, y0 - px, 2 * px + delta, color, framebuffer); + if (corners & 2) + epd_draw_vline(x0 - py, y0 - px, 2 * px + delta, color, framebuffer); + py = y; + } + px = x; + } +} + +void epd_draw_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer) +{ + epd_draw_hline(x, y, w, color, framebuffer); + epd_draw_hline(x, y + h - 1, w, color, framebuffer); + epd_draw_vline(x, y, h, color, framebuffer); + epd_draw_vline(x + w - 1, y, h, color, framebuffer); +} + +void epd_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer) +{ + for (int32_t i = x; i < x + w; i++) { + epd_draw_vline(i, y, h, color, framebuffer); + } +} + +void epd_write_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer) +{ + int32_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int(x0, y0); + _swap_int(x1, y1); + } + + if (x0 > x1) { + _swap_int(x0, x1); + _swap_int(y0, y1); + } + + int32_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int32_t err = dx / 2; + int32_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0 <= x1; x0++) { + if (steep) { + epd_draw_pixel(y0, x0, color, framebuffer); + } else { + epd_draw_pixel(x0, y0, color, framebuffer); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +void epd_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer) +{ + // Update in subclasses if desired! + if (x0 == x1) { + if (y0 > y1) + _swap_int(y0, y1); + epd_draw_vline(x0, y0, y1 - y0 + 1, color, framebuffer); + } else if (y0 == y1) { + if (x0 > x1) + _swap_int(x0, x1); + epd_draw_hline(x0, y0, x1 - x0 + 1, color, framebuffer); + } else { + epd_write_line(x0, y0, x1, y1, color, framebuffer); + } +} + +void epd_draw_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, + uint8_t *framebuffer) +{ + epd_draw_line(x0, y0, x1, y1, color, framebuffer); + epd_draw_line(x1, y1, x2, y2, color, framebuffer); + epd_draw_line(x2, y2, x0, y0, color, framebuffer); +} + +void epd_fill_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, + uint8_t *framebuffer) +{ + int32_t a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + _swap_int(y0, y1); + _swap_int(x0, x1); + } + if (y1 > y2) { + _swap_int(y2, y1); + _swap_int(x2, x1); + } + if (y0 > y1) { + _swap_int(y0, y1); + _swap_int(x0, x1); + } + + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + epd_draw_hline(a, y0, b - a + 1, color, framebuffer); + return; + } + + int32_t dx01 = x1 - x0; + int32_t dy01 = y1 - y0; + int32_t dx02 = x2 - x0; + int32_t dy02 = y2 - y0; + int32_t dx12 = x2 - x1; + int32_t dy12 = y2 - y1; + int32_t sa = 0; + int32_t sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1 - 1; // Skip it + + for (y = y0; y <= last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int(a, b); + epd_draw_hline(a, y, b - a + 1, color, framebuffer); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = (int32_t)dx12 * (y - y1); + sb = (int32_t)dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int(a, b); + epd_draw_hline(a, y, b - a + 1, color, framebuffer); + } +} + +void epd_copy_to_framebuffer(Rect_t image_area, uint8_t *image_data, uint8_t *framebuffer) +{ + assert(image_data != NULL || framebuffer != NULL); + + for (uint32_t i = 0; i < image_area.width * image_area.height; i++) { + uint32_t value_index = i; + // for images of uneven width, + // consume an additional nibble per row. + if (image_area.width % 2) { + value_index += i / image_area.width; + } + uint8_t val = (value_index % 2) ? (image_data[value_index / 2] & 0xF0) >> 4 : image_data[value_index / 2] & 0x0F; + + int32_t xx = image_area.x + i % image_area.width; + if (xx < 0 || xx >= EPD_WIDTH) { + continue; + } + int32_t yy = image_area.y + i / image_area.width; + if (yy < 0 || yy >= EPD_HEIGHT) { + continue; + } + uint8_t *buf_ptr = &framebuffer[yy * EPD_WIDTH / 2 + xx / 2]; + if (xx % 2) { + *buf_ptr = (*buf_ptr & 0x0F) | (val << 4); + } else { + *buf_ptr = (*buf_ptr & 0xF0) | val; + } + } +} + +void IRAM_ATTR epd_draw_grayscale_image(Rect_t area, uint8_t *data) +{ + epd_draw_image(area, data, BLACK_ON_WHITE); +} + +void IRAM_ATTR epd_draw_frame_1bit(Rect_t area, uint8_t *ptr, DrawMode_t mode, int32_t time) +{ + epd_start_frame(); + uint8_t line[EPD_WIDTH / 8]; + memset(line, 0, sizeof(line)); + + if (area.x < 0) { + ptr += -area.x / 8; + } + + int32_t ceil_byte_width = (area.width / 8 + (area.width % 8 > 0)); + if (area.y < 0) { + ptr += ceil_byte_width * -area.y; + } + + for (int32_t i = 0; i < EPD_HEIGHT; i++) { + if (i < area.y || i >= area.y + area.height) { + skip_row(time); + continue; + } + + uint8_t *lp; + bool shifted = 0; + if (area.width == EPD_WIDTH && area.x == 0) { + lp = ptr; + ptr += EPD_WIDTH / 8; + } else { + uint8_t *buf_start = (uint8_t *)line; + uint32_t line_bytes = ceil_byte_width; + if (area.x >= 0) { + buf_start += area.x / 8; + } else { + // reduce line_bytes to actually used bytes + line_bytes += area.x / 8; + } + line_bytes = min(line_bytes, EPD_WIDTH / 8 - (uint32_t)(buf_start - line)); + memcpy(buf_start, ptr, line_bytes); + ptr += ceil_byte_width; + + // mask last n bits if width is not divisible by 8 + if (area.width % 8 != 0 && ceil_byte_width + 1 < EPD_WIDTH) { + uint8_t mask = 0; + for (int32_t s = 0; s < area.width % 8; s++) { + mask = (mask << 1) | 1; + } + *(buf_start + line_bytes - 1) &= mask; + } + + if (area.x % 8 != 0 && area.x < EPD_WIDTH) { + // shift to right + shifted = true; + bit_shift_buffer_right(buf_start, min(line_bytes + 1, (uint32_t)line + EPD_WIDTH / 8 - (uint32_t)buf_start), + area.x % 8); + } + lp = line; + } + calc_epd_input_1bpp(lp, epd_get_current_buffer(), mode); + epd_output_row(time); + if (shifted) { + memset(line, 0, sizeof(line)); + } + } + if (!skipping) { + epd_output_row(time); + } + epd_end_frame(); +} + +void IRAM_ATTR epd_draw_image(Rect_t area, uint8_t *data, DrawMode_t mode) +{ + uint8_t frame_count = 15; + + SemaphoreHandle_t fetch_sem = xSemaphoreCreateBinary(); + SemaphoreHandle_t feed_sem = xSemaphoreCreateBinary(); + vTaskDelay(10); + for (uint8_t k = 0; k < frame_count; k++) { + OutputParams p1 = { + .area = area, + .data_ptr = data, + .frame = k, + .mode = mode, + .done_smphr = fetch_sem, + }; + OutputParams p2 = { + .area = area, + .data_ptr = data, + .frame = k, + .mode = mode, + .done_smphr = feed_sem, + }; + + TaskHandle_t t1, t2; + xTaskCreatePinnedToCore((void (*)(void *))provide_out, "privide_out", 8192, &p1, 10, &t1, 0); + xTaskCreatePinnedToCore((void (*)(void *))feed_display, "render", 8192, &p2, 10, &t2, 1); + + xSemaphoreTake(fetch_sem, portMAX_DELAY); + xSemaphoreTake(feed_sem, portMAX_DELAY); + + vTaskDelete(t1); + vTaskDelete(t2); + vTaskDelay(5); + } + vSemaphoreDelete(fetch_sem); + vSemaphoreDelete(feed_sem); +} + +/******************************************************************************/ +/*** local functions ***/ +/******************************************************************************/ + +static void write_row(uint32_t output_time_dus) +{ + // avoid too light output after skipping on some displays + if (skipping) { + // vTaskDelay(20); + } + skipping = 0; + epd_output_row(output_time_dus); +} + +static void skip_row(uint8_t pipeline_finish_time) +{ + // output previously loaded row, fill buffer with no-ops. + if (skipping == 0) { + epd_switch_buffer(); + memset(epd_get_current_buffer(), 0, EPD_LINE_BYTES); + epd_switch_buffer(); + memset(epd_get_current_buffer(), 0, EPD_LINE_BYTES); + epd_output_row(pipeline_finish_time); + // avoid tainting of following rows by + // allowing residual charge to dissipate + // vTaskDelay(10); + /* + unsigned counts = XTHAL_GET_CCOUNT() + 50 * 240; + while (XTHAL_GET_CCOUNT() < counts) { + }; + */ + } else if (skipping < 2) { + epd_output_row(10); + } else { + // epd_output_row(5); + epd_skip(); + } + skipping++; +} + +static void reorder_line_buffer(uint32_t *line_data) +{ + for (uint32_t i = 0; i < EPD_LINE_BYTES / 4; i++) { + uint32_t val = *line_data; + *(line_data++) = val >> 16 | ((val & 0x0000FFFF) << 16); + } +} + +static void IRAM_ATTR reset_lut(uint8_t *lut_mem, DrawMode_t mode) +{ + switch (mode) { + case BLACK_ON_WHITE: + memset(lut_mem, 0x55, (1 << 16)); + break; + case WHITE_ON_BLACK: + case WHITE_ON_WHITE: + memset(lut_mem, 0xAA, (1 << 16)); + break; + default: + ESP_LOGW("epd_driver", "unknown draw mode %d!", mode); + break; + } +} + +static void IRAM_ATTR update_LUT(uint8_t *lut_mem, uint8_t k, DrawMode_t mode) +{ + if (mode == BLACK_ON_WHITE || mode == WHITE_ON_WHITE) { + k = 15 - k; + } + + // reset the pixels which are not to be lightened / darkened + // any longer in the current frame + for (uint32_t l = k; l < (1 << 16); l += 16) { + lut_mem[l] &= 0xFC; + } + + for (uint32_t l = (k << 4); l < (1 << 16); l += (1 << 8)) { + for (uint32_t p = 0; p < 16; p++) { + lut_mem[l + p] &= 0xF3; + } + } + for (uint32_t l = (k << 8); l < (1 << 16); l += (1 << 12)) { + for (uint32_t p = 0; p < (1 << 8); p++) { + lut_mem[l + p] &= 0xCF; + } + } + for (uint32_t p = (k << 12); p < ((k + 1) << 12); p++) { + lut_mem[p] &= 0x3F; + } +} + +static void IRAM_ATTR bit_shift_buffer_right(uint8_t *buf, uint32_t len, int32_t shift) +{ + uint8_t carry = 0x00; + for (uint32_t i = 0; i < len; i++) { + uint8_t val = buf[i]; + buf[i] = (val << shift) | carry; + carry = val >> (8 - shift); + } +} + +static void IRAM_ATTR nibble_shift_buffer_right(uint8_t *buf, uint32_t len) +{ + uint8_t carry = 0xF; + for (uint32_t i = 0; i < len; i++) { + uint8_t val = buf[i]; + buf[i] = (val << 4) | carry; + carry = (val & 0xF0) >> 4; + } +} + +static void IRAM_ATTR provide_out(OutputParams *params) +{ + uint8_t line[EPD_WIDTH / 2]; + memset(line, 255, EPD_WIDTH / 2); + Rect_t area = params->area; + uint8_t *ptr = params->data_ptr; + + if (params->frame == 0) { + reset_lut(conversion_lut, params->mode); + } + + update_LUT(conversion_lut, params->frame, params->mode); + + if (area.x < 0) { + ptr += -area.x / 2; + } + if (area.y < 0) { + ptr += (area.width / 2 + area.width % 2) * -area.y; + } + + for (int32_t i = 0; i < EPD_HEIGHT; i++) { + if (i < area.y || i >= area.y + area.height) { + continue; + } + + uint32_t *lp; + bool shifted = false; + if (area.width == EPD_WIDTH && area.x == 0) { + lp = (uint32_t *)ptr; + ptr += EPD_WIDTH / 2; + } else { + uint8_t *buf_start = (uint8_t *)line; + uint32_t line_bytes = area.width / 2 + area.width % 2; + if (area.x >= 0) { + buf_start += area.x / 2; + } else { + // reduce line_bytes to actually used bytes + line_bytes += area.x / 2; + } + line_bytes = min(line_bytes, EPD_WIDTH / 2 - (uint32_t)(buf_start - line)); + memcpy(buf_start, ptr, line_bytes); + ptr += area.width / 2 + area.width % 2; + + // mask last nibble for uneven width + if (area.width % 2 == 1 && area.x / 2 + area.width / 2 + 1 < EPD_WIDTH) { + *(buf_start + line_bytes - 1) |= 0xF0; + } + if (area.x % 2 == 1 && area.x < EPD_WIDTH) { + shifted = true; + // shift one nibble to right + nibble_shift_buffer_right(buf_start, min(line_bytes + 1, (uint32_t)line + EPD_WIDTH / 2 - (uint32_t)buf_start)); + } + lp = (uint32_t *)line; + } + xQueueSendToBack(output_queue, lp, portMAX_DELAY); + if (shifted) { + memset(line, 255, EPD_WIDTH / 2); + } + } + + xSemaphoreGive(params->done_smphr); + vTaskDelay(portMAX_DELAY); +} + +static void IRAM_ATTR feed_display(OutputParams *params) +{ + Rect_t area = params->area; + const int32_t *contrast_lut = contrast_cycles_4; + switch (params->mode) { + case WHITE_ON_WHITE: + case BLACK_ON_WHITE: + contrast_lut = contrast_cycles_4; + break; + case WHITE_ON_BLACK: + contrast_lut = contrast_cycles_4_white; + break; + } + + epd_start_frame(); + for (int32_t i = 0; i < EPD_HEIGHT; i++) { + if (i < area.y || i >= area.y + area.height) { + skip_row(contrast_lut[params->frame]); + continue; + } + uint8_t output[EPD_WIDTH / 2]; + xQueueReceive(output_queue, output, portMAX_DELAY); + calc_epd_input_4bpp((uint32_t *)output, epd_get_current_buffer(), params->frame, conversion_lut); + write_row(contrast_lut[params->frame]); + } + if (!skipping) { + // Since we "pipeline" row output, we still have to latch out the last row. + write_row(contrast_lut[params->frame]); + } + epd_end_frame(); + + xSemaphoreGive(params->done_smphr); + vTaskDelay(portMAX_DELAY); +} + +/******************************************************************************/ +/*** END OF FILE ***/ +/******************************************************************************/ \ No newline at end of file diff --git a/src/graphics/epd_driver.h b/src/graphics/epd_driver.h new file mode 100644 index 000000000..c47ccddc8 --- /dev/null +++ b/src/graphics/epd_driver.h @@ -0,0 +1,386 @@ +/** + * A high-level library for drawing to an EPD. + */ + +#ifndef _EPD_DRIVER_H_ +#define _EPD_DRIVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/*** include files ***/ +/******************************************************************************/ + +#include + +#include "utilities.h" +#include +#include +/******************************************************************************/ +/*** macro definitions ***/ +/******************************************************************************/ + +/** + * @brief Width of the display area in pixels. + */ +#define EPD_WIDTH 960 + +/** + * @brief Height of the display area in pixels. + */ +#define EPD_HEIGHT 540 + +/******************************************************************************/ +/*** type definitions ***/ +/******************************************************************************/ + +/** + * @brief An area on the display. + */ +typedef struct { + int32_t x; /** Horizontal position. */ + int32_t y; /** Vertical position. */ + int32_t width; /** Area / image width, must be positive. */ + int32_t height; /** Area / image height, must be positive. */ +} Rect_t; + +/** + * @brief The image drawing mode. + */ +typedef enum { + BLACK_ON_WHITE = 1 << 0, /** Draw black / grayscale image on a white display. */ + WHITE_ON_WHITE = 1 << 1, /** "Draw with white ink" on a white display. */ + WHITE_ON_BLACK = 1 << 2, /** Draw with white ink on a black display. */ +} DrawMode_t; + +/** + * @brief Font drawing flags. + */ +enum DrawFlags { + DRAW_BACKGROUND = 1 << 0, /** Draw a background. Take the background into account when calculating the size. */ +}; + +/** + * @brief Font properties. + */ +typedef struct { + uint8_t fg_color : 4; /** Foreground color */ + uint8_t bg_color : 4; /** Background color */ + uint32_t fallback_glyph; /** Use the glyph for this codepoint for missing glyphs. */ + uint32_t flags; /** Additional flags, reserved for future use */ +} FontProperties; + +/******************************************************************************/ +/*** exported variables ***/ +/******************************************************************************/ + +/******************************************************************************/ +/*** exported functions ***/ +/******************************************************************************/ + +/** + * @brief Initialize the ePaper display + */ +void epd_init(); + +/** + * @brief Enable display power supply. + */ +void epd_poweron(); + +/** + * @brief Disable display power supply. + */ +void epd_poweroff(); + +/** + * @brief Clear the whole screen by flashing it. + */ +void epd_clear(); + +void epd_poweroff_all(); + +/** + * @brief Clear an area by flashing it. + * + * @param area The area to clear. + */ +void epd_clear_area(Rect_t area); + +/** + * @brief Clear an area by flashing it. + * + * @param area The area to clear. + * @param cycles The number of black-to-white clear cycles. + * @param cycle_time Length of a cycle. Default: 50 (us). + */ +void epd_clear_area_cycles(Rect_t area, int32_t cycles, int32_t cycle_time); + +/** + * @brief Darken / lighten an area for a given time. + * + * @param area The area to darken / lighten. + * @param time The time in us to apply voltage to each pixel. + * @param color 1: lighten, 0: darken. + */ +void epd_push_pixels(Rect_t area, int16_t time, int32_t color); + +/** + * @brief Draw a picture to a given area. The image area is not cleared and + * assumed to be white before drawing. + * + * @param area The display area to draw to. `width` and `height` of the area + * must correspond to the image dimensions in pixels. + * @param data The image data, as a buffer of 4 bit wide brightness values. + * Pixel data is packed (two pixels per byte). A byte cannot wrap + * over multiple rows, images of uneven width must add a padding + * nibble per line. + */ +void IRAM_ATTR epd_draw_grayscale_image(Rect_t area, uint8_t *data); + +/** + * @brief Draw a picture to a given area, with some draw mode. + * + * @note The image area is not cleared before drawing. For example, this can be + * used for pixel-aligned clearing. + * + * @param area The display area to draw to. `width` and `height` of the area + * must correspond to the image dimensions in pixels. + * @param data The image data, as a buffer of 4 bit wide brightness values. + * Pixel data is packed (two pixels per byte). A byte cannot wrap + * over multiple rows, images of uneven width must add a padding + * nibble per line. + */ +void IRAM_ATTR epd_draw_image(Rect_t area, uint8_t *data, DrawMode_t mode); + +void IRAM_ATTR epd_draw_frame_1bit(Rect_t area, uint8_t *ptr, DrawMode_t mode, int32_t time); + +/** + * @brief Rectancle representing the whole screen area. + */ +Rect_t epd_full_screen(); + +/** + * @brief Draw a picture to a given framebuffer. + * + * @param image_area The area to copy to. `width` and `height` of the area must + * correspond to the image dimensions in pixels. + * @param image_data The image data, as a buffer of 4 bit wide brightness values. + * Pixel data is packed (two pixels per byte). A byte cannot + * wrap over multiple rows, images of uneven width must add a + * padding nibble per line. + * @param framebuffer The framebuffer object, which must + * be `EPD_WIDTH / 2 * EPD_HEIGHT` large. + */ +void epd_copy_to_framebuffer(Rect_t image_area, uint8_t *image_data, uint8_t *framebuffer); + +/** + * @brief Draw a pixel a given framebuffer. + * + * @param x Horizontal position in pixels. + * @param y Vertical position in pixels. + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to. + */ +void epd_draw_pixel(int32_t x, int32_t y, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a horizontal line to a given framebuffer. + * + * @param x Horizontal start position in pixels. + * @param y Vertical start position in pixels. + * @param length Length of the line in pixels. + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to, which must + * be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large. + */ +void epd_draw_hline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a horizontal line to a given framebuffer. + * + * @param x Horizontal start position in pixels. + * @param y Vertical start position in pixels. + * @param length Length of the line in pixels. + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to, which must + * be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large. + */ +void epd_draw_vline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a circle with given center and radius + * + * @param x0 Center-point x coordinate + * @param y0 Center-point y coordinate + * @param r Radius of the circle in pixels + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_draw_circle(int32_t x, int32_t y, int32_t r, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a circle with fill with given center and radius + * + * @param x0 Center-point x coordinate + * @param y0 Center-point y coordinate + * @param r Radius of the circle in pixels + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to, + */ +void epd_fill_circle(int32_t x, int32_t y, int32_t r, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a rectanle with no fill color + * + * @param x Top left corner x coordinate + * @param y Top left corner y coordinate + * @param w Width in pixels + * @param h Height in pixels + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to, + */ +void epd_draw_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a rectanle with fill color + * + * @param x Top left corner x coordinate + * @param y Top left corner y coordinate + * @param w Width in pixels + * @param h Height in pixels + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Write a line. Bresenham's algorithm - thx wikpedia + * + * @param x0 Start point x coordinate + * @param y0 Start point y coordinate + * @param x1 End point x coordinate + * @param y1 End point y coordinate + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_write_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a line + * + * @param x0 Start point x coordinate + * @param y0 Start point y coordinate + * @param x1 End point x coordinate + * @param y1 End point y coordinate + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer); + +/** + * @brief Draw a triangle with no fill color + * + * @param x0 Vertex #0 x coordinate + * @param y0 Vertex #0 y coordinate + * @param x1 Vertex #1 x coordinate + * @param y1 Vertex #1 y coordinate + * @param x2 Vertex #2 x coordinate + * @param y2 Vertex #2 y coordinate + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_draw_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, + uint8_t *framebuffer); + +/** + * @brief Draw a triangle with color-fill + * + * @param x0 Vertex #0 x coordinate + * @param y0 Vertex #0 y coordinate + * @param x1 Vertex #1 x coordinate + * @param y1 Vertex #1 y coordinate + * @param x2 Vertex #2 x coordinate + * @param y2 Vertex #2 y coordinate + * @param color The gray value of the line (0-255); + * @param framebuffer The framebuffer to draw to + */ +void epd_fill_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, + uint8_t *framebuffer); + +/** + * @brief Font data stored PER GLYPH + */ +typedef struct { + uint8_t width; /** Bitmap dimensions in pixels */ + uint8_t height; /** Bitmap dimensions in pixels */ + uint8_t advance_x; /** Distance to advance cursor (x axis) */ + int16_t left; /** X dist from cursor pos to UL corner */ + int16_t top; /** Y dist from cursor pos to UL corner */ + uint16_t compressed_size; /** Size of the zlib-compressed font data. */ + uint32_t data_offset; /** Pointer into GFXfont->bitmap */ +} GFXglyph; + +/** + * @brief Glyph interval structure + */ +typedef struct { + uint32_t first; /** The first unicode code point of the interval */ + uint32_t last; /** The last unicode code point of the interval */ + uint32_t offset; /** Index of the first code point into the glyph array */ +} UnicodeInterval; + +/** + * @brief Data stored for FONT AS A WHOLE + */ +typedef struct { + uint8_t *bitmap; /** Glyph bitmaps, concatenated */ + GFXglyph *glyph; /** Glyph array */ + UnicodeInterval *intervals; /** Valid unicode intervals for this font */ + uint32_t interval_count; /** Number of unicode intervals. */ + bool compressed; /** Does this font use compressed glyph bitmaps? */ + uint8_t advance_y; /** Newline distance (y axis) */ + int32_t ascender; /** Maximal height of a glyph above the base line */ + int32_t descender; /** Maximal height of a glyph below the base line */ +} GFXfont; + +/** + * @brief Get the text bounds for string, when drawn at (x, y). + * Set font properties to NULL to use the defaults. + */ +void get_text_bounds(const GFXfont *font, const char *string, int32_t *x, int32_t *y, int32_t *x1, int32_t *y1, int32_t *w, + int32_t *h, const FontProperties *props); + +/** + * @brief Write text to the EPD. + */ +void writeln(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer); + +/** + * @brief Write text to the EPD. + * + * @note If framebuffer is NULL, draw mode `mode` is used for direct drawing. + */ +void write_mode(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer, + DrawMode_t mode, const FontProperties *properties); + +/** + * @brief Get the font glyph for a unicode code point. + */ +void get_glyph(const GFXfont *font, uint32_t code_point, GFXglyph **glyph); + +/** + * @brief Write a (multi-line) string to the EPD. + */ +void write_string(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer); + +#ifdef __cplusplus +} +#endif + +#endif +/******************************************************************************/ +/*** END OF FILE ***/ +/******************************************************************************/ \ No newline at end of file From 1cc096fe2b3c21415caebbbd76e17ca850b26f87 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:40:29 +0200 Subject: [PATCH 06/10] fix lora, add v1/v2 variant targets --- src/graphics/Screen.cpp | 2 +- src/main.cpp | 11 +++ variants/esp32s3/t5s3_epaper/pins_arduino.h | 6 +- variants/esp32s3/t5s3_epaper/platformio.ini | 39 +++++++-- variants/esp32s3/t5s3_epaper/variant.h | 94 +++++++++++++++++---- 5 files changed, 124 insertions(+), 28 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index e1cc0ccad..96ae87545 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -637,7 +637,7 @@ void Screen::setup() touchScreenImpl1->init(); } } -#elif HAS_TOUCHSCREEN && !defined(USE_EINK) +#elif HAS_TOUCHSCREEN && !defined(USE_EINK) && !defined(USE_EPD) touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); diff --git a/src/main.cpp b/src/main.cpp index bb97a1aa6..0a3bcd3a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,6 +394,17 @@ void setup() io.pinMode(EXPANDS_GPIO_EN, OUTPUT); io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.pinMode(EXPANDS_SD_PULLEN, INPUT); +#elif defined(T5_S3_EPAPER_PRO) + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(BOARD_BL_EN, OUTPUT); + io.begin(Wire, PCA9535_ADDR, SDA, SCL); + io.configPort(ExtensionIOXL9555::PORT0, 0x00); + io.configPort(ExtensionIOXL9555::PORT1, 0xFF); + io.digitalWrite(PCA9535_IO00_LORA_EN, HIGH); + delay(100); #endif concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO diff --git a/variants/esp32s3/t5s3_epaper/pins_arduino.h b/variants/esp32s3/t5s3_epaper/pins_arduino.h index 1949124a6..a786213e8 100644 --- a/variants/esp32s3/t5s3_epaper/pins_arduino.h +++ b/variants/esp32s3/t5s3_epaper/pins_arduino.h @@ -12,9 +12,9 @@ static const uint8_t SCL = 40; // Default SPI will be mapped to Radio static const uint8_t SS = 46; -static const uint8_t MOSI = 17; -static const uint8_t MISO = 8; -static const uint8_t SCK = 18; +static const uint8_t MOSI = 13; +static const uint8_t MISO = 21; +static const uint8_t SCK = 14; #define SPI_MOSI (13) #define SPI_SCK (14) diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini index 1eeb7f27d..b65952be9 100644 --- a/variants/esp32s3/t5s3_epaper/platformio.ini +++ b/variants/esp32s3/t5s3_epaper/platformio.ini @@ -1,27 +1,48 @@ -[env:t5s3-epaper-inkhud] -extends = esp32s3_base, inkhud +[t5s3_epaper_base] +extends = esp32s3_base board = t5-epaper-s3 board_build.partition = default_16MB.csv board_check = false upload_protocol = esptool - build_flags = ${esp32_base.build_flags} - ${inkhud.build_flags} -I variants/esp32s3/t5s3_epaper -D T5_S3_EPAPER_PRO -D PRIVATE_HW -D GPS_POWER_TOGGLE - build_src_filter = ${esp32s3_base.build_src_filter} - ${inkhud.build_src_filter} - lib_deps = - ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip lewisxhe/XPowersLib@0.3.1 - lewisxhe/SensorLib@0.3.1 \ No newline at end of file + lewisxhe/SensorLib@0.3.1 + + +[env:t5s3_epaper_inkhud] +extends = t5s3_epaper_base, inkhud +build_flags = + ${t5s3_epaper_base.build_flags} + ${inkhud.build_flags} +build_src_filter = + ${t5s3_epaper_base.build_src_filter} + ${inkhud.build_src_filter} +lib_deps = + ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX + ${t5s3_epaper_base.lib_deps} + + +[env:t5s3-epaper-v1] ; H752 +extends = t5s3_epaper_base +build_flags = + ${t5s3_epaper_base.build_flags} + -D T5_S3_EPAPER_PRO_V1 + -D GPS_DEFAULT_NOT_PRESENT=1 + +[env:t5s3-epaper-v2] ; H752-01 +extends = t5s3_epaper_base +build_flags = + ${t5s3_epaper_base.build_flags} + -D T5_S3_EPAPER_PRO_V2 diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index e5f3fa127..2f988dc5b 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -1,12 +1,24 @@ -// TODO: Display (E-Ink) -#define PIN_EINK_EN 11 // BL -#define PIN_EINK_CS 11 -#define PIN_EINK_BUSY -1 -#define PIN_EINK_DC 21 -#define PIN_EINK_RES -1 -#define PIN_EINK_SCLK 14 -#define PIN_EINK_MOSI 13 // SDI +// Display (E-Ink) ED047TC1 +#define USE_EPD +#define BOARD_BL_EN 11 +#define EP_I2C_PORT I2C_NUM_0 +#define EP_SCL (40) +#define EP_SDA (39) +#define EP_INTR (38) +#define EP_D7 (8) +#define EP_D6 (18) +#define EP_D5 (17) +#define EP_D4 (16) +#define EP_D3 (15) +#define EP_D2 (7) +#define EP_D1 (6) +#define EP_D0 (5) +#define EP_CKV (48) +#define EP_STH (41) +#define EP_LEH (42) +#define EP_STV (45) +#define EP_CKH (4) #define EPD_WIDTH 960 #define EPD_HEIGHT 540 @@ -17,17 +29,17 @@ #define HAS_TOUCHSCREEN 1 #define GT911_PIN_SDA 39 #define GT911_PIN_SCL 40 -#define GT911_PIN_INT 15 -#define GT911_PIN_RST 41 +#define GT911_PIN_INT 3 +#define GT911_PIN_RST 9 #define PCF85063_RTC 0x51 #define HAS_RTC 1 +#define PCF85063_INT 2 #define USE_POWERSAVE #define SLEEP_TIME 120 -// optional GPS -#define GPS_DEFAULT_NOT_PRESENT 1 +// GPS #define GPS_RX_PIN 44 #define GPS_TX_PIN 43 @@ -52,13 +64,27 @@ // TPS651851 +// PCA9535 IO extender +#define USE_XL9555 +#define PCA9535_ADDR 0x20 +#define PCA9535_INT 38 +#define PCA9535_IO00_LORA_EN 00 +#define PCA9535_IO10_EP_OE 10 // EP Output enable source driver +#define PCA9535_IO11_EP_MODE 11 // EP Output mode selection gate driver +#define PCA9535_IO12_BUTTON 12 +#define PCA9535_IO13_TPS_PWRUP 13 +#define PCA9535_IO14_VCOM_CTRL 14 +#define PCA9535_IO15_TPS_WAKEUP 15 +#define PCA9535_IO16_TPS_PWR_GOOD 16 +#define PCA9535_IO17_TPS_INT 17 + // LoRa #define USE_SX1262 #define USE_SX1268 -#define LORA_SCK 18 -#define LORA_MISO 8 -#define LORA_MOSI 17 +#define LORA_SCK 14 // 18 +#define LORA_MISO 21 // 8 +#define LORA_MOSI 13 // 17 #define LORA_CS 46 #define LORA_DIO0 -1 @@ -73,3 +99,41 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 2.4 + +/* V1 + +#define BOARD_SCL (5) +#define BOARD_SDA (6) + +#define BOARD_SPI_MISO (8) +#define BOARD_SPI_MOSI (17) +#define BOARD_SPI_SCLK (18) + +#define BOARD_SD_MISO (BOARD_SPI_MISO) +#define BOARD_SD_MOSI (BOARD_SPI_MOSI) +#define BOARD_SD_SCLK (BOARD_SPI_SCLK) +#define BOARD_SD_CS (16) + +#define BOARD_LORA_MISO (BOARD_SPI_MISO) +#define BOARD_LORA_MOSI (BOARD_SPI_MOSI) +#define BOARD_LORA_SCLK (BOARD_SPI_SCLK) +#define BOARD_LORA_CS (46) +#define BOARD_LORA_IRQ (3) +#define BOARD_LORA_RST (43) +#define BOARD_LORA_BUSY (44) + +#define BOARD_TOUCH_SCL (BOARD_SCL) +#define BOARD_TOUCH_SDA (BOARD_SDA) +#define BOARD_TOUCH_INT (15) +#define BOARD_TOUCH_RST (41) + +#define BOARD_RTC_INT 7 +#define BOARD_RT_SCL (BOARD_SCL) +#define BOARD_RT_SDA (BOARD_SDA) + +#define BOARD_BL_EN (40) +#define BOARD_BATT_PIN (4) +#define BOARD_BOOT_BTN (0) +#define BOARD_KEY_BTN (48) + +*/ \ No newline at end of file From d29d02ce361d4aca4704fa846a37b1ac4a64bb23 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 19 Oct 2025 21:41:44 +0200 Subject: [PATCH 07/10] adapt pins for v1/v2 --- src/main.cpp | 2 + variants/esp32s3/t5s3_epaper/pins_arduino.h | 21 +++++- variants/esp32s3/t5s3_epaper/platformio.ini | 2 + variants/esp32s3/t5s3_epaper/variant.h | 72 ++++++++------------- 4 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0a3bcd3a0..c009b5096 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -400,11 +400,13 @@ void setup() pinMode(SDCARD_CS, OUTPUT); digitalWrite(SDCARD_CS, HIGH); pinMode(BOARD_BL_EN, OUTPUT); +#if !defined(T5_S3_EPAPER_PRO_V1) io.begin(Wire, PCA9535_ADDR, SDA, SCL); io.configPort(ExtensionIOXL9555::PORT0, 0x00); io.configPort(ExtensionIOXL9555::PORT1, 0xFF); io.digitalWrite(PCA9535_IO00_LORA_EN, HIGH); delay(100); +#endif #endif concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO diff --git a/variants/esp32s3/t5s3_epaper/pins_arduino.h b/variants/esp32s3/t5s3_epaper/pins_arduino.h index a786213e8..4978cff2a 100644 --- a/variants/esp32s3/t5s3_epaper/pins_arduino.h +++ b/variants/esp32s3/t5s3_epaper/pins_arduino.h @@ -6,7 +6,24 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -// The default Wire will be mapped to RTC and Touch +#if defined(T5_S3_EPAPER_PRO_V1) +// The default Wire will be mapped to RTC, Touch, BQ25896, and BQ27220 +static const uint8_t SDA = 6; +static const uint8_t SCL = 5; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 46; +static const uint8_t MOSI = 17; +static const uint8_t MISO = 8; +static const uint8_t SCK = 18; + +#define SPI_MOSI (17) +#define SPI_SCK (18) +#define SPI_MISO (8) +#define SPI_CS (16) + +#else // T5_S3_EPAPER_PRO_V2 +// The default Wire will be mapped to RTC, Touch, PCA9535, BQ25896, and BQ27220 static const uint8_t SDA = 39; static const uint8_t SCL = 40; @@ -21,4 +38,6 @@ static const uint8_t SCK = 14; #define SPI_MISO (21) #define SPI_CS (12) +#endif + #endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini index b65952be9..1a09099da 100644 --- a/variants/esp32s3/t5s3_epaper/platformio.ini +++ b/variants/esp32s3/t5s3_epaper/platformio.ini @@ -29,6 +29,7 @@ build_flags = build_src_filter = ${t5s3_epaper_base.build_src_filter} ${inkhud.build_src_filter} + -D SDCARD_USE_SPI1 lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${t5s3_epaper_base.lib_deps} @@ -46,3 +47,4 @@ extends = t5s3_epaper_base build_flags = ${t5s3_epaper_base.build_flags} -D T5_S3_EPAPER_PRO_V2 + -D SDCARD_USE_SPI1 diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index 2f988dc5b..c96318338 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -1,7 +1,6 @@ // Display (E-Ink) ED047TC1 #define USE_EPD -#define BOARD_BL_EN 11 #define EP_I2C_PORT I2C_NUM_0 #define EP_SCL (40) #define EP_SDA (39) @@ -23,14 +22,25 @@ #define EPD_WIDTH 960 #define EPD_HEIGHT 540 +#if defined(T5_S3_EPAPER_PRO_V1) +#define BOARD_BL_EN 40 +#else +#define BOARD_BL_EN 11 +#endif + #define I2C_SDA SDA #define I2C_SCL SCL #define HAS_TOUCHSCREEN 1 -#define GT911_PIN_SDA 39 -#define GT911_PIN_SCL 40 +#define GT911_PIN_SDA SDA +#define GT911_PIN_SCL SCL +#if defined(T5_S3_EPAPER_PRO_V1) +#define GT911_PIN_INT 15 +#define GT911_PIN_RST 41 +#else #define GT911_PIN_INT 3 #define GT911_PIN_RST 9 +#endif #define PCF85063_RTC 0x51 #define HAS_RTC 1 @@ -40,15 +50,16 @@ #define SLEEP_TIME 120 // GPS +#if !defined(T5_S3_EPAPER_PRO_V1) #define GPS_RX_PIN 44 #define GPS_TX_PIN 43 +#endif #define BUTTON_PIN 48 #define BUTTON_PIN_SECONDARY 0 // SD card #define HAS_SDCARD -#define SDCARD_USE_SPI1 #define SDCARD_CS SPI_CS #define SD_SPI_FREQUENCY 75000000U @@ -62,6 +73,7 @@ #define BQ27220_I2C_SCL SCL #define BQ27220_DESIGN_CAPACITY 1500 +#if !defined(T5_S3_EPAPER_PRO_V1) // TPS651851 // PCA9535 IO extender @@ -77,21 +89,29 @@ #define PCA9535_IO15_TPS_WAKEUP 15 #define PCA9535_IO16_TPS_PWR_GOOD 16 #define PCA9535_IO17_TPS_INT 17 +#endif // LoRa #define USE_SX1262 #define USE_SX1268 -#define LORA_SCK 14 // 18 -#define LORA_MISO 21 // 8 -#define LORA_MOSI 13 // 17 +#define LORA_SCK SCK +#define LORA_MISO MISO +#define LORA_MOSI MOSI #define LORA_CS 46 #define LORA_DIO0 -1 +#if defined(T5_S3_EPAPER_PRO_V1) +#define LORA_RESET 43 +#define LORA_DIO1 3 // SX1262 IRQ +#define LORA_DIO2 44 // SX1262 BUSY +#define LORA_DIO3 +#else #define LORA_RESET 1 #define LORA_DIO1 10 // SX1262 IRQ #define LORA_DIO2 47 // SX1262 BUSY #define LORA_DIO3 +#endif #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 @@ -99,41 +119,3 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 2.4 - -/* V1 - -#define BOARD_SCL (5) -#define BOARD_SDA (6) - -#define BOARD_SPI_MISO (8) -#define BOARD_SPI_MOSI (17) -#define BOARD_SPI_SCLK (18) - -#define BOARD_SD_MISO (BOARD_SPI_MISO) -#define BOARD_SD_MOSI (BOARD_SPI_MOSI) -#define BOARD_SD_SCLK (BOARD_SPI_SCLK) -#define BOARD_SD_CS (16) - -#define BOARD_LORA_MISO (BOARD_SPI_MISO) -#define BOARD_LORA_MOSI (BOARD_SPI_MOSI) -#define BOARD_LORA_SCLK (BOARD_SPI_SCLK) -#define BOARD_LORA_CS (46) -#define BOARD_LORA_IRQ (3) -#define BOARD_LORA_RST (43) -#define BOARD_LORA_BUSY (44) - -#define BOARD_TOUCH_SCL (BOARD_SCL) -#define BOARD_TOUCH_SDA (BOARD_SDA) -#define BOARD_TOUCH_INT (15) -#define BOARD_TOUCH_RST (41) - -#define BOARD_RTC_INT 7 -#define BOARD_RT_SCL (BOARD_SCL) -#define BOARD_RT_SDA (BOARD_SDA) - -#define BOARD_BL_EN (40) -#define BOARD_BATT_PIN (4) -#define BOARD_BOOT_BTN (0) -#define BOARD_KEY_BTN (48) - -*/ \ No newline at end of file From 799ffe1e903b86eebbc5e85e8b1a8b51ae3726a1 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 19 Oct 2025 22:00:29 +0200 Subject: [PATCH 08/10] alt button --- variants/esp32s3/t5s3_epaper/variant.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index c96318338..e0e2e2d47 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -55,8 +55,9 @@ #define GPS_TX_PIN 43 #endif -#define BUTTON_PIN 48 -#define BUTTON_PIN_SECONDARY 0 +#define BUTTON_PIN 0 +#define PIN_BUTTON2 48 +#define ALT_BUTTON_PIN PIN_BUTTON2 // SD card #define HAS_SDCARD From 32b84138f34bf5d490a0dfe268e617297ecc3422 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 19 Oct 2025 22:21:05 +0200 Subject: [PATCH 09/10] add compile guards --- src/graphics/ed047tc1.c | 8 ++++---- src/graphics/epd_driver.c | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/graphics/ed047tc1.c b/src/graphics/ed047tc1.c index d254f3196..4f4d4f73e 100644 --- a/src/graphics/ed047tc1.c +++ b/src/graphics/ed047tc1.c @@ -1,9 +1,9 @@ +#if defined(T5_S3_EPAPER_PRO) + /******************************************************************************/ /*** include files ***/ /******************************************************************************/ -#if defined(LILYGO_T5_EPD47_S3) - #include "ed047tc1.h" #include "i2s_data_bus.h" #include "rmt_pulse.h" @@ -258,7 +258,7 @@ uint8_t *IRAM_ATTR epd_get_current_buffer() /*** local functions ***/ /******************************************************************************/ -#endif /******************************************************************************/ /*** END OF FILE ***/ -/******************************************************************************/ \ No newline at end of file +/******************************************************************************/ +#endif diff --git a/src/graphics/epd_driver.c b/src/graphics/epd_driver.c index 96e831e5f..8f37a0bba 100644 --- a/src/graphics/epd_driver.c +++ b/src/graphics/epd_driver.c @@ -1,3 +1,5 @@ +#if defined(T5_S3_EPAPER_PRO) + /******************************************************************************/ /*** include files ***/ /******************************************************************************/ @@ -886,4 +888,5 @@ static void IRAM_ATTR feed_display(OutputParams *params) /******************************************************************************/ /*** END OF FILE ***/ -/******************************************************************************/ \ No newline at end of file +/******************************************************************************/ +#endif \ No newline at end of file From 066188c62b21318688ed89c681dd7fafde0a3b01 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:00:00 +0200 Subject: [PATCH 10/10] use lilygo epd47 lib --- src/graphics/ed047tc1.c | 264 ------ src/graphics/ed047tc1.h | 117 --- src/graphics/epd_driver.c | 892 -------------------- src/graphics/epd_driver.h | 386 --------- variants/esp32s3/t5s3_epaper/platformio.ini | 1 + variants/esp32s3/t5s3_epaper/variant.h | 20 +- 6 files changed, 2 insertions(+), 1678 deletions(-) delete mode 100644 src/graphics/ed047tc1.c delete mode 100644 src/graphics/ed047tc1.h delete mode 100644 src/graphics/epd_driver.c delete mode 100644 src/graphics/epd_driver.h diff --git a/src/graphics/ed047tc1.c b/src/graphics/ed047tc1.c deleted file mode 100644 index 4f4d4f73e..000000000 --- a/src/graphics/ed047tc1.c +++ /dev/null @@ -1,264 +0,0 @@ -#if defined(T5_S3_EPAPER_PRO) - -/******************************************************************************/ -/*** include files ***/ -/******************************************************************************/ - -#include "ed047tc1.h" -#include "i2s_data_bus.h" -#include "rmt_pulse.h" - -#include - -#include - -/******************************************************************************/ -/*** macro definitions ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** type definitions ***/ -/******************************************************************************/ - -typedef struct { - bool ep_latch_enable : 1; - bool power_disable : 1; - bool pos_power_enable : 1; - bool neg_power_enable : 1; - bool ep_stv : 1; - bool ep_scan_direction : 1; - bool ep_mode : 1; - bool ep_output_enable : 1; -} epd_config_register_t; - -/******************************************************************************/ -/*** local function prototypes ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** exported variables ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** local variables ***/ -/******************************************************************************/ - -static epd_config_register_t config_reg; - -/******************************************************************************/ -/*** exported functions ***/ -/******************************************************************************/ - -/* - * Write bits directly using the registers. - * Won't work for some pins (>= 32). - */ -inline static void fast_gpio_set_hi(gpio_num_t gpio_num) -{ - gpio_set_level(gpio_num, 1); -} - -inline static void fast_gpio_set_lo(gpio_num_t gpio_num) -{ - gpio_set_level(gpio_num, 0); -} - -inline static void IRAM_ATTR push_cfg_bit(bool bit) -{ - fast_gpio_set_lo(CFG_CLK); - if (bit) { - fast_gpio_set_hi(CFG_DATA); - } else { - fast_gpio_set_lo(CFG_DATA); - } - fast_gpio_set_hi(CFG_CLK); -} - -static void IRAM_ATTR push_cfg(epd_config_register_t *cfg) -{ - fast_gpio_set_lo(CFG_STR); - - // push config bits in reverse order - push_cfg_bit(cfg->ep_output_enable); - push_cfg_bit(cfg->ep_mode); - push_cfg_bit(cfg->ep_scan_direction); - push_cfg_bit(cfg->ep_stv); - - push_cfg_bit(cfg->neg_power_enable); - push_cfg_bit(cfg->pos_power_enable); - push_cfg_bit(cfg->power_disable); - push_cfg_bit(cfg->ep_latch_enable); - - fast_gpio_set_hi(CFG_STR); -} - -void IRAM_ATTR busy_delay(uint32_t cycles) -{ - volatile uint64_t counts = XTHAL_GET_CCOUNT() + cycles; - while (XTHAL_GET_CCOUNT() < counts) - ; -} - -void epd_base_init(uint32_t epd_row_width) -{ - config_reg.ep_latch_enable = false; - config_reg.power_disable = true; - config_reg.pos_power_enable = false; - config_reg.neg_power_enable = false; - config_reg.ep_stv = true; - config_reg.ep_scan_direction = true; - config_reg.ep_mode = false; - config_reg.ep_output_enable = false; - - /* Power Control Output/Off */ - gpio_reset_pin(CFG_CLK); - gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT); - gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT); - gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT); - fast_gpio_set_lo(CFG_STR); - - push_cfg(&config_reg); - - // Setup I2S - i2s_bus_config i2s_config; - // add an offset off dummy bytes to allow for enough timing headroom - i2s_config.epd_row_width = epd_row_width + 32; - i2s_config.clock = CKH; - i2s_config.start_pulse = STH; - i2s_config.data_0 = D0; - i2s_config.data_1 = D1; - i2s_config.data_2 = D2; - i2s_config.data_3 = D3; - i2s_config.data_4 = D4; - i2s_config.data_5 = D5; - i2s_config.data_6 = D6; - i2s_config.data_7 = D7; - - i2s_bus_init(&i2s_config); - - rmt_pulse_init(CKV); -} - -void epd_poweron() -{ - config_reg.ep_scan_direction = true; - config_reg.power_disable = false; - push_cfg(&config_reg); - busy_delay(100 * 240); - config_reg.neg_power_enable = true; - push_cfg(&config_reg); - busy_delay(500 * 240); - config_reg.pos_power_enable = true; - push_cfg(&config_reg); - busy_delay(100 * 240); - config_reg.ep_stv = true; - push_cfg(&config_reg); - fast_gpio_set_hi(STH); -} - -void epd_poweroff() -{ - config_reg.pos_power_enable = false; - push_cfg(&config_reg); - busy_delay(10 * 240); - config_reg.neg_power_enable = false; - push_cfg(&config_reg); - busy_delay(100 * 240); - config_reg.power_disable = true; - push_cfg(&config_reg); - - config_reg.ep_stv = false; - push_cfg(&config_reg); -} - -void epd_poweroff_all() -{ - memset(&config_reg, 0, sizeof(config_reg)); - push_cfg(&config_reg); -} - -void epd_start_frame() -{ - while (i2s_is_busy()) - ; - - config_reg.ep_mode = true; - push_cfg(&config_reg); - - pulse_ckv_us(1, 1, true); - - // This is very timing-sensitive! - config_reg.ep_stv = false; - push_cfg(&config_reg); - busy_delay(240); - pulse_ckv_us(10, 10, false); - config_reg.ep_stv = true; - push_cfg(&config_reg); - pulse_ckv_us(0, 10, true); - - config_reg.ep_output_enable = true; - push_cfg(&config_reg); - - pulse_ckv_us(1, 1, true); -} - -static inline void latch_row() -{ - config_reg.ep_latch_enable = true; - push_cfg(&config_reg); - - config_reg.ep_latch_enable = false; - push_cfg(&config_reg); -} - -void IRAM_ATTR epd_skip() -{ -#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2) - pulse_ckv_ticks(2, 2, false); -#else - // According to the spec, the OC4 maximum CKV frequency is 200kHz. - pulse_ckv_ticks(45, 5, false); -#endif -} - -void IRAM_ATTR epd_output_row(uint32_t output_time_dus) -{ - while (i2s_is_busy()) - ; - - latch_row(); - - pulse_ckv_ticks(output_time_dus, 50, false); - - i2s_start_line_output(); - i2s_switch_buffer(); -} - -void epd_end_frame() -{ - config_reg.ep_output_enable = false; - push_cfg(&config_reg); - config_reg.ep_mode = false; - push_cfg(&config_reg); - pulse_ckv_us(1, 1, true); - pulse_ckv_us(1, 1, true); -} - -void IRAM_ATTR epd_switch_buffer() -{ - i2s_switch_buffer(); -} - -uint8_t *IRAM_ATTR epd_get_current_buffer() -{ - return (uint8_t *)i2s_get_current_buffer(); -} - -/******************************************************************************/ -/*** local functions ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** END OF FILE ***/ -/******************************************************************************/ -#endif diff --git a/src/graphics/ed047tc1.h b/src/graphics/ed047tc1.h deleted file mode 100644 index 0e1f8eaa3..000000000 --- a/src/graphics/ed047tc1.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef _ED047TC1_H_ -#define _ED047TC1_H_ - -#if defined(T5_S3_EPAPER_PRO) - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/*** include files ***/ -/******************************************************************************/ - -#include - -#include - -/******************************************************************************/ -/*** macro definitions ***/ -/******************************************************************************/ - -/* Config Reggister Control */ -#define CFG_DATA GPIO_NUM_2 -#define CFG_CLK GPIO_NUM_42 -#define CFG_STR GPIO_NUM_1 - -/* Control Lines */ -#define CKV GPIO_NUM_39 -#define STH GPIO_NUM_9 - -/* Edges */ -#define CKH GPIO_NUM_10 - -/* Data Lines */ -#define D7 GPIO_NUM_38 -#define D6 GPIO_NUM_45 -#define D5 GPIO_NUM_47 -#define D4 GPIO_NUM_21 -#define D3 GPIO_NUM_14 -#define D2 GPIO_NUM_13 -#define D1 GPIO_NUM_12 -#define D0 GPIO_NUM_11 - -#else -#error "Unknown SOC" -#endif - -/******************************************************************************/ -/*** type definitions ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** exported variables ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** exported functions ***/ -/******************************************************************************/ - -void epd_base_init(uint32_t epd_row_width); -void epd_poweron(); -void epd_poweroff(); - -/** - * @brief Start a draw cycle. - */ -void epd_start_frame(); - -/** - * @brief End a draw cycle. - */ -void epd_end_frame(); - -/** - * @brief output row data - * - * @note Waits until all previously submitted data has been written. - * Then, the following operations are initiated: - * - * 1. Previously submitted data is latched to the output register. - * 2. The RMT peripheral is set up to pulse the vertical (gate) driver - * for `output_time_dus` / 10 microseconds. - * 3. The I2S peripheral starts transmission of the current buffer to - * the source driver. - * 4. The line buffers are switched. - * - * This sequence of operations allows for pipelining data preparation and - * transfer, reducing total refresh times. - */ -void IRAM_ATTR epd_output_row(uint32_t output_time_dus); - -/** - * @brief Skip a row without writing to it. - */ -void IRAM_ATTR epd_skip(); - -/** - * @brief Get the currently writable line buffer. - */ -uint8_t *IRAM_ATTR epd_get_current_buffer(); - -/** - * @brief Switches front and back line buffer. - * - * @note If the switched-to line buffer is currently in use, this function - * blocks until transmission is done. - */ -void IRAM_ATTR epd_switch_buffer(); - -#ifdef __cplusplus -} -#endif - -#endif -/******************************************************************************/ -/*** END OF FILE ***/ -/******************************************************************************/ \ No newline at end of file diff --git a/src/graphics/epd_driver.c b/src/graphics/epd_driver.c deleted file mode 100644 index 8f37a0bba..000000000 --- a/src/graphics/epd_driver.c +++ /dev/null @@ -1,892 +0,0 @@ -#if defined(T5_S3_EPAPER_PRO) - -/******************************************************************************/ -/*** include files ***/ -/******************************************************************************/ - -#include "epd_driver.h" -#include "ed047tc1.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -/******************************************************************************/ -/*** macro definitions ***/ -/******************************************************************************/ - -/** - * @brief number of bytes needed for one line of EPD pixel data. - */ -#define EPD_LINE_BYTES EPD_WIDTH / 4 - -#define CLEAR_BYTE 0B10101010 -#define DARK_BYTE 0B01010101 - -#ifndef _swap_int -#define _swap_int(a, b) \ - { \ - int32_t t = a; \ - a = b; \ - b = t; \ - } -#endif - -/******************************************************************************/ -/*** type definitions ***/ -/******************************************************************************/ - -typedef struct { - uint8_t *data_ptr; - SemaphoreHandle_t done_smphr; - Rect_t area; - int32_t frame; - DrawMode_t mode; -} OutputParams; - -/******************************************************************************/ -/*** local function prototypes ***/ -/******************************************************************************/ - -/** - * @brief Reorder the output buffer to account for I2S FIFO order. - */ -static void reorder_line_buffer(uint32_t *line_data); - -/** - * @brief output a row to the display. - */ -static void write_row(uint32_t output_time_dus); - -/** - * @brief skip a display row - */ -static void skip_row(uint8_t pipeline_finish_time); - -static void IRAM_ATTR reset_lut(uint8_t *lut_mem, DrawMode_t mode); - -static void IRAM_ATTR update_LUT(uint8_t *lut_mem, uint8_t k, DrawMode_t mode); - -/** - * @brief bit-shift a buffer `shift` <= 7 bits to the right. - */ -static void IRAM_ATTR bit_shift_buffer_right(uint8_t *buf, uint32_t len, int32_t shift); - -static void IRAM_ATTR nibble_shift_buffer_right(uint8_t *buf, uint32_t len); - -static void IRAM_ATTR provide_out(OutputParams *params); - -static void IRAM_ATTR feed_display(OutputParams *params); - -static void epd_fill_circle_helper(int32_t x0, int32_t y0, int32_t r, int32_t corners, int32_t delta, uint8_t color, - uint8_t *framebuffer); - -/******************************************************************************/ -/*** exported variables ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** local variables ***/ -/******************************************************************************/ - -/** - * @brief status tracker for row skipping - */ -static uint32_t skipping; - -/* 4bpp Contrast cycles in order of contrast (Darkest first). */ -static const int32_t contrast_cycles_4[15] = {30, 30, 20, 20, 30, 30, 30, 40, 40, 50, 50, 50, 100, 200, 300}; - -static const int32_t contrast_cycles_4_white[15] = {10, 10, 8, 8, 8, 8, 8, 10, 10, 15, 15, 20, 20, 100, 300}; - -// Heap space to use for the EPD output lookup table, which -// is calculated for each cycle. -static uint8_t *conversion_lut; -static QueueHandle_t output_queue; - -static const DRAM_ATTR uint32_t lut_1bpp[256] = { - 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, - 0x0055, 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, - 0x0154, 0x0155, 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, - 0x0451, 0x0454, 0x0455, 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, 0x0540, 0x0541, 0x0544, 0x0545, - 0x0550, 0x0551, 0x0554, 0x0555, 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 0x1040, 0x1041, 0x1044, - 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141, - 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, - 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, - 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, - 0x4015, 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, - 0x4114, 0x4115, 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, - 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504, 0x4505, - 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, - 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, 0x5100, 0x5101, - 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400, - 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, - 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, - 0x5555}; - -/******************************************************************************/ -/*** exported functions ***/ -/******************************************************************************/ - -void epd_init() -{ - skipping = 0; - epd_base_init(EPD_WIDTH); - - conversion_lut = (uint8_t *)heap_caps_malloc(1 << 16, MALLOC_CAP_8BIT); - assert(conversion_lut != NULL); - output_queue = xQueueCreate(64, EPD_WIDTH / 2); -} - -void epd_push_pixels(Rect_t area, int16_t time, int32_t color) -{ - uint8_t row[EPD_LINE_BYTES] = {0}; - - for (uint32_t i = 0; i < area.width; i++) { - uint32_t position = i + area.x % 4; - uint8_t mask = (color ? CLEAR_BYTE : DARK_BYTE) & (0b00000011 << (2 * (position % 4))); - row[area.x / 4 + position / 4] |= mask; - } - reorder_line_buffer((uint32_t *)row); - - epd_start_frame(); - - for (int32_t i = 0; i < EPD_HEIGHT; i++) { - // before are of interest: skip - if (i < area.y) { - skip_row(time); - // start area of interest: set row data - } else if (i == area.y) { - epd_switch_buffer(); - memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES); - epd_switch_buffer(); - memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES); - - write_row(time * 10); - // load nop row if done with area - } else if (i >= area.y + area.height) { - skip_row(time); - // output the same as before - } else { - write_row(time * 10); - } - } - // Since we "pipeline" row output, we still have to latch out the last row. - write_row(time * 10); - - epd_end_frame(); -} - -void epd_clear_area(Rect_t area) -{ - epd_clear_area_cycles(area, 4, 50); -} - -void epd_clear_area_cycles(Rect_t area, int32_t cycles, int32_t cycle_time) -{ - const int16_t white_time = cycle_time; - const int16_t dark_time = cycle_time; - - for (int32_t c = 0; c < cycles; c++) { - for (int32_t i = 0; i < 4; i++) { - epd_push_pixels(area, dark_time, 0); - } - for (int32_t i = 0; i < 4; i++) { - epd_push_pixels(area, white_time, 1); - } - } -} - -Rect_t epd_full_screen() -{ - Rect_t area = {.x = 0, .y = 0, .width = EPD_WIDTH, .height = EPD_HEIGHT}; - return area; -} - -void epd_clear() -{ - epd_clear_area(epd_full_screen()); -} - -void IRAM_ATTR calc_epd_input_4bpp(uint32_t *line_data, uint8_t *epd_input, uint8_t k, uint8_t *conversion_lut) -{ - uint32_t *wide_epd_input = (uint32_t *)epd_input; - uint16_t *line_data_16 = (uint16_t *)line_data; - - // this is reversed for little-endian, but this is later compensated - // through the output peripheral. - for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) { - uint16_t v1 = *(line_data_16++); - uint16_t v2 = *(line_data_16++); - uint16_t v3 = *(line_data_16++); - uint16_t v4 = *(line_data_16++); -#if USER_I2S_REG - uint32_t pixel = conversion_lut[v1] << 16 | conversion_lut[v2] << 24 | conversion_lut[v3] | conversion_lut[v4] << 8; -#else - uint32_t pixel = - (conversion_lut[v1]) << 0 | (conversion_lut[v2]) << 8 | (conversion_lut[v3]) << 16 | (conversion_lut[v4]) << 24; -#endif - wide_epd_input[j] = pixel; - } -} - -void IRAM_ATTR calc_epd_input_1bpp(uint8_t *line_data, uint8_t *epd_input, DrawMode_t mode) -{ - uint32_t *wide_epd_input = (uint32_t *)epd_input; - - // this is reversed for little-endian, but this is later compensated - // through the output peripheral. - for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) { - uint8_t v1 = *(line_data++); - uint8_t v2 = *(line_data++); - wide_epd_input[j] = (lut_1bpp[v1] << 16) | lut_1bpp[v2]; - } -} - -inline uint32_t min(uint32_t x, uint32_t y) -{ - return x < y ? x : y; -} - -void epd_draw_hline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer) -{ - for (int32_t i = 0; i < length; i++) { - int32_t xx = x + i; - epd_draw_pixel(xx, y, color, framebuffer); - } -} - -void epd_draw_vline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer) -{ - for (int32_t i = 0; i < length; i++) { - int32_t yy = y + i; - epd_draw_pixel(x, yy, color, framebuffer); - } -} - -void epd_draw_pixel(int32_t x, int32_t y, uint8_t color, uint8_t *framebuffer) -{ - if (x < 0 || x >= EPD_WIDTH) { - return; - } - if (y < 0 || y >= EPD_HEIGHT) { - return; - } - uint8_t *buf_ptr = &framebuffer[y * EPD_WIDTH / 2 + x / 2]; - if (x % 2) { - *buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0); - } else { - *buf_ptr = (*buf_ptr & 0xF0) | (color >> 4); - } -} - -void epd_draw_circle(int32_t x0, int32_t y0, int32_t r, uint8_t color, uint8_t *framebuffer) -{ - int32_t f = 1 - r; - int32_t ddF_x = 1; - int32_t ddF_y = -2 * r; - int32_t x = 0; - int32_t y = r; - - epd_draw_pixel(x0, y0 + r, color, framebuffer); - epd_draw_pixel(x0, y0 - r, color, framebuffer); - epd_draw_pixel(x0 + r, y0, color, framebuffer); - epd_draw_pixel(x0 - r, y0, color, framebuffer); - - while (x < y) { - if (f >= 0) { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - - epd_draw_pixel(x0 + x, y0 + y, color, framebuffer); - epd_draw_pixel(x0 - x, y0 + y, color, framebuffer); - epd_draw_pixel(x0 + x, y0 - y, color, framebuffer); - epd_draw_pixel(x0 - x, y0 - y, color, framebuffer); - epd_draw_pixel(x0 + y, y0 + x, color, framebuffer); - epd_draw_pixel(x0 - y, y0 + x, color, framebuffer); - epd_draw_pixel(x0 + y, y0 - x, color, framebuffer); - epd_draw_pixel(x0 - y, y0 - x, color, framebuffer); - } -} - -void epd_fill_circle(int32_t x0, int32_t y0, int32_t r, uint8_t color, uint8_t *framebuffer) -{ - epd_draw_vline(x0, y0 - r, 2 * r + 1, color, framebuffer); - epd_fill_circle_helper(x0, y0, r, 3, 0, color, framebuffer); -} - -static void epd_fill_circle_helper(int32_t x0, int32_t y0, int32_t r, int32_t corners, int32_t delta, uint8_t color, - uint8_t *framebuffer) -{ - int32_t f = 1 - r; - int32_t ddF_x = 1; - int32_t ddF_y = -2 * r; - int32_t x = 0; - int32_t y = r; - int32_t px = x; - int32_t py = y; - - delta++; // Avoid some +1's in the loop - - while (x < y) { - if (f >= 0) { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - // These checks avoid double-drawing certain lines, important - // for the SSD1306 library which has an INVERT drawing mode. - if (x < (y + 1)) { - if (corners & 1) - epd_draw_vline(x0 + x, y0 - y, 2 * y + delta, color, framebuffer); - if (corners & 2) - epd_draw_vline(x0 - x, y0 - y, 2 * y + delta, color, framebuffer); - } - if (y != py) { - if (corners & 1) - epd_draw_vline(x0 + py, y0 - px, 2 * px + delta, color, framebuffer); - if (corners & 2) - epd_draw_vline(x0 - py, y0 - px, 2 * px + delta, color, framebuffer); - py = y; - } - px = x; - } -} - -void epd_draw_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer) -{ - epd_draw_hline(x, y, w, color, framebuffer); - epd_draw_hline(x, y + h - 1, w, color, framebuffer); - epd_draw_vline(x, y, h, color, framebuffer); - epd_draw_vline(x + w - 1, y, h, color, framebuffer); -} - -void epd_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer) -{ - for (int32_t i = x; i < x + w; i++) { - epd_draw_vline(i, y, h, color, framebuffer); - } -} - -void epd_write_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer) -{ - int32_t steep = abs(y1 - y0) > abs(x1 - x0); - if (steep) { - _swap_int(x0, y0); - _swap_int(x1, y1); - } - - if (x0 > x1) { - _swap_int(x0, x1); - _swap_int(y0, y1); - } - - int32_t dx, dy; - dx = x1 - x0; - dy = abs(y1 - y0); - - int32_t err = dx / 2; - int32_t ystep; - - if (y0 < y1) { - ystep = 1; - } else { - ystep = -1; - } - - for (; x0 <= x1; x0++) { - if (steep) { - epd_draw_pixel(y0, x0, color, framebuffer); - } else { - epd_draw_pixel(x0, y0, color, framebuffer); - } - err -= dy; - if (err < 0) { - y0 += ystep; - err += dx; - } - } -} - -void epd_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer) -{ - // Update in subclasses if desired! - if (x0 == x1) { - if (y0 > y1) - _swap_int(y0, y1); - epd_draw_vline(x0, y0, y1 - y0 + 1, color, framebuffer); - } else if (y0 == y1) { - if (x0 > x1) - _swap_int(x0, x1); - epd_draw_hline(x0, y0, x1 - x0 + 1, color, framebuffer); - } else { - epd_write_line(x0, y0, x1, y1, color, framebuffer); - } -} - -void epd_draw_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, - uint8_t *framebuffer) -{ - epd_draw_line(x0, y0, x1, y1, color, framebuffer); - epd_draw_line(x1, y1, x2, y2, color, framebuffer); - epd_draw_line(x2, y2, x0, y0, color, framebuffer); -} - -void epd_fill_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, - uint8_t *framebuffer) -{ - int32_t a, b, y, last; - - // Sort coordinates by Y order (y2 >= y1 >= y0) - if (y0 > y1) { - _swap_int(y0, y1); - _swap_int(x0, x1); - } - if (y1 > y2) { - _swap_int(y2, y1); - _swap_int(x2, x1); - } - if (y0 > y1) { - _swap_int(y0, y1); - _swap_int(x0, x1); - } - - if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing - a = b = x0; - if (x1 < a) - a = x1; - else if (x1 > b) - b = x1; - if (x2 < a) - a = x2; - else if (x2 > b) - b = x2; - epd_draw_hline(a, y0, b - a + 1, color, framebuffer); - return; - } - - int32_t dx01 = x1 - x0; - int32_t dy01 = y1 - y0; - int32_t dx02 = x2 - x0; - int32_t dy02 = y2 - y0; - int32_t dx12 = x2 - x1; - int32_t dy12 = y2 - y1; - int32_t sa = 0; - int32_t sb = 0; - - // For upper part of triangle, find scanline crossings for segments - // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 - // is included here (and second loop will be skipped, avoiding a /0 - // error there), otherwise scanline y1 is skipped here and handled - // in the second loop...which also avoids a /0 error here if y0=y1 - // (flat-topped triangle). - if (y1 == y2) - last = y1; // Include y1 scanline - else - last = y1 - 1; // Skip it - - for (y = y0; y <= last; y++) { - a = x0 + sa / dy01; - b = x0 + sb / dy02; - sa += dx01; - sb += dx02; - /* longhand: - a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); - b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); - */ - if (a > b) - _swap_int(a, b); - epd_draw_hline(a, y, b - a + 1, color, framebuffer); - } - - // For lower part of triangle, find scanline crossings for segments - // 0-2 and 1-2. This loop is skipped if y1=y2. - sa = (int32_t)dx12 * (y - y1); - sb = (int32_t)dx02 * (y - y0); - for (; y <= y2; y++) { - a = x1 + sa / dy12; - b = x0 + sb / dy02; - sa += dx12; - sb += dx02; - /* longhand: - a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); - b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); - */ - if (a > b) - _swap_int(a, b); - epd_draw_hline(a, y, b - a + 1, color, framebuffer); - } -} - -void epd_copy_to_framebuffer(Rect_t image_area, uint8_t *image_data, uint8_t *framebuffer) -{ - assert(image_data != NULL || framebuffer != NULL); - - for (uint32_t i = 0; i < image_area.width * image_area.height; i++) { - uint32_t value_index = i; - // for images of uneven width, - // consume an additional nibble per row. - if (image_area.width % 2) { - value_index += i / image_area.width; - } - uint8_t val = (value_index % 2) ? (image_data[value_index / 2] & 0xF0) >> 4 : image_data[value_index / 2] & 0x0F; - - int32_t xx = image_area.x + i % image_area.width; - if (xx < 0 || xx >= EPD_WIDTH) { - continue; - } - int32_t yy = image_area.y + i / image_area.width; - if (yy < 0 || yy >= EPD_HEIGHT) { - continue; - } - uint8_t *buf_ptr = &framebuffer[yy * EPD_WIDTH / 2 + xx / 2]; - if (xx % 2) { - *buf_ptr = (*buf_ptr & 0x0F) | (val << 4); - } else { - *buf_ptr = (*buf_ptr & 0xF0) | val; - } - } -} - -void IRAM_ATTR epd_draw_grayscale_image(Rect_t area, uint8_t *data) -{ - epd_draw_image(area, data, BLACK_ON_WHITE); -} - -void IRAM_ATTR epd_draw_frame_1bit(Rect_t area, uint8_t *ptr, DrawMode_t mode, int32_t time) -{ - epd_start_frame(); - uint8_t line[EPD_WIDTH / 8]; - memset(line, 0, sizeof(line)); - - if (area.x < 0) { - ptr += -area.x / 8; - } - - int32_t ceil_byte_width = (area.width / 8 + (area.width % 8 > 0)); - if (area.y < 0) { - ptr += ceil_byte_width * -area.y; - } - - for (int32_t i = 0; i < EPD_HEIGHT; i++) { - if (i < area.y || i >= area.y + area.height) { - skip_row(time); - continue; - } - - uint8_t *lp; - bool shifted = 0; - if (area.width == EPD_WIDTH && area.x == 0) { - lp = ptr; - ptr += EPD_WIDTH / 8; - } else { - uint8_t *buf_start = (uint8_t *)line; - uint32_t line_bytes = ceil_byte_width; - if (area.x >= 0) { - buf_start += area.x / 8; - } else { - // reduce line_bytes to actually used bytes - line_bytes += area.x / 8; - } - line_bytes = min(line_bytes, EPD_WIDTH / 8 - (uint32_t)(buf_start - line)); - memcpy(buf_start, ptr, line_bytes); - ptr += ceil_byte_width; - - // mask last n bits if width is not divisible by 8 - if (area.width % 8 != 0 && ceil_byte_width + 1 < EPD_WIDTH) { - uint8_t mask = 0; - for (int32_t s = 0; s < area.width % 8; s++) { - mask = (mask << 1) | 1; - } - *(buf_start + line_bytes - 1) &= mask; - } - - if (area.x % 8 != 0 && area.x < EPD_WIDTH) { - // shift to right - shifted = true; - bit_shift_buffer_right(buf_start, min(line_bytes + 1, (uint32_t)line + EPD_WIDTH / 8 - (uint32_t)buf_start), - area.x % 8); - } - lp = line; - } - calc_epd_input_1bpp(lp, epd_get_current_buffer(), mode); - epd_output_row(time); - if (shifted) { - memset(line, 0, sizeof(line)); - } - } - if (!skipping) { - epd_output_row(time); - } - epd_end_frame(); -} - -void IRAM_ATTR epd_draw_image(Rect_t area, uint8_t *data, DrawMode_t mode) -{ - uint8_t frame_count = 15; - - SemaphoreHandle_t fetch_sem = xSemaphoreCreateBinary(); - SemaphoreHandle_t feed_sem = xSemaphoreCreateBinary(); - vTaskDelay(10); - for (uint8_t k = 0; k < frame_count; k++) { - OutputParams p1 = { - .area = area, - .data_ptr = data, - .frame = k, - .mode = mode, - .done_smphr = fetch_sem, - }; - OutputParams p2 = { - .area = area, - .data_ptr = data, - .frame = k, - .mode = mode, - .done_smphr = feed_sem, - }; - - TaskHandle_t t1, t2; - xTaskCreatePinnedToCore((void (*)(void *))provide_out, "privide_out", 8192, &p1, 10, &t1, 0); - xTaskCreatePinnedToCore((void (*)(void *))feed_display, "render", 8192, &p2, 10, &t2, 1); - - xSemaphoreTake(fetch_sem, portMAX_DELAY); - xSemaphoreTake(feed_sem, portMAX_DELAY); - - vTaskDelete(t1); - vTaskDelete(t2); - vTaskDelay(5); - } - vSemaphoreDelete(fetch_sem); - vSemaphoreDelete(feed_sem); -} - -/******************************************************************************/ -/*** local functions ***/ -/******************************************************************************/ - -static void write_row(uint32_t output_time_dus) -{ - // avoid too light output after skipping on some displays - if (skipping) { - // vTaskDelay(20); - } - skipping = 0; - epd_output_row(output_time_dus); -} - -static void skip_row(uint8_t pipeline_finish_time) -{ - // output previously loaded row, fill buffer with no-ops. - if (skipping == 0) { - epd_switch_buffer(); - memset(epd_get_current_buffer(), 0, EPD_LINE_BYTES); - epd_switch_buffer(); - memset(epd_get_current_buffer(), 0, EPD_LINE_BYTES); - epd_output_row(pipeline_finish_time); - // avoid tainting of following rows by - // allowing residual charge to dissipate - // vTaskDelay(10); - /* - unsigned counts = XTHAL_GET_CCOUNT() + 50 * 240; - while (XTHAL_GET_CCOUNT() < counts) { - }; - */ - } else if (skipping < 2) { - epd_output_row(10); - } else { - // epd_output_row(5); - epd_skip(); - } - skipping++; -} - -static void reorder_line_buffer(uint32_t *line_data) -{ - for (uint32_t i = 0; i < EPD_LINE_BYTES / 4; i++) { - uint32_t val = *line_data; - *(line_data++) = val >> 16 | ((val & 0x0000FFFF) << 16); - } -} - -static void IRAM_ATTR reset_lut(uint8_t *lut_mem, DrawMode_t mode) -{ - switch (mode) { - case BLACK_ON_WHITE: - memset(lut_mem, 0x55, (1 << 16)); - break; - case WHITE_ON_BLACK: - case WHITE_ON_WHITE: - memset(lut_mem, 0xAA, (1 << 16)); - break; - default: - ESP_LOGW("epd_driver", "unknown draw mode %d!", mode); - break; - } -} - -static void IRAM_ATTR update_LUT(uint8_t *lut_mem, uint8_t k, DrawMode_t mode) -{ - if (mode == BLACK_ON_WHITE || mode == WHITE_ON_WHITE) { - k = 15 - k; - } - - // reset the pixels which are not to be lightened / darkened - // any longer in the current frame - for (uint32_t l = k; l < (1 << 16); l += 16) { - lut_mem[l] &= 0xFC; - } - - for (uint32_t l = (k << 4); l < (1 << 16); l += (1 << 8)) { - for (uint32_t p = 0; p < 16; p++) { - lut_mem[l + p] &= 0xF3; - } - } - for (uint32_t l = (k << 8); l < (1 << 16); l += (1 << 12)) { - for (uint32_t p = 0; p < (1 << 8); p++) { - lut_mem[l + p] &= 0xCF; - } - } - for (uint32_t p = (k << 12); p < ((k + 1) << 12); p++) { - lut_mem[p] &= 0x3F; - } -} - -static void IRAM_ATTR bit_shift_buffer_right(uint8_t *buf, uint32_t len, int32_t shift) -{ - uint8_t carry = 0x00; - for (uint32_t i = 0; i < len; i++) { - uint8_t val = buf[i]; - buf[i] = (val << shift) | carry; - carry = val >> (8 - shift); - } -} - -static void IRAM_ATTR nibble_shift_buffer_right(uint8_t *buf, uint32_t len) -{ - uint8_t carry = 0xF; - for (uint32_t i = 0; i < len; i++) { - uint8_t val = buf[i]; - buf[i] = (val << 4) | carry; - carry = (val & 0xF0) >> 4; - } -} - -static void IRAM_ATTR provide_out(OutputParams *params) -{ - uint8_t line[EPD_WIDTH / 2]; - memset(line, 255, EPD_WIDTH / 2); - Rect_t area = params->area; - uint8_t *ptr = params->data_ptr; - - if (params->frame == 0) { - reset_lut(conversion_lut, params->mode); - } - - update_LUT(conversion_lut, params->frame, params->mode); - - if (area.x < 0) { - ptr += -area.x / 2; - } - if (area.y < 0) { - ptr += (area.width / 2 + area.width % 2) * -area.y; - } - - for (int32_t i = 0; i < EPD_HEIGHT; i++) { - if (i < area.y || i >= area.y + area.height) { - continue; - } - - uint32_t *lp; - bool shifted = false; - if (area.width == EPD_WIDTH && area.x == 0) { - lp = (uint32_t *)ptr; - ptr += EPD_WIDTH / 2; - } else { - uint8_t *buf_start = (uint8_t *)line; - uint32_t line_bytes = area.width / 2 + area.width % 2; - if (area.x >= 0) { - buf_start += area.x / 2; - } else { - // reduce line_bytes to actually used bytes - line_bytes += area.x / 2; - } - line_bytes = min(line_bytes, EPD_WIDTH / 2 - (uint32_t)(buf_start - line)); - memcpy(buf_start, ptr, line_bytes); - ptr += area.width / 2 + area.width % 2; - - // mask last nibble for uneven width - if (area.width % 2 == 1 && area.x / 2 + area.width / 2 + 1 < EPD_WIDTH) { - *(buf_start + line_bytes - 1) |= 0xF0; - } - if (area.x % 2 == 1 && area.x < EPD_WIDTH) { - shifted = true; - // shift one nibble to right - nibble_shift_buffer_right(buf_start, min(line_bytes + 1, (uint32_t)line + EPD_WIDTH / 2 - (uint32_t)buf_start)); - } - lp = (uint32_t *)line; - } - xQueueSendToBack(output_queue, lp, portMAX_DELAY); - if (shifted) { - memset(line, 255, EPD_WIDTH / 2); - } - } - - xSemaphoreGive(params->done_smphr); - vTaskDelay(portMAX_DELAY); -} - -static void IRAM_ATTR feed_display(OutputParams *params) -{ - Rect_t area = params->area; - const int32_t *contrast_lut = contrast_cycles_4; - switch (params->mode) { - case WHITE_ON_WHITE: - case BLACK_ON_WHITE: - contrast_lut = contrast_cycles_4; - break; - case WHITE_ON_BLACK: - contrast_lut = contrast_cycles_4_white; - break; - } - - epd_start_frame(); - for (int32_t i = 0; i < EPD_HEIGHT; i++) { - if (i < area.y || i >= area.y + area.height) { - skip_row(contrast_lut[params->frame]); - continue; - } - uint8_t output[EPD_WIDTH / 2]; - xQueueReceive(output_queue, output, portMAX_DELAY); - calc_epd_input_4bpp((uint32_t *)output, epd_get_current_buffer(), params->frame, conversion_lut); - write_row(contrast_lut[params->frame]); - } - if (!skipping) { - // Since we "pipeline" row output, we still have to latch out the last row. - write_row(contrast_lut[params->frame]); - } - epd_end_frame(); - - xSemaphoreGive(params->done_smphr); - vTaskDelay(portMAX_DELAY); -} - -/******************************************************************************/ -/*** END OF FILE ***/ -/******************************************************************************/ -#endif \ No newline at end of file diff --git a/src/graphics/epd_driver.h b/src/graphics/epd_driver.h deleted file mode 100644 index c47ccddc8..000000000 --- a/src/graphics/epd_driver.h +++ /dev/null @@ -1,386 +0,0 @@ -/** - * A high-level library for drawing to an EPD. - */ - -#ifndef _EPD_DRIVER_H_ -#define _EPD_DRIVER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/*** include files ***/ -/******************************************************************************/ - -#include - -#include "utilities.h" -#include -#include -/******************************************************************************/ -/*** macro definitions ***/ -/******************************************************************************/ - -/** - * @brief Width of the display area in pixels. - */ -#define EPD_WIDTH 960 - -/** - * @brief Height of the display area in pixels. - */ -#define EPD_HEIGHT 540 - -/******************************************************************************/ -/*** type definitions ***/ -/******************************************************************************/ - -/** - * @brief An area on the display. - */ -typedef struct { - int32_t x; /** Horizontal position. */ - int32_t y; /** Vertical position. */ - int32_t width; /** Area / image width, must be positive. */ - int32_t height; /** Area / image height, must be positive. */ -} Rect_t; - -/** - * @brief The image drawing mode. - */ -typedef enum { - BLACK_ON_WHITE = 1 << 0, /** Draw black / grayscale image on a white display. */ - WHITE_ON_WHITE = 1 << 1, /** "Draw with white ink" on a white display. */ - WHITE_ON_BLACK = 1 << 2, /** Draw with white ink on a black display. */ -} DrawMode_t; - -/** - * @brief Font drawing flags. - */ -enum DrawFlags { - DRAW_BACKGROUND = 1 << 0, /** Draw a background. Take the background into account when calculating the size. */ -}; - -/** - * @brief Font properties. - */ -typedef struct { - uint8_t fg_color : 4; /** Foreground color */ - uint8_t bg_color : 4; /** Background color */ - uint32_t fallback_glyph; /** Use the glyph for this codepoint for missing glyphs. */ - uint32_t flags; /** Additional flags, reserved for future use */ -} FontProperties; - -/******************************************************************************/ -/*** exported variables ***/ -/******************************************************************************/ - -/******************************************************************************/ -/*** exported functions ***/ -/******************************************************************************/ - -/** - * @brief Initialize the ePaper display - */ -void epd_init(); - -/** - * @brief Enable display power supply. - */ -void epd_poweron(); - -/** - * @brief Disable display power supply. - */ -void epd_poweroff(); - -/** - * @brief Clear the whole screen by flashing it. - */ -void epd_clear(); - -void epd_poweroff_all(); - -/** - * @brief Clear an area by flashing it. - * - * @param area The area to clear. - */ -void epd_clear_area(Rect_t area); - -/** - * @brief Clear an area by flashing it. - * - * @param area The area to clear. - * @param cycles The number of black-to-white clear cycles. - * @param cycle_time Length of a cycle. Default: 50 (us). - */ -void epd_clear_area_cycles(Rect_t area, int32_t cycles, int32_t cycle_time); - -/** - * @brief Darken / lighten an area for a given time. - * - * @param area The area to darken / lighten. - * @param time The time in us to apply voltage to each pixel. - * @param color 1: lighten, 0: darken. - */ -void epd_push_pixels(Rect_t area, int16_t time, int32_t color); - -/** - * @brief Draw a picture to a given area. The image area is not cleared and - * assumed to be white before drawing. - * - * @param area The display area to draw to. `width` and `height` of the area - * must correspond to the image dimensions in pixels. - * @param data The image data, as a buffer of 4 bit wide brightness values. - * Pixel data is packed (two pixels per byte). A byte cannot wrap - * over multiple rows, images of uneven width must add a padding - * nibble per line. - */ -void IRAM_ATTR epd_draw_grayscale_image(Rect_t area, uint8_t *data); - -/** - * @brief Draw a picture to a given area, with some draw mode. - * - * @note The image area is not cleared before drawing. For example, this can be - * used for pixel-aligned clearing. - * - * @param area The display area to draw to. `width` and `height` of the area - * must correspond to the image dimensions in pixels. - * @param data The image data, as a buffer of 4 bit wide brightness values. - * Pixel data is packed (two pixels per byte). A byte cannot wrap - * over multiple rows, images of uneven width must add a padding - * nibble per line. - */ -void IRAM_ATTR epd_draw_image(Rect_t area, uint8_t *data, DrawMode_t mode); - -void IRAM_ATTR epd_draw_frame_1bit(Rect_t area, uint8_t *ptr, DrawMode_t mode, int32_t time); - -/** - * @brief Rectancle representing the whole screen area. - */ -Rect_t epd_full_screen(); - -/** - * @brief Draw a picture to a given framebuffer. - * - * @param image_area The area to copy to. `width` and `height` of the area must - * correspond to the image dimensions in pixels. - * @param image_data The image data, as a buffer of 4 bit wide brightness values. - * Pixel data is packed (two pixels per byte). A byte cannot - * wrap over multiple rows, images of uneven width must add a - * padding nibble per line. - * @param framebuffer The framebuffer object, which must - * be `EPD_WIDTH / 2 * EPD_HEIGHT` large. - */ -void epd_copy_to_framebuffer(Rect_t image_area, uint8_t *image_data, uint8_t *framebuffer); - -/** - * @brief Draw a pixel a given framebuffer. - * - * @param x Horizontal position in pixels. - * @param y Vertical position in pixels. - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to. - */ -void epd_draw_pixel(int32_t x, int32_t y, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a horizontal line to a given framebuffer. - * - * @param x Horizontal start position in pixels. - * @param y Vertical start position in pixels. - * @param length Length of the line in pixels. - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to, which must - * be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large. - */ -void epd_draw_hline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a horizontal line to a given framebuffer. - * - * @param x Horizontal start position in pixels. - * @param y Vertical start position in pixels. - * @param length Length of the line in pixels. - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to, which must - * be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large. - */ -void epd_draw_vline(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a circle with given center and radius - * - * @param x0 Center-point x coordinate - * @param y0 Center-point y coordinate - * @param r Radius of the circle in pixels - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_draw_circle(int32_t x, int32_t y, int32_t r, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a circle with fill with given center and radius - * - * @param x0 Center-point x coordinate - * @param y0 Center-point y coordinate - * @param r Radius of the circle in pixels - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to, - */ -void epd_fill_circle(int32_t x, int32_t y, int32_t r, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a rectanle with no fill color - * - * @param x Top left corner x coordinate - * @param y Top left corner y coordinate - * @param w Width in pixels - * @param h Height in pixels - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to, - */ -void epd_draw_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a rectanle with fill color - * - * @param x Top left corner x coordinate - * @param y Top left corner y coordinate - * @param w Width in pixels - * @param h Height in pixels - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Write a line. Bresenham's algorithm - thx wikpedia - * - * @param x0 Start point x coordinate - * @param y0 Start point y coordinate - * @param x1 End point x coordinate - * @param y1 End point y coordinate - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_write_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a line - * - * @param x0 Start point x coordinate - * @param y0 Start point y coordinate - * @param x1 End point x coordinate - * @param y1 End point y coordinate - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color, uint8_t *framebuffer); - -/** - * @brief Draw a triangle with no fill color - * - * @param x0 Vertex #0 x coordinate - * @param y0 Vertex #0 y coordinate - * @param x1 Vertex #1 x coordinate - * @param y1 Vertex #1 y coordinate - * @param x2 Vertex #2 x coordinate - * @param y2 Vertex #2 y coordinate - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_draw_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, - uint8_t *framebuffer); - -/** - * @brief Draw a triangle with color-fill - * - * @param x0 Vertex #0 x coordinate - * @param y0 Vertex #0 y coordinate - * @param x1 Vertex #1 x coordinate - * @param y1 Vertex #1 y coordinate - * @param x2 Vertex #2 x coordinate - * @param y2 Vertex #2 y coordinate - * @param color The gray value of the line (0-255); - * @param framebuffer The framebuffer to draw to - */ -void epd_fill_triangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t color, - uint8_t *framebuffer); - -/** - * @brief Font data stored PER GLYPH - */ -typedef struct { - uint8_t width; /** Bitmap dimensions in pixels */ - uint8_t height; /** Bitmap dimensions in pixels */ - uint8_t advance_x; /** Distance to advance cursor (x axis) */ - int16_t left; /** X dist from cursor pos to UL corner */ - int16_t top; /** Y dist from cursor pos to UL corner */ - uint16_t compressed_size; /** Size of the zlib-compressed font data. */ - uint32_t data_offset; /** Pointer into GFXfont->bitmap */ -} GFXglyph; - -/** - * @brief Glyph interval structure - */ -typedef struct { - uint32_t first; /** The first unicode code point of the interval */ - uint32_t last; /** The last unicode code point of the interval */ - uint32_t offset; /** Index of the first code point into the glyph array */ -} UnicodeInterval; - -/** - * @brief Data stored for FONT AS A WHOLE - */ -typedef struct { - uint8_t *bitmap; /** Glyph bitmaps, concatenated */ - GFXglyph *glyph; /** Glyph array */ - UnicodeInterval *intervals; /** Valid unicode intervals for this font */ - uint32_t interval_count; /** Number of unicode intervals. */ - bool compressed; /** Does this font use compressed glyph bitmaps? */ - uint8_t advance_y; /** Newline distance (y axis) */ - int32_t ascender; /** Maximal height of a glyph above the base line */ - int32_t descender; /** Maximal height of a glyph below the base line */ -} GFXfont; - -/** - * @brief Get the text bounds for string, when drawn at (x, y). - * Set font properties to NULL to use the defaults. - */ -void get_text_bounds(const GFXfont *font, const char *string, int32_t *x, int32_t *y, int32_t *x1, int32_t *y1, int32_t *w, - int32_t *h, const FontProperties *props); - -/** - * @brief Write text to the EPD. - */ -void writeln(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer); - -/** - * @brief Write text to the EPD. - * - * @note If framebuffer is NULL, draw mode `mode` is used for direct drawing. - */ -void write_mode(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer, - DrawMode_t mode, const FontProperties *properties); - -/** - * @brief Get the font glyph for a unicode code point. - */ -void get_glyph(const GFXfont *font, uint32_t code_point, GFXglyph **glyph); - -/** - * @brief Write a (multi-line) string to the EPD. - */ -void write_string(const GFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer); - -#ifdef __cplusplus -} -#endif - -#endif -/******************************************************************************/ -/*** END OF FILE ***/ -/******************************************************************************/ \ No newline at end of file diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini index 1a09099da..c4187a807 100644 --- a/variants/esp32s3/t5s3_epaper/platformio.ini +++ b/variants/esp32s3/t5s3_epaper/platformio.ini @@ -15,6 +15,7 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 + ;https://github.com/vroland/epdiy/archive/c61e9e923ce2418150d54f88cea5d196cdc40c54.zip https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip lewisxhe/XPowersLib@0.3.1 diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index e0e2e2d47..134e79d4a 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -1,24 +1,6 @@ -// Display (E-Ink) ED047TC1 +// Display (E-Ink) ED047TC1 - 8bit parallel #define USE_EPD -#define EP_I2C_PORT I2C_NUM_0 -#define EP_SCL (40) -#define EP_SDA (39) -#define EP_INTR (38) -#define EP_D7 (8) -#define EP_D6 (18) -#define EP_D5 (17) -#define EP_D4 (16) -#define EP_D3 (15) -#define EP_D2 (7) -#define EP_D1 (6) -#define EP_D0 (5) -#define EP_CKV (48) -#define EP_STH (41) -#define EP_LEH (42) -#define EP_STV (45) -#define EP_CKH (4) - #define EPD_WIDTH 960 #define EPD_HEIGHT 540