Skip to content

Instantly share code, notes, and snippets.

@grisevg
Last active May 7, 2018 07:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grisevg/13a28049696678cc27b171b4e3bf9899 to your computer and use it in GitHub Desktop.
Save grisevg/13a28049696678cc27b171b4e3bf9899 to your computer and use it in GitHub Desktop.
// '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