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