/ 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 }