/ Kinetic_POV / supernova_poi / supernova_poi.ino
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  }