Skip to content

Instantly share code, notes, and snippets.

@microtherion
Created December 13, 2016 06:51
Show Gist options
  • Save microtherion/32faaa4d07997bfa3e49e4d9b4903ed6 to your computer and use it in GitHub Desktop.
Save microtherion/32faaa4d07997bfa3e49e4d9b4903ed6 to your computer and use it in GitHub Desktop.
/*
* Derived from Nathan Seidle's LED strip demo code
*
* You will need to connect 5V/Gnd from the Arduino (USB power seems to be sufficient).
*
* For the data pins, please pay attention to the arrow printed on the strip. You will need to connect to
* the end that is the begining of the arrows (data connection)--->
*
* The documented wire colors are very unreliable. Follow the labels printed on the LED strip.
*
* Color sorting adapted from http://www.alanzucconi.com/2015/09/30/colour-sorting/
* Color conversion adapted from https://github.com/ratkins/RGBConverter
*/
int SDI = 2; // Data
int CKI = 3; // Clock
int ledPin = 13; //On board LED
// #define SINGLE_ANIMATION BubbleSortAnimation
// #define ROTATING_ANIMATIONS
#define STRIP_LENGTH 32 //32 LEDs on this strip
long strip_colors[STRIP_LENGTH];
class Animation {
public:
virtual ~Animation() {}
virtual void generateNextFrame() { }
virtual bool suggestSwitch() { return animationTimeout(); }
virtual int speed() { return 250; }
virtual int finalHold() { return 1; }
protected:
long randomColor(byte maxRed=0xFF, byte maxGreen=0xFF, byte maxBlue=0xFF);
};
Animation * gCurrentAnimation;
unsigned long gAnimationTime;
class ShiftRandomAnimation : public Animation {
public:
ShiftRandomAnimation();
virtual void generateNextFrame();
virtual int speed() { return 250; }
};
class SnowAnimation : public Animation {
public:
SnowAnimation();
virtual void generateNextFrame();
virtual bool suggestSwitch();
virtual int speed() { return 30; }
private:
int height;
int density;
long color;
};
class CompromiseAnimation : public Animation {
public:
CompromiseAnimation();
virtual void generateNextFrame();
virtual bool suggestSwitch();
virtual int speed() { return 100; }
virtual int finalHold() { return 500; }
private:
long startColor;
long endColor;
long curColor;
int startPos;
int endPos;
int curPos;
int direction;
long compromiseColor(long end, long start);
};
class NoiseAnimation : public Animation {
public:
NoiseAnimation();
virtual void generateNextFrame();
virtual int speed() { return 100; }
private:
int noisiness;
};
class BubbleSortAnimation : public Animation {
public:
BubbleSortAnimation();
virtual void generateNextFrame();
virtual bool suggestSwitch() { return sorted; }
virtual int speed() { return 20; }
virtual int finalHold() { return 2000; }
private:
bool sorted;
};
void startNextAnimation()
{
if (gCurrentAnimation)
delay(gCurrentAnimation->finalHold());
gAnimationTime = millis();
for(int x = 0 ; x < STRIP_LENGTH ; x++)
strip_colors[x] = 0;
delete gCurrentAnimation;
#ifdef SINGLE_ANIMATION
gCurrentAnimation = new SINGLE_ANIMATION;
#else
#ifdef ROTATING_ANIMATIONS
static int anim = -1;
switch (anim = (anim+1)%5) {
#else
switch (random(5)) {
#endif
case 1:
gCurrentAnimation = new SnowAnimation;
break;
case 2:
gCurrentAnimation = new NoiseAnimation;
break;
case 3:
gCurrentAnimation = new CompromiseAnimation;
break;
case 4:
gCurrentAnimation = new BubbleSortAnimation;
break;
default:
gCurrentAnimation = new ShiftRandomAnimation;
break;
}
#endif
post_frame();
}
bool animationTimeout()
{
unsigned long now = millis();
if (now-gAnimationTime > 30000)
return true;
else if ((now ^ gAnimationTime) & 0x8000000)
return true;
else
return false;
}
void setup() {
pinMode(SDI, OUTPUT);
pinMode(CKI, OUTPUT);
pinMode(ledPin, OUTPUT);
randomSeed(analogRead(0));
startNextAnimation();
delay(2000);
}
void loop() {
gCurrentAnimation->generateNextFrame();
post_frame(); //Push the current color frame to the strip
digitalWrite(ledPin, HIGH); // set the LED on
delay(gCurrentAnimation->speed());
digitalWrite(ledPin, LOW); // set the LED off
delay(gCurrentAnimation->speed());
if (gCurrentAnimation->suggestSwitch()) {
delay(2000);
startNextAnimation();
}
}
//Takes the current strip color array and pushes it out
void post_frame (void) {
//Each LED requires 24 bits of data
//MSB: R7, R6, R5..., G7, G6..., B7, B6... B0
//Once the 24 bits have been delivered, the IC immediately relays these bits to its neighbor
//Pulling the clock low for 500us or more causes the IC to post the data.
for(int LED_number = 0 ; LED_number < STRIP_LENGTH ; LED_number++) {
long this_led_color = strip_colors[LED_number]; //24 bits of color data
// My LED strip actually has colors ordered BBBBBBBBGGGGGGGGRRRRRRRR
this_led_color = ((this_led_color & 0xFF) << 16) | (this_led_color & 0xFF00L) | ((this_led_color & 0xFF0000L) >> 16);
for(byte color_bit = 23 ; color_bit != 255 ; color_bit--) {
//Feed color bit 23 first (red data MSB)
digitalWrite(CKI, LOW); //Only change data when clock is low
long mask = 1L << color_bit;
//The 1'L' forces the 1 to start as a 32 bit number, otherwise it defaults to 16-bit.
if(this_led_color & mask)
digitalWrite(SDI, HIGH);
else
digitalWrite(SDI, LOW);
digitalWrite(CKI, HIGH); //Data is latched when clock goes high
}
}
//Pull clock low to put strip into reset/post mode
digitalWrite(CKI, LOW);
delayMicroseconds(500); //Wait for 500us to go into reset
}
long
Animation::randomColor(byte maxRed, byte maxGreen, byte maxBlue)
{
return (random(maxRed) << 16)
| (random(maxGreen) << 8)
| (random(maxBlue));
}
ShiftRandomAnimation::ShiftRandomAnimation()
{
//Pre-fill the color array with known values
strip_colors[0] = 0xFF0000; //Bright Red
strip_colors[1] = 0x00FF00; //Bright Green
strip_colors[2] = 0x0000FF; //Bright Blue
strip_colors[3] = 0x010000; //Faint red
strip_colors[4] = 0x800000; //1/2 red (0x80 = 128 out of 256)
}
//Throws random colors down the strip array
void
ShiftRandomAnimation::generateNextFrame() {
int x;
//First, shuffle all the current colors down one spot on the strip
for(x = (STRIP_LENGTH - 1) ; x > 0 ; x--)
strip_colors[x] = strip_colors[x - 1];
//Now form a new RGB color
long new_color = randomColor();
strip_colors[0] = new_color; //Add the new random color to the strip
}
SnowAnimation::SnowAnimation()
: height(STRIP_LENGTH-1), density(random(10, 20))
{
color = randomColor(0x3F, 0x3F) | 0xC0C000; // Blue-ish
}
void
SnowAnimation::generateNextFrame()
{
for (int i=height; i>0; --i) {
strip_colors[i] = strip_colors[i-1];
}
strip_colors[0] = (random(100) < density) ? color : 0;
if (strip_colors[height]) {
--height;
}
}
bool
SnowAnimation::suggestSwitch()
{
return height < (STRIP_LENGTH>>2);
}
CompromiseAnimation::CompromiseAnimation()
: startColor(randomColor()), endColor(randomColor()), startPos(0), endPos(STRIP_LENGTH-1), curPos(startPos), direction(-1)
{
switch (random(6)) {
case 0:
startColor &= 0xFF0000;
endColor &= 0x00FF00;
break;
case 1:
startColor &= 0xFF0000;
endColor &= 0x0000FF;
break;
case 2:
startColor &= 0x00FF00;
endColor &= 0x0000FF;
break;
case 3:
startColor &= 0x00FF00;
endColor &= 0xFF0000;
break;
case 4:
startColor &= 0x0000FF;
endColor &= 0xFF0000;
break;
case 5:
startColor &= 0x0000FF;
endColor &= 0x00FF00;
break;
}
curColor = startColor;
strip_colors[startPos] = startColor;
strip_colors[endPos--] = endColor;
}
long
CompromiseAnimation::compromiseColor(long end, long start)
{
long newRed = (((end >> 16) & 0xFF)*9+((start >> 16) & 0xFF)) / 10;
long newGreen = (((end >> 8) & 0xFF)*9+((start >> 8) & 0xFF)) / 10;
long newBlue = (( end & 0xFF)*9+( start & 0xFF)) / 10;
return (newRed << 16) | (newGreen << 8) | newBlue;
}
void
CompromiseAnimation::generateNextFrame()
{
strip_colors[curPos] = curColor;
if (direction < 0) {
if (curPos == startPos) {
startColor = curColor;
direction = 1;
curColor = compromiseColor(endColor, startColor);
curPos = ++startPos;
} else {
if (curPos < endPos) {
strip_colors[curPos+1] = 0;
}
--curPos;
}
} else {
if (curPos == endPos) {
endColor = curColor;
direction = -1;
curColor = compromiseColor(startColor, endColor);
curPos = --endPos;
} else {
if (curPos > startPos) {
strip_colors[curPos-1] = 0;
}
++curPos;
}
}
}
bool
CompromiseAnimation::suggestSwitch()
{
return startPos >= endPos;
}
NoiseAnimation::NoiseAnimation()
: noisiness(random(20, 60))
{
for (int i=0; i<STRIP_LENGTH; ++i)
strip_colors[i] = randomColor();
}
void
NoiseAnimation::generateNextFrame()
{
for (int i=0; i<STRIP_LENGTH; ++i)
if (random(100) < noisiness)
strip_colors[i] = randomColor();
}
BubbleSortAnimation::BubbleSortAnimation()
: sorted(false)
{
for (int i=0; i<STRIP_LENGTH; ++i)
strip_colors[i] = randomColor();
}
void
rgb2hsv(long color, float rgb[], float hsv[]) {
float r = ((color >> 16) & 0xFF) / 255.0;
float g = ((color >> 8) & 0xFF) / 255.0;
float b = ( color & 0xFF) / 255.0;
float cmax = max(r,g); cmax = max(cmax,b);
float cmin = min(r,g); cmin = min(cmin,b);
float h;
float s;
float v = cmax;
float d = cmax - cmin;
s = cmax == 0 ? 0 : d / cmax;
if (cmax == cmin) {
h = 0; // achromatic
} else {
if (cmax == r) {
h = (g - b) / d + (g < b ? 6 : 0);
} else if (cmax == g) {
h = (b - r) / d + 2;
} else if (cmax == b) {
h = (r - g) / d + 4;
}
h /= 6;
}
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
hsv[0] = h;
hsv[1] = s;
hsv[2] = v;
}
long
colorIndex(long color, int h_quant, int l_quant, int v_quant)
{
float hsv[3], rgb[3];
rgb2hsv(color, rgb, hsv);
float lum = sqrt( .241 * rgb[0] + .691 * rgb[1] + .068 * rgb[2]);
long hq = int(hsv[0] * h_quant);
long lq = l_quant - int(lum * l_quant);
long vq = int(hsv[2] * v_quant);
#if 0
if (hq % 2 == 1) {
vq = v_quant - vq;
lq = l_quant - lq;
}
#endif
return (hq << 20) | (lq << 10) | vq;
}
long
colorIndex(long color)
{
return colorIndex(color, 8, 8, 256);
}
void
BubbleSortAnimation::generateNextFrame()
{
for (int i=0; i<STRIP_LENGTH-1; ++i)
if (colorIndex(strip_colors[i]) < colorIndex(strip_colors[i+1])) {
long swap = strip_colors[i];
strip_colors[i] = strip_colors[i+1];
strip_colors[i+1] = swap;
return;
}
sorted = true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment