Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
|
- Add Blink to M5StickC & M5StickC-Plus Built-in LED
- Fixed the brightness for M5StickC & M5StickC-Plus
- Add beep to M5StickC-Plus
- Removed SPIFF dependency
- Fix minor typo
Update 2023.09A & 2023.12A
- By popular demand, I have updated this sketch to work with M5StickC-Plus 🎉. Please follow the new instructions.
Update 2023.02A
- Emulation (BETA) is available at https://wokwi.com/projects/331150819227337298
Version 2022.05B
- Arduino_GFX bug fixed. This script now works with Arduino_GFX_Library version 1.2.0 & above (aka GFX library for Arduino, under Library Manager).
- Add gfxScreenTest
Based on Arduino_GFX and gifdec, espgfxGIF is an Arduino sketch that plays animated GIF on TFT screen of some Arduino Dev modules, mainly esp32 and esp8266.
- Button A - Invert display
- Button B - Adjust Brightness
- Power button - Reboot and play the designated animated GIF, or a random GIF file in the SPIFFS if the GIF_FILENAME is not defined.
- High performance. If the GIF has no delay, most esp32 module is capable of playing 70fps for a 135 x 240 animated GIF.
M5Stack M5StickC-Plus (esp32 Pico+ ST7789)
M5Stack M5StickC (esp32 Pico + ST7735)
Lilygo T-Display S3 (esp32-S3 + ST7789)
Lilygo T-Display (esp32 Pico + ST7789)
Woki esp32 Module + ILI9341 Emu
D1 mini with TFT-2.4 shield (esp8266 + ILI9341)
Libraries used:https://github.com/moononournation/Arduino_GFX version 1.2.0
https://github.com/lecram/gifdec (https://github.com/BasementCat/arduino-tft-gif edition)
plus 3 helper files.
Backgrounds:TFT_eSPI, which is the most common TFT graphic library, supports BMP, and MJPEG/JPEG files via drawBmp() and drawJpeg(). However, due to the way how GIF handles cmap with custom color palettes, drawGIF is not supported (as what I am aware of). Adafruit_GFX also lack support for animated GIF. Color corruption is a common issue.
Arduino_GFX is a rewritten library from Adafruit_GFX, TFT_eSPI to support various displays with various data bus interfaces. Using gifdec to fill the GIF frames into to display data bus, an animated GIF can be played on the TFT display.
Installations:(15 minutes setup. 1 minute GIF upload to SPIFFS. 2 minutes to compile and flash)
1. Download esp32fs to your Ardunio IDE from https://github.com/me-no-dev/arduino-esp32fs-plugin
2. Unzip the folder into %USERPROFILE%\Documents\Arduino\tools
3. Restart Arduino IDE 1.8.x
4. Make sure esp32, esp8266 board info are installed.
5. Make sure M5StickC & M5StickC-Plus libraries are installed.
6. Install "GFX Library for Arduino" (aka Arduino_GFX) from Library Manager.
7. Download the sketch from my github https://github.com/tommykho/IOT-cookbook
8. Extract and create a folder "espgfxGIF" from the zip file.
/espgfxGIF
|- espgfxGIF.ino
|- gifdec.cpp
|- gifdec.h
|- gfx-helper.h
|- spiffs-helper.h
|- led-helper.h
|- data/
|- youranimated1.gif
|- youranimeted2.gif
9. Put your own animated GIF files on the "espgfxGIF/data" folder. Please note most esp32 DEV modules only have 1Mb of SPIFFS. Limit your total file size to 900Kb.
10. Open the "espgfxGIF.ino" with your Arduino IDE.
11. Connect your DEV module to your Arduino IDE. Select the correct COM port.
12. Edit your own settings with the Module and GIF file that you used.
// *** BEGIN editing of your settings ...
#define ARDUINO_M5STICKCPLUS
//#define GIF_FILENAME "/suatmm_240x135.gif" /* comment out for random GIF */
// *** END editing of your settings ...
13. To enable debug screen test, uncomment line 23
#define DEBUG /* uncomment this line to start with screen test */
14. Click "Upload" to compile and upload the sketch to your DEV Module.
15. Select "Tools, ESP32 Sketch Data Upload" to upload the GIF files to the SPIFFS of your DEV Module.
*** IMPORTANT ***
If you are not using m5Stack m5StickC or TTGO T-Display, please add your own configuration to the script after line 52. You need to declare your canvas and data-bus class, MOSI, SCLK, CS, DC, RST, BL pins, as well as control button pins in the configuration. If you need help with the configuration, please leave a comment or visit
https://github.com/moononournation/Arduino_GFX/wiki/Canvas-Class
https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class
For example,
#elif defined(ARDUINO_RPI-PICO)
/* Raspberry Pi Pico with GC9A01 display */
Arduino_G *output_display = new Arduino_GC9A01(bus, TFT_RST, 0 /* rotation */, true /* IPS */);
Arduino_GFX *gfx = new Arduino_Canvas(240 /* width */, 240 /* height */, output_display);
Arduino_DataBus *bus = new Arduino_RPiPicoSPI(27 /* DC */, 17 /* CS */, PIN_SPI0_SCK /* SCK */, PIN_SPI0_MOSI /* MOSI */, PIN_SPI0_MISO /* MISO */, spi0 /* spi */);
Known bugs:- Only play a single GIF file in a loop until the power cycle
- Minor artifact with M5StickC
- Multiple devices sync (playing GIF/MPEG in synchronization)
- Gesture control with IMU
- Play multiple GIF files in a loop
- MP3 or Radio playback (see my other projects)
- BLE connection for wireless GIF upload (similar to Wireless Image Transfer with Circuit Playground Bluefruit and TFT Gizmo)
- IFTTT and MQTT integration for remote power cycle or GIF change
- MJPEG support (live webcam or IP CAM)Multiple
- Feel free to buy me a coffee. https://www.paypal.com/donate/?business=YF44M264KV8CC&no_recurring=1¤cy_code=USD
espgfxGIF.ino
ArduinoPlease note this is only one of the complete set of files. You must have all the files in the folder to work. See Instructions.
/*
* espgfxGIF Version 2023.12A (Brightness Edition)
* Board: T-Display S3, T-Display, M5StickC-Plus, M5StickC (esp32)
* Author: tommyho510@gmail.com
* Adapted from: moononournation
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
* Required: Arduino library Arduino_GFX 1.3.7
* Dependency: gifdec.h
* IOT-cookbook Helpers: spiffs, gfx
*
* Please upload SPIFFS data with ESP32 Sketch Data Upload:
* https://github.com/me-no-dev/arduino-esp32fs-plugin
* GIF src: various
*/
#include <Arduino_GFX_Library.h> /* Install via Arduino Library Manager */
// *** BEGIN editing of your settings ...
#define ARDUINO_M5STICKCPLUS
//#define GIF_FILENAME "/your_file.gif" /* comment out for random GIF */
//#define DEBUG /* uncomment this line to start with screen test */
// *** END editing of your settings ...
#if defined(ARDUINO_M5STICKCPLUS)
/* M5Stack */
/* 1.14" ST7789 IPS LCD 135x240 M5StickC Plus * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS 5
#define TFT_DC 23
#define TFT_RST 18
#define LED 10
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */,
53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickCPlus.h>
#elif defined(ARDUINO_M5STICKC)
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5StickC * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS 5
#define TFT_DC 23
#define TFT_RST 18
#define LED 10
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* RST */, 3 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */,
26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickC.h>
#elif defined(ARDUINO_TDISPLAYS3)
/* LILYGO T-Display */
/* 1.90" ST7789V IPS LCD 170x320 TTGO T-Display (Rotation: 0 bottom, 1 left, 2 top, 3 right) */
#define TFT_CS 6
#define TFT_DC 7
#define TFT_RST 5
#define TFT_BL 38
#define LED 2
Arduino_DataBus *bus = new Arduino_ESP32LCD8(TFT_DC /* DC */, TFT_CS /* CS */, 8 /* WR */, 9 /* RD */,
39 /* D0 */, 40 /* D1 */, 41 /* D2 */, 42 /* D3 */,
45 /* D4 */, 46 /* D5 */, 47 /* D6 */, 48 /* D7 */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 0 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */,
35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 14;
#elif defined(ARDUINO_TDISPLAY)
/* TTGO T-Display */
/* 1.14" ST7789 IPS LCD 135x240 TTGO T-Display (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
//#define TFT_MOSI 19
//#define TFT_SCLK 18
//#define TFT_CS 5
//#define TFT_DC 16
#define TFT_RST -1
#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 35;
#elif defined(ARDUINO_D1MINI)
/* LOLIN D1 Mini esp8266 + TFT-2.4 Shield */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 14
#define TFT_CS 16
#define TFT_DC 15
#define TFT_RST 5
//#define TFT_BL NC
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 3;
const int BTN_B = 4;
#elif defined(ARDUINO_DEVKITV1)
/* DOIT ESP32 DEVKIT V1 + ILI9341 */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 23
#define TFT_MISO 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 17
//#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 13;
const int BTN_B = 15;
#endif /* not selected specific hardware */
// Sleep timer
//#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
//#define TIME_TO_SLEEP 1 /* Time ESP32 will go to sleep (in seconds) */
//RTC_DATA_ATTR int bootCount = 0;
// Load IOT-cookbook helpers
#include "gifdec.h"
#include "spiffs-helper.h"
#include "gfx-helper.h"
#include "led-helper.h"
// Rotation & Brightness control
int rot[2] = {1, 3};
int backlight[5] = {10, 30, 60, 120, 240};
int axp[5] = {20, 40, 60, 80, 100};
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
byte a = 1;
byte b = 5;
unsigned long p = 0;
bool inv = 0;
int pressA = 0;
int pressB = 0;
String gifArray[30], randGIF_FILENAME, playFile, gifFile;
int gifArraySize;
File vFile;
// Main subroutine
void gfxPlayGIF() {
#if defined(_SPIFFS_H)
loadSPIFFS();
#endif
if (!vFile || vFile.isDirectory()) {
Serial.println(F("ERROR: Failed to open file for reading"));
gfx->println(F("ERROR: Failed to open file for reading"));
gfx->println(playFile);
} else {
gd_GIF *gif = gd_open_gif(&vFile);
if (!gif) {
Serial.println(F("gd_open_gif() failed!"));
} else {
int32_t s = gif->width * gif->height;
uint8_t *buf = (uint8_t *)malloc(s);
if (!buf) {
Serial.println(F("buf malloc failed!"));
} else {
Serial.println(F("{acion:play, GIF:started, Info:["));
Serial.printf(" {canvas size: %ux%u}\n", gif->width, gif->height);
Serial.printf(" {number of colors: %d}\n", gif->palette->size);
Serial.println(F("]}"));
int t_fstart, t_delay = 0, t_real_delay, res, delay_until;
int duration = 0, remain = 0;
while (1) {
gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height);
t_fstart = millis();
t_delay = gif->gce.delay * 10;
res = gd_get_frame(gif, buf);
if (res < 0) {
Serial.println(F("ERROR: gd_get_frame() failed!"));
break;
} else if (res == 0) {
Serial.printf("{action:rewind, duration:%d, remain:%d (%0.1f%%)}\n", duration, remain, 100.0 * remain / duration);
duration = 0;
remain = 0;
gd_rewind(gif);
continue;
}
gfx->startWrite();
gfx->writeIndexedPixels(buf, gif->palette->colors, s);
gfx->endWrite();
t_real_delay = t_delay - (millis() - t_fstart);
duration += t_delay;
remain += t_real_delay;
delay_until = millis() + t_real_delay;
do {
delay(1);
} while (millis() < delay_until);
adjBrightness();
adjGIF();
}
Serial.println(F("action:stop, GIF:ended"));
Serial.printf("{duration: %d, remain: %d (%0.1f %%)}\n", duration, remain, 100.0 * remain / duration);
gd_close_gif(gif);
}
}
}
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT);
Serial.println("{Device:Started}");
#if defined(ARDUINO_M5STICKC)
M5.begin();
#endif
#if defined(ARDUINO_M5STICKCPLUS)
M5.begin();
M5.Beep.tone(4000);
delay(250);
M5.Beep.mute();
#endif
#if defined(_LED_H)
ledTimer();
#endif
#if defined(_SPIFFS_H)
listSPIFFS();
//eraseSPIFFS();
#endif
// Init Video
gfx->begin();
gfx->fillScreen(BLACK);
// Turn on Backlight
#ifdef TFT_BL
//M5.Axp.ScreenBreath(b);
ledcSetup(pwmLedChannelTFT, pwmFreq, pwmResolution); // 5 kHz PWM, 8-bit resolution
ledcAttachPin(TFT_BL, pwmLedChannelTFT); // assign TFT_BL pin to channel
ledcWrite(pwmLedChannelTFT, backlight[b]); // brightness 0 - 255
#endif
#ifdef DEBUG
gfxScreenTest();
#endif
gfxPlayGIF();
// Turn off Backlight
#ifdef TFT_BL
delay(60000);
ledcDetachPin(TFT_BL);
#endif
// Put device to sleep
gfx->displayOff();
/*
++bootCount;
Serial.println("Boot number: " + String(bootCount));
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.flush();
*/
esp_deep_sleep_start();
}
void loop() {
}
espgfxGIF-dstarc.ino
ArduinoD1MINI + ST7789
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 7
#define TFT_CS 16
#define TFT_DC 4
#define TFT_RST 0
const int BTN_A = 3;
const int BTN_B = 4;
#include <SPIFFS.h>
#include <Arduino_GFX_Library.h> /* Install via Arduino Library Manager */
#include "gifdec.h"
/*
* espgfxGIF Version 2022.05A (Brightness Edition)
* Board: TTGO T-Display & M5Stack M5StickC (esp32)
* Author: tommyho510@gmail.com
* Original Author: moononournation
* Required: Arduino library Arduino_GFX 1.2.0
* Dependency: gifdec.h
*
* Please upload SPIFFS data with ESP32 Sketch Data Upload:
* https://github.com/me-no-dev/arduino-esp32fs-plugin
* GIF src: various
*/
// *** BEGIN editing of your settings ...
// #define ARDUINO_M5STICKC
#define ARDUINO_D1MINI_ST7789
#define GIF_FILENAME "/pcboot.gif" /* comment out for random GIF */
// *** END editing of your settings ...
#if defined(ARDUINO_D1MINI_ST7789)
/* LOLIN D1 Mini esp8266 + TFT Shield */
/* 1.54" ST7789 TFT LCD 240x240 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 7
#define TFT_CS 16
#define TFT_DC 4
#define TFT_RST 0
//#define TFT_BL NC
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 240 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 3;
const int BTN_B = 4;
#endif /* not selected specific hardware */
// Rotation & Brightness control
int rot[2] = {1, 3};
int backlight[5] = {10, 30, 60, 120, 240};
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
byte a = 1;
byte b = 4;
unsigned long p = 0;
bool inv = 0;
int pressA = 0;
int pressB = 0;
String gifArray[30], randGIF_FILENAME, playFile;
int gifArraySize;
void adjBrightness() {
if (digitalRead(BTN_B) == 0) {
if (pressB == 0) {
pressB = 1;
b++;
//if (b > 15) b = 7;
//M5.Axp.ScreenBreath(b);
if (b >= 5) b = 0;
ledcWrite(pwmLedChannelTFT, backlight[b]);
Serial.println("{BTN_B:Pressed, Brightness:" + String(backlight[b]) + "}");
}
} else pressB = 0;
}
void adjGIF() {
if (digitalRead(BTN_A) == 0) {
if (pressA == 0) {
pressA = 1;
inv = !inv;
gfx->invertDisplay(inv);
Serial.println("{BTN_A:Pressed, Inverted:" + String(inv) + "}");
//a++;
//if (a >= 2)
// a = 0;
//gfx->setRotation(rot[a]);
//Serial.println("{BTN_A:Pressed, Rotation:" + String(rot[a]) + "}");
//Serial.println("{BTN_A:Pressed}");
listSPIFFS();
}
} else pressA = 0;
}
void listSPIFFS() {
gifArraySize = 0;
Serial.println("{ls /:[");
if (SPIFFS.begin(true)) {
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file){
gifArray[gifArraySize] = String(file.name());
Serial.println((" {#:" + String(gifArraySize) + ", name:" + String(file.name()) + ", ").substring(0,40) + "\tsize:" + String(file.size()) + "}");
file = root.openNextFile();
gifArraySize++;
}
randGIF_FILENAME = gifArray[random(0, gifArraySize)];
//p = millis();
//randGIF_FILENAME = gifArray[millis() % gifArraySize];
Serial.println(" {randomGIF:" + String(randGIF_FILENAME) +"}");
Serial.println("]}");
}
}
void eraseSPIFFS() {
if(SPIFFS.begin(true)) {
bool formatted = SPIFFS.format();
if(formatted) {
Serial.println("\n\nSuccess formatting");
listSPIFFS();
} else {
Serial.println("\n\nError formatting");
}
}
}
void gfxPlayGIF() {
// Init SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println(F("ERROR: SPIFFS mount failed!"));
gfx->println(F("ERROR: SPIFFS mount failed!"));
} else {
#ifndef GIF_FILENAME
#define GIF_FILENAME
playFile = "/" + String(randGIF_FILENAME);
#else
playFile = GIF_FILENAME;
#endif
File vFile = SPIFFS.open(playFile);
if (!vFile || vFile.isDirectory()) {
Serial.println(F("ERROR: Failed to open file for reading"));
gfx->println(F("ERROR: Failed to open file for reading"));
gfx->println(playFile);
} else {
gd_GIF *gif = gd_open_gif(&vFile);
if (!gif) {
Serial.println(F("gd_open_gif() failed!"));
} else {
int32_t s = gif->width * gif->height;
uint8_t *buf = (uint8_t *)malloc(s);
if (!buf) {
Serial.println(F("buf malloc failed!"));
} else {
Serial.println(F("{acion:play, GIF:started, Info:["));
Serial.printf(" {canvas size: %ux%u}\n", gif->width, gif->height);
Serial.printf(" {number of colors: %d}\n", gif->palette->size);
Serial.println(F("]}"));
int t_fstart, t_delay = 0, t_real_delay, res, delay_until;
int duration = 0, remain = 0;
while (1) {
gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height);
t_fstart = millis();
t_delay = gif->gce.delay * 10;
res = gd_get_frame(gif, buf);
if (res < 0) {
Serial.println(F("ERROR: gd_get_frame() failed!"));
break;
} else if (res == 0) {
Serial.printf("{action:rewind, duration:%d, remain:%d (%0.1f%%)}\n", duration, remain, 100.0 * remain / duration);
duration = 0;
remain = 0;
gd_rewind(gif);
continue;
}
gfx->startWrite();
gfx->writeIndexedPixels(buf, gif->palette->colors, s);
gfx->endWrite();
t_real_delay = t_delay - (millis() - t_fstart);
duration += t_delay;
remain += t_real_delay;
delay_until = millis() + t_real_delay;
do {
delay(1);
} while (millis() < delay_until);
adjBrightness();
adjGIF();
}
Serial.println(F("action:stop, GIF:ended"));
Serial.printf("{duration: %d, remain: %d (%0.1f %%)}\n", duration, remain, 100.0 * remain / duration);
gd_close_gif(gif);
}
}
}
}
}
void setup() {
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT);
#if defined(ARDUINO_M5STICKC)
M5.begin();
#endif
Serial.begin(115200);
delay(500);
Serial.println("{Device:Started}");
listSPIFFS();
//eraseSPIFFS();
// Init Video
gfx->begin();
gfx->fillScreen(BLACK);
// Turn on Backlight
#ifdef TFT_BL
//M5.Axp.ScreenBreath(b);
ledcSetup(pwmLedChannelTFT, pwmFreq, pwmResolution); // 5 kHz PWM, 8-bit resolution
ledcAttachPin(TFT_BL, pwmLedChannelTFT); // assign TFT_BL pin to channel
ledcWrite(pwmLedChannelTFT, backlight[b]); // brightness 0 - 255
#endif
gfxPlayGIF();
// Turn off Backlight
#ifdef TFT_BL
delay(60000);
ledcDetachPin(TFT_BL);
#endif
// Put device to sleep
gfx->displayOff();
}
void loop() {
}
espgfxGIF_Timer.ino
Arduino#include <Arduino_GFX_Library.h> /* Install via Arduino Library Manager */
/*
* espgfxGIF Version 2023.08B (Brightness Edition)
* Board: T-Display S3, T-Display, M5StickC-Plus, M5StickC (esp32)
* Author: tommyho510@gmail.com
* Original Author: moononournation
* Required: Arduino library Arduino_GFX 1.3.7
* Dependency: gifdec.h
*
* Please upload SPIFFS data with ESP32 Sketch Data Upload:
* https://github.com/me-no-dev/arduino-esp32fs-plugin
* GIF src: various
*/
// *** BEGIN editing of your settings ...
#define ARDUINO_M5STICKCPLUS
//#define GIF_FILENAME "/your_file.gif" /* comment out for random GIF */
// *** END editing of your settings ...
//#define DEBUG /* uncomment this line to start with screen test */
#if defined(ARDUINO_M5STICKCPLUS)
/* M5Stack */
/* 1.14" ST7789 IPS LCD 135x240 M5StickC Plus * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS 5
#define TFT_DC 23
#define TFT_RST 18
#define LED 10
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */,
53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickCPlus.h>
#elif defined(ARDUINO_M5STICKC)
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5StickC * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS 5
#define TFT_DC 23
#define TFT_RST 18
#define LED 10
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* RST */, 3 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */,
26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickC.h>
#elif defined(ARDUINO_TDISPLAYS3)
/* LILYGO T-Display */
/* 1.90" ST7789V IPS LCD 170x320 TTGO T-Display (Rotation: 0 bottom, 1 left, 2 top, 3 right) */
#define TFT_CS 6
#define TFT_DC 7
#define TFT_RST 5
#define TFT_BL 38
#define LED 2
Arduino_DataBus *bus = new Arduino_ESP32LCD8(TFT_DC /* DC */, TFT_CS /* CS */, 8 /* WR */, 9 /* RD */,
39 /* D0 */, 40 /* D1 */, 41 /* D2 */, 42 /* D3 */,
45 /* D4 */, 46 /* D5 */, 47 /* D6 */, 48 /* D7 */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 0 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */,
35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 14;
#elif defined(ARDUINO_TDISPLAY)
/* TTGO T-Display */
/* 1.14" ST7789 IPS LCD 135x240 TTGO T-Display (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
//#define TFT_MOSI 19
//#define TFT_SCLK 18
//#define TFT_CS 5
//#define TFT_DC 16
#define TFT_RST -1
#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 35;
#elif defined(ARDUINO_D1MINI)
/* LOLIN D1 Mini esp8266 + TFT-2.4 Shield */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 14
#define TFT_CS 16
#define TFT_DC 15
#define TFT_RST 5
//#define TFT_BL NC
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 3;
const int BTN_B = 4;
#elif defined(ARDUINO_DEVKITV1)
/* DOIT ESP32 DEVKIT V1 + ILI9341 */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 23
#define TFT_MISO 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 17
//#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 13;
const int BTN_B = 15;
#endif /* not selected specific hardware */
// Sleep timer
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 1 /* Time ESP32 will go to sleep (in seconds) */
RTC_DATA_ATTR int bootCount = 0;
// Rotation & Brightness control
int rot[2] = {1, 3};
int backlight[5] = {10, 30, 60, 120, 240};
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
byte a = 1;
byte b = 4;
unsigned long p = 0;
bool inv = 0;
int pressA = 0;
int pressB = 0;
String gifArray[30], randGIF_FILENAME, playFile;
int gifArraySize;
#include "gifdec.h"
#include "spiffs-helper.h"
#include "gfx-helper.h"
// Main subroutine
void gfxPlayGIF() {
// Init SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println(F("ERROR: SPIFFS mount failed!"));
gfx->println(F("ERROR: SPIFFS mount failed!"));
} else {
#ifndef GIF_FILENAME
#define GIF_FILENAME
playFile = "/" + String(randGIF_FILENAME);
Serial.println("{Opening random GIF_FILENAME " + playFile + "}");
#else
playFile = GIF_FILENAME;
Serial.println("{Opening designated GIF_FILENAME " + playFile + "}");
#endif
File vFile = SPIFFS.open(playFile);
if (!vFile || vFile.isDirectory()) {
Serial.println(F("ERROR: Failed to open file for reading"));
gfx->println(F("ERROR: Failed to open file for reading"));
gfx->println(playFile);
} else {
gd_GIF *gif = gd_open_gif(&vFile);
if (!gif) {
Serial.println(F("gd_open_gif() failed!"));
} else {
int32_t s = gif->width * gif->height;
uint8_t *buf = (uint8_t *)malloc(s);
if (!buf) {
Serial.println(F("buf malloc failed!"));
} else {
Serial.println(F("{acion:play, GIF:started, Info:["));
Serial.printf(" {canvas size: %ux%u}\n", gif->width, gif->height);
Serial.printf(" {number of colors: %d}\n", gif->palette->size);
Serial.println(F("]}"));
int t_fstart, t_delay = 0, t_real_delay, res, delay_until;
int duration = 0, remain = 0;
while (1) {
gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height);
t_fstart = millis();
t_delay = gif->gce.delay * 10;
res = gd_get_frame(gif, buf);
if (res < 0) {
Serial.println(F("ERROR: gd_get_frame() failed!"));
break;
} else if (res == 0) {
Serial.printf("{action:rewind, duration:%d, remain:%d (%0.1f%%)}\n", duration, remain, 100.0 * remain / duration);
duration = 0;
remain = 0;
gd_rewind(gif);
continue;
}
gfx->startWrite();
gfx->writeIndexedPixels(buf, gif->palette->colors, s);
gfx->endWrite();
t_real_delay = t_delay - (millis() - t_fstart);
duration += t_delay;
remain += t_real_delay;
delay_until = millis() + t_real_delay;
do {
delay(1);
} while (millis() < delay_until);
adjBrightness();
adjGIF();
}
Serial.println(F("action:stop, GIF:ended"));
Serial.printf("{duration: %d, remain: %d (%0.1f %%)}\n", duration, remain, 100.0 * remain / duration);
gd_close_gif(gif);
}
}
}
}
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT);
Serial.println("{Device:Started}");
#if defined(ARDUINO_M5STICKC)
M5.begin();
#endif
#if defined(ARDUINO_M5STICKCPLUS)
M5.begin();
#endif
listSPIFFS();
//eraseSPIFFS();
// Init Video
gfx->begin();
gfx->fillScreen(BLACK);
// Turn on Backlight
#ifdef TFT_BL
//M5.Axp.ScreenBreath(b);
ledcSetup(pwmLedChannelTFT, pwmFreq, pwmResolution); // 5 kHz PWM, 8-bit resolution
ledcAttachPin(TFT_BL, pwmLedChannelTFT); // assign TFT_BL pin to channel
ledcWrite(pwmLedChannelTFT, backlight[b]); // brightness 0 - 255
#endif
#ifdef DEBUG
gfxScreenTest();
#endif
gfxPlayGIF();
// Turn off Backlight
#ifdef TFT_BL
delay(60000);
ledcDetachPin(TFT_BL);
#endif
// Put device to sleep
gfx->displayOff();
++bootCount;
Serial.println("Boot number: " + String(bootCount));
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.flush();
esp_deep_sleep_start();
}
void loop() {
}
/*
* Adapted from: https://github.com/BasementCat/arduino-tft-gif
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/
#include "gifdec.h"
#include <sys/types.h>
#include <FS.h>
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
int gif_buf_last_idx, gif_buf_idx, file_pos;
uint8_t gif_buf[GIF_BUF_SIZE];
static bool gif_buf_seek(File *fd, int len)
{
if (len > (gif_buf_last_idx - gif_buf_idx))
{
fd->seek(len - (gif_buf_last_idx - gif_buf_idx), SeekCur);
gif_buf_idx = gif_buf_last_idx;
}
else
{
gif_buf_idx += len;
}
return true;
}
static int gif_buf_read(File *fd, uint8_t *dest, int len)
{
while (len--)
{
if (gif_buf_idx == gif_buf_last_idx)
{
gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE);
gif_buf_idx = 0;
}
file_pos++;
*(dest++) = gif_buf[gif_buf_idx++];
}
return len;
}
static uint8_t gif_buf_read(File *fd)
{
if (gif_buf_idx == gif_buf_last_idx)
{
gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE);
gif_buf_idx = 0;
}
file_pos++;
return gif_buf[gif_buf_idx++];
}
static uint16_t gif_buf_read16(File *fd)
{
return gif_buf_read(fd) + (((uint16_t)gif_buf_read(fd)) << 8);
}
static void read_palette(File *fd, gd_Palette *dest, int32_t num_colors)
{
uint8_t r, g, b;
dest->size = num_colors;
for (int32_t i = 0; i < num_colors; i++)
{
r = gif_buf_read(fd);
g = gif_buf_read(fd);
b = gif_buf_read(fd);
dest->colors[i] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
}
}
gd_GIF *gd_open_gif(File *fd)
{
uint8_t sigver[3];
uint16_t width, height, depth;
uint8_t fdsz, bgidx, aspect;
int32_t gct_sz;
gd_GIF *gif;
// init global variables
gif_buf_last_idx = GIF_BUF_SIZE;
gif_buf_idx = gif_buf_last_idx; // no buffer yet
file_pos = 0;
/* Header */
gif_buf_read(fd, sigver, 3);
if (memcmp(sigver, "GIF", 3) != 0)
{
Serial.println(F("invalid signature"));
return NULL;
}
/* Version */
gif_buf_read(fd, sigver, 3);
if (memcmp(sigver, "89a", 3) != 0)
{
Serial.println(F("invalid version"));
return NULL;
}
/* Width x Height */
width = gif_buf_read16(fd);
height = gif_buf_read16(fd);
/* FDSZ */
gif_buf_read(fd, &fdsz, 1);
/* Presence of GCT */
if (!(fdsz & 0x80))
{
Serial.println(F("no global color table"));
return NULL;
}
/* Color Space's Depth */
depth = ((fdsz >> 4) & 7) + 1;
/* Ignore Sort Flag. */
/* GCT Size */
gct_sz = 1 << ((fdsz & 0x07) + 1);
/* Background Color Index */
gif_buf_read(fd, &bgidx, 1);
/* Aspect Ratio */
gif_buf_read(fd, &aspect, 1);
/* Create gd_GIF Structure. */
gif = (gd_GIF *)calloc(1, sizeof(*gif));
gif->fd = fd;
gif->width = width;
gif->height = height;
gif->depth = depth;
/* Read GCT */
read_palette(fd, &gif->gct, gct_sz);
gif->palette = &gif->gct;
gif->bgindex = bgidx;
gif->anim_start = file_pos; // fd->position();
gif->table = new_table();
return gif;
}
static void discard_sub_blocks(gd_GIF *gif)
{
uint8_t size;
do
{
gif_buf_read(gif->fd, &size, 1);
gif_buf_seek(gif->fd, size);
} while (size);
}
static void read_plain_text_ext(gd_GIF *gif)
{
if (gif->plain_text)
{
uint16_t tx, ty, tw, th;
uint8_t cw, ch, fg, bg;
off_t sub_block;
gif_buf_seek(gif->fd, 1); /* block size = 12 */
tx = gif_buf_read16(gif->fd);
ty = gif_buf_read16(gif->fd);
tw = gif_buf_read16(gif->fd);
th = gif_buf_read16(gif->fd);
cw = gif_buf_read(gif->fd);
ch = gif_buf_read(gif->fd);
fg = gif_buf_read(gif->fd);
bg = gif_buf_read(gif->fd);
gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
}
else
{
/* Discard plain text metadata. */
gif_buf_seek(gif->fd, 13);
}
/* Discard plain text sub-blocks. */
discard_sub_blocks(gif);
}
static void read_graphic_control_ext(gd_GIF *gif)
{
uint8_t rdit;
/* Discard block size (always 0x04). */
gif_buf_seek(gif->fd, 1);
gif_buf_read(gif->fd, &rdit, 1);
gif->gce.disposal = (rdit >> 2) & 3;
gif->gce.input = rdit & 2;
gif->gce.transparency = rdit & 1;
gif->gce.delay = gif_buf_read16(gif->fd);
gif_buf_read(gif->fd, &gif->gce.tindex, 1);
/* Skip block terminator. */
gif_buf_seek(gif->fd, 1);
}
static void read_comment_ext(gd_GIF *gif)
{
if (gif->comment)
{
gif->comment(gif);
}
/* Discard comment sub-blocks. */
discard_sub_blocks(gif);
}
static void read_application_ext(gd_GIF *gif)
{
char app_id[8];
char app_auth_code[3];
/* Discard block size (always 0x0B). */
gif_buf_seek(gif->fd, 1);
/* Application Identifier. */
gif_buf_read(gif->fd, (uint8_t *)app_id, 8);
/* Application Authentication Code. */
gif_buf_read(gif->fd, (uint8_t *)app_auth_code, 3);
if (!strncmp(app_id, "NETSCAPE", sizeof(app_id)))
{
/* Discard block size (0x03) and constant byte (0x01). */
gif_buf_seek(gif->fd, 2);
gif->loop_count = gif_buf_read16(gif->fd);
/* Skip block terminator. */
gif_buf_seek(gif->fd, 1);
}
else if (gif->application)
{
gif->application(gif, app_id, app_auth_code);
discard_sub_blocks(gif);
}
else
{
discard_sub_blocks(gif);
}
}
static void read_ext(gd_GIF *gif)
{
uint8_t label;
gif_buf_read(gif->fd, &label, 1);
switch (label)
{
case 0x01:
read_plain_text_ext(gif);
break;
case 0xF9:
read_graphic_control_ext(gif);
break;
case 0xFE:
read_comment_ext(gif);
break;
case 0xFF:
read_application_ext(gif);
break;
default:
Serial.print("unknown extension: ");
Serial.println(label, HEX);
}
}
static gd_Table *new_table()
{
// int key;
// int init_bulk = MAX(1 << (key_size + 1), 0x100);
// Table *table = (Table*) malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
// if (table) {
// table->bulk = init_bulk;
// table->nentries = (1 << key_size) + 2;
// table->entries = (Entry *) &table[1];
// for (key = 0; key < (1 << key_size); key++)
// table->entries[key] = (Entry) {1, 0xFFF, key};
// }
// return table;
int s = sizeof(gd_Table) + (sizeof(gd_Entry) * 4096);
gd_Table *table = (gd_Table *)malloc(s);
if (table)
{
Serial.printf("new_table() malloc %d.\n", s);
}
else
{
Serial.printf("new_table() malloc %d failed!\n", s);
}
table->entries = (gd_Entry *)&table[1];
return table;
}
static void reset_table(gd_Table *table, int32_t key_size)
{
table->nentries = (1 << key_size) + 2;
for (int32_t key = 0; key < (1 << key_size); key++)
{
table->entries[key] = (gd_Entry){1, 0xFFF, key};
}
}
/* Add table entry. Return value:
* 0 on success
* +1 if key size must be incremented after this addition
* -1 if could not realloc table */
static int32_t add_entry(gd_Table *table, int32_t length, uint16_t prefix, uint8_t suffix)
{
// Table *table = *tablep;
// if (table->nentries == table->bulk) {
// table->bulk *= 2;
// table = (Table*) realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
// if (!table) return -1;
// table->entries = (Entry *) &table[1];
// *tablep = table;
// }
table->entries[table->nentries] = (gd_Entry){length, prefix, suffix};
table->nentries++;
if ((table->nentries & (table->nentries - 1)) == 0)
return 1;
return 0;
}
static uint16_t get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte)
{
int bits_read;
int rpad;
int frag_size;
uint16_t key;
key = 0;
for (bits_read = 0; bits_read < key_size; bits_read += frag_size)
{
rpad = (*shift + bits_read) % 8;
if (rpad == 0)
{
/* Update byte. */
if (*sub_len == 0)
gif_buf_read(gif->fd, sub_len, 1); /* Must be nonzero! */
gif_buf_read(gif->fd, byte, 1);
(*sub_len)--;
}
frag_size = MIN(key_size - bits_read, 8 - rpad);
key |= ((uint16_t)((*byte) >> rpad)) << bits_read;
}
/* Clear extra bits to the left. */
key &= (1 << key_size) - 1;
*shift = (*shift + key_size) % 8;
return key;
}
/* Compute output index of y-th input line, in frame of height h. */
static int interlaced_line_index(int h, int y)
{
int p; /* number of lines in current pass */
p = (h - 1) / 8 + 1;
if (y < p) /* pass 1 */
return y * 8;
y -= p;
p = (h - 5) / 8 + 1;
if (y < p) /* pass 2 */
return y * 8 + 4;
y -= p;
p = (h - 3) / 4 + 1;
if (y < p) /* pass 3 */
return y * 4 + 2;
y -= p;
/* pass 4 */
return y * 2 + 1;
}
/* Decompress image pixels.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int32_t read_image_data(gd_GIF *gif, int interlace, uint8_t *frame)
{
uint8_t sub_len, shift, byte;
int init_key_size, key_size, table_is_full;
int frm_off, str_len, p, x, y;
uint16_t key, clear, stop;
int32_t ret;
gd_Entry entry;
off_t start, end;
// Serial.println("Read key size");
gif_buf_read(gif->fd, &byte, 1);
key_size = (int)byte;
// Serial.println("Set pos, discard sub blocks");
// start = gif->fd->position();
// discard_sub_blocks(gif);
// end = gif->fd->position();
// gif_buf_seek(gif->fd, start, SeekSet);
clear = 1 << key_size;
stop = clear + 1;
// Serial.println("New LZW table");
// table = new_table(key_size);
reset_table(gif->table, key_size);
key_size++;
init_key_size = key_size;
sub_len = shift = 0;
// Serial.println("Get init key");
key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
frm_off = 0;
ret = 0;
while (1)
{
if (key == clear)
{
// Serial.println("Clear key, reset nentries");
key_size = init_key_size;
gif->table->nentries = (1 << (key_size - 1)) + 2;
table_is_full = 0;
}
else if (!table_is_full)
{
// Serial.println("Add entry to table");
ret = add_entry(gif->table, str_len + 1, key, entry.suffix);
// if (ret == -1) {
// // Serial.println("Table entry add failure");
// free(table);
// return -1;
// }
if (gif->table->nentries == 0x1000)
{
// Serial.println("Table is full");
ret = 0;
table_is_full = 1;
}
}
// Serial.println("Get key");
key = get_key(gif, key_size, &sub_len, &shift, &byte);
if (key == clear)
continue;
if (key == stop)
break;
if (ret == 1)
key_size++;
entry = gif->table->entries[key];
str_len = entry.length;
uint8_t tindex = gif->gce.tindex;
// Serial.println("Interpret key");
while (1)
{
p = frm_off + entry.length - 1;
x = p % gif->fw;
y = p / gif->fw;
if (interlace)
{
y = interlaced_line_index((int)gif->fh, y);
}
if (tindex != entry.suffix)
{
frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
}
if (entry.prefix == 0xFFF)
break;
else
entry = gif->table->entries[entry.prefix];
}
frm_off += str_len;
if (key < gif->table->nentries - 1 && !table_is_full)
gif->table->entries[gif->table->nentries - 1].suffix = entry.suffix;
}
// Serial.println("Done w/ img data, free table and seek to end");
// free(table);
gif_buf_read(gif->fd, &sub_len, 1); /* Must be zero! */
// gif_buf_seek(gif->fd, end, SeekSet);
return 0;
}
/* Read image.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int32_t read_image(gd_GIF *gif, uint8_t *frame)
{
uint8_t fisrz;
int interlace;
/* Image Descriptor. */
// Serial.println("Read image descriptor");
gif->fx = gif_buf_read16(gif->fd);
gif->fy = gif_buf_read16(gif->fd);
gif->fw = gif_buf_read16(gif->fd);
gif->fh = gif_buf_read16(gif->fd);
// Serial.println("Read fisrz?");
gif_buf_read(gif->fd, &fisrz, 1);
interlace = fisrz & 0x40;
/* Ignore Sort Flag. */
/* Local Color Table? */
if (fisrz & 0x80)
{
/* Read LCT */
// Serial.println("Read LCT");
read_palette(gif->fd, &gif->lct, 1 << ((fisrz & 0x07) + 1));
gif->palette = &gif->lct;
}
else
{
gif->palette = &gif->gct;
}
/* Image Data. */
// Serial.println("Read image data");
return read_image_data(gif, interlace, frame);
}
static void render_frame_rect(gd_GIF *gif, uint16_t *buffer, uint8_t *frame)
{
int i, j, k;
uint8_t index, *color;
i = gif->fy * gif->width + gif->fx;
for (j = 0; j < gif->fh; j++)
{
for (k = 0; k < gif->fw; k++)
{
index = frame[(gif->fy + j) * gif->width + gif->fx + k];
// color = &gif->palette->colors[index*2];
if (!gif->gce.transparency || index != gif->gce.tindex)
buffer[(i + k)] = gif->palette->colors[index];
// memcpy(&buffer[(i+k)*2], color, 2);
}
i += gif->width;
}
}
/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame)
{
char sep;
while (1)
{
gif_buf_read(gif->fd, (uint8_t *)&sep, 1);
if (sep == 0)
{
gif_buf_read(gif->fd, (uint8_t *)&sep, 1);
}
if (sep == ',')
{
break;
}
if (sep == ';')
{
return 0;
}
if (sep == '!')
{
read_ext(gif);
}
else
{
Serial.printf("Read sep: [%d].\n", sep);
return -1;
}
}
// Serial.println("Do read image");
if (read_image(gif, frame) == -1)
return -1;
return 1;
}
void gd_rewind(gd_GIF *gif)
{
gif->fd->seek(gif->anim_start, SeekSet);
file_pos = gif->anim_start;
gif_buf_idx = gif_buf_last_idx; // reset buffer
}
void gd_close_gif(gd_GIF *gif)
{
gif->fd->close();
free(gif->table);
free(gif);
}
/*
* Adapted from: https://github.com/BasementCat/arduino-tft-gif
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/
#include <FS.h>
#ifndef _GIFDEC_H_
#define _GIFDEC_H_
#define GIF_BUF_SIZE 1024
typedef struct gd_Palette {
int size;
uint16_t colors[256];
} gd_Palette;
typedef struct gd_GCE {
uint16_t delay;
uint8_t tindex;
uint8_t disposal;
int input;
int transparency;
} gd_GCE;
typedef struct gd_Entry {
int32_t length;
uint16_t prefix;
uint8_t suffix;
} gd_Entry;
typedef struct gd_Table {
int bulk;
int nentries;
gd_Entry *entries;
} gd_Table;
typedef struct gd_GIF {
File* fd;
off_t anim_start;
uint16_t width, height;
uint16_t depth;
uint16_t loop_count;
gd_GCE gce;
gd_Palette *palette;
gd_Palette lct, gct;
void (*plain_text)(
struct gd_GIF *gif, uint16_t tx, uint16_t ty,
uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
uint8_t fg, uint8_t bg
);
void (*comment)(struct gd_GIF *gif);
void (*application)(struct gd_GIF *gif, char id[8], char auth[3]);
uint16_t fx, fy, fw, fh;
uint8_t bgindex;
gd_Table* table;
} gd_GIF;
gd_GIF *gd_open_gif(File* fd);
static gd_Table * new_table();
static void reset_table(gd_Table* table, int key_size);
// int32_t add_entry(gd_Table* table, int32_t length, uint16_t prefix, uint8_t suffix)
int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame);
void gd_rewind(gd_GIF *gif);
void gd_close_gif(gd_GIF *gif);
#endif /* _GIFDEC_H_ */
/*
* Author: tommyho510@gmail.com
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/
#include <SPIFFS.h>
#ifndef _SPIFFS_H
#define _SPIFFS_H
void listSPIFFS() {
gifArraySize = 0;
Serial.println("{ls /:[");
if (SPIFFS.begin(true)) {
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file){
gifArray[gifArraySize] = String(file.name());
Serial.println((" {#:" + String(gifArraySize) + ", name:" + String(file.name()) + ", ").substring(0,40) + "\tsize:" + String(file.size()) + "}");
file = root.openNextFile();
gifArraySize++;
}
randGIF_FILENAME = gifArray[random(0, gifArraySize)];
//p = millis();
//randGIF_FILENAME = gifArray[millis() % gifArraySize];
Serial.println(" {randomGIF:" + String(randGIF_FILENAME) +"}");
Serial.println("]}");
}
}
void eraseSPIFFS() {
if(SPIFFS.begin(true)) {
bool formatted = SPIFFS.format();
if(formatted) {
Serial.println("\n\nSuccess formatting");
listSPIFFS();
} else {
Serial.println("\n\nError formatting");
}
}
}
void loadSPIFFS() {
// Init SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println(F("ERROR: SPIFFS mount failed!"));
gfx->println(F("ERROR: SPIFFS mount failed!"));
} else {
#ifndef GIF_FILENAME
#define GIF_FILENAME
playFile = "/" + String(randGIF_FILENAME);
Serial.println("{Opening random GIF_FILENAME " + playFile + "}");
#else
playFile = GIF_FILENAME;
Serial.println("{Opening designated GIF_FILENAME " + playFile + "}");
#endif
vFile = SPIFFS.open(playFile);
}
}
#endif /* _SPIFFS_H */
/*
* Author: tommyho510@gmail.com
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/
#include <Arduino_GFX_Library.h> /* Install via Arduino Library Manager */
#ifndef _Arduino_GFX_H
#define _Arduino_GFX_H
// Press BTN_A to invert display
void adjGIF() {
if (digitalRead(BTN_A) == 0) {
if (pressA == 0) {
#if defined(ARDUINO_M5STICKCPLUS)
M5.Beep.tone(3000);
delay(250);
M5.Beep.mute();
#endif
pressA = 1;
inv = !inv;
gfx->invertDisplay(inv);
Serial.println("{BTN_A:Pressed, Inverted:" + String(inv) + "}");
//a++;
//if (a >= 2)
// a = 0;
//gfx->setRotation(rot[a]);
//Serial.println("{BTN_A:Pressed, Rotation:" + String(rot[a]) + "}");
//Serial.println("{BTN_A:Pressed}");
listSPIFFS();
}
} else {
pressA = 0;
M5.Beep.mute();
}
}
// Press BTN_B to adjust brightness
void adjBrightness() {
if (digitalRead(BTN_B) == 0) {
if (pressB == 0) {
b++;
if (b >= 5) b = 0;
#if defined(ARDUINO_M5STICKCPLUS)
if (b == 4) {
M5.Beep.tone(2000);
delay(250);
}
M5.Beep.tone(3000);
delay(250);
M5.Beep.mute();
M5.Axp.ScreenBreath(axp[b]);
#endif
#if defined(ARDUINO_M5STICKC)
M5.Axp.ScreenBreath(axp[b]);
#endif
ledcWrite(pwmLedChannelTFT, backlight[b]);
Serial.println("{BTN_B:Pressed, Brightness:" + String(backlight[b]) + "}");
pressB = 1;
}
} else {
pressB = 0;
M5.Beep.mute();
}
}
unsigned long gfxRainbow(uint8_t cIndex) {
gfx->fillScreen(BLACK);
unsigned long start = micros();
int w = gfx->width(), h = gfx->height(), s = h / 8;
uint16_t arr [] = { PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE, PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE };
gfx->fillRect(0, 0, w, s, arr [cIndex]);
gfx->fillRect(0, s, w, 2 * s, arr [cIndex + 1]);
gfx->fillRect(0, 2 * s, w, 3 * s, arr [cIndex + 2]);
gfx->fillRect(0, 3 * s, w, 4 * s, arr [cIndex + 3]);
gfx->fillRect(0, 4 * s, w, 5 * s, arr [cIndex + 4]);
gfx->fillRect(0, 5 * s, w, 6 * s, arr [cIndex + 5]);
gfx->fillRect(0, 6 * s, w, 7 * s, arr [cIndex + 6]);
gfx->fillRect(0, 7 * s, w, 8 * s, arr [cIndex + 7]);
return micros() - start;
}
unsigned long gfxChar(uint16_t colorT, uint16_t colorB) {
gfx->fillScreen(colorB);
unsigned long start = micros();
gfx->setTextColor(GREEN);
for (int x = 0; x < 16; x++){
gfx->setCursor(10 + x * 8, 2);
gfx->print(x, 16);
}
gfx->setTextColor(BLUE);
for (int y = 0; y < 16; y++){
gfx->setCursor(2, 12 + y * 10);
gfx->print(y, 16);
}
char c = 0;
for (int y = 0; y < 16; y++){
for (int x = 0; x < 16; x++){
gfx->drawChar(10 + x * 8, 12 + y * 10, c++, colorT, colorB);
}
}
return micros() - start;
}
unsigned long gfxFilledCircles(uint8_t radius, uint16_t color) {
gfx->fillScreen(BLACK);
unsigned long start;
int x, y, r2 = radius * 2,
w = gfx->width(), h = gfx->height();
start = micros();
for(x=radius; x<w; x+=r2) {
for(y=radius; y<h; y+=r2) {
gfx->fillCircle(x, y, radius, color);
}
}
return micros() - start;
}
unsigned long gfxCircles(uint8_t radius, uint16_t color) {
// gfx->fillScreen(BLACK);
// Screen is not cleared for this one -- this is
// intentional and does not affect the reported time.
unsigned long start;
int x, y, r2 = radius * 2,
w = gfx->width() + radius, h = gfx->height() + radius;
start = micros();
for(x=0; x<w; x+=r2) {
for(y=0; y<h; y+=r2) {
gfx->drawCircle(x, y, radius, color);
}
}
return micros() - start;
}
void gfxScreenTest() {
Serial.print(F("Draw Ranbow: "));
Serial.println(gfxRainbow(0));
delay(500);
Serial.print(F("Draw Ranbow: "));
Serial.println(gfxRainbow(2));
delay(500);
Serial.print(F("Draw Ranbow: "));
Serial.println(gfxRainbow(4));
delay(500);
Serial.print(F("Draw Ranbow: "));
Serial.println(gfxRainbow(6));
delay(500);
Serial.print(F("Draw Filled Circles: "));
Serial.println(gfxFilledCircles(10, MAGENTA));
delay(500);
Serial.print(F("Draw Circles: "));
Serial.println(gfxCircles(10, BLACK));
delay(500);
Serial.print(F("Draw Filled Circles: "));
Serial.println(gfxFilledCircles(10, YELLOW));
delay(500);
Serial.print(F("Draw Circles: "));
Serial.println(gfxCircles(10, BLUE));
delay(500);
Serial.print(F("Draw Filled Circles: "));
Serial.println(gfxFilledCircles(10, RED));
delay(500);
Serial.print(F("Draw Circles: "));
Serial.println(gfxCircles(10, WHITE));
delay(500);
Serial.print(F("Draw Text: "));
Serial.println(gfxChar(WHITE, BLACK));
delay(500);
Serial.print(F("Draw Text: "));
Serial.println(gfxChar(BLUE, WHITE));
delay(500);
gfx->fillScreen(BLACK);
}
#endif /* _Arduino_GFX_H */
/*
* Author: tommyho510@gmail.com
* Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/
#ifndef _LED_H
#define _LED_H
#ifndef LED
#define LED 2
#endif
hw_timer_t *timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint8_t TimerCount = 0;
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux);
digitalWrite(LED, TimerCount % 100);
TimerCount++;
portEXIT_CRITICAL_ISR(&timerMux);
}
void ledTimer() {
pinMode(LED, OUTPUT);
timerSemaphore = xSemaphoreCreateBinary();
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 50000, true);
timerAlarmEnable(timer);
}
#endif /* _LED_H */
Comments