Last active
May 7, 2018 07:52
-
-
Save grisevg/13a28049696678cc27b171b4e3bf9899 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 'Cyber falls' sketch, adapted from code for Firewalker sneakers. | |
// Creates a fiery rain-like effect on multiple NeoPixel strips. | |
// Requires Adafruit Trinket and NeoPixel strips. Strip length is | |
// inherently limited by Trinket RAM and processing power; this is | |
// written for five 15-pixel strands, which are paired up per pin | |
// for ten 15-pixel strips total. | |
#include <Adafruit_NeoPixel.h> | |
#include <avr/interrupt.h> | |
#include <avr/power.h> | |
#include <avr/io.h> | |
const uint8_t ribline[] PROGMEM = { | |
2, 30, 60, 85 | |
}; | |
const uint8_t gamma[] PROGMEM = { // Gamma correction table for LED brightness | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, | |
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, | |
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, | |
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, | |
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, | |
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, | |
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, | |
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, | |
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, | |
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, | |
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, | |
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, | |
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, | |
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; | |
#define DIM_BRIGHTNESS 4 | |
#define DROP_DIM_BRIGHTNESS 2 | |
#define ANGRY_SPEED 4 | |
#define STRIPLEN 50 // Length of LED strips | |
#define MAXDROPS 12 // Max concurrent 'raindrops' | |
#define N_STRIPS 6 // Connect strips to pins 0 to (N_STRIPS-1) | |
#define STRIP_DELAY_MIN 9 | |
#define STRIP_DELAY_MAX 37 | |
uint8_t pins[N_STRIPS] = { 2,3,4,5,6,7 }; // Connect strips to these pins | |
Adafruit_NeoPixel strip = Adafruit_NeoPixel(STRIPLEN, 0); | |
struct Drop { | |
uint8_t strip; | |
int16_t pos; | |
uint8_t speed; | |
uint16_t brightness; | |
}; | |
// Everything's declared volatile because state changes inside | |
// an interrupt routine. | |
#define BUTTON_ANGRY (16) | |
#define BUTTON_MAGIC_ON (14) | |
#define BUTTON_MAGIC_OFF (15) | |
#define BUTTON_PRESS_TOLERANCE 5 | |
volatile bool angry = false, magic = false; | |
volatile uint8_t angry_on_counter = 0, angry_off_counter = 0, magic_on_counter = 0, magic_off_counter = 0; | |
volatile Drop drop[MAXDROPS]; | |
volatile uint8_t | |
nDrops = 0, // Number of 'active' raindrops | |
prevStrip = 255; // Last selected strip | |
volatile uint16_t | |
countdown = 0; // Time to next raindrop | |
volatile bool heart_was_going_up = false; | |
volatile uint8_t last_heart = 0; | |
volatile int ribline_pos = 0; | |
volatile uint8_t rainbow_hair_pos[N_STRIPS]; | |
volatile uint8_t rainbow_ribs_pos = 0; | |
volatile uint8_t rainbow_heart_pos = 0; | |
#define RIBLINE_LENGTH 4 | |
#define RIBLEN 10 | |
#define HEARTLEN 8 | |
#define RIBPIN 9 | |
#define HEARTPIN 8 | |
Adafruit_NeoPixel strip_heart = Adafruit_NeoPixel(HEARTLEN, 0); | |
Adafruit_NeoPixel strip_rib = Adafruit_NeoPixel(RIBLEN, 0); | |
#define FANCY_MAGIC 1 | |
#if (FANCY_MAGIC) | |
#define COLOR_VAL() \ | |
r = g = b = 0; \ | |
if (angry) { \ | |
r = val; \ | |
} else { \ | |
g = b = val; \ | |
} | |
#else | |
#define COLOR_VAL() \ | |
r = g = b = 0; \ | |
if (angry) { \ | |
r = val; \ | |
} else if (magic) {\ | |
r = g = val; \ | |
} else { \ | |
g = b = val; \ | |
} | |
#endif | |
// Input a value 0 to 255 to get a color value. | |
// The colours are a transition r - g - b - back to r. | |
inline uint32_t Wheel(byte WheelPos) { | |
if (angry) { | |
WheelPos = 255 - WheelPos; | |
if (WheelPos < 127) { | |
return strip.Color((255) / DIM_BRIGHTNESS, 0, (WheelPos * 2) / DIM_BRIGHTNESS); | |
} else { | |
WheelPos -= 127; | |
return strip.Color((255) / DIM_BRIGHTNESS, 0, (255 - WheelPos * 2) /DIM_BRIGHTNESS); | |
} | |
} else | |
{ | |
WheelPos = 255 - WheelPos; | |
if(WheelPos < 85) { | |
return strip.Color((255 - WheelPos * 3) / DIM_BRIGHTNESS, 0, (WheelPos * 3) / DIM_BRIGHTNESS); | |
} | |
if(WheelPos < 170) { | |
WheelPos -= 85; | |
return strip.Color(0, (WheelPos * 3) / DIM_BRIGHTNESS, (255 - WheelPos * 3) / DIM_BRIGHTNESS); | |
} | |
WheelPos -= 170; | |
return strip.Color((WheelPos * 3) / DIM_BRIGHTNESS, (255 - WheelPos * 3) /DIM_BRIGHTNESS, 0); | |
} | |
} | |
inline uint32_t Wheel2(byte WheelPos, float brightness) { | |
if (angry) { | |
WheelPos = 255 - WheelPos; | |
if (WheelPos < 127) { | |
return strip.Color(round(255 * brightness) / DIM_BRIGHTNESS, 0, round((WheelPos * 2) * brightness / DIM_BRIGHTNESS)); | |
} else { | |
WheelPos -= 127; | |
return strip.Color(round(255 * brightness) / DIM_BRIGHTNESS, 0, round((255 - WheelPos * 2) * brightness /DIM_BRIGHTNESS)); | |
} | |
} else | |
{ | |
WheelPos = 255 - WheelPos; | |
if(WheelPos < 85) { | |
return strip.Color(round((255 - WheelPos * 3) * brightness / DIM_BRIGHTNESS), 0, round((WheelPos * 3) * brightness / DIM_BRIGHTNESS)); | |
} | |
if(WheelPos < 170) { | |
WheelPos -= 85; | |
return strip.Color(0, round((WheelPos * 3) * brightness / DIM_BRIGHTNESS), round((255 - WheelPos * 3) * brightness / DIM_BRIGHTNESS)); | |
} | |
WheelPos -= 170; | |
return strip.Color(round((WheelPos * 3) * brightness / DIM_BRIGHTNESS), round((255 - WheelPos * 3) * brightness / DIM_BRIGHTNESS), 0); | |
} | |
} | |
void setup() { | |
pinMode(BUTTON_ANGRY, INPUT_PULLUP); | |
pinMode(BUTTON_MAGIC_ON, INPUT_PULLUP); | |
pinMode(BUTTON_MAGIC_OFF, INPUT_PULLUP); | |
// Set up Timer/Counter1 for ~30 Hz interrupt | |
// ARDUINO UNO (and similar boards) VERSION (not for Trinket) | |
TCCR1A = _BV(WGM11); // Mode 14 (fast PWM), 1:64 prescale | |
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10); | |
ICR1 = F_CPU / 64 / 30; // ~30 Hz (~33 ms) | |
TIMSK1 |= _BV(TOIE1); // Enable overflow interrupt | |
// Turn strips off ASAP (must follow clock_prescale_set) | |
strip.begin(); | |
for(uint8_t s=0; s<N_STRIPS; s++) { | |
rainbow_hair_pos[s] = random(256); | |
strip.setPin(pins[s]); | |
strip.show(); | |
} | |
strip_rib.begin(); | |
strip_rib.setPin(RIBPIN); | |
strip_rib.show(); | |
strip_heart.begin(); | |
strip_heart.setPin(HEARTPIN); | |
strip_heart.show(); | |
} | |
void loop() { } // Not used -- everything's in interrupt below | |
// A timer interrupt is used so that everything runs at regular | |
// intervals, regardless of current amount of motion. | |
ISR(TIMER1_OVF_vect) { | |
uint16_t mag[STRIPLEN]; | |
uint8_t s, d, p, val, r, g, b; | |
int mx1, m, level; | |
if (digitalRead(BUTTON_ANGRY) == LOW) { | |
angry_off_counter = 0; | |
angry_on_counter++; | |
angry_on_counter = min(angry_on_counter,BUTTON_PRESS_TOLERANCE); | |
} else { | |
angry_on_counter = 0; | |
angry_off_counter++; | |
angry_off_counter = min(angry_off_counter,BUTTON_PRESS_TOLERANCE); | |
} | |
if (digitalRead(BUTTON_MAGIC_ON) == LOW) { | |
magic_on_counter++; | |
magic_on_counter = min(magic_on_counter,BUTTON_PRESS_TOLERANCE); | |
} else { | |
magic_on_counter = 0; | |
} | |
if (digitalRead(BUTTON_MAGIC_OFF) == LOW) { | |
magic_off_counter++; | |
magic_off_counter = min(magic_off_counter,BUTTON_PRESS_TOLERANCE); | |
} else { | |
magic_off_counter = 0; | |
} | |
if (angry_on_counter == BUTTON_PRESS_TOLERANCE) { | |
angry = true; | |
} | |
if (angry_off_counter == BUTTON_PRESS_TOLERANCE) { | |
angry = false; | |
} | |
if (magic_on_counter == BUTTON_PRESS_TOLERANCE) { | |
magic = true; | |
} | |
if (magic_off_counter == BUTTON_PRESS_TOLERANCE) { | |
magic = false; | |
} | |
// magic = true; | |
// angry = true; | |
const uint8_t angry_speed_mod = (angry) ? ANGRY_SPEED : 1; | |
#if (FANCY_MAGIC) | |
if (magic) { | |
for(s=0; s<N_STRIPS; s++) { // For each strip... | |
uint8_t pos = rainbow_hair_pos[s]; | |
for(p=0; p<STRIPLEN; p++) { // For each pixel in strip | |
strip.setPixelColor(p, Wheel((pos + (STRIPLEN - p - 1) * 5) % 255)); | |
} | |
strip.setPin(pins[s]); // Select output pin | |
strip.show(); // Strips don't need to refresh in sync | |
rainbow_hair_pos[s]+=5 * angry_speed_mod; | |
} | |
} else | |
#endif | |
{ | |
if(countdown == 0) { // Time to launch new drop? | |
if(nDrops < MAXDROPS-1) { // Is there space for one in the list? | |
do { | |
s = random(N_STRIPS); | |
} while(s == prevStrip); // Don't repeat prior strip | |
drop[nDrops].strip = prevStrip = s; | |
drop[nDrops].pos = -32; // Start off top of strip | |
drop[nDrops].speed = 1 * angry_speed_mod + random(3); | |
drop[nDrops].brightness = 250 + random(520); | |
nDrops++; | |
countdown = STRIP_DELAY_MIN + random(STRIP_DELAY_MAX - STRIP_DELAY_MIN); // Time to launch next one | |
} | |
} else countdown--; | |
for(s=0; s<N_STRIPS; s++) { // For each strip... | |
memset(mag, 0, sizeof(mag)); // Clear magnitude table | |
// Render active drops for this strip into magnitude table | |
for(d=0; d<nDrops; d++) { | |
if(drop[d].strip == s) { | |
for(p=0; p<STRIPLEN; p++) { // For each pixel... | |
mx1 = (p << 2) - drop[d].pos; // Position of LED along wave | |
if((mx1 <= 0) || (mx1 >= 32)) continue; // Out of range | |
if(mx1 > 24) { // Rising edge of wave; ramp up fast (2 px) | |
m = ((long)drop[d].brightness * (long)(32 - mx1)) >> 4; | |
} else { // Falling edge of wave; fade slow (6 px) | |
m = ((long)drop[d].brightness * (long)mx1) / 24; | |
} | |
mag[p] += m; // Accumulate result in magnitude buffer | |
} | |
} | |
} | |
// Remap magnitude table to RGB for strand | |
for(p=0; p<STRIPLEN; p++) { // For each pixel in strip | |
level = mag[p]; // Pixel magnitude (brightness) | |
if(level < 255) { // 0-254 = black to green-1 | |
val = pgm_read_byte(&gamma[level]) / DROP_DIM_BRIGHTNESS; | |
} else { // 765+ = white | |
val = 255 / DIM_BRIGHTNESS; | |
} | |
COLOR_VAL(); | |
strip.setPixelColor(p, r, g, b); | |
} | |
strip.setPin(pins[s]); // Select output pin | |
strip.show(); // Strips don't need to refresh in sync | |
} | |
// Move 'active' drops | |
for(d=0; d<nDrops; d++) { | |
drop[d].pos += drop[d].speed; | |
if(drop[d].pos >= (STRIPLEN * 4)) { // Off end? | |
// Remove drop from list (move last one to this place) | |
memcpy((void *)&drop[d], (void *)&drop[nDrops-1], sizeof(drop[0])); | |
nDrops--; | |
} | |
} | |
} | |
// HEART | |
const float heart_brightness = pow(sin(rainbow_heart_pos * 0.1f * angry_speed_mod)+1.0f/2.0f, 2.2f); | |
#if (FANCY_MAGIC) | |
if (magic) { | |
if (angry) { | |
uint8_t red = round(255 * heart_brightness / DIM_BRIGHTNESS); | |
for(int i=0; i<HEARTLEN; ++i) { // For each pixel in strip | |
strip_heart.setPixelColor(i, red, 0, 0); | |
} | |
} else { | |
const uint32_t col = Wheel2(rainbow_heart_pos, heart_brightness); | |
for(int i=0; i<HEARTLEN; ++i) { // For each pixel in strip | |
strip_heart.setPixelColor(i, col); | |
} | |
} | |
} else | |
#endif | |
{ | |
val = round(255 * heart_brightness / DIM_BRIGHTNESS); | |
COLOR_VAL(); | |
for(int i=0; i<HEARTLEN; ++i) { // For each pixel in strip | |
strip_heart.setPixelColor(i, r, g, b); | |
} | |
if (heart_was_going_up && (val < last_heart)){ | |
ribline_pos = ribline_pos = -RIBLINE_LENGTH*2 + 2; | |
} | |
heart_was_going_up = (val >= last_heart); | |
last_heart = val; | |
} | |
strip_heart.show(); | |
rainbow_heart_pos++; | |
// RIBS | |
#if (FANCY_MAGIC) | |
if (magic) { | |
uint8_t pos = rainbow_ribs_pos; | |
for(p=0; p<RIBLEN; p++) { // For each pixel in strip | |
strip_rib.setPixelColor(p, Wheel((pos + (RIBLEN - p - 1) * 5) % 255)); | |
} | |
strip_rib.show(); // Strips don't need to refresh in sync | |
rainbow_ribs_pos+=3 * angry_speed_mod; | |
} else | |
#endif | |
{ | |
int pos = ribline_pos / 2; | |
uint8_t ribline_start = max(min(pos, RIBLEN), 0); | |
uint8_t ribline_end = max(min(pos + RIBLINE_LENGTH, RIBLEN), 0); | |
for(int i=0; i<RIBLEN; ++i) { // For each pixel in strip | |
strip_rib.setPixelColor(i, 0, 0, 0); | |
} | |
int j = 0; | |
for ( int i = ribline_start; i < ribline_end; ++i) { | |
val = pgm_read_byte(&ribline[j]); | |
COLOR_VAL(); | |
strip_rib.setPixelColor(i, r, g, b); | |
j++; | |
} | |
if ((pos) < (RIBLEN + RIBLINE_LENGTH)) | |
{ | |
ribline_pos += 1 * min(angry_speed_mod, 2); | |
} | |
} | |
strip_rib.show(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment