diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a53cb3..ca1bfc2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -86,7 +86,18 @@ partitions: { bootloader_addr: "0x1000", }, - } + } + - { + vendor: "M5Stack", + name: "Core4mb", + description: "", + link: "https://shop.m5stack.com/products/esp32-basic-core-lot-development-kit-v2-7?ref=Pirata", + family: "ESP32", + env: "m5stack-core-4Mb", + partitions: { + bootloader_addr: "0x1000", + }, + } - { vendor: "M5Stack", name: "CoreS3", @@ -274,6 +285,17 @@ bootloader_addr: "0x0", }, } + - { + vendor: "Lilygo", + name: "T5_E-Paper_S3_Pro", + description: "Pro Only!", + link: "https://lilygo.cc/products/t5-e-paper-s3-pro", + family: "ESP32-S3", + env: "lilygo-t5-epaper-s3-pro", + partitions: { + bootloader_addr: "0x0", + }, + } steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index bc903d7..36b17e8 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,12 @@ Sourcecode will be released in the future.. * UiFlow 1 doesn´t work with Launcher.. it uses an old MicroPython distro, that uses an old ESP-IDF distro with lots os secrets that I couldn´t figure out. ## Changelog +* 2.4.3: + * [x] Fixed buttons on Core devices + * [x] Fixed random restartings when dimming screen + * [x] Ported to Lilygo E-Paper S3 Pro (Only Pro for now) + * [x] Fixed T-Embed return from deepSleep + * 2.4.2: * [x] UiFlow2 v2.2.0 compatibility https://github.com/bmorcelli/M5Stick-Launcher/issues/92 * [x] Fix for https://github.com/bmorcelli/M5Stick-Launcher/issues/93 https://github.com/bmorcelli/M5Stick-Launcher/issues/97 https://github.com/bmorcelli/M5Stick-Launcher/issues/95 diff --git a/boards/lilygo-t-embed-cc1101/platformio.ini b/boards/lilygo-t-embed-cc1101/platformio.ini index 8505169..15faaef 100644 --- a/boards/lilygo-t-embed-cc1101/platformio.ini +++ b/boards/lilygo-t-embed-cc1101/platformio.ini @@ -11,7 +11,7 @@ #################################### EStart OF LILYGO MODELS ####################################### [env:lilygo-t-embed] platform_packages = - framework-arduinoespressif32 @ https://github.com/bmorcelli/arduino-esp32/releases/download/2.0.17d/esp32-2.0.17d.zip + framework-arduinoespressif32 @ https://github.com/bmorcelli/arduino-esp32/releases/download/2.0.17f/esp32-2.0.17f.zip board = lilygo-t-embed board_build.arduino.memory_type = qio_opi board_build.partitions = custom_16Mb.csv @@ -94,7 +94,7 @@ lib_deps = [env:lilygo-t-embed-cc1101] platform_packages = - framework-arduinoespressif32 @ https://github.com/bmorcelli/arduino-esp32/releases/download/2.0.17d/esp32-2.0.17d.zip + framework-arduinoespressif32 @ https://github.com/bmorcelli/arduino-esp32/releases/download/2.0.17f/esp32-2.0.17f.zip board = lilygo-t-embed-cc1101 board_build.arduino.memory_type = qio_opi board_build.partitions = custom_16Mb.csv diff --git a/boards/lilygo-t5-epaper-s3-pro.json b/boards/lilygo-t5-epaper-s3-pro.json new file mode 100644 index 0000000..c6d1217 --- /dev/null +++ b/boards/lilygo-t5-epaper-s3-pro.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DE_PAPER_DISPLAY", + "-DLYLYGO_T5S3_PRO", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_RUNNING_CORE=0", + "-DARDUINO_EVENT_RUNNING_CORE=0", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "pinouts" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "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" +} \ No newline at end of file diff --git a/boards/lilygo-t5-epaper-s3-pro/interface.cpp b/boards/lilygo-t5-epaper-s3-pro/interface.cpp new file mode 100644 index 0000000..c3f43ca --- /dev/null +++ b/boards/lilygo-t5-epaper-s3-pro/interface.cpp @@ -0,0 +1,202 @@ +#include +#include +#include "powerSave.h" +#include + +#include "epd_driver.h" +#include "utilities.h" +TouchDrvGT911 touch; + +#include +BQ27220 bq; + +#include +XPowersPPM PPM; + +#define BOARD_I2C_SDA 6 +#define BOARD_I2C_SCL 5 +#define BOARD_SENSOR_IRQ 15 +#define BOARD_TOUCH_RST 41 + +/*************************************************************************************** +** Function name: _setup_gpio() +** Location: main.cpp +** Description: initial setup for the device +***************************************************************************************/ +void _setup_gpio() { + pinMode(SEL_BTN, INPUT); + pinMode(DW_BTN, INPUT); + + // CS pins of SPI devices to HIGH + pinMode(46, OUTPUT); // LORA module + digitalWrite(46,HIGH); + + Wire.begin(BOARD_SDA, BOARD_SCL); + + // Assuming that the previous touch was in sleep state, wake it up + pinMode(TOUCH_INT, OUTPUT); + digitalWrite(TOUCH_INT, HIGH); + + /* + * The touch reset pin uses hardware pull-up, + * and the function of setting the I2C device address cannot be used. + * Use scanning to obtain the touch device address.*/ + uint8_t touchAddress = 0; + Wire.beginTransmission(0x14); + if (Wire.endTransmission() == 0) { + touchAddress = 0x14; + } + Wire.beginTransmission(0x5D); + if (Wire.endTransmission() == 0) { + touchAddress = 0x5D; + } + if (touchAddress == 0) { + while (1) { + Serial.println("Failed to find GT911 - check your wiring!"); + delay(1000); + } + } + touch.setPins(-1, TOUCH_INT); + if (!touch.begin(Wire, touchAddress, BOARD_SDA, BOARD_SCL )) { + while (1) { + Serial.println("Failed to find GT911 - check your wiring!"); + delay(1000); + } + } + touch.setMaxCoordinates(EPD_WIDTH, EPD_HEIGHT); + touch.setSwapXY(true); + touch.setMirrorXY(false, true); + + Serial.println("Started Touchscreen poll..."); + + + // BQ25896 --- 0x6B + Wire.beginTransmission(0x6B); + if (Wire.endTransmission() == 0) + { + // battery_25896.begin(); + PPM.init(Wire, BOARD_SDA, BOARD_SCL, BQ25896_SLAVE_ADDRESS); + // Set the minimum operating voltage. Below this voltage, the PPM will protect + PPM.setSysPowerDownVoltage(3300); + // Set input current limit, default is 500mA + PPM.setInputCurrentLimit(3250); + Serial.printf("getInputCurrentLimit: %d mA\n",PPM.getInputCurrentLimit()); + // Disable current limit pin + PPM.disableCurrentLimitPin(); + // Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV + PPM.setChargeTargetVoltage(4208); + // Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA + PPM.setPrechargeCurr(64); + // The premise is that Limit Pin is disabled, or it will only follow the maximum charging current set by Limi tPin. + // Set the charging current , Range:0~5056mA ,step:64mA + PPM.setChargerConstantCurr(832); + // Get the set charging current + PPM.getChargerConstantCurr(); + Serial.printf("getChargerConstantCurr: %d mA\n",PPM.getChargerConstantCurr()); + PPM.enableADCMeasure(); + PPM.enableCharge(); + PPM.disableOTG(); + } +} + +/*************************************************************************************** +** Function name: _post_setup_gpio() +** Location: main.cpp +** Description: second stage gpio setup to make a few functions work +***************************************************************************************/ + #define TFT_BRIGHT_CHANNEL 0 + #define TFT_BRIGHT_Bits 8 + #define TFT_BRIGHT_FREQ 5000 + #define TFT_BL 40 +void _post_setup_gpio() { +// Brightness control must be initialized after tft in this case @Pirata + pinMode(TFT_BL,OUTPUT); + ledcSetup(TFT_BRIGHT_CHANNEL,TFT_BRIGHT_FREQ, TFT_BRIGHT_Bits); //Channel 0, 10khz, 8bits + ledcAttachPin(TFT_BL, TFT_BRIGHT_CHANNEL); + ledcWrite(TFT_BRIGHT_CHANNEL,125); +} + + +/*************************************************************************************** +** Function name: getBattery() +** location: display.cpp +** Description: Delivers the battery value from 1-100 +***************************************************************************************/ +int getBattery() { + int percent=0; + percent=bq.getChargePcnt(); + + return (percent < 0) ? 0 + : (percent >= 100) ? 100 + : percent; +} + + +/********************************************************************* +** Function: setBrightness +** location: settings.cpp +** set brightness value +**********************************************************************/ +void _setBrightness(uint8_t brightval) { + int dutyCycle; + if (brightval==100) dutyCycle=255; + else if (brightval==75) dutyCycle=130; + else if (brightval==50) dutyCycle=70; + else if (brightval==25) dutyCycle=20; + else if (brightval==0) dutyCycle=0; + else dutyCycle = ((brightval*255)/100); + + log_i("dutyCycle for bright 0-255: %d",dutyCycle); + ledcWrite(TFT_BRIGHT_CHANNEL,dutyCycle); // Channel 0 +} + +struct TouchPointPro { + int16_t x=0; + int16_t y=0; +}; + +/********************************************************************* +** Function: InputHandler +** Handles the variables PrevPress, NextPress, SelPress, AnyKeyPress and EscPress +**********************************************************************/ +void InputHandler(void) { + static long _tmptmp; + TouchPointPro t; + uint8_t touched = 0; + touched = touch.getPoint(&t.x, &t.y); + if((millis()-_tmptmp)>150) { // one reading each 500ms + + //Serial.printf("\nPressed x=%d , y=%d, rot: %d",t.x, t.y, rotation); + if (touched) { + + Serial.printf("\nPressed x=%d , y=%d, rot: %d, millis=%d, tmp=%d",t.x, t.y, rotation, millis(), _tmptmp); + _tmptmp=millis(); + + // if(!wakeUpScreen()) AnyKeyPress = true; + // else goto END; + + // Touch point global variable + touchPoint.x = t.x; + touchPoint.y = t.y; + touchPoint.pressed=true; + touchHeatMap(touchPoint); + touched=0; + } + END: + yield(); + } +} + +/********************************************************************* +** Function: powerOff +** location: mykeyboard.cpp +** Turns off the device (or try to) +**********************************************************************/ +void powerOff() { } + +/********************************************************************* +** Function: checkReboot +** location: mykeyboard.cpp +** Btn logic to tornoff the device (name is odd btw) +**********************************************************************/ +void checkReboot() { } diff --git a/boards/lilygo-t5-epaper-s3-pro/platformio.ini b/boards/lilygo-t5-epaper-s3-pro/platformio.ini new file mode 100644 index 0000000..a12ea8a --- /dev/null +++ b/boards/lilygo-t5-epaper-s3-pro/platformio.ini @@ -0,0 +1,59 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +[env:lilygo-t5-epaper-s3-pro] +platform = https://github.com/bmorcelli/platform-espressif32/releases/download/0.0.3/platform-espressif32.zip +board = lilygo-t5-epaper-s3-pro +board_build.partitions = custom_16Mb.csv +build_src_filter =${env.build_src_filter} +<../boards/lilygo-t5-epaper-s3-pro> +build_flags = + ${env.build_flags} + -Iboards/lilygo-t5-epaper-s3-pro + ;-DCORE_DEBUG_LEVEL=5 + -DARDUINO_USB_CDC_ON_BOOT=1 + + ;-DPART_04MB =0 + ;-DPART_08MB =0 + -DPART_16MB=1 + -DROTATION=1 # 0,2 Portrait, 1,3 to landscape + -DHAS_BTN=1 + -DSEL_BTN=0 + -DUP_BTN=-1 + -DDW_BTN=48 + -DBTN_ACT=LOW + -DBAT_PIN=4 + -DBTN_ALIAS='"Sel"' + -DMINBRIGHT=190 + -DBACKLIGHT=40 + -DLED=-1 + -DLED_ON=HIGH + + -DFP=1 + -DFM=2 + -DFG=3 + + -DLH=14 + -DLW=11 + + -DHAS_TOUCH=1 + + -DTFT_WIDTH=500 + -DTFT_HEIGHT=960 + + -DSDCARD_CS=16 + -DSDCARD_SCK=18 + -DSDCARD_MISO=8 + -DSDCARD_MOSI=17 + +lib_deps = + ${env.lib_deps} + lewisxhe/XPowersLib @0.2.6 + https://github.com/bmorcelli/LilyGo-EPD47#esp32s3 diff --git a/boards/lilygo-t5-epaper.json b/boards/lilygo-t5-epaper.json new file mode 100644 index 0000000..e441ee7 --- /dev/null +++ b/boards/lilygo-t5-epaper.json @@ -0,0 +1,38 @@ + +{ + "build": { + "arduino":{ + "ldscript": "esp32_out.ld", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DE_PAPER_DISPLAY", + "-DLYLYGO_T5", + "-DBOARD_HAS_PSRAM" + ], + "f_cpu": "240000000L", + "f_flash": "40000000L", + "flash_mode": "dio", + "mcu": "esp32", + "variant": "pinouts" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "frameworks": [ + "arduino", + "espidf" + ], + "name": "LILYGO T5-ePaper-ESP32", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 4521984, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://www.lilygo.cc/products/t5-4-7-inch-e-paper-v2-3", + "vendor": "LILYGO" +} \ No newline at end of file diff --git a/boards/m5stack-core/interface.cpp b/boards/m5stack-core/interface.cpp index d1c7660..f043a3d 100644 --- a/boards/m5stack-core/interface.cpp +++ b/boards/m5stack-core/interface.cpp @@ -43,6 +43,7 @@ void _setBrightness(uint8_t brightval) { ** Handles the variables PrevPress, NextPress, check(SelPress), AnyKeyPress and EscPress **********************************************************************/ void InputHandler(void) { + M5.update(); if(M5.BtnA.isPressed() || M5.BtnB.isPressed() || M5.BtnC.isPressed()) { if(!wakeUpScreen()) AnyKeyPress = true; else goto END; @@ -60,7 +61,8 @@ void InputHandler(void) { END: if(AnyKeyPress) { long tmp=millis(); - while((millis()-tmp)<200 && (M5.BtnA.isPressed() || M5.BtnB.isPressed() || M5.BtnC.isPressed())); + M5.update(); + while((millis()-tmp)<200 && (M5.BtnA.isPressed() || M5.BtnB.isPressed() || M5.BtnC.isPressed())) { delay(50); M5.update(); }; } } diff --git a/boards/m5stack-core/platformio.ini b/boards/m5stack-core/platformio.ini index 41eb797..7af7121 100644 --- a/boards/m5stack-core/platformio.ini +++ b/boards/m5stack-core/platformio.ini @@ -73,8 +73,11 @@ lib_deps = [env:m5stack-core-4Mb] extends=env:m5stack-core +board = m5stack-core +board_build.partitions = custom_4Mbcore.csv build_flags = - ${env:m5stack-core} + ${env:m5stack-core.build_flags} -DPART_04MB=1 + -DCORE_4MB build_unflags= -DPART_16MB diff --git a/boards/pinouts/esp32s3.h b/boards/pinouts/esp32s3.h new file mode 100644 index 0000000..339b47d --- /dev/null +++ b/boards/pinouts/esp32s3.h @@ -0,0 +1,70 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include "soc/soc_caps.h" + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Some boards have too low voltage on this pin (board design bug) +// Use different pin with 3V and connect with 48 +// and change this setup for the chosen pin (for example 38) +#define PIN_NEOPIXEL 48 +// BUILTIN_LED can be used in new Arduino API digitalWrite() like in Blink.ino +static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT+PIN_NEOPIXEL; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN +// RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API neopixelWrite() +#define RGB_BUILTIN LED_BUILTIN +#define RGB_BRIGHTNESS 64 + + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; + +static const uint8_t SS = 10; +static const uint8_t MOSI = 11; +static const uint8_t MISO = 13; +static const uint8_t SCK = 12; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/boards/pinouts/pins_arduino.h b/boards/pinouts/pins_arduino.h index 79323c9..33dc077 100644 --- a/boards/pinouts/pins_arduino.h +++ b/boards/pinouts/pins_arduino.h @@ -36,4 +36,8 @@ #include "lilygo-t-display-s3-pro.h" #elif SMOOCHIEE_BOARD #include "smoochiee-board.h" +#elif LYLYGO_T5S3_PRO +#include "esp32s3.h" +#elif LYLYGO_T5 +#include "esp32dev.h" #endif \ No newline at end of file diff --git a/custom_4Mbcore.csv b/custom_4Mbcore.csv new file mode 100644 index 0000000..077f799 --- /dev/null +++ b/custom_4Mbcore.csv @@ -0,0 +1,13 @@ +# Default, , , , , +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +test, app, test, 0x10000, 0x190000, +app0, app, ota_0, 0x1A0000, 0x240000, +spiffs, data, spiffs, 0x3E0000, 0x20000, +# +# Orca, , , , , +# Name, Type, SubType, Offset, Size, Flags +#nvs, data, nvs, 0x9000, 0x6000, +#test, app, test, 0x10000, 0x170000, +#app0, app, ota_0, 0x180000, 0x200000, +#spiffs, data, spiffs, 0x380000, 0x80000, \ No newline at end of file diff --git a/custom_8Mb.csv b/custom_8Mb.csv index aba3ab2..275331f 100644 --- a/custom_8Mb.csv +++ b/custom_8Mb.csv @@ -18,6 +18,6 @@ coredump, data, coredump,0x7F0000, 0x10000, # Name, Type, SubType, Offset, Size, Flags #nvs, data, nvs, 0x9000, 0x6000, #app0, app, test, 0x10000, 0x160000, -#app1, app, ota_0, 0x170000, 0x490000, +#app1, app, ota_0, 0x180000, 0x480000, #sys, data, fat, 0x600000, 0x100000, #vfs, data, fat, 0x700000, 0x100000, diff --git a/include/pre_compiler.h b/include/pre_compiler.h index d523c38..bcdc2c0 100644 --- a/include/pre_compiler.h +++ b/include/pre_compiler.h @@ -49,4 +49,10 @@ #endif #ifndef SDCARD_SCK #define SDCARD_SCK -1 -#endif \ No newline at end of file +#endif +#ifndef LH + #define LH 8 +#endif +#ifndef LW + #define LW 6 +#endif diff --git a/platformio.ini b/platformio.ini index acffcab..4caf832 100644 --- a/platformio.ini +++ b/platformio.ini @@ -54,8 +54,6 @@ build_flags = -DLAUNCHER='"dev"' -DMAXFILES=256 -DEEPROMSIZE=128 - -DLH=8 - -DLW=6 -DCONFIG_FILE='"/config.conf"' -w -Wl,--print-memory-usage diff --git a/src/display.cpp b/src/display.cpp index 3f32bec..44d0f9c 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -7,6 +7,8 @@ #if defined(HEADLESS) SerialDisplayClass tft; +#elif E_PAPER_DISPLAY +EPD_translate tft; #else TFT_eSPI tft = TFT_eSPI(); // Invoke custom library #endif @@ -95,18 +97,27 @@ void TouchFooter2(uint16_t color) { void initDisplay(bool doAll) { static uint8_t _name=random(0,3); String name="@Pirata"; + String txt; + int cor, _x, _y, show; + + #ifdef E_PAPER_DISPLAY // epaper display draws only once + static bool runOnce=false; + if(runOnce) goto END; + else runOnce=true; + tft.stopCallback(); + #endif + if(_name == 1) name="u/bmorcelli"; else if(_name == 2) name="gh/bmorcelli"; tft.drawRoundRect(3,3,tftWidth-6,tftHeight-6,5,FGCOLOR); tft.setTextSize(FP); tft.setCursor(10,10); - int cor = 0; - String txt; - int show = random(0,40); - int _x=tft.getCursorX(); - int _y=tft.getCursorY(); + cor = 0; + show = random(0,40); + _x=tft.getCursorX(); + _y=tft.getCursorY(); - while(tft.getCursorY()<(tftHeight-12)) { + while(tft.getCursorY()<(tftHeight-(LH+4))) { cor = random(0,11); tft.setTextSize(FP); show = random(0,40); @@ -115,24 +126,24 @@ void initDisplay(bool doAll) { else if (cor & 1) { tft.setTextColor(odd_color,BGCOLOR); txt=String(cor); } else { tft.setTextColor(even_color,BGCOLOR); txt=String(cor); } - if(_x>=(tftWidth-10)) {_x=10; _y+=8; } + if(_x>=(tftWidth-(LW+4))) {_x=10; _y+=LH; } else if(_x<10) { _x = 10; } - if(_y>=(tftHeight-12)) break; + if(_y>=(tftHeight-(LH+LH/2))) break; tft.setCursor(_x,_y); - if(_y>(tftHeight-20) && _x>=(tftWidth-(10+LW*name.length()))) { + if(_y>(tftHeight-(LH*FM+LH/2)) && _x>=(tftWidth-((LW+4)+LW*name.length()))) { tft.setTextColor(FGCOLOR); tft.print(name); _x+=LW*name.length(); } else { tft.print(txt); - _x+=6; + _x+=LW; } } else { - if(_y>(tftHeight-20) && _x>=(tftWidth-(10+LW*name.length()))) _x+=LW*name.length(); - else _x+=6; + if(_y>(tftHeight-(LH*FM+LH/2)) && _x>=(tftWidth-((LW+4)+LW*name.length()))) _x+=LW*name.length(); + else _x+=LW; - if(_x>=(tftWidth-10)) { _x=10; _y+=8; } + if(_x>=(tftWidth-(LW+4))) { _x=10; _y+=LH; } } tft.setCursor(_x,_y); } @@ -145,7 +156,12 @@ void initDisplay(bool doAll) { #endif tft.setTextSize(FG); tft.setTextColor(FGCOLOR); + + #ifdef E_PAPER_DISPLAY // epaper display draws only once + tft.startCallback(); + #endif + END: delay(50); } @@ -169,6 +185,9 @@ void initDisplayLoop() { ** Description: Display Item on Screen before instalation ***************************************************************************************/ void displayCurrentItem(JsonDocument doc, int currentIndex) { + #ifdef E_PAPER_DISPLAY + tft.stopCallback(); + #endif JsonObject item = doc[currentIndex]; const char* name = item["name"]; @@ -213,9 +232,6 @@ void displayCurrentItem(JsonDocument doc, int currentIndex) { tft.println(texto); #endif - - - #if defined(HAS_TOUCH) TouchFooter(); #endif @@ -224,6 +240,10 @@ void displayCurrentItem(JsonDocument doc, int currentIndex) { if (bar<5) bar = 5; tft.fillRect((tftWidth*currentIndex)/doc.size(),tftHeight-5,bar,5,FGCOLOR); + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif + } /*************************************************************************************** @@ -231,6 +251,9 @@ void displayCurrentItem(JsonDocument doc, int currentIndex) { ** Description: Display Version on Screen before instalation ***************************************************************************************/ void displayCurrentVersion(String name, String author, String version, String published_at, int versionIndex, JsonArray versions) { + #ifdef E_PAPER_DISPLAY + tft.stopCallback(); + #endif //tft.fillScreen(BGCOLOR); tft.fillRect(0,tftHeight-5,tftWidth,5,BGCOLOR); tft.drawRoundRect(5,5,tftWidth-10,tftHeight-10,5,FGCOLOR); @@ -279,6 +302,10 @@ void displayCurrentVersion(String name, String author, String version, String pu int bar = int(tftWidth/div); if (bar<5) bar = 5; tft.fillRect((tftWidth*versionIndex)/div,tftHeight-5,bar,5,ALCOLOR); + + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif } /*************************************************************************************** @@ -391,24 +418,33 @@ void progressHandler(int progress, size_t total) { ** Function name: drawOptions ** Description: Função para desenhar e mostrar as opçoes de contexto ***************************************************************************************/ -#define MAX_MENU_SIZE (int)(tftHeight/25) +#ifdef E_PAPER_DISPLAY + #define MAX_MENU_SIZE 13 + #define FONT_S (FM*(LH+3)+4) +#else + #define FONT_S (FM*LH+4) + #define MAX_MENU_SIZE (int)(tftHeight/25) +#endif Opt_Coord drawOptions(int index,const std::vector>>& options, uint16_t fgcolor, uint16_t bgcolor) { + #ifdef E_PAPER_DISPLAY + tft.stopCallback(); + #endif Opt_Coord coord; int menuSize = options.size(); if(options.size()>MAX_MENU_SIZE) { menuSize = MAX_MENU_SIZE; } - if(index==0) tft.fillRoundRect(tftWidth*0.10,tftHeight/2-menuSize*(FM*8+4)/2 -5,tftWidth*0.8,(FM*8+4)*menuSize+10,5,bgcolor); + if(index==0) tft.fillRoundRect(tftWidth*0.10,tftHeight/2-menuSize*FONT_S/2 -5,tftWidth*0.8,FONT_S*menuSize+10,5,bgcolor); tft.setTextColor(fgcolor,bgcolor); tft.setTextSize(FM); - tft.setCursor(tftWidth*0.10+5,tftHeight/2-menuSize*(FM*8+4)/2); + tft.setCursor(tftWidth*0.10+5,tftHeight/2-menuSize*FONT_S/2); int i=0; int init = 0; int cont = 1; - if(index==0) tft.fillRoundRect(tftWidth*0.10,tftHeight/2-menuSize*(FM*8+4)/2 -5,tftWidth*0.8,(FM*8+4)*menuSize+10,5,bgcolor); + if(index==0) tft.fillRoundRect(tftWidth*0.10,tftHeight/2-menuSize*FONT_S/2 -5,tftWidth*0.8,FONT_S*menuSize+10,5,bgcolor); menuSize = options.size(); if(index>=MAX_MENU_SIZE) init=index-MAX_MENU_SIZE+1; for(i=0;iMAX_MENU_SIZE) menuSize = MAX_MENU_SIZE; - tft.drawRoundRect(tftWidth*0.10,tftHeight/2-menuSize*(FM*8+4)/2 -5,tftWidth*0.8,(FM*8+4)*menuSize+10,5,fgcolor); + tft.drawRoundRect(tftWidth*0.10,tftHeight/2-menuSize*FONT_S/2 -5,tftWidth*0.8,FONT_S*menuSize+10,5,fgcolor); + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif return coord; } @@ -441,6 +480,9 @@ Opt_Coord drawOptions(int index,const std::vector2) offset=index-2; const int border = 10; @@ -470,7 +512,7 @@ void drawMainMenu(int index) { tft.print("Launcher " + String(LAUNCHER)); tft.setTextSize(FM); #else - tft.print("Launcher " + String(LAUNCHER)); + tft.drawString("Launcher " + String(LAUNCHER),12,12); tft.setTextSize(FG); #endif for (int i = 0; i < 3; ++i) { @@ -491,6 +533,9 @@ void drawMainMenu(int index) { drawDeviceBorder(); drawBatteryStatus(); + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif } void drawSection(int x, int y, int w, int h, uint16_t color, const char* text, bool isSelected) { @@ -531,8 +576,15 @@ void drawBatteryStatus() { ** Function name: listFiles ** Description: Função para desenhar e mostrar o menu principal ***************************************************************************************/ -#define MAX_ITEMS (int)(tftHeight-20)/(LH*2) +#ifdef E_PAPER_DISPLAY + #define MAX_ITEMS 14 +#else + #define MAX_ITEMS (int)(tftHeight-20)/(LH*2) +#endif Opt_Coord listFiles(int index, String fileList[][3]) { + #ifdef E_PAPER_DISPLAY + tft.stopCallback(); + #endif Opt_Coord coord; tft.setCursor(10,10); tft.setTextSize(FM); @@ -574,6 +626,9 @@ Opt_Coord listFiles(int index, String fileList[][3]) { #if defined(HAS_TOUCH) TouchFooter(); #endif + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif return coord; } @@ -598,6 +653,9 @@ void loopOptions(const std::vector> setBrightness(100*(numOpt-index)/numOpt,false); } redraw=false; + #ifdef E_PAPER_DISPLAY + delay(200); + #endif } String txt=options[index].first.c_str(); displayScrollingText(txt, coord); @@ -686,6 +744,9 @@ void loopVersions() { displayCurrentVersion(String(name), String(author), String(version), String(published_at), versionIndex, versions); redraw = false; + #ifdef E_PAPER_DISPLAY + delay(200); + #endif } /* DW Btn to next item */ if(check(NextPress)) { @@ -784,12 +845,18 @@ void loopFirmware(){ if(currentIndex==0) currentIndex = doc.size() - 1; else if(currentIndex>0) currentIndex--; displayCurrentItem(doc, currentIndex); + #ifdef E_PAPER_DISPLAY + delay(200); + #endif } /* DW Btn to next item */ if(check(NextPress)) { currentIndex++; if((currentIndex+1)>doc.size()) currentIndex = 0; displayCurrentItem(doc, currentIndex); + #ifdef E_PAPER_DISPLAY + delay(200); + #endif } diff --git a/src/display.h b/src/display.h index 8bf4114..5d41cf9 100644 --- a/src/display.h +++ b/src/display.h @@ -4,6 +4,8 @@ #ifdef HEADLESS #include +#elif E_PAPER_DISPLAY +#include #else #include #endif @@ -14,6 +16,8 @@ // Declaração dos objetos TFT #if defined(HEADLESS) extern SerialDisplayClass tft; +#elif E_PAPER_DISPLAY +extern EPD_translate tft; #else extern TFT_eSPI tft; #endif @@ -29,8 +33,8 @@ void setTftDisplay(int x = 0, int y = 0, uint16_t fc = tft.textcolor, int size = void displayCurrentItem(JsonDocument doc, int currentIndex); void displayCurrentVersion(String name, String author, String version, String published_at, int versionIndex, JsonArray versions); - -void displayRedStripe(String text, uint16_t fgcolor = TFT_WHITE, uint16_t bgcolor = ALCOLOR); +uint16_t getComplementaryColor(uint16_t color); +void displayRedStripe(String text, uint16_t fgcolor = getComplementaryColor(BGCOLOR), uint16_t bgcolor = ALCOLOR); void progressHandler(int progress, size_t total); @@ -63,6 +67,4 @@ void tftprintln(String txt, int margin, int numlines = 0); void tftprint(String txt, int margin, int numlines = 0); -uint16_t getComplementaryColor(uint16_t color); - #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4efe020..59cc5d5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include #ifdef HEADLESS #include +#elif E_PAPER_DISPLAY +#include #else #include #endif @@ -413,6 +415,9 @@ void loop() { #endif redraw = false; returnToMenu = false; + #ifdef E_PAPER_DISPLAY + delay(200); + #endif } if(check(PrevPress)) { @@ -482,11 +487,14 @@ void loop() { if(index == 3) { options = { - + #ifndef E_PAPER_DISPLAY {"Charge Mode", [=](){ chargeMode(); }}, + #endif {"Brightness", [=]() { setBrightnessMenu(); saveConfigs();}}, {"Dim time", [=]() { setdimmerSet(); saveConfigs();}}, + #ifndef E_PAPER_DISPLAY {"UI Color", [=]() { setUiColor(); saveConfigs();}}, + #endif }; if(sdcardMounted) { if(onlyBins) options.push_back({"All Files", [=]() { gsetOnlyBins(true, false); saveConfigs();}}); @@ -495,9 +503,14 @@ void loop() { if(askSpiffs) options.push_back({"Avoid Spiffs", [=]() { gsetAskSpiffs(true, false); saveConfigs();}}); else options.push_back({"Ask Spiffs", [=]() { gsetAskSpiffs(true, true); saveConfigs();}}); + #ifndef E_PAPER_DISPLAY options.push_back( {"Orientation", [=]() { gsetRotation(true); saveConfigs(); }}); + #endif + #if !defined(CORE_4MB) options.push_back( {"Partition Change", [=]() { partitioner(); }}); + #endif options.push_back( {"List of Partitions", [=]() { partList(); }}); + #ifndef PART_04MB options.push_back({"Clear FAT", [=]() { eraseFAT(); }}); diff --git a/src/mykeyboard.cpp b/src/mykeyboard.cpp index 66adf17..7e646a1 100644 --- a/src/mykeyboard.cpp +++ b/src/mykeyboard.cpp @@ -29,14 +29,15 @@ struct box_t tft.fillRect(x, y, w, h,BGCOLOR); } } - void draw(void) + void draw(bool shift) { int ie = touch_id < 0 ? 4 : 8; for (int i = 0; i < ie; ++i) { tft.drawRect(x, y, w, h,color); tft.setTextColor(color); - tft.drawChar(key,x+w/2-FM*LW/2,y+h/2-FM*LH/2); + if(shift) tft.drawChar(key_sh,x+w/2-FM*LW/2,y+h/2-FM*LH/2); + else tft.drawChar(key,x+w/2-FM*LW/2,y+h/2-FM*LH/2); } } bool contain(int x, int y) @@ -123,10 +124,36 @@ String keyboard(String mytext, int maxSize, String msg) { { '/', '/' } //12 } }; + #if defined(E_PAPER_DISPLAY) + #define KBLH LH*FM+LH/2 + const int ofs[4][3] = { + {7 , 3*LW*FM, 7+ 0 + LW/2}, + {7+3*LW*FM+2 , 4*LW*FM, 7+3 *LW*FM+2 + LW/2}, + {7+7*LW*FM+4 , 4*LW*FM, 7+7 *LW*FM+4 + LW/2}, + {7+11*LW*FM+6 , 6*LW*FM, 7+11*LW*FM+6 + LW/2}, + }; + + #elif FM>1 // Normal keyboard size + //#if FM>1 // Normal keyboard size + #define KBLH 20 + const int ofs[4][3] = { + {7 , 46, 18 }, + {55 , 50, 64 }, + {107, 50, 115}, + {159, 74, 168}, + }; + #else // small keyboard size, for small letters (smaller screen) + #define KBLH 10 + const int ofs[4][3] = { + {7 , 20, 10}, + {27, 25, 30}, + {52, 25, 55}, + {77, 50, 80}, + }; + #endif const int _x = tftWidth/12; const int _y = (tftHeight - 54)/4; const int _xo = _x/2-3; - #if defined(HAS_TOUCH) int k=0; for(x2=0; x2<12;x2++) { @@ -135,7 +162,7 @@ String keyboard(String mytext, int maxSize, String msg) { box_list[k].key_sh=keys[y2][x2][1]; box_list[k].color = ~BGCOLOR; box_list[k].x=x2*_x; - box_list[k].y=y2*_y+54; + box_list[k].y=y2*_y+2*KBLH+LH*FM; box_list[k].w=_x; box_list[k].h=_y; k++; @@ -145,37 +172,37 @@ String keyboard(String mytext, int maxSize, String msg) { box_list[k].key=' '; box_list[k].key_sh=' '; box_list[k].color = ~BGCOLOR; - box_list[k].x=0; + box_list[k].x=ofs[0][0]; box_list[k].y=0; - box_list[k].w=53; - box_list[k].h=22; + box_list[k].w=ofs[0][1]; + box_list[k].h=KBLH; k++; // CAP box_list[k].key=' '; box_list[k].key_sh=' '; box_list[k].color = ~BGCOLOR; - box_list[k].x=55; + box_list[k].x=ofs[1][0]; box_list[k].y=0; - box_list[k].w=50; - box_list[k].h=22; + box_list[k].w=ofs[1][1]; + box_list[k].h=KBLH; k++; // DEL box_list[k].key=' '; box_list[k].key_sh=' '; box_list[k].color = ~BGCOLOR; - box_list[k].x=107; + box_list[k].x=ofs[2][0]; box_list[k].y=0; - box_list[k].w=50; - box_list[k].h=22; + box_list[k].w=ofs[2][1]; + box_list[k].h=KBLH; k++; // SPACE box_list[k].key=' '; box_list[k].key_sh=' '; box_list[k].color = ~BGCOLOR; - box_list[k].x=159; + box_list[k].x=ofs[3][0]; box_list[k].y=0; - box_list[k].w=tftWidth-164; - box_list[k].h=22; + box_list[k].w=ofs[3][1]; + box_list[k].h=KBLH; k=0; x2=0; @@ -189,32 +216,21 @@ String keyboard(String mytext, int maxSize, String msg) { bool longPrevPress = false; long longPressTmp=millis(); #endif + const int maxFMSize = tftWidth/(LW*FM)-1; + const int maxFPSize = tftWidth/(LW)-2; while(1) { if(redraw) { + #ifdef E_PAPER_DISPLAY + tft.stopCallback(); + #endif tft.setCursor(0,0); tft.setTextColor(getComplementaryColor(BGCOLOR), BGCOLOR); tft.setTextSize(FM); //Draw the rectangles if(y<0 || y2<0) { - #if FM>1 // Normal keyboard size - #define KBLH 20 - int ofs[4][3] = { - {7 , 46, 18 }, - {55 , 50, 64 }, - {107, 50, 115}, - {159, 74, 168}, - }; - #else // small keyboard size, for small letters (smaller screen) - #define KBLH 10 - int ofs[4][3] = { - {7 , 20, 10}, - {27, 25, 30}, - {52, 25, 55}, - {77, 50, 80}, - }; - #endif - tft.fillRect(0,1,tftWidth,22,BGCOLOR); + + tft.fillRect(0,1,tftWidth,KBLH,BGCOLOR); tft.drawRect(ofs[0][0],2,ofs[0][1],KBLH,getComplementaryColor(BGCOLOR)); // Ok Rectangle tft.drawRect(ofs[1][0],2,ofs[1][1],KBLH,getComplementaryColor(BGCOLOR)); // CAP Rectangle tft.drawRect(ofs[2][0],2,ofs[2][1],KBLH,getComplementaryColor(BGCOLOR)); // DEL Rectangle @@ -241,37 +257,38 @@ String keyboard(String mytext, int maxSize, String msg) { else tft.setTextColor(getComplementaryColor(BGCOLOR), BGCOLOR); tft.drawString("SPACE", ofs[3][2], 4); } - tft.setTextSize(FP); tft.setTextColor(getComplementaryColor(BGCOLOR), 0x5AAB); - tft.drawString(msg.substring(0,38), 3, KBLH+4); + + tft.drawString(msg.substring(0,maxFPSize), 3, KBLH+4); tft.setTextSize(FM); + // reseta o quadrado do texto - if (mytext.length() == 19 || mytext.length() == 20 || mytext.length() == 38 || mytext.length() == 39) tft.fillRect(3,KBLH+12,tftWidth-3,KBLH,BGCOLOR); // mystring Rectangle + if (mytext.length() == (maxFMSize) || mytext.length() == (maxFMSize+1) || mytext.length() == (maxFPSize) || mytext.length() == (maxFPSize+1)) tft.fillRect(3,KBLH+12,tftWidth-3,KBLH,BGCOLOR); // mystring Rectangle // escreve o texto tft.setTextColor(getComplementaryColor(BGCOLOR)); - if(mytext.length()>19) { + if(mytext.length()>(maxFMSize)) { tft.setTextSize(FP); - if(mytext.length()>38) { - tft.drawString(mytext.substring(0,38), 5, KBLH+14); - tft.drawString(mytext.substring(38,mytext.length()), 5, KBLH+22); + if(mytext.length()>maxFPSize) { + tft.drawString(mytext.substring(0,maxFPSize), 5, KBLH+LH+6); + tft.drawString(mytext.substring(maxFPSize,mytext.length()), 5, KBLH+2*LH+6); } else { - tft.drawString(mytext, 5, KBLH+14); + tft.drawString(mytext, 5, KBLH+LH+6); } } else { - tft.drawString(mytext, 5, KBLH+14); + tft.drawString(mytext, 5, KBLH+LH+6); } //desenha o retangulo colorido - tft.drawRect(3,32,tftWidth-3,KBLH,FGCOLOR); // mystring Rectangle + tft.drawRect(3,KBLH+12,tftWidth-3,KBLH,FGCOLOR); // mystring Rectangle tft.setTextColor(getComplementaryColor(BGCOLOR), BGCOLOR); tft.setTextSize(FM); - + int _i=0; for(int i=0;i<4;i++) { for(int j=0;j<12;j++) { //use last coordenate to paint only this letter @@ -281,8 +298,12 @@ String keyboard(String mytext, int maxSize, String msg) { /* Print the letters */ - if(!caps) tft.drawChar(keys[i][j][0], (j*_x+_xo), (i*_y+KBLH*2+16)); - else tft.drawChar(keys[i][j][1], (j*_x+_xo), (i*_y+KBLH*2+16)); + #ifdef HAS_TOUCH + box_list[_i++].draw(caps); + #else + if(!caps) tft.drawChar(keys[i][j][0], (j*_x+_xo), (i*_y+2*KBLH+LH*FM)); + else tft.drawChar(keys[i][j][1], (j*_x+_xo), (i*_y+2*KBLH+LH*FM)); + #endif /* Return colors to normal to print the other letters */ if(x==j && y==i) { tft.setTextColor(~BGCOLOR, BGCOLOR); } @@ -292,14 +313,17 @@ String keyboard(String mytext, int maxSize, String msg) { x2=x; y2=y; redraw = false; + #ifdef E_PAPER_DISPLAY + tft.startCallback(); + #endif } //cursor handler - if(mytext.length()>19) { + if(mytext.length()>(maxFMSize)) { tft.setTextSize(FP); - if(mytext.length()>38) { + if(mytext.length()>(maxFPSize)) { cY=42; - cX=5+(mytext.length()-38)*LW; + cX=5+(mytext.length()-maxFPSize)*LW; } else { cY=34; @@ -439,17 +463,17 @@ String keyboard(String mytext, int maxSize, String msg) { if(mytext.length() 0) { // delete 0x08 // Handle backspace key mytext.remove(mytext.length() - 1); int fS=FM; - if(mytext.length()>19) { tft.setTextSize(FP); fS=FP; } + if(mytext.length()>maxFPSize) { tft.setTextSize(FP); fS=FP; } else tft.setTextSize(FM); tft.setCursor((cX-fS*LW),cY); tft.setTextColor(FGCOLOR,BGCOLOR); @@ -458,8 +482,8 @@ String keyboard(String mytext, int maxSize, String msg) { tft.setCursor(cX-fS*LW,cY); cX=tft.getCursorX(); cY=tft.getCursorY(); - if(mytext.length()==19) redraw = true; - if(mytext.length()==38) redraw = true; + if(mytext.length()==maxFMSize) redraw = true; + if(mytext.length()==maxFPSize) redraw = true; } if (KeyStroke.enter) { break; @@ -482,7 +506,7 @@ String keyboard(String mytext, int maxSize, String msg) { DEL: mytext.remove(mytext.length()-1); int fS=FM; - if(mytext.length()>19) { tft.setTextSize(FP); fS=FP; } + if(mytext.length()>maxFPSize) { tft.setTextSize(FP); fS=FP; } else tft.setTextSize(FM); tft.setCursor((cX-fS*LW),cY); tft.setTextColor(FGCOLOR,BGCOLOR); @@ -496,7 +520,7 @@ String keyboard(String mytext, int maxSize, String msg) { else if(y>-1 && mytext.length()= WIDTH - 10: + x = 10 + y += LH + elif x < 10: + x = 10 + if y >= HEIGHT - 12: + break + + if y > HEIGHT - 25 and x >= WIDTH - ( LW * 20) and do_all: + + x += LW * 25 + else: + draw.rectangle((x, y, x + LW - 1, y + LH - 1), fill=BGCOLOR) + draw.text((x, y), txt, fill=color, font=font_small) + x += LW + else: + if y > HEIGHT - 25 and x >= WIDTH - (LW * 20): + x += LW * 13 + else: + x += LW + + if x >= WIDTH - 10: + x = 10 + y += LH + + width=3 + + t_width = draw.textlength(name,font=font_medium) + draw.rectangle((WIDTH - (LW * 20), HEIGHT - 25, WIDTH, HEIGHT - 25 + LH*2 - 1), fill=BGCOLOR) + draw.text((WIDTH - (LW * 10) - t_width/2, HEIGHT - 25), name, fill=FGCOLOR, font=font_medium) + + t_width = draw.textlength(launcher_text,font=font_large) + draw.text((WIDTH // 2 - t_width/2, HEIGHT // 2 - 5*LH), launcher_text, fill=FGCOLOR, font=font_large, stroke_width=3, stroke_fill=(0,50,0)) + + draw.rounded_rectangle( + (width, width, WIDTH - width, HEIGHT - width), + radius=5, + outline=FGCOLOR, + width=width, + ) + return img + +# Gera o primeiro frame com fundo completo e borda +img = Image.new("RGB", (WIDTH, HEIGHT), BGCOLOR) +img = update_frame_with_border(img, do_all=True,var_name=1) +frames = [img.copy()] + +# Gera frames subsequentes utilizando o frame anterior como base +for i in range(49): # Total de 50 frames + if i < 25: + img = update_frame_with_border(img, do_all=False,var_name=1) + else: + img = update_frame_with_border(img, do_all=False,var_name=0) + frames.append(img.copy()) + +# Salva o GIF com a borda arredondada +frames[0].save( + "animation_with_border.gif", + save_all=True, + append_images=frames[1:], + optimize=False, + duration=100, # Duração por frame em ms + loop=0 +)