├── .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 |
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 | }
--------------------------------------------------------------------------------