/ NeoPixel_Cyber_Falls_Wig / NeoPixel_Cyber_Falls_Wig.ino
NeoPixel_Cyber_Falls_Wig.ino
  1  // SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // 'Cyber falls' sketch, adapted from code for Firewalker sneakers.
  6  // Creates a fiery rain-like effect on multiple NeoPixel strips.
  7  // Requires Adafruit Trinket and NeoPixel strips.  Strip length is
  8  // inherently limited by Trinket RAM and processing power; this is
  9  // written for five 15-pixel strands, which are paired up per pin
 10  // for ten 15-pixel strips total.
 11  
 12  #include <Adafruit_NeoPixel.h>
 13  #include <avr/power.h>
 14  
 15  const uint8_t gamma[] PROGMEM = { // Gamma correction table for LED brightness
 16      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 17      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
 18      1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
 19      2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
 20      5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
 21     10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
 22     17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
 23     25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
 24     37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
 25     51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
 26     69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
 27     90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
 28    115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
 29    144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
 30    177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
 31    215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
 32  
 33  #define STRIPLEN 15 // Length of LED strips
 34  #define MAXDROPS  5 // Max concurrent 'raindrops'
 35  #define N_STRIPS  5 // Connect strips to pins 0 to (N_STRIPS-1)
 36  
 37  Adafruit_NeoPixel strip = Adafruit_NeoPixel(STRIPLEN, 0);
 38  
 39  // Everything's declared volatile because state changes inside
 40  // an interrupt routine.
 41  volatile struct {
 42    uint8_t  strip;
 43    int16_t  pos;
 44    uint8_t  speed;
 45    uint16_t brightness;
 46  } drop[MAXDROPS];
 47  volatile uint8_t
 48    nDrops    = 0,   // Number of 'active' raindrops
 49    prevStrip = 255; // Last selected strip
 50  volatile uint16_t
 51    countdown = 0;   // Time to next raindrop
 52  
 53  void setup() {
 54  
 55    // Set up Timer/Counter1 for ~30 Hz interrupt
 56  #if F_CPU == 16000000L
 57    clock_prescale_set(clock_div_1);
 58    TCCR1  = _BV(PWM1A) |_BV(CS13) | _BV(CS12);             // 1:2048 prescale
 59  #else
 60    TCCR1  = _BV(PWM1A) |_BV(CS13) | _BV(CS11) | _BV(CS10); // 1:1024 prescale
 61  #endif
 62  
 63    // Turn strips off ASAP (must follow clock_prescale_set)
 64    strip.begin();
 65    for(uint8_t s=0; s<N_STRIPS; s++) {
 66      strip.setPin(s);
 67      strip.show();
 68    }
 69  
 70    // Finish timer/counter setup
 71    OCR1C  = 255;        // ~30 Hz
 72    GTCCR  = 0;          // No PWM out
 73    TIMSK |= _BV(TOIE1); // Enable overflow interrupt
 74  }
 75  
 76  void loop() { } // Not used -- everything's in interrupt below
 77  
 78  // A timer interrupt is used so that everything runs at regular
 79  // intervals, regardless of current amount of motion.
 80  ISR(TIMER1_OVF_vect) {
 81  
 82    uint16_t mag[STRIPLEN];
 83    uint8_t  s, d, p, r, g, b;
 84    int      mx1, m, level;
 85  
 86    if(countdown == 0) {         // Time to launch new drop?
 87      if(nDrops < MAXDROPS-1) {  // Is there space for one in the list?
 88        do {
 89          s = random(N_STRIPS);
 90        } while(s == prevStrip); // Don't repeat prior strip
 91        drop[nDrops].strip      = prevStrip = s;
 92        drop[nDrops].pos        = -32; // Start off top of strip
 93        drop[nDrops].speed      = 1 + random(3);
 94        drop[nDrops].brightness = 250 + random(520);
 95        nDrops++;
 96        countdown = 9 + random(28); // Time to launch next one
 97      }
 98    } else countdown--;
 99  
100  
101    for(s=0; s<N_STRIPS; s++) { // For each strip...
102      memset(mag, 0, sizeof(mag)); // Clear magnitude table
103  
104      // Render active drops for this strip into magnitude table
105      for(d=0; d<nDrops; d++) {
106        if(drop[d].strip == s) {
107          for(p=0; p<STRIPLEN; p++) { // For each pixel...
108            mx1 = (p << 2) - drop[d].pos; // Position of LED along wave
109            if((mx1 <= 0) || (mx1 >= 32)) continue; // Out of range
110            if(mx1 > 24) { // Rising edge of wave; ramp up fast (2 px)
111              m = ((long)drop[d].brightness * (long)(32 - mx1)) >> 4;
112            } else { // Falling edge of wave; fade slow (6 px)
113              m = ((long)drop[d].brightness * (long)mx1) / 24;
114            }
115            mag[p] += m; // Accumulate result in magnitude buffer
116          }
117        }
118      }
119  
120      // Remap magnitude table to RGB for strand
121      for(p=0; p<STRIPLEN; p++) {      // For each pixel in strip
122        level = mag[p];                // Pixel magnitude (brightness)
123        if(level < 255) {              // 0-254 = black to green-1
124          r = b = 0;
125          g = pgm_read_byte(&gamma[level]);
126        } else if(level < 510) {       // 255-509 = green to yellow-1
127          r = pgm_read_byte(&gamma[level - 255]);
128          g = 255;
129          b = 0;
130        } else if(level < 765) {       // 510-764 = yellow to white-1
131          r = g = 255;
132          b = pgm_read_byte(&gamma[level - 510]);
133        } else {                       // 765+ = white
134          r = g = b = 255;
135        }
136        strip.setPixelColor(p, r, g, b);
137      }
138  
139      strip.setPin(s); // Select output pin
140      strip.show();    // Strips don't need to refresh in sync
141    }
142  
143    // Move 'active' drops
144    for(d=0; d<nDrops; d++) {
145      drop[d].pos += drop[d].speed;
146      if(drop[d].pos >= (STRIPLEN * 4)) { // Off end?
147        // Remove drop from list (move last one to this place)
148        memcpy((void *)&drop[d], (void *)&drop[nDrops-1], sizeof(drop[0]));
149        nDrops--;
150      }
151    }
152  }