/ Gemma_Firewalker_AnimLite / Gemma_Firewalker_AnimLite.ino
Gemma_Firewalker_AnimLite.ino
  1  // SPDX-FileCopyrightText: 2020 Phillip Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // 'Firewalker' LED sneakers sketch for Adafruit NeoPixels by Phillip Burgess
  6  
  7  #include <Adafruit_NeoPixel.h>
  8  
  9  const uint8_t gamma1[] PROGMEM = { // Gamma correction table for LED brightness
 10      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 11      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
 12      1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
 13      2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
 14      5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
 15     10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
 16     17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
 17     25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
 18     37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
 19     51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
 20     69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
 21     90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
 22    115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
 23    144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
 24    177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
 25    215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
 26  
 27  // LEDs go around the full perimeter of the shoe sole, but the step animation
 28  // is mirrored on both the inside and outside faces, while the strip doesn't
 29  // necessarily start and end at the heel or toe.  These constants help configure
 30  // the strip and shoe sizes, and the positions of the front- and rear-most LEDs.
 31  // Becky's shoes: 39 LEDs total, 20 LEDs long, LED #5 at back.
 32  // Phil's shoes: 43 LEDs total, 22 LEDs long, LED #6 at back.
 33  #define N_LEDS        39 // TOTAL number of LEDs in strip
 34  #define SHOE_LEN_LEDS 20 // Number of LEDs down ONE SIDE of shoe
 35  #define SHOE_LED_BACK  5 // Index of REAR-MOST LED on shoe
 36  #define STEP_PIN      A2 // Analog input for footstep
 37  #define LED_PIN       A0 // NeoPixel strip is connected here
 38  #define MAXSTEPS       3 // Process (up to) this many concurrent steps
 39  
 40  Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
 41  
 42  // The readings from the sensors are usually around 250-350 when not being pressed,
 43  // then dip below 100 when the heel is standing on it (for Phil's shoes; Becky's
 44  // don't dip quite as low because she's smaller).
 45  #define STEP_TRIGGER    150  // Reading must be below this to trigger step
 46  #define STEP_HYSTERESIS 200  // After trigger, must return to this level
 47  
 48  int
 49    stepMag[MAXSTEPS],  // Magnitude of steps
 50    stepX[MAXSTEPS],    // Position of 'step wave' along strip
 51    mag[SHOE_LEN_LEDS], // Brightness buffer (one side of shoe)
 52    stepFiltered,       // Current filtered pressure reading
 53    stepCount,          // Number of 'frames' current step has lasted
 54    stepMin;            // Minimum reading during current step
 55  uint8_t
 56    stepNum = 0,        // Current step number in stepMag/stepX tables
 57    dup[SHOE_LEN_LEDS]; // Inside/outside copy indexes
 58  boolean
 59    stepping  = false;  // If set, step was triggered, waiting to release
 60  
 61  
 62  void setup() {
 63    pinMode(9, INPUT_PULLUP); // Set internal pullup resistor for sensor pin
 64    // As previously mentioned, the step animation is mirrored on the inside and
 65    // outside faces of the shoe.  To avoid a bunch of math and offsets later, the
 66    // 'dup' array indicates where each pixel on the outside face of the shoe should
 67    // be copied on the inside.  (255 = don't copy, as on front- or rear-most LEDs).
 68    // Later, the colors for the outside face of the shoe are calculated and then get
 69    // copied to the appropriate positions on the inside face.
 70    memset(dup, 255, sizeof(dup));
 71    int8_t a, b;
 72    for(a=1              , b=SHOE_LED_BACK-1            ; b>=0    ;) dup[a++] = b--;
 73    for(a=SHOE_LEN_LEDS-2, b=SHOE_LED_BACK+SHOE_LEN_LEDS; b<N_LEDS;) dup[a--] = b++;
 74  
 75    // Clear step magnitude and position buffers
 76    memset(stepMag, 0, sizeof(stepMag));
 77    memset(stepX  , 0, sizeof(stepX));
 78    strip.begin();
 79    stepFiltered = analogRead(STEP_PIN); // Initial input
 80  }
 81  
 82  void loop() {
 83    uint8_t i, j;
 84  
 85    // Read analog input, with a little noise filtering
 86    //stepFiltered = ((stepFiltered * 3) + analogRead(STEP_PIN)) >> 2;
 87      stepFiltered = (((stepFiltered * 3) - 100) + analogRead(STEP_PIN)) >> 2;
 88      
 89    // The strip doesn't simply display the current pressure reading.  Instead,
 90    // there's a bit of an animated flourish from heel to toe.  This takes time,
 91    // and during quick foot-tapping there could be multiple step animations
 92    // 'in flight,' so a short list is kept.
 93    if(stepping) { // If a step was previously triggered...
 94      if(stepFiltered >= STEP_HYSTERESIS) { // Has step let up?
 95        stepping = false;                   // Yep! Stop monitoring.
 96        // Add new step to the step list (may be multiple in flight)
 97        stepMag[stepNum] = (STEP_HYSTERESIS - stepMin) * 6; // Step intensity
 98        stepX[stepNum]   = -80; // Position starts behind heel, moves forward
 99        if(++stepNum >= MAXSTEPS) stepNum = 0; // If many, overwrite oldest
100      } else if(stepFiltered < stepMin) stepMin = stepFiltered; // Track min val
101    } else if(stepFiltered < STEP_TRIGGER) { // No step yet; watch for trigger
102      stepping = true;         // Got one!
103      stepMin  = stepFiltered; // Note initial value
104    }
105  
106    // Render a 'brightness map' for all steps in flight.  It's like
107    // a grayscale image; there's no color yet, just intensities.
108    int mx1, px1, px2, m;
109    memset(mag, 0, sizeof(mag));    // Clear magnitude buffer
110    for(i=0; i<MAXSTEPS; i++) {     // For each step...
111      if(stepMag[i] <= 0) continue; // Skip if inactive
112      for(j=0; j<SHOE_LEN_LEDS; j++) { // For each LED...
113        // Each step has sort of a 'wave' that's part of the animation,
114        // moving from heel to toe.  The wave position has sub-pixel
115        // resolution (4X), and is up to 80 units (20 pixels) long.
116        mx1 = (j << 2) - stepX[i]; // Position of LED along wave
117        if((mx1 <= 0) || (mx1 >= 80)) continue; // Out of range
118        if(mx1 > 64) { // Rising edge of wave; ramp up fast (4 px)
119          m = ((long)stepMag[i] * (long)(80 - mx1)) >> 4;
120        } else { // Falling edge of wave; fade slow (16 px)
121          m = ((long)stepMag[i] * (long)mx1) >> 6;
122        }
123        mag[j] += m; // Add magnitude to buffered sum
124      }
125      stepX[i]++; // Update position of step wave
126      if(stepX[i] >= (80 + (SHOE_LEN_LEDS << 2)))
127        stepMag[i] = 0; // Off end; disable step wave
128      else
129        stepMag[i] = ((long)stepMag[i] * 127L) >> 7; // Fade
130    }
131  
132    // For a little visual interest, some 'sparkle' is added.
133    // The cumulative step magnitude is added to one pixel at random.
134    long sum = 0;
135    for(i=0; i<MAXSTEPS; i++) sum += stepMag[i];
136    if(sum > 0) {
137      i = random(SHOE_LEN_LEDS);
138      mag[i] += sum / 4;
139    }
140  
141    // Now the grayscale magnitude buffer is remapped to color for the LEDs.
142    // The code below uses a blackbody palette, which fades from white to yellow
143    // to red to black.  The goal here was specifically a "walking on fire"
144    // aesthetic, so the usual ostentatious rainbow of hues seen in most LED
145    // projects is purposefully skipped in favor of a more plain effect.
146    uint8_t r, g, b;
147    int     level;
148    for(i=0; i<SHOE_LEN_LEDS; i++) { // For each LED on one side...
149      level = mag[i];                // Pixel magnitude (brightness)
150      if(level < 255) {              // 0-254 = black to red-1
151        r = pgm_read_byte(&gamma1[level]);
152        g = b = 0;
153      } else if(level < 510) {       // 255-509 = red to yellow-1
154        r = 255;
155        g = pgm_read_byte(&gamma1[level - 255]);
156        b = 0;
157      } else if(level < 765) {       // 510-764 = yellow to white-1
158        r = g = 255;
159        b = pgm_read_byte(&gamma1[level - 510]);
160      } else {                       // 765+ = white
161        r = g = b = 255;
162      }
163      // Set R/G/B color along outside of shoe
164      strip.setPixelColor(i+SHOE_LED_BACK, r, g, b);
165      // Pixels along inside are funny...
166      j = dup[i];
167      if(j < 255) strip.setPixelColor(j, r, g, b);
168    }
169  
170    strip.show();
171    delayMicroseconds(1500);
172  }