/ Gemma_Firewalker_Lite_Sneakers / Gemma_Firewalker_Lite_Sneakers.ino
Gemma_Firewalker_Lite_Sneakers.ino
  1  // SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*------------------------------------------------------------------------
  6    Gemma "Firewalker Lite" sneakers sketch.
  7    Uses the following Adafruit parts (X2 for two shoes):
  8  
  9    - Gemma 3V microcontroller (adafruit.com/product/1222 or 2470)
 10    - 150 mAh LiPoly battery (#1317) or larger
 11    - Medium vibration sensor switch (#2384)
 12    - 60/m NeoPixel RGB LED strip (#1138 or #1461)
 13    - LiPoly charger such as #1304 (only one is required, unless you want
 14      to charge both shoes at the same time)
 15  
 16    Needs Adafruit_NeoPixel library: github.com/adafruit/Adafruit_NeoPixel
 17  
 18    THIS CODE USES FEATURES SPECIFIC TO THE GEMMA MICROCONTROLLER BOARD AND
 19    WILL NOT COMPILE OR RUN ON MOST OTHERS.  OK on basic Trinket but NOT
 20    Pro Trinket nor anything else.  VERY specific to the Gemma!
 21  
 22    Adafruit invests time and resources providing this open source code,
 23    please support Adafruit and open-source hardware by purchasing
 24    products from Adafruit!
 25  
 26    Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
 27    MIT license, all text above must be included in any redistribution.
 28    See 'COPYING' file for additional notes.
 29    ------------------------------------------------------------------------*/
 30  
 31  #include <Arduino.h>
 32  #include <Adafruit_NeoPixel.h>
 33  #include <avr/power.h>
 34  #include <avr/sleep.h>
 35  
 36  // CONFIGURABLE STUFF ------------------------------------------------------
 37  
 38  #define NUM_LEDS      40 // Actual number of LEDs in NeoPixel strip
 39  #define CIRCUMFERENCE 40 // Shoe circumference, in pixels, may be > NUM_LEDS
 40  #define FPS           50 // Animation frames per second
 41  #define LED_DATA_PIN   1 // NeoPixels are connected here
 42  #define MOTION_PIN     0 // Vibration switch from here to GND
 43  // CIRCUMFERENCE is distinct from NUM_LEDS to allow a gap (if desired) in
 44  // LED strip around perimeter of shoe (might not want any on inside egde)
 45  // so animation is spatially coherent (doesn't jump across gap, but moves
 46  // as if pixels were in that space).  CIRCUMFERENCE can be equal or larger
 47  // than NUM_LEDS, but not less.
 48  
 49  // The following features are OPTIONAL and require ADDITIONAL COMPONENTS.
 50  // Only ONE of these may be enabled due to limited pins on Gemma:
 51  
 52  // BATTERY LEVEL graph on power-up: requires two resistors of same value,
 53  // 10K or higher, connected to pin D2 -- one goes to Vout pin, other to GND.
 54  //#define BATT_LVL_PIN  2 // Un-comment this line to enable battery level.
 55  // Pin number cannot be changed, this is the only AREF pin on ATtiny85.
 56  // Empty and full thresholds (millivolts) used for battery level display:
 57  #define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V
 58  #define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V
 59  // Graph works only on battery power; USB power will always show 100% full.
 60  
 61  // POWER DOWN NeoPixels when idle to prolong battery: requires P-channel
 62  // power MOSFET + 220 Ohm resistor, is a 'high side' switch to NeoPixel +V.
 63  // DO NOT do this w/N-channel on GND side, could DESTROY strip!
 64  //#define LED_PWR_PIN   2 // Un-comment this for NeoPixel power-down.
 65  // Could be moved to other pin on Trinket to allow both options together.
 66  
 67  // GLOBAL STUFF ------------------------------------------------------------
 68  
 69  Adafruit_NeoPixel    strip(NUM_LEDS, LED_DATA_PIN);
 70  void                 sleep(void); // Power-down function
 71  extern const uint8_t gamma[];     // Table at the bottom of this code
 72  
 73  // INTERMISSION explaining the animation code.  This works procedurally --
 74  // using mathematical functions -- rather than moving discrete pixels left
 75  // or right incrementally.  This sometimes confuses people because they go
 76  // looking for some "setpixel(x+1, color)" type of code and can't find it.
 77  // Also makes extensive use of fixed-point math, where discrete integer
 78  // values are used to represent fractions (0.0 to 1.0) without relying on
 79  // floating-point math, which just wouldn't even fit in Gemma's limited
 80  // code space, RAM or speed.  The reason the animation's done this way is
 81  // to produce smoother, more organic motion...when pixels are the smallest
 82  // discrete unit, animation tends to have a stuttery, 1980s quality to it.
 83  // We can do better!
 84  // Picture the perimeter of the shoe in two different coordinate spaces:
 85  // One is "pixel space," each pixel spans exactly one integer unit, from
 86  // zero to N-1 when there are N pixels.  This is how NeoPixels are normally
 87  // addressed.  Now, overlaid on this, imagine another coordinate space,
 88  // spanning the same physical width (the perimeter of the shoe, or length
 89  // of the LED strip), but this one has 256 discrete steps (8 bits)...finer
 90  // resolution than the pixel steps...and we do most of the math using
 91  // these units rather than pixel units.  It's then possible to move things
 92  // by fractions of a pixel, but render each whole pixel by taking a sample
 93  // at its approximate center in the alternate coordinate space.
 94  // More explanation further in the code.
 95  //
 96  // |Pixel|Pixel|Pixel|    ...    |Pixel|Pixel|Pixel|<- end of strip
 97  // |  0  |  1  |  2  |           |  3  |  4  | N-1 |
 98  // |0...                  ...                ...255|<- fixed-point space
 99  //
100  // So, inspired by the mothership in Close Encounters of the Third Kind,
101  // the animation in this sketch is a series of waves moving around the
102  // perimeter and interacting as they cross.  They're triangle waves,
103  // height proportional to LED brightness, determined by the time since
104  // motion was last detected.
105  //
106  //   <- /\       /\ -> <- /\          Pretend these are triangle waves
107  // ____/  \_____/  \_____/  \____  <- moving in 8-bit coordinate space.
108  
109  struct {
110    uint8_t center;    // Center point of wave in fixed-point space (0 - 255)
111    int8_t  speed;     // Distance to move between frames (-128 - +127)
112    uint8_t width;     // Width from peak to bottom of triangle wave (0 - 128)
113    uint8_t hue;       // Current wave hue (color) see comments later
114    uint8_t hueTarget; // Final hue we're aiming for
115    uint8_t r, g, b;   // LED RGB color calculated from hue
116  } wave[] = {
117    { 0,  3, 60 },     // Gemma can animate 3 of these on 40 LEDs at 50 FPS
118    { 0, -5, 45 },     // More LEDs and/or more waves will need lower FPS
119    { 0,  7, 30 }
120  };
121  // Note that the speeds of each wave are different prime numbers.  This
122  // avoids repetition as the waves move around the perimeter...if they were
123  // even numbers or multiples of each other, there'd be obvious repetition
124  // in the pattern of motion...beat frequencies.
125  #define N_WAVES (sizeof(wave) / sizeof(wave[0]))
126  
127  // ONE-TIME INITIALIZATION -------------------------------------------------
128  
129  void setup() {
130  #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
131    clock_prescale_set(clock_div_1); // Allow 16 MHz Trinket too
132  #endif
133  #ifdef POWER_PIN
134    pinMode(POWER_PIN, OUTPUT);
135    digitalWrite(POWER_PIN, LOW);    // Power-on LED strip
136  #endif
137    strip.begin();                   // Allocate NeoPixel buffer
138    strip.clear();                   // Make sure strip is clear
139    strip.show();                    // before measuring battery
140  
141  #ifdef BATT_LVL_PIN
142    // Battery monitoring code does some low-level Gemma-specific stuff...
143    int      i, prev;
144    uint8_t  count;
145    uint16_t mV;
146  
147    pinMode(BATT_LVL_PIN, INPUT);    // No pullup
148  
149    // Select AREF (PB0) voltage reference + Bandgap (1.8V) input
150    ADMUX  = _BV(REFS0) | _BV(MUX3) | _BV(MUX2);   // AREF, Bandgap input
151    ADCSRA = _BV(ADEN)  |                          // Enable ADC
152             _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 1/128 prescale (125 KHz)
153    delay(1); // Allow 1 ms settling time as per datasheet
154    // Bandgap readings may still be settling.  Repeated readings are
155    // taken until four concurrent readings stabilize within 5 mV.
156    for(prev=9999, count=0; count<4; ) {
157      for(ADCSRA |= _BV(ADSC); ADCSRA & _BV(ADSC); ); // Start, await ADC conv
158      i  = ADC;                                       // Result
159      mV = i ? (1100L * 1023 / i) : 0;                // Scale to millivolts
160      if(abs((int)mV - prev) <= 5) count++;   // +1 stable reading
161      else                         count = 0; // too much change, start over
162      prev = mV;
163    }
164    ADCSRA = 0; // ADC off
165    mV *= 2;    // Because 50% resistor voltage divider (to work w/3.3V MCU)
166  
167    uint8_t  lvl = (mV >= BATT_MAX_MV) ? NUM_LEDS : // Full (or nearly)
168                   (mV <= BATT_MIN_MV) ?        1 : // Drained
169                   1 + ((mV - BATT_MIN_MV) * NUM_LEDS + (NUM_LEDS / 2)) /
170                   (BATT_MAX_MV - BATT_MIN_MV + 1); // # LEDs lit (1-NUM_LEDS)
171    for(uint8_t i=0; i<lvl; i++) {                  // Each LED to batt level
172      uint8_t g = (i * 5 + 2) / NUM_LEDS;           // Red to green
173      strip.setPixelColor(i, 4-g, g, 0);
174      strip.show();                                 // Animate a bit
175      delay(250 / NUM_LEDS);
176    }
177    delay(1500);                                    // Hold last state a moment
178    strip.clear();                                  // Then clear strip
179    strip.show();
180    randomSeed(mV);
181  #else
182    randomSeed(analogRead(2));
183  #endif // BATT_LVL_PIN
184  
185    // Assign random starting colors to waves
186    for(uint8_t w=0; w<N_WAVES; w++) {
187      wave[w].hue = wave[w].hueTarget = 90 + random(90);
188      wave[w].r = h2rgb(wave[w].hue - 30);
189      wave[w].g = h2rgb(wave[w].hue);
190      wave[w].b = h2rgb(wave[w].hue + 30);
191    }
192  
193    // Configure motion pin for change detect & interrupt
194    pinMode(MOTION_PIN, INPUT_PULLUP);
195    PCMSK = _BV(MOTION_PIN); // Pin change interrupt mask
196    GIFR  = 0xFF;            // Clear interrupt flags
197    // Interrupt is not actually enabled yet, that's in sleep function...
198  
199    sleep(); // Sleep until motion is detected
200  }
201  
202  // MAIN LOOP ---------------------------------------------------------------
203  
204  uint32_t prevFrameTime = 0L;    // Used for animation timing
205  uint8_t  brightness    = 0;     // Current wave height
206  boolean  rampingUp     = false; // If true, brightness is increasing
207  
208  void loop() {
209    uint32_t t;
210    uint16_t r, g, b, m, n;
211    uint8_t  i, x, w, d1, d2, y;
212  
213    // Pause until suitable interval since prior frame has elapsed.
214    // This is preferable to delay(), as the time to render each frame
215    // can vary.
216    while(((t = micros()) - prevFrameTime) < (1000000L / FPS));
217  
218    // Immediately show results calculated on -prior- pass,
219    // so frame-to-frame timing is consistent.  Then render next frame.
220    strip.show();
221    prevFrameTime = t;     // Save frame update time for next pass
222  
223    if(GIFR & _BV(PCIF)) { // Pin change detected?
224      rampingUp = true;    // Set brightness-ramping flag
225      GIFR      = 0xFF;    // Clear interrupt masks
226    }
227  
228    // Okay, here's where the animation starts to happen...
229  
230    // First, check the 'rampingUp' flag.  If set, this indicates that
231    // the vibration switch was activated recently, and the LEDs should
232    // increase in brightness.  If clear, the LEDs ramp down.
233    if(rampingUp) {
234      // But it's not just a straight shot that it ramps up.  This is a
235      // low-pass filter...it makes the brightness value decelerate as it
236      // approaches a target (200 in this case).  207 is used here because
237      // integers round down on division and we'd never reach the target;
238      // it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200;
239      brightness = ((brightness * 7) + 207) / 8;
240      // Once max brightness is reached, switch off the rampingUp flag.
241      if(brightness >= 200) rampingUp = false;
242    } else {
243      // If not ramping up, we're ramping down.  This too uses a low-pass
244      // filter so it eases out, but with different constants so it's a
245      // slower fade.  Also, no addition here because we want it rounding
246      // down toward zero...
247      if(!(brightness = (brightness * 15) / 16)) { // Hit zero?
248        sleep(); // Turn off animation
249        return;  // Start over at top of loop() on wake
250      }
251    }
252  
253    // Wave positions and colors are updated...
254    for(w=0; w<N_WAVES; w++) {
255      wave[w].center += wave[w].speed; // Move wave; wraps around ends, is OK!
256      if(wave[w].hue == wave[w].hueTarget) { // Hue not currently changing?
257        // There's a tiny random chance of picking a new hue...
258        if(!random(FPS * 4)) {
259          // Within 1/3 color wheel
260          wave[w].hueTarget = random(wave[w].hue - 30, wave[w].hue + 30);
261        }
262      } else { // This wave's hue is currently shifting...
263        if(wave[w].hue < wave[w].hueTarget) wave[w].hue++; // Move up or
264        else                                wave[w].hue--; // down as needed
265        if(wave[w].hue == wave[w].hueTarget) { // Reached destination?
266          wave[w].hue = 90 + wave[w].hue % 90; // Clamp to 90-180 range
267          wave[w].hueTarget = wave[w].hue;     // Copy to target
268        }
269        wave[w].r = h2rgb(wave[w].hue - 30);
270        wave[w].g = h2rgb(wave[w].hue);
271        wave[w].b = h2rgb(wave[w].hue + 30);
272      }
273    }
274  
275    // Now render the LED strip using the current brightness & wave states
276  
277    for(i=0; i<NUM_LEDS; i++) { // Each LED in strip is visited just once...
278  
279      // Transform 'i' (LED number in pixel space) to the equivalent point
280      // in 8-bit fixed-point space (0-255).  "* 256" because that would be
281      // the start of the (N+1)th pixel.  "+ 127" to get pixel center.
282      x = (i * 256 + 127) / CIRCUMFERENCE;
283  
284      r = g = b = 0; // LED assumed off, but wave colors will add up here
285  
286      for(w=0; w<N_WAVES; w++) { // For each item in wave[] array...
287  
288        // Calculate distance from pixel center to wave center point,
289        // using both signed and unsigned 8-bit integers...
290        d1 = abs((int8_t)x  - (int8_t)wave[w].center);
291        d2 = abs((uint8_t)x - (uint8_t)wave[w].center);
292        // Then take the lesser of the two, resulting in a distance (0-128)
293        // that 'wraps around' the ends of the strip as necessary...it's a
294        // contiguous ring, and waves can move smoothly across the gap.
295        if(d2 < d1) d1 = d2;       // d1 is pixel-to-wave-center distance
296        if(d1 < wave[w].width) {   // Is distance within wave's influence?
297          d2 = wave[w].width - d1; // d2 is opposite; distance to wave's end
298  
299          // d2 distance, relative to wave width, is then proportional to the
300          // wave's brightness at this pixel (basic linear y=mx+b stuff).
301          // Normally this would require a fraction -- floating-point math --
302          // but by reordering the operations we can get the same result with
303          // integers -- fixed-point math -- that's why brightness is cast
304          // here to a 16-bit type; the interim result of the multiplication
305          // is a big integer that's then divided by wave width (back to an
306          // 8-bit value) to yield the pixel's brightness.  This massive wall
307          // of comments is basically to explain that fixed-point math is
308          // faster and less resource-intensive on processors with limited
309          // capabilities.  Topic for another Adafruit Learning System guide?
310          y = (uint16_t)brightness * d2 / wave[w].width; // 0 to 200
311  
312          // y is a brightness scale value -- proportional to, but not
313          // exactly equal to, the resulting RGB value.  Values from 0-127
314          // represent a color ramp from black to the wave's assigned RGB
315          // color.  Values from 128-255 ramp from the RGB color to white.
316          // It's by design that y only goes to 200...white peaks then occur
317          // only when certain waves overlap.
318          if(y < 128) { // Fade black to RGB color
319            // In HSV colorspace, this would be tweaking 'value'
320            n  = (uint16_t)y * 2 + 1;  // 1-256
321            r += (wave[w].r * n) >> 8; // More fixed-point math
322            g += (wave[w].g * n) >> 8; // Wave color is scaled by 'n'
323            b += (wave[w].b * n) >> 8; // >>8 is equiv to /256
324          } else {      // Fade RGB color to white
325            // In HSV colorspace, this would be tweaking 'saturation'
326            n  = (uint16_t)(y - 128) * 2; // 0-255 affects white level
327            m  = 256 * n;
328            n  = 256 - n;                 // 1-256 affects RGB level
329            r += (m + wave[w].r * n) >> 8;
330            g += (m + wave[w].g * n) >> 8;
331            b += (m + wave[w].b * n) >> 8;
332          }
333        }
334      }
335  
336      // r,g,b are 16-bit types that accumulate brightness from all waves
337      // that affect this pixel; may exceed 255.  Now clip to 0-255 range:
338      if(r > 255) r = 255;
339      if(g > 255) g = 255;
340      if(b > 255) b = 255;
341      // Store resulting RGB value and we're done with this pixel!
342      strip.setPixelColor(i, r, g, b);
343    }
344  
345    // Once rendering is complete, a second pass is made through pixel data
346    // applying gamma correction, for more perceptually linear colors.
347    // https://learn.adafruit.com/led-tricks-gamma-correction
348    uint8_t *pixels = strip.getPixels(); // Pointer to LED strip buffer
349    for(i=0; i<NUM_LEDS*3; i++) pixels[i] = pgm_read_byte(&gamma[pixels[i]]);
350  }
351  
352  // SLEEP/WAKE CODE is very Gemma-specific  ---------------------------------
353  
354  void sleep() {
355    strip.clear();                       // Clear pixel buffer
356  #ifdef POWER_PIN
357    pinMode(LED_DATA_PIN, INPUT);        // Avoid parasitic power to strip
358    digitalWrite(POWER_PIN, HIGH);       // Cut power to pixels
359  #else
360    strip.show();                        // Turn off LEDs
361  #endif // POWER_PIN
362    power_all_disable();                 // Peripherals ALL OFF
363    GIMSK = _BV(PCIE);                   // Allow pin-change interrupt only
364    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode
365    sleep_enable();
366    interrupts();                        // Needed for pin-change wake
367    sleep_mode();                        // Power down (stops here)
368    //                                   ** RESUMES HERE ON WAKE **
369    GIMSK = 0;                           // Clear global interrupt mask
370    // Pin change when awake is done by polling GIFR register, not interrupt
371  #ifdef POWER_PIN
372    digitalWrite(POWER_PIN, LOW);        // Power-up LEDs
373    pinMode(LED_DATA_PIN, OUTPUT);
374    strip.show();                        // Clear any startup garbage
375  #endif
376    power_timer0_enable();               // Used by micros()
377    // Remaining peripherals (ADC, Timer1, etc) are NOT re-enabled, as they're
378    // not used elsewhere in the sketch.  If adding features, you might need
379    // to re-enable some/all peripherals here.
380    rampingUp = true;
381  }
382  
383  EMPTY_INTERRUPT(PCINT0_vect); // Pin change (does nothing, but required)
384  
385  // COLOR-HANDLING CODE -----------------------------------------------------
386  
387  // A full HSV-to-RGB function wasn't required by sketch, just needed limited
388  // hue-to-RGB.  There are 90 distinct hues (0-89) around color wheel (to
389  // allow 4-bit table entries).  This function gets called three times (for
390  // R,G,B, with different offsets relative to hue) to produce a fully-
391  // saturated color.  Was a little more compact than a full HSV function.
392  
393  static const uint8_t PROGMEM hueTable[45] = {
394   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xED,0xCB,0xA9,0x87,0x65,0x43,0x21,
395   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
396   0x12,0x34,0x56,0x78,0x9A,0xBC,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
397  };
398  
399  uint8_t h2rgb(uint8_t hue) {
400    hue %= 90;
401    uint8_t h = pgm_read_byte(&hueTable[hue >> 1]);
402    return ((hue & 1) ? (h & 15) : (h >> 4)) * 17;
403  }
404  
405  // Gamma-correction table (see earlier comments).  It's big and ugly
406  // and would interrupt trying to read the code, so I put it down here.
407  const uint8_t gamma[] PROGMEM = {
408      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
409      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
410      1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
411      2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
412      5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
413     10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
414     17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
415     25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
416     37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
417     51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
418     69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
419     90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
420    115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
421    144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
422    177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
423    215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };