supernova_poi.ino
1 // SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 /*------------------------------------------------------------------------ 6 POV IR Supernova Poi sketch. Uses the following Adafruit parts 7 (X2 for two poi): 8 9 - Teensy 3.2 (required - NOT compatible w/AVR-based boards) 10 - 2200 mAh Lithium Ion Battery https://www.adafruit.com/product/1781 11 - LiPoly Backpack https://www.adafruit.com/product/2124 12 - 144 LED/m DotStar strip (#2328 or #2329) 13 (ONE METER is enough for TWO poi) 14 - Infrared Sensor: https://www.adafruit.com/product/157 15 - Mini Remote Control: https://www.adafruit.com/product/389 16 (only one remote is required for multiple poi) 17 18 Needs Adafruit_DotStar library: github.com/adafruit/Adafruit_DotStar 19 Also, uses version of IRremote library from the Teensyduino installer, 20 the stock IRremote lib will NOT work here! 21 22 This is based on the LED poi code (also included in the repository), 23 but AVR-specific code has been stripped out for brevity, since these 24 mega-poi pretty much require a Teensy 3.X. 25 26 Adafruit invests time and resources providing this open source code, 27 please support Adafruit and open-source hardware by purchasing 28 products from Adafruit! 29 30 Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. 31 MIT license, all text above must be included in any redistribution. 32 See 'COPYING' file for additional notes. 33 ------------------------------------------------------------------------*/ 34 35 #include <Arduino.h> 36 #include <Adafruit_DotStar.h> 37 #include <avr/power.h> 38 #include <avr/sleep.h> 39 #include <IRremote.h> 40 #include <SPI.h> 41 42 typedef uint16_t line_t; 43 44 // CONFIGURABLE STUFF ------------------------------------------------------ 45 46 #include "graphics.h" // Graphics data is contained in this header file. 47 // It's generated using the 'convert.py' Python script. Various image 48 // formats are supported, trading off color fidelity for PROGMEM space. 49 // Handles 1-, 4- and 8-bit-per-pixel palette-based images, plus 24-bit 50 // truecolor. 1- and 4-bit palettes can be altered in RAM while running 51 // to provide additional colors, but be mindful of peak & average current 52 // draw if you do that! Power limiting is normally done in convert.py 53 // (keeps this code relatively small & fast). 54 55 // Ideally you use hardware SPI as it's much faster, though limited to 56 // specific pins. If you really need to bitbang DotStar data & clock on 57 // different pins, optionally define those here: 58 //#define LED_DATA_PIN 0 59 //#define LED_CLOCK_PIN 1 60 61 // Empty and full thresholds (millivolts) used for battery level display: 62 #define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V 63 #define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V 64 65 boolean autoCycle = true; // Set to true to cycle images by default 66 uint32_t CYCLE_TIME = 12; // Time, in seconds, between auto-cycle images 67 68 int RECV_PIN = 5; 69 IRrecv irrecv(RECV_PIN); 70 decode_results results; 71 72 // Adafruit IR Remote Codes: 73 // Button Code Button Code 74 // ----------- ------ ------ ----- 75 // VOL-: 0xFD00FF 0/10+: 0xFD30CF 76 // Play/Pause: 0xFD807F 1: 0xFD08F7 77 // VOL+: 0xFD40BF 2: 0xFD8877 78 // SETUP: 0xFD20DF 3: 0xFD48B7 79 // STOP/MODE: 0xFD609F 4: 0xFD28D7 80 // UP: 0xFDA05F 5: 0xFDA857 81 // DOWN: 0xFDB04F 6: 0xFD6897 82 // LEFT: 0xFD10EF 7: 0xFD18E7 83 // RIGHT: 0xFD50AF 8: 0xFD9867 84 // ENTER/SAVE: 0xFD906F 9: 0xFD58A7 85 // Back: 0xFD708F 86 87 #define BTN_BRIGHT_UP 0xFD40BF 88 #define BTN_BRIGHT_DOWN 0xFD00FF 89 #define BTN_RESTART 0xFD807F 90 #define BTN_BATTERY 0xFD20DF 91 #define BTN_FASTER 0xFD805F 92 #define BTN_SLOWER 0xFDB04F 93 #define BTN_OFF 0xFD609F 94 #define BTN_PATTERN_PREV 0xFD10EF 95 #define BTN_PATTERN_NEXT 0xFD50AF 96 #define BTN_AUTOPLAY 0XFD906F 97 #define BTN_NONE -1 98 99 // ------------------------------------------------------------------------- 100 101 #if defined(LED_DATA_PIN) && defined(LED_CLOCK_PIN) 102 // Older DotStar LEDs use GBR order. If colors are wrong, edit here. 103 Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, 104 LED_DATA_PIN, LED_CLOCK_PIN, DOTSTAR_BGR); 105 #else 106 Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, DOTSTAR_BGR); 107 #endif 108 109 void imageInit(void), 110 IRinterrupt(void); 111 uint16_t readVoltage(void); 112 113 void setup() { 114 strip.begin(); // Allocate DotStar buffer, init SPI 115 strip.clear(); // Make sure strip is clear 116 strip.show(); // before measuring battery 117 118 imageInit(); // Initialize pointers for default image 119 120 irrecv.enableIRIn(); // Start the receiver 121 } 122 123 124 // GLOBAL STATE STUFF ------------------------------------------------------ 125 126 uint32_t lastImageTime = 0L, // Time of last image change 127 lastLineTime = 0L; 128 uint8_t imageNumber = 0, // Current image being displayed 129 imageType, // Image type: PALETTE[1,4,8] or TRUECOLOR 130 *imagePalette, // -> palette data in PROGMEM 131 *imagePixels, // -> pixel data in PROGMEM 132 palette[16][3]; // RAM-based color table for 1- or 4-bit images 133 line_t imageLines, // Number of lines in active image 134 imageLine; // Current line number in image 135 volatile uint16_t irCode = BTN_NONE; // Last valid IR code received 136 137 const uint8_t PROGMEM brightness[] = { 15, 31, 63, 127, 255 }; 138 uint8_t bLevel = sizeof(brightness) - 1; 139 140 // Microseconds per line for various speed settings 141 const uint16_t PROGMEM lineTable[] = { // 375 * 2^(n/3) 142 1000000L / 375, // 375 lines/sec = slowest 143 1000000L / 472, 144 1000000L / 595, 145 1000000L / 750, // 750 lines/sec = mid 146 1000000L / 945, 147 1000000L / 1191, 148 1000000L / 1500 // 1500 lines/sec = fastest 149 }; 150 uint8_t lineIntervalIndex = 3; 151 uint16_t lineInterval = 1000000L / 750; 152 153 void imageInit() { // Initialize global image state for current imageNumber 154 imageType = images[imageNumber].type; 155 imageLines = images[imageNumber].lines; 156 imageLine = 0; 157 imagePalette = (uint8_t *)images[imageNumber].palette; 158 imagePixels = (uint8_t *)images[imageNumber].pixels; 159 // 1- and 4-bit images have their color palette loaded into RAM both for 160 // faster access and to allow dynamic color changing. Not done w/8-bit 161 // because that would require inordinate RAM (328P could handle it, but 162 // I'd rather keep the RAM free for other features in the future). 163 if(imageType == PALETTE1) memcpy_P(palette, imagePalette, 2 * 3); 164 else if(imageType == PALETTE4) memcpy_P(palette, imagePalette, 16 * 3); 165 lastImageTime = millis(); // Save time of image init for next auto-cycle 166 } 167 168 void nextImage(void) { 169 if(++imageNumber >= NUM_IMAGES) imageNumber = 0; 170 imageInit(); 171 } 172 173 void prevImage(void) { 174 imageNumber = imageNumber ? imageNumber - 1 : NUM_IMAGES - 1; 175 imageInit(); 176 } 177 178 // MAIN LOOP --------------------------------------------------------------- 179 180 void loop() { 181 uint32_t t = millis(); // Current time, milliseconds 182 183 if(autoCycle) { 184 if((t - lastImageTime) >= (CYCLE_TIME * 1000L)) nextImage(); 185 // CPU clocks vary slightly; multiple poi won't stay in perfect sync. 186 // Keep this in mind when using auto-cycle mode, you may want to cull 187 // the image selection to avoid unintentional regrettable combinations. 188 } 189 190 // Transfer one scanline from pixel data to LED strip: 191 192 switch(imageType) { 193 194 case PALETTE1: { // 1-bit (2 color) palette-based image 195 uint8_t pixelNum = 0, byteNum, bitNum, pixels, idx, 196 *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 8]; 197 for(byteNum = NUM_LEDS/8; byteNum--; ) { // Always padded to next byte 198 pixels = *ptr++; // 8 pixels of data (pixel 0 = LSB) 199 for(bitNum = 8; bitNum--; pixels >>= 1) { 200 idx = pixels & 1; // Color table index for pixel (0 or 1) 201 strip.setPixelColor(pixelNum++, 202 palette[idx][0], palette[idx][1], palette[idx][2]); 203 } 204 } 205 break; 206 } 207 208 case PALETTE4: { // 4-bit (16 color) palette-based image 209 uint8_t pixelNum, p1, p2, 210 *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 2]; 211 for(pixelNum = 0; pixelNum < NUM_LEDS; ) { 212 p2 = *ptr++; // Data for two pixels... 213 p1 = p2 >> 4; // Shift down 4 bits for first pixel 214 p2 &= 0x0F; // Mask out low 4 bits for second pixel 215 strip.setPixelColor(pixelNum++, 216 palette[p1][0], palette[p1][1], palette[p1][2]); 217 strip.setPixelColor(pixelNum++, 218 palette[p2][0], palette[p2][1], palette[p2][2]); 219 } 220 break; 221 } 222 223 case PALETTE8: { // 8-bit (256 color) PROGMEM-palette-based image 224 uint16_t o; 225 uint8_t pixelNum, 226 *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS]; 227 for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) { 228 o = *ptr++ * 3; // Offset into imagePalette 229 strip.setPixelColor(pixelNum, 230 imagePalette[o], 231 imagePalette[o + 1], 232 imagePalette[o + 2]); 233 } 234 break; 235 } 236 237 case TRUECOLOR: { // 24-bit ('truecolor') image (no palette) 238 uint8_t pixelNum, r, g, b, 239 *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3]; 240 for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) { 241 r = *ptr++; 242 g = *ptr++; 243 b = *ptr++; 244 strip.setPixelColor(pixelNum, r, g, b); 245 } 246 break; 247 } 248 } 249 250 if(++imageLine >= imageLines) imageLine = 0; // Next scanline, wrap around 251 IRinterrupt(); 252 while(((t = micros()) - lastLineTime) < lineInterval) { 253 if(results.value != BTN_NONE) { 254 if(!strip.getBrightness()) { // If strip is off... 255 // Set brightness to last level 256 strip.setBrightness(brightness[bLevel]); 257 // and ignore button press (don't fall through) 258 // effectively, first press is 'wake' 259 } else { 260 switch(results.value) { 261 case BTN_BRIGHT_UP: 262 if(bLevel < (sizeof(brightness) - 1)) 263 strip.setBrightness(brightness[++bLevel]); 264 break; 265 case BTN_BRIGHT_DOWN: 266 if(bLevel) 267 strip.setBrightness(brightness[--bLevel]); 268 break; 269 case BTN_FASTER: 270 CYCLE_TIME++; 271 if(lineIntervalIndex < (sizeof(lineTable) / sizeof(lineTable[0]) - 1)) 272 lineInterval = lineTable[++lineIntervalIndex]; 273 break; 274 case BTN_SLOWER: 275 if(CYCLE_TIME > 0) CYCLE_TIME--; 276 if(lineIntervalIndex) 277 lineInterval = lineTable[--lineIntervalIndex]; 278 break; 279 case BTN_RESTART: 280 imageNumber = 0; 281 imageInit(); 282 break; 283 case BTN_OFF: 284 strip.setBrightness(0); 285 break; 286 case BTN_PATTERN_PREV: 287 prevImage(); 288 break; 289 case BTN_PATTERN_NEXT: 290 nextImage(); 291 break; 292 case BTN_AUTOPLAY: 293 autoCycle = !autoCycle; 294 break; 295 } 296 } 297 results.value = BTN_NONE; 298 } 299 } 300 301 strip.show(); // Refresh LEDs 302 lastLineTime = t; 303 } 304 305 void IRinterrupt() { 306 if (irrecv.decode(&results)) { 307 Serial.println(results.value, HEX); 308 irrecv.resume(); // Receive the next value 309 } 310 }