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