├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── button_sketch.h ├── esp32_task_demo.ino ├── gif ├── alien_eye.h ├── bb8.h ├── colortest.h ├── hyperspace.h ├── supernova.h └── x_wing.h ├── neopixels_sketch.h └── screen_sketch.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://www.buymeacoffee.com/thelastoutpostworkshop 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 thelastoutpostworkshop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tasks on ESP32 Made Easy - Animated GIF, Neopixels & Button using Tasks 2 | 3 | Buy Me A Coffee 4 | 5 | 6 | ### Struggling to understand tasks on the ESP32? 7 | 8 | This video breaks down the concepts in a simple, practical way to help you overcome mental roadblocks and take your projects to the next level. Learn how to run multiple components like LEDs, animated GIF, and NeoPixels seamlessly with ESP32 tasks! 9 | 10 | [](https://youtu.be/382p1NT1Wcs) -------------------------------------------------------------------------------- /button_sketch.h: -------------------------------------------------------------------------------- 1 | // Push Button Sketch 2 | // 3 | #define PUSH_BUTTION_PIN 1 4 | unsigned long lastDebounceTime; 5 | int lastState = -1; 6 | int state = -1; 7 | 8 | extern int currentGifPlayed; 9 | extern volatile bool gifChangeRequested; 10 | 11 | void buttonSketch(void *parameter); 12 | bool ButtonPressed(); 13 | 14 | void buttonSketch(void *parameter) 15 | { 16 | pinMode(PUSH_BUTTION_PIN, INPUT_PULLUP); 17 | lastDebounceTime = millis(); 18 | 19 | for (;;) 20 | { 21 | if (ButtonPressed()) 22 | { 23 | Serial.println("Button Pressed, switching to the next GIF"); 24 | currentGifPlayed = (currentGifPlayed + 1) % GIF_COUNT; // Update the GIF index 25 | gifChangeRequested = true; // Signal screenTask 26 | } 27 | delay(5); // Small delay for debouncing 28 | } 29 | } 30 | 31 | bool ButtonPressed() 32 | { 33 | int currentState = digitalRead(PUSH_BUTTION_PIN); 34 | if (currentState != lastState) 35 | { 36 | lastDebounceTime = millis(); 37 | } 38 | 39 | if ((millis() - lastDebounceTime) > 1) 40 | { 41 | if (currentState != state) 42 | { 43 | state = currentState; 44 | lastState = currentState; 45 | return state == LOW; // Returns true if the button is pressed 46 | } 47 | } 48 | 49 | lastState = currentState; 50 | return false; // No change in state 51 | } -------------------------------------------------------------------------------- /esp32_task_demo.ino: -------------------------------------------------------------------------------- 1 | //Youtube tutorial : https://youtu.be/382p1NT1Wcs 2 | 3 | #include "screen_sketch.h" // Screen Sketch 4 | #include "button_sketch.h" // Button Sketch 5 | #include "neopixels_sketch.h" // Neopixels Sketch 6 | 7 | void setup() 8 | { 9 | Serial.begin(115200); 10 | 11 | xTaskCreatePinnedToCore( 12 | screenSketch, // Task function 13 | "taskName", // Task name 14 | 8192, // Stack size 15 | NULL, // Task input parameters 16 | 1, // Task priority, be carefull when changing this 17 | NULL, // Task handle, add one if you want control over the task (resume or suspend the task) 18 | 1 // Core to run the task on 19 | ); 20 | 21 | xTaskCreatePinnedToCore( 22 | buttonSketch, // Task function 23 | "taskName", // Task name 24 | 8192, // Stack size 25 | NULL, // Task input parameters 26 | 1, // Task priority, be carefull when changing this 27 | NULL, // Task handle, add one if you want control over the task (resume or suspend the task) 28 | 1 // Core to run the task on 29 | ); 30 | 31 | xTaskCreatePinnedToCore( 32 | neopixelsSketch, // Task function 33 | "taskName", // Task name 34 | 8192, // Stack size 35 | NULL, // Task input parameters 36 | 1, // Task priority, be carefull when changing this 37 | NULL, // Task handle, add one if you want control over the task (resume or suspend the task) 38 | 1 // Core to run the task on 39 | ); 40 | } 41 | 42 | void loop() 43 | { 44 | } 45 | -------------------------------------------------------------------------------- /neopixels_sketch.h: -------------------------------------------------------------------------------- 1 | // Neopixels Sketch 2 | 3 | #include // Install this library with the Arduino IDE Library Manager 4 | // Tested on version 1.12.3 5 | 6 | // Neopixels 7 | // Define macros for color indices 8 | #define COLOR_RED 0 9 | #define COLOR_GREEN 1 10 | #define COLOR_BLUE 2 11 | #define COLOR_CYAN 3 12 | #define COLOR_MAGENTA 4 13 | #define COLOR_YELLOW 5 14 | #define COLOR_WHITE 6 15 | #define COLOR_ORANGE 7 16 | #define COLOR_PURPLE 8 17 | #define COLOR_PINK 9 18 | 19 | #define PIXELS_PIN 4 20 | #define PIXELS_COUNT 6 21 | Adafruit_NeoPixel neopixels(PIXELS_COUNT, PIXELS_PIN, NEO_GRB + NEO_KHZ800); 22 | volatile int currentPixelColor = COLOR_CYAN; 23 | 24 | void neopixelsSketch(void *parameter); 25 | void showRandomPixels(); 26 | uint32_t randomColor(); 27 | 28 | void neopixelsSketch(void *parameter) 29 | { 30 | // Setup start here 31 | neopixels.begin(); 32 | neopixels.setBrightness(30); 33 | randomSeed(analogRead(0)); // Seed the random generator 34 | 35 | // End of your setup 36 | 37 | // Loop function, run repeatedly 38 | for (;;) 39 | { 40 | showRandomPixels(); 41 | delay(2000); // 2 seconds before choosing random pixels to lit 42 | } 43 | } 44 | 45 | void showRandomPixels() 46 | { 47 | // Turn off all pixels first 48 | neopixels.clear(); 49 | 50 | // Light up a random number of pixels (1 to PIXEL_COUNT) 51 | int numPixelsToLight = random(1, PIXELS_COUNT + 1); 52 | uint32_t color = randomColor(); // Generate a random color 53 | for (int i = 0; i < numPixelsToLight; i++) 54 | { 55 | int randomPixel = random(0, PIXELS_COUNT); // Random pixel index 56 | neopixels.setPixelColor(randomPixel, color); 57 | } 58 | 59 | neopixels.show(); // Update the pixels to reflect changes 60 | } 61 | 62 | uint32_t randomColor() 63 | { 64 | // Generate a random color 65 | return neopixels.Color(random(0, 256), random(0, 256), random(0, 256)); 66 | } 67 | -------------------------------------------------------------------------------- /screen_sketch.h: -------------------------------------------------------------------------------- 1 | // Screen Sketch 2 | // 3 | #include // Install this library with the Arduino IDE Library Manager 4 | // Tested on version 2.7.1 5 | #include // Install this library with the Arduino IDE Library Manager 6 | // Tested on version 2.1.1 7 | 8 | #include "gif/alien_eye.h" 9 | #include "gif/supernova.h" 10 | 11 | // Main object for the display driver 12 | BB_SPI_LCD tft; 13 | 14 | // Pins definition for the display 15 | #define TFT_MISO -1 16 | #define TFT_MOSI 9 // SDA 17 | #define TFT_SCLK 3 // SDA 18 | #define TFT_CS 8 // Chip select control pin 19 | #define TFT_DC 18 // Data Command control pin 20 | #define TFT_RST 17 // Reset pin (could connect to RST pin) 21 | 22 | // GIFs to display 23 | #define GIF_COUNT 2 // Number of GIFs to cycle through, if you have enough space on flash memory 24 | int currentGifPlayed = 0; 25 | const uint8_t *gifData[GIF_COUNT] = {alien_eye,supernova}; // Add more GIFs here if you have enough space on flash memory 26 | const size_t gifSizes[GIF_COUNT] = {sizeof(alien_eye), sizeof(supernova)}; // Add corresponding sizes here 27 | AnimatedGIF *gifTopPlay[GIF_COUNT]; 28 | volatile bool gifChangeRequested = false; 29 | 30 | void GIFDraw(GIFDRAW *pDraw); 31 | AnimatedGIF *openGif(uint8_t *gifdata, size_t gifsize); 32 | void screenSketch(void *parameter); 33 | void *GIFAlloc(uint32_t u32Size); 34 | void printGifErrorMessage(int errorCode); 35 | 36 | void screenSketch(void *parameter) 37 | { 38 | tft.begin(LCD_GC9A01, FLAGS_SWAP_RB | FLAGS_FLIPX, 40000000, TFT_CS, TFT_DC, TFT_RST, -1, TFT_MISO, TFT_MOSI, TFT_SCLK); 39 | tft.setRotation(LCD_ORIENTATION_0); 40 | tft.fillScreen(TFT_BLACK); 41 | 42 | // Opening all the GIFs 43 | for (int i = 0; i < GIF_COUNT; i++) 44 | { 45 | gifTopPlay[i] = openGif((uint8_t *)gifData[i], gifSizes[i]); 46 | if (gifTopPlay[i] == NULL) 47 | { 48 | Serial.println("Cannot open GIF"); 49 | while (true) 50 | { 51 | } 52 | } 53 | } 54 | 55 | for (;;) 56 | { 57 | // Play the current frame of the current GIF 58 | bool framePlayed = gifTopPlay[currentGifPlayed]->playFrame(true, NULL); 59 | 60 | // Check if a new GIF has been requested 61 | if (framePlayed && gifChangeRequested) 62 | { 63 | gifChangeRequested = false; // Reset the flag 64 | tft.fillScreen(TFT_BLACK); // Clear the screen 65 | gifTopPlay[currentGifPlayed]->reset(); 66 | Serial.printf("Switching to GIF %d\n", currentGifPlayed); 67 | } 68 | } 69 | } 70 | 71 | // Open Gif and allocate memory 72 | AnimatedGIF *openGif(uint8_t *gifdata, size_t gifsize) 73 | { 74 | AnimatedGIF *gif; 75 | gif = (AnimatedGIF *)malloc(sizeof(AnimatedGIF)); 76 | if (gif == NULL) 77 | { 78 | Serial.println("Not RAM Enough memory for GIF structure"); 79 | return NULL; 80 | } 81 | 82 | gif->begin(GIF_PALETTE_RGB565_BE); // Set the cooked output type we want (compatible with SPI LCDs) 83 | 84 | if (gif->open(gifdata, gifsize, GIFDraw)) 85 | { 86 | Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif->getCanvasWidth(), gif->getCanvasHeight()); 87 | Serial.printf("GIF memory size is %ld (%2.2f MB)\n", gifsize, (float)gifsize / (1024 * 1024)); 88 | gif->setDrawType(GIF_DRAW_COOKED); // We want the Animated GIF library to generate ready-made pixels 89 | if (gif->allocFrameBuf(GIFAlloc) != GIF_SUCCESS) 90 | { 91 | Serial.println("Not Enough RAM memory for frame buffer"); 92 | return NULL; 93 | } 94 | return gif; 95 | } 96 | else 97 | { 98 | printGifErrorMessage(gif->getLastError()); 99 | return NULL; 100 | } 101 | } 102 | 103 | // Draw callback from the AnimatedGIF decoder 104 | void GIFDraw(GIFDRAW *pDraw) 105 | { 106 | if (pDraw->y == 0) 107 | { // set the memory window (once per frame) when the first line is rendered 108 | tft.setAddrWindow(pDraw->iX, pDraw->iY, pDraw->iWidth, pDraw->iHeight); 109 | } 110 | // For all other lines, just push the pixels to the display. We requested 'COOKED'big-endian RGB565 and 111 | tft.pushPixels((uint16_t *)pDraw->pPixels, pDraw->iWidth); 112 | } 113 | 114 | void *GIFAlloc(uint32_t u32Size) 115 | { 116 | return malloc(u32Size); 117 | } 118 | 119 | // Get human-readable error related to GIF 120 | void printGifErrorMessage(int errorCode) 121 | { 122 | switch (errorCode) 123 | { 124 | case GIF_DECODE_ERROR: 125 | Serial.println("GIF Decoding Error"); 126 | break; 127 | case GIF_TOO_WIDE: 128 | Serial.println("GIF Too Wide"); 129 | break; 130 | case GIF_INVALID_PARAMETER: 131 | Serial.println("Invalid Parameter for gif open"); 132 | break; 133 | case GIF_UNSUPPORTED_FEATURE: 134 | Serial.println("Unsupported feature in GIF"); 135 | break; 136 | case GIF_FILE_NOT_OPEN: 137 | Serial.println("GIF File not open"); 138 | break; 139 | case GIF_EARLY_EOF: 140 | Serial.println("GIF early end of file"); 141 | break; 142 | case GIF_EMPTY_FRAME: 143 | Serial.println("GIF with empty frame"); 144 | break; 145 | case GIF_BAD_FILE: 146 | Serial.println("GIF bad file"); 147 | break; 148 | case GIF_ERROR_MEMORY: 149 | Serial.println("GIF memory Error"); 150 | break; 151 | default: 152 | Serial.println("Unknown Error"); 153 | break; 154 | } 155 | } --------------------------------------------------------------------------------