/ Temperature_GIF_Player / Temperature_GIF_Player.ino
Temperature_GIF_Player.ino
1 // SPDX-FileCopyrightText: 2020 Limor Fried/ladyada for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // please read credits at the bottom of file 6 7 #include <Adafruit_Arcada.h> 8 #include <Arcada_GifDecoder.h> 9 10 /*************** Display setup */ 11 Adafruit_Arcada arcada; 12 GifDecoder<ARCADA_TFT_WIDTH, ARCADA_TFT_HEIGHT, 12> decoder; 13 14 File file; 15 int16_t gif_offset_x, gif_offset_y; 16 #define GIF_DIRECTORY "/" // on SD or QSPI 17 18 float hotTemp = 24; // how warm it has to be to be considered hot 19 float coldTemp = 20; // how cool it has to be to be considered cold 20 21 22 #if defined(ARDUINO_NRF52840_CIRCUITPLAY) 23 void setupTemperature() { 24 } 25 float readTemperature() { 26 return CircuitPlayground.temperature(); 27 } 28 #endif 29 30 #define BRIGHTER_BUTTON ARCADA_BUTTONMASK_A 31 #define DIMMER_BUTTON ARCADA_BUTTONMASK_B 32 33 34 // Setup method runs once, when the sketch starts 35 void setup() { 36 decoder.setScreenClearCallback(screenClearCallback); 37 decoder.setUpdateScreenCallback(updateScreenCallback); 38 decoder.setDrawPixelCallback(drawPixelCallback); 39 decoder.setDrawLineCallback(drawLineCallback); 40 41 decoder.setFileSeekCallback(fileSeekCallback); 42 decoder.setFilePositionCallback(filePositionCallback); 43 decoder.setFileReadCallback(fileReadCallback); 44 decoder.setFileReadBlockCallback(fileReadBlockCallback); 45 46 // Start arcada! 47 if (!arcada.arcadaBegin()) { 48 Serial.println("Couldn't start Arcada"); 49 while(1) yield(); 50 } 51 // If we are using TinyUSB & QSPI we will have the filesystem show up! 52 arcada.filesysBeginMSD(); 53 54 //while (!Serial) delay(10); 55 56 Serial.begin(115200); 57 Serial.println("Animated GIFs Demo"); 58 59 arcada.displayBegin(); 60 arcada.display->fillScreen(ARCADA_BLUE); 61 arcada.setBacklight(255); 62 63 if (arcada.filesysBegin()) { 64 Serial.println("Found filesystem!"); 65 } else { 66 arcada.haltBox("No filesystem found! For QSPI flash, load CircuitPython. For SD cards, format with FAT"); 67 } 68 69 if (! arcada.loadConfigurationFile()) { 70 //arcada.infoBox("No configuration file found, using default 10 seconds per GIF"); 71 arcada.display->fillScreen(ARCADA_BLUE); 72 } else { 73 if (arcada.configJSON.containsKey("hot_temp")) { 74 hotTemp = arcada.configJSON["hot_temp"]; 75 } 76 if (arcada.configJSON.containsKey("cold_temp")) { 77 coldTemp = arcada.configJSON["hot_temp"]; 78 } 79 } 80 } 81 82 uint32_t cycle_start = 0L; 83 int8_t currGIF = 0, nextGIF = 0; // 0 for no change, +1 for hot gif, -1 for cool gif 84 85 void loop() { 86 if (arcada.recentUSB()) { 87 yield(); 88 return; // prioritize USB over GIF decoding 89 } 90 91 // Check button presses 92 arcada.readButtons(); 93 uint8_t buttons = arcada.justPressedButtons(); 94 if (buttons & BRIGHTER_BUTTON) { 95 int16_t newbrightness = arcada.getBacklight(); // brightness up 96 newbrightness = min(255, newbrightness+25); // about 10 levels 97 Serial.printf("New brightness %d", newbrightness); 98 arcada.setBacklight(newbrightness, true); // save to disk 99 } 100 if (buttons & DIMMER_BUTTON) { 101 int16_t newbrightness = arcada.getBacklight(); // brightness down 102 newbrightness = max(25, newbrightness-25); // about 10 levels 103 Serial.printf("New brightness %d", newbrightness); 104 arcada.setBacklight(newbrightness, true); // save to disk 105 } 106 107 uint32_t now = millis(); 108 109 // at least one 'cycle' elapsed, check if its time to change gifs 110 if(decoder.getCycleNo() > 1) { 111 // Print the stats for this GIF 112 char buf[80]; 113 int32_t frames = decoder.getFrameCount(); 114 int32_t cycle_design = decoder.getCycleTime(); // Intended duration 115 int32_t cycle_actual = now - cycle_start; // Actual duration 116 int32_t percent = 100 * cycle_design / cycle_actual; 117 sprintf(buf, "[%ld frames = %ldms] actual: %ldms speed: %ld%%", 118 frames, cycle_design, cycle_actual, percent); 119 Serial.println(buf); 120 121 float temp = readTemperature(); 122 Serial.printf("Temp = %0.1f C\n", temp); 123 if (temp >= hotTemp) { 124 file = arcada.open("hot.gif"); 125 } else if (temp <= coldTemp) { 126 file = arcada.open("cold.gif"); 127 } else { 128 file = arcada.open("neutral.gif"); 129 } 130 131 cycle_start = now; 132 133 if (!file) { 134 Serial.println("Gif not found!"); 135 return; 136 } 137 138 arcada.display->dmaWait(); 139 arcada.display->endWrite(); // End transaction from any prior callback 140 arcada.display->fillScreen(ARCADA_BLACK); 141 decoder.startDecoding(); 142 143 // Center the GIF 144 uint16_t w, h; 145 decoder.getSize(&w, &h); 146 Serial.print("Width: "); Serial.print(w); Serial.print(" height: "); Serial.println(h); 147 if (w < arcada.display->width()) { 148 gif_offset_x = (arcada.display->width() - w) / 2; 149 } else { 150 gif_offset_x = 0; 151 } 152 if (h < arcada.display->height()) { 153 gif_offset_y = (arcada.display->height() - h) / 2; 154 } else { 155 gif_offset_y = 0; 156 } 157 } 158 159 decoder.decodeFrame(); 160 } 161 162 /******************************* Drawing functions */ 163 164 void updateScreenCallback(void) { } 165 166 void screenClearCallback(void) { } 167 168 void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) { 169 arcada.display->drawPixel(x, y, arcada.display->color565(red, green, blue)); 170 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 171 arcada.display2->drawPixel(x, y, arcada.display->color565(red, green, blue)); 172 #endif 173 } 174 175 void drawLineCallback(int16_t x, int16_t y, uint8_t *buf, int16_t w, uint16_t *palette, int16_t skip) { 176 uint16_t maxline = arcada.display->width(); 177 bool splitdisplay = false; 178 179 uint8_t pixel; 180 //uint32_t t = millis(); 181 x += gif_offset_x; 182 y += gif_offset_y; 183 if (y >= arcada.display->height() || x >= maxline ) { 184 return; 185 } 186 187 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 188 // two possibilities 189 if ((x + w) > 2*maxline) { 190 w = 2*maxline - x; 191 } 192 if ((x + w) > maxline) { 193 splitdisplay = true; // split the gif over both displays 194 } 195 #else 196 if (x + w > maxline) { 197 w = maxline - x; 198 } 199 #endif 200 if (w <= 0) return; 201 202 //Serial.printf("Line (%d, %d) %d pixels skipping %d\n", x, y, w, skip); 203 204 uint16_t buf565[2][w]; 205 bool first = true; // First write op on this line? 206 uint8_t bufidx = 0; 207 uint16_t *ptr; 208 209 for (int i = 0; i < w; ) { 210 int n = 0, startColumn = i; 211 ptr = &buf565[bufidx][0]; 212 // Handle opaque span of pixels (stop at end of line or first transparent pixel) 213 if (skip == -1) {// no transparent pixels 214 while(i < w) { 215 ptr[n++] = palette[buf[i++]]; 216 } 217 } 218 else { 219 while((i < w) && ((pixel = buf[i++]) != skip)) { 220 ptr[n++] = palette[pixel]; 221 } 222 } 223 if (n) { 224 arcada.display->dmaWait(); // Wait for prior DMA transfer to complete 225 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 226 arcada.display2->dmaWait(); // Wait for prior DMA transfer to complete 227 #endif 228 if (first) { 229 arcada.display->endWrite(); // End transaction from prior callback 230 arcada.display->startWrite(); // Start new display transaction 231 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 232 arcada.display2->endWrite(); // End transaction from prior callback 233 arcada.display2->startWrite(); // Start new display transaction 234 #endif 235 first = false; 236 } 237 arcada.display->setAddrWindow(x + startColumn, y, min(maxline, n), 1); 238 arcada.display->writePixels(ptr, min(maxline, n), false, true); 239 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 240 if (! splitdisplay) { // same image on both! 241 arcada.display2->setAddrWindow(x + startColumn, y, min(maxline, n), 1); 242 arcada.display2->writePixels(ptr, min(maxline, n), false, true); 243 } else { 244 arcada.display2->setAddrWindow(x + startColumn, y, n-maxline, 1); 245 arcada.display2->writePixels(ptr+maxline, n-maxline, false, true); 246 } 247 #endif 248 bufidx = 1 - bufidx; 249 } 250 } 251 // arcada.display->dmaWait(); // Wait for last DMA transfer to complete 252 #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS 253 arcada.display2->dmaWait(); // Wait for last DMA transfer to complete 254 #endif 255 } 256 257 258 bool fileSeekCallback(unsigned long position) { 259 return file.seek(position); 260 } 261 262 unsigned long filePositionCallback(void) { 263 return file.position(); 264 } 265 266 int fileReadCallback(void) { 267 return file.read(); 268 } 269 270 int fileReadBlockCallback(void * buffer, int numberOfBytes) { 271 return file.read((uint8_t*)buffer, numberOfBytes); //.kbv 272 } 273 274 275 /* 276 Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels 277 278 Uses SmartMatrix Library for Teensy 3.1 written by Louis Beaudoin at pixelmatix.com 279 280 Written by: Craig A. Lindley 281 282 Copyright (c) 2014 Craig A. Lindley 283 Refactoring by Louis Beaudoin (Pixelmatix) 284 285 Permission is hereby granted, free of charge, to any person obtaining a copy of 286 this software and associated documentation files (the "Software"), to deal in 287 the Software without restriction, including without limitation the rights to 288 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 289 the Software, and to permit persons to whom the Software is furnished to do so, 290 subject to the following conditions: 291 292 The above copyright notice and this permission notice shall be included in all 293 copies or substantial portions of the Software. 294 295 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 296 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 297 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 298 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 299 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 300 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 301 */ 302 303 /* 304 This example displays 32x32 GIF animations loaded from a SD Card connected to the Teensy 3.1 305 The GIFs can be up to 32 pixels in width and height. 306 This code has been tested with 32x32 pixel and 16x16 pixel GIFs, but is optimized for 32x32 pixel GIFs. 307 308 Wiring is on the default Teensy 3.1 SPI pins, and chip select can be on any GPIO, 309 set by defining SD_CS in the code below 310 Function | Pin 311 DOUT | 11 312 DIN | 12 313 CLK | 13 314 CS (default) | 15 315 316 This code first looks for .gif files in the /gifs/ directory 317 (customize below with the GIF_DIRECTORY definition) then plays random GIFs in the directory, 318 looping each GIF for displayTimeSeconds 319 320 This example is meant to give you an idea of how to add GIF playback to your own sketch. 321 For a project that adds GIF playback with other features, take a look at 322 Light Appliance and Aurora: 323 https://github.com/CraigLindley/LightAppliance 324 https://github.com/pixelmatix/aurora 325 326 If you find any GIFs that won't play properly, please attach them to a new 327 Issue post in the GitHub repo here: 328 https://github.com/pixelmatix/AnimatedGIFs/issues 329 */ 330 331 /* 332 CONFIGURATION: 333 - If you're using SmartLED Shield V4 (or above), uncomment the line that includes <SmartMatrixShieldV4.h> 334 - update the "SmartMatrix configuration and memory allocation" section to match the width and height and other configuration of your display 335 - Note for 128x32 and 64x64 displays with Teensy 3.2 - need to reduce RAM: 336 set kRefreshDepth=24 and kDmaBufferRows=2 or set USB Type: "None" in Arduino, 337 decrease refreshRate in setup() to 90 or lower to get good an accurate GIF frame rate 338 - Set the chip select pin for your board. On Teensy 3.5/3.6, the onboard microSD CS pin is "BUILTIN_SDCARD" 339 */