Christmas2013

Fork project on GitHub

Pixel Strips, water sensor, Ultrasonic ranging and a Christmas Tree

AdafruitPixel
AdafruitPixel
Arduino-GroveShield
Arduino-GroveShield
LCD-Panel
LCD-Panel
LEDStripDriver
LEDStripDriver
Tree-NoWater
Tree-NoWater
Tree-normal
Tree-normal
Ultrasonic
Ultrasonic
WaterSensor-detail
WaterSensor-detail
WaterSensor
WaterSensor

 

Download Christmas2013.ino - Arduino Sketch

 

Documentation

This Christmas, I decided to see what I could do with all the Arduino gear I have collected - after all, blinking lights and Christmas Trees naturally go together!

I started with a bunch of RGB LEDS:

I have been using the Adafruit Pixel- based RGB LED strings as the basis for my model train cTc work - each lamp is individually addressable - the string of 50 RGB LED lamps is treated as a 50-element shift register; the WS2801 LED controller chip latches the (R,G,B) triple and manages the PWM and current control for each LED segment in its lamp.

The Seeed Grove LED Strip Driver also uses a WS2801, but adds a high current driver FET to each channel so it can drive a 5-meter RGB LED strip.

I wrapped the RGB strip around the Christmas tree trunk in a spiral, connected it to the strip driver and plugged it into an Arduino by way of a Seeed Grove Shield. Chained off the strip driver are two chains of Pixels, for a total of 101 RGB lamps.

The strip driver runs off of 12v at 1A or more (900 LEDs (300 each of red, green and blue) at even a couple of mA each adds up!), while the Pixels require 5VDC so the embedded driver chips don't blow. The Arduino is happy with 5V, so I only needed two wall warts and a splitter cable to power everything.

Thanks to Pololu, I found a nice collection of RGB LED routines. The bad news is that they were written for a TM1804/WS2812 driver chips; the good news is that it was easy to rip out the chip specific routines and replace them with ones for the WS2801. With 100 lamps in the chain the update rate is only a few HZ, which makes brightness changes easily visible - whether or not this is a bug or a feature is still up in the air; I personally don't mind slowly changing light patterns...

One of the problems with a live (or, should I say, formerly live) tree is that you need to keep water in the stand or it will dry out. My next hack was to wire up a water sensor - esentially an analog input with a 1MegOhm pullup wired to a perf board next to a wire connected to ground: The water "resistance" forms a voltage divider with the pullup resistor which results in a fluctuating voltage on the A-to-D pin, with "dry" probe (a higher resistance) resulting in a higher voltage than a "wet" (lower resistance) one. When the wire sensor "triggered", the code turns all the lights RED and leaves them that way as a reminder to water the tree.

Another problem with a tree on a table is that little kids tend to get too close and pull at the branches, lights and ornaments that they can reach. As hosts, continually telling other parents to watch their kids gets old pretty quickly - wouldn't it be nice if the tree itself could alert them instead? I added a Grove Ultrasonic ranger to the mix, with a triggered routine to flash the lights quickly between full-on RED and full-on GREEN. Now, whenever anyone gets within a few inches of the tree, everyone in the room notices!

Unfortunately, this blink-on-demand feature ended up attracting the kids; thankefully, they were appreciative and well behaved!

Next year, I'm aiming for several more Pixel chains (or maybe some higher speed TM1804/WS2812 chains), a better water sensor (this hack suffers from wire corrosion/crud build-up that changes the range of "good" readings over time) and different/more interesting feedback-based light patterns (e.g., cheerlights or web usage analytics or ...)

If you got this far, I hope you have a Merry Christmas, a happy New Year, and an Arduino in a Fir tree!!!

Christmas2013

/* LedStripXmas: A series of fun patterns for use with LED
 * strips set up as a Christmas lighting display.  You can see an
 * earlier version of this code running in this youtube video:
 * http://www.youtube.com/watch?v=VZRN0UrQSlc
 * To use this, you will need to plug the signal wire of an
 * Addressable RGB LED strip from Pololu into pin 12.
 *
 *
 * NOTE: Timing is determined entirely by a counter incremented
 * by the main loop, and the execution time of the main loop is
 * dominated by the time it takes to write the LED colors array
 * to the LED strips.  Changing LED_COUNT will change the
 * timing, so if you like the default timing and you have fewer
 * than 509 LEDs, you might want to add an appropriate delay to
 * the main loop.  Timing is not done with the Arduino's system
 * timer because that does not work properly when this program
 * is running (the interrupts that maintain the system time must
 * be disabled while the LED strips are being updated or else
 * they will cause glitches on the LEDs).
 *
 * This sketch is derived from many sources, too many to remember :-)
 * -John
 */

#define DEBUG

#include "RGBdriver.h"
#include "Ultrasonic.h"
#ifdef DEBUG
#include <SoftwareSerial.h>

#define rxPin         5        // rxPin is immaterial - not used - just make this an unused Arduino pin number
#define txPin        10        // pin 14 is analog pin 0, on a BBB just use a servo cable
#endif

#define RANGERPIN 3
#define WATERPIN A1
#define SETPOINT 500
#define RANGE 30

#define CLK 12//pins definitions for the driver        
#define DIO 13
RGBdriver Driver(CLK,DIO);
Ultrasonic ultrasonic1(3);
//Ultrasonic ultrasonic2(7);
#ifdef DEBUG
SoftwareSerial slcd =  SoftwareSerial(rxPin, txPin);//this is a must, assign soft serial pins
#endif

// Create a buffer for holding all the colors.
// This uses up most of the 2KB RAM on an Uno,
// so we should be very sparing with additional RAM use and keep
// an eye out for possible stack overflow problems.
#define LED_COUNT 101

  typedef struct rgb_color
  {
    unsigned char red, green, blue;
  } rgb_color;

rgb_color colors[LED_COUNT];

#define NUM_STATES  7  // number of patterns to cycle through

// system timer, incremented by one every time through the main loop
unsigned int loopCount = 0;

unsigned int seed = 0;  // used to initialize random number generator

// enumerate the possible patterns in the order they will cycle
enum Pattern {
  WarmWhiteShimmer = 0,
  RandomColorWalk = 1,
  TraditionalColors = 2,
  ColorExplosion = 3,
  Gradient = 4,
  BrightTwinkle = 5,
  Collision = 6,
  AllOff = 255
};
unsigned char pattern = AllOff;
unsigned int maxLoops;  // go to next state when loopCount >= maxLoops


// initialization stuff
void setup()
{
  // initialize the random number generator with a seed obtained by
  // summing the voltages on the disconnected analog inputs
  for (int i = 0; i < 8; i++)
  {
    seed += analogRead(i);
  }  
     // initialize the flood Sensor pin as an input:
  pinMode(WATERPIN, INPUT);     

  delay(10);  // give pull-ups time raise the input voltage
#ifdef DEBUG
    pinMode(txPin, OUTPUT);
    delay(1000);
    slcd.begin(9600);      // 9600 baud is chip comm speed
    slcd.print("?G420");   // set display geometry,
    slcd.print("?B7f");    // set backlight to ff hex, maximum brightness
    slcd.print("?c0"); 
    delay(10);
    slcd.print("?x00?y0  Tree Monitor        "); 
    slcd.print("?x00?y1                      "); 
    slcd.print("?x00?y2                      "); 
    slcd.print("?x00?y3                      "); 
    delay(500);
#endif

}


// main loop
void loop()
{
#ifdef DEBUG
  long wv = analogRead(WATERPIN);
      slcd.print("?x01?y0 water: "); 
      slcd.print(wv, DEC);
      slcd.print(" ("); slcd.print(SETPOINT, DEC);  slcd.print(")    ");
#endif

  // handleNextPatternButton();
  byte isRed = 0;
  while (analogRead(WATERPIN) > SETPOINT ) {
    if (!isRed) {
      Driver.begin();
      for(byte i = 0; i < LED_COUNT; i++){
        Driver.SetColor(255, 0, 0); 
      }
      Driver.end();
    }
    isRed = 1;
#ifdef DEBUG
  long wv = analogRead(WATERPIN);
      slcd.print("?x01?y0 water: "); 
      slcd.print(wv, DEC);
      slcd.print(" ("); slcd.print(SETPOINT, DEC);  slcd.print(")    ");
#endif

  }
#ifdef DEBUG
  long rv = ultrasonic1.MeasureInCentimeters();
  slcd.print("?x01?y1 range: "); 
  slcd.print(rv, DEC);
  slcd.print(" ("); slcd.print(RANGE, DEC);  slcd.print(")    ");
#endif
  int x = 100; int r = 0; int g = 0; int b = 0; int dir=1;
  while (rv < RANGE) {
    Driver.begin();
    Driver.SetColor(x, x, x); 
    Driver.end();
    if (dir) x--; else x++;
    if (x == 0) { dir = 0; x = 1; }
    else if (x == 101) { dir = 1; x = 100; }
#ifdef DEBUG
    long nrv = ultrasonic1.MeasureInCentimeters();
    if (nrv != rv) {
      rv = nrv;
      slcd.print("?x01?y1 range: "); 
      slcd.print(rv, DEC);
      slcd.print(" ("); slcd.print(RANGE, DEC);  slcd.print(")    ");
    }
#endif
  }
  /*while (ultrasonic2.MeasureInCentimeters() < 50) {
    Driver.begin();
    for(byte i = 0; i < LED_COUNT; i++){
      Driver.SetColor(255, 0, 255); 
    }
    Driver.end();
    Driver.begin();
    for(byte i = 0; i < LED_COUNT; i++){
      Driver.SetColor(0, 0, 255); 
    }
    Driver.end();
  }
/* */
  if (loopCount == 0) {
    // whenever timer resets, clear the LED colors array (all off)
    for (int i = 0; i < LED_COUNT; i++) {
      colors[i] = (rgb_color){0, 0, 0};
    }
  }
  
  if (pattern == WarmWhiteShimmer || pattern == RandomColorWalk)
  {
    // for these two patterns, we want to make sure we get the same
    // random sequence six times in a row (this provides smoother
    // random fluctuations in brightness/color)
    if (loopCount % 6 == 0)
    {
      seed = random(30000);
    }
    randomSeed(seed);
  }
  
  // call the appropriate pattern routine based on state; these
  // routines just set the colors in the colors array
  switch (pattern)
  {
    case WarmWhiteShimmer:
      // warm white shimmer for 300 loopCounts, fading over last 70
      maxLoops = 300;
      warmWhiteShimmer(loopCount > maxLoops - 70);
      break;
      
    case RandomColorWalk:
      // start with alternating red and green colors that randomly walk
      // to other colors for 400 loopCounts, fading over last 80
      maxLoops = 400;
      randomColorWalk(loopCount == 0 ? 1 : 0, loopCount > maxLoops - 80);
      break;
      
    case TraditionalColors:
      // repeating pattern of red, green, orange, blue, magenta that
      // slowly moves for 400 loopCounts
      maxLoops = 400;
      traditionalColors();
      break;
      
    case ColorExplosion:
      // bursts of random color that radiate outwards from random points
      // for 630 loop counts; no burst generation for the last 70 counts
      // of every 200 count cycle or over the over final 100 counts
      // (this creates a repeating bloom/decay effect)
      maxLoops = 630;
      colorExplosion((loopCount % 200 > 130) || (loopCount > maxLoops - 100));
      break;
      
    case Gradient:
      // red -> white -> green -> white -> red ... gradiant that scrolls
      // across the strips for 250 counts; this pattern is overlaid with
      // waves of dimness that also scroll (at twice the speed)
      maxLoops = 250;
      gradient();
      //delay(6);  // add an extra 6ms delay to slow things down
      break;
      
    case BrightTwinkle:
      // random LEDs light up brightly and fade away; it is a very similar
      // algorithm to colorExplosion (just no radiating outward from the
      // LEDs that light up); as time goes on, allow progressively more
      // colors, halting generation of new twinkles for last 100 counts.
      maxLoops = 1200;
      if (loopCount < 400)
      {
        brightTwinkle(0, 1, 0);  // only white for first 400 loopCounts
      }
      else if (loopCount < 650)
      {
        brightTwinkle(0, 2, 0);  // white and red for next 250 counts
      }
      else if (loopCount < 900)
      {
        brightTwinkle(1, 2, 0);  // red, and green for next 250 counts
      }
      else
      {
        // red, green, blue, cyan, magenta, yellow for the rest of the time
        brightTwinkle(1, 6, loopCount > maxLoops - 100);
      }
      break;
      
    case Collision:
      // colors grow towards each other from the two ends of the strips,
      // accelerating until they collide and the whole strip flashes
      // white and fades; this repeats until the function indicates it
      // is done by returning 1, at which point we stop keeping maxLoops
      // just ahead of loopCount
      if (!collision())
      {
        maxLoops = loopCount + 2;
      }
      break;
  } 

  // update the LED strips with the colors in the colors array  
  // Update the colors.
  Driver.begin(); // begin
  for(byte i = 0; i < LED_COUNT; i++){
    Driver.SetColor(colors[i].red, colors[i].green, colors[i].blue); 
  }
  Driver.end();

  loopCount++;  // increment our loop counter/timer.

  if (loopCount >= maxLoops)
  {
    // if the time is up for the current pattern and the optional hold
    // switch is not grounding the AUTOCYCLE_SWITCH_PIN, clear the
    // loop counter and advance to the next pattern in the cycle
    loopCount = 0;  // reset timer
    pattern = ((unsigned char)(pattern+1))%NUM_STATES;  // advance to next pattern
  }
}


// This function applies a random walk to val by increasing or
// decreasing it by changeAmount or by leaving it unchanged.
// val is a pointer to the byte to be randomly changed.
// The new value of val will always be within [0, maxVal].
// A walk direction of 0 decreases val and a walk direction of 1
// increases val.  The directions argument specifies the number of
// possible walk directions to choose from, so when directions is 1, val
// will always decrease; when directions is 2, val will have a 50% chance
// of increasing and a 50% chance of decreasing; when directions is 3,
// val has an equal chance of increasing, decreasing, or staying the same.
void randomWalk(unsigned char *val, unsigned char maxVal, unsigned char changeAmount, unsigned char directions)
{
  unsigned char walk = random(directions);  // direction of random walk
  if (walk == 0)
  {
    // decrease val by changeAmount down to a min of 0
    if (*val >= changeAmount)
    {
      *val -= changeAmount;
    }
    else
    {
      *val = 0;
    }
  }
  else if (walk == 1)
  {
    // increase val by changeAmount up to a max of maxVal
    if (*val <= maxVal - changeAmount)
    {
      *val += changeAmount;
    }
    else
    {
      *val = maxVal;
    }
  }
}


// This function fades val by decreasing it by an amount proportional
// to its current value.  The fadeTime argument determines the
// how quickly the value fades.  The new value of val will be:
//   val = val - val*2^(-fadeTime)
// So a smaller fadeTime value leads to a quicker fade.
// If val is greater than zero, val will always be decreased by
// at least 1.
// val is a pointer to the byte to be faded.
void fade(unsigned char *val, unsigned char fadeTime)
{
  if (*val != 0)
  {
    unsigned char subAmt = *val >> fadeTime;  // val * 2^-fadeTime
    if (subAmt < 1)
      subAmt = 1;  // make sure we always decrease by at least 1
    *val -= subAmt;  // decrease value of byte pointed to by val
  }
}


// ***** PATTERN WarmWhiteShimmer *****
// This function randomly increases or decreases the brightness of the 
// even red LEDs by changeAmount, capped at maxBrightness.  The green
// and blue LED values are set proportional to the red value so that
// the LED color is warm white.  Each odd LED is set to a quarter the
// brightness of the preceding even LEDs.  The dimOnly argument
// disables the random increase option when it is true, causing
// all the LEDs to get dimmer by changeAmount; this can be used for a
// fade-out effect.
void warmWhiteShimmer(unsigned char dimOnly)
{
  const unsigned char maxBrightness = 120;  // cap on LED brighness
  const unsigned char changeAmount = 2;   // size of random walk step

  for (int i = 0; i < LED_COUNT; i += 2)
  {
    // randomly walk the brightness of every even LED
    randomWalk(&colors[i].red, maxBrightness, changeAmount, dimOnly ? 1 : 2);
    
    // warm white: red = x, green = 0.8x, blue = 0.125x
    colors[i].green = colors[i].red*4/5;  // green = 80% of red
    colors[i].blue = colors[i].red >> 3;  // blue = red/8
    
    // every odd LED gets set to a quarter the brighness of the preceding even LED
    if (i + 1 < LED_COUNT)
    {
      colors[i+1] = (rgb_color){colors[i].red >> 2, colors[i].green >> 2, colors[i].blue >> 2};
    }
  }
}


// ***** PATTERN RandomColorWalk *****
// This function randomly changes the color of every seventh LED by
// randomly increasing or decreasing the red, green, and blue components
// by changeAmount (capped at maxBrightness) or leaving them unchanged.
// The two preceding and following LEDs are set to progressively dimmer
// versions of the central color.  The initializeColors argument
// determines how the colors are initialized:
//   0: randomly walk the existing colors
//   1: set the LEDs to alternating red and green segments
//   2: set the LEDs to random colors
// When true, the dimOnly argument changes the random walk into a 100%
// chance of LEDs getting dimmer by changeAmount; this can be used for
// a fade-out effect.
void randomColorWalk(unsigned char initializeColors, unsigned char dimOnly)
{
  const unsigned char maxBrightness = 180;  // cap on LED brightness
  const unsigned char changeAmount = 3;  // size of random walk step
  
  // pick a good starting point for our pattern so the entire strip
  // is lit well (if we pick wrong, the last four LEDs could be off)
  unsigned char start;
  switch (LED_COUNT % 7)
  {
    case 0:
      start = 3;
      break;
    case 1:
      start = 0;
      break;
    case 2:
      start = 1;
      break;
    default:
      start = 2;
  }

  for (int i = start; i < LED_COUNT; i+=7)
  {
    if (initializeColors == 0)
    {
      // randomly walk existing colors of every seventh LED
      // (neighboring LEDs to these will be dimmer versions of the same color)
      randomWalk(&colors[i].red, maxBrightness, changeAmount, dimOnly ? 1 : 3);
      randomWalk(&colors[i].green, maxBrightness, changeAmount, dimOnly ? 1 : 3);
      randomWalk(&colors[i].blue, maxBrightness, changeAmount, dimOnly ? 1 : 3);
    }
    else if (initializeColors == 1)
    {
      // initialize LEDs to alternating red and green
      if (i % 2)
      {
        colors[i] = (rgb_color){maxBrightness, 0, 0};
      }
      else
      {
        colors[i] = (rgb_color){0, maxBrightness, 0};
      }
    }
    else
    {
      // initialize LEDs to a string of random colors
      colors[i] = (rgb_color){random(maxBrightness), random(maxBrightness), random(maxBrightness)};
    }
    
    // set neighboring LEDs to be progressively dimmer versions of the color we just set
    if (i >= 1)
    {
      colors[i-1] = (rgb_color){colors[i].red >> 2, colors[i].green >> 2, colors[i].blue >> 2};
    }
    if (i >= 2)
    {
      colors[i-2] = (rgb_color){colors[i].red >> 3, colors[i].green >> 3, colors[i].blue >> 3};
    }
    if (i + 1 < LED_COUNT)
    {
      colors[i+1] = colors[i-1];
    }
    if (i + 2 < LED_COUNT)
    {
      colors[i+2] = colors[i-2];
    }
  }
}


// ***** PATTERN TraditionalColors *****
// This function creates a repeating patern of traditional Christmas
// light colors: red, green, orange, blue, magenta.
// Every fourth LED is colored, and the pattern slowly moves by fading
// out the current set of lit LEDs while gradually brightening a new
// set shifted over one LED.
void traditionalColors()
{
  // loop counts to leave strip initially dark
  const unsigned char initialDarkCycles = 10;
  // loop counts it takes to go from full off to fully bright
  const unsigned char brighteningCycles = 20;
  
  if (loopCount < initialDarkCycles)  // leave strip fully off for 20 cycles
  {
    return;
  }

  // if LED_COUNT is not an exact multiple of our repeating pattern size,
  // it will not wrap around properly, so we pick the closest LED count
  // that is an exact multiple of the pattern period (20) and is not smaller
  // than the actual LED count.
  unsigned int extendedLEDCount = (((LED_COUNT-1)/20)+1)*20;

  for (int i = 0; i < extendedLEDCount; i++)
  {
    unsigned char brightness = (loopCount - initialDarkCycles)%brighteningCycles + 1;
    unsigned char cycle = (loopCount - initialDarkCycles)/brighteningCycles;

    // transform i into a moving idx space that translates one step per
    // brightening cycle and wraps around
    unsigned int idx = (i + cycle)%extendedLEDCount;
    if (idx < LED_COUNT)  // if our transformed index exists
    {
      if (i % 4 == 0)
      {
        // if this is an LED that we are coloring, set the color based
        // on the LED and the brightness based on where we are in the
        // brightening cycle
        switch ((i/4)%5)
        {
           case 0:  // red
             colors[idx].red = 200 * brightness/brighteningCycles; 
             colors[idx].green = 10 * brightness/brighteningCycles; 
             colors[idx].blue = 10 * brightness/brighteningCycles;  
             break;
           case 1:  // green
             colors[idx].red = 10 * brightness/brighteningCycles; 
             colors[idx].green = 200 * brightness/brighteningCycles;  
             colors[idx].blue = 10 * brightness/brighteningCycles; 
             break;
           case 2:  // orange
             colors[idx].red = 200 * brightness/brighteningCycles;  
             colors[idx].green = 120 * brightness/brighteningCycles; 
             colors[idx].blue = 0 * brightness/brighteningCycles; 
             break;
           case 3:  // blue
             colors[idx].red = 10 * brightness/brighteningCycles; 
             colors[idx].green = 10 * brightness/brighteningCycles; 
             colors[idx].blue = 200 * brightness/brighteningCycles; 
             break;
           case 4:  // magenta
             colors[idx].red = 200 * brightness/brighteningCycles; 
             colors[idx].green = 64 * brightness/brighteningCycles;  
             colors[idx].blue = 145 * brightness/brighteningCycles;  
             break;
        }
      }
      else
      {
        // fade the 3/4 of LEDs that we are not currently brightening
        fade(&colors[idx].red, 3);
        fade(&colors[idx].green, 3);
        fade(&colors[idx].blue, 3);
      }
    }
  }
}


// Helper function for adjusting the colors for the BrightTwinkle
// and ColorExplosion patterns.  Odd colors get brighter and even
// colors get dimmer.
void brightTwinkleColorAdjust(unsigned char *color)
{
  if (*color == 255)
  {
    // if reached max brightness, set to an even value to start fade
    *color = 254;
  }
  else if (*color % 2)
  {
    // if odd, approximately double the brightness
    // you should only use odd values that are of the form 2^n-1,
    // which then gets a new value of 2^(n+1)-1
    // using other odd values will break things
    *color = *color * 2 + 1;
  }
  else if (*color > 0)
  {
    fade(color, 4);
    if (*color % 2)
    {
      (*color)--;  // if faded color is odd, subtract one to keep it even
    }
  }
}


// Helper function for adjusting the colors for the ColorExplosion
// pattern.  Odd colors get brighter and even colors get dimmer.
// The propChance argument determines the likelihood that neighboring
// LEDs are put into the brightening stage when the central LED color
// is 31 (chance is: 1 - 1/(propChance+1)).  The neighboring LED colors
// are pointed to by leftColor and rightColor (it is not important that
// the leftColor LED actually be on the "left" in your setup).
void colorExplosionColorAdjust(unsigned char *color, unsigned char propChance,
 unsigned char *leftColor, unsigned char *rightColor)
{
  if (*color == 31 && random(propChance+1) != 0)
  {
    if (leftColor != 0 && *leftColor == 0)
    {
      *leftColor = 1;  // if left LED exists and color is zero, propagate
    }
    if (rightColor != 0 && *rightColor == 0)
    {
      *rightColor = 1;  // if right LED exists and color is zero, propagate
    }
  }
  brightTwinkleColorAdjust(color);
}


// ***** PATTERN ColorExplosion *****
// This function creates bursts of expanding, overlapping colors by
// randomly picking LEDs to brighten and then fade away.  As these LEDs
// brighten, they have a chance to trigger the same process in
// neighboring LEDs.  The color of the burst is randomly chosen from
// among red, green, blue, and white.  If a red burst meets a green
// burst, for example, the overlapping portion will be a shade of yellow
// or orange.
// When true, the noNewBursts argument changes prevents the generation
// of new bursts; this can be used for a fade-out effect.
// This function uses a very similar algorithm to the BrightTwinkle
// pattern.  The main difference is that the random twinkling LEDs of
// the BrightTwinkle pattern do not propagate to neighboring LEDs.
void colorExplosion(unsigned char noNewBursts)
{
  // adjust the colors of the first LED
  colorExplosionColorAdjust(&colors[0].red, 9, (unsigned char*)0, &colors[1].red);
  colorExplosionColorAdjust(&colors[0].green, 9, (unsigned char*)0, &colors[1].green);
  colorExplosionColorAdjust(&colors[0].blue, 9, (unsigned char*)0, &colors[1].blue);

  for (int i = 1; i < LED_COUNT - 1; i++)
  {
    // adjust the colors of second through second-to-last LEDs
    colorExplosionColorAdjust(&colors[i].red, 9, &colors[i-1].red, &colors[i+1].red);
    colorExplosionColorAdjust(&colors[i].green, 9, &colors[i-1].green, &colors[i+1].green);
    colorExplosionColorAdjust(&colors[i].blue, 9, &colors[i-1].blue, &colors[i+1].blue);
  }
  
  // adjust the colors of the last LED
  colorExplosionColorAdjust(&colors[LED_COUNT-1].red, 9, &colors[LED_COUNT-2].red, (unsigned char*)0);
  colorExplosionColorAdjust(&colors[LED_COUNT-1].green, 9, &colors[LED_COUNT-2].green, (unsigned char*)0);
  colorExplosionColorAdjust(&colors[LED_COUNT-1].blue, 9, &colors[LED_COUNT-2].blue, (unsigned char*)0);

  if (!noNewBursts)
  {
    // if we are generating new bursts, randomly pick one new LED
    // to light up
    for (int i = 0; i < 1; i++)
    {
      int j = random(LED_COUNT);  // randomly pick an LED

      switch(random(7))  // randomly pick a color
      {
        // 2/7 chance we will spawn a red burst here (if LED has no red component)
        case 0:
        case 1:
          if (colors[j].red == 0)
          {
            colors[j].red = 1;
          }
          break;
        
        // 2/7 chance we will spawn a green burst here (if LED has no green component)
        case 2:
        case 3:
          if (colors[j].green == 0)
          {
            colors[j].green = 1;
          }
          break;

        // 2/7 chance we will spawn a white burst here (if LED is all off)
        case 4:
        case 5:
          if ((colors[j].red == 0) && (colors[j].green == 0) && (colors[j].blue == 0))
          {
            colors[j] = (rgb_color){1, 1, 1};
          }
          break;
        
        // 1/7 chance we will spawn a blue burst here (if LED has no blue component)
        case 6:
          if (colors[j].blue == 0)
          {
            colors[j].blue = 1;
          }
          break;
          
        default:
          break;
      }
    }
  }
}


// ***** PATTERN Gradient *****
// This function creates a scrolling color gradient that smoothly
// transforms from red to white to green back to white back to red.
// This pattern is overlaid with waves of brightness and dimness that
// scroll at twice the speed of the color gradient.
void gradient()
{
  unsigned int j = 0;
  
  // populate colors array with full-brightness gradient colors
  // (since the array indices are a function of loopCount, the gradient
  // colors scroll over time)
  while (j < LED_COUNT)
  {
    // transition from red to green over 8 LEDs
    for (int i = 0; i < 8; i++)
    {
      if (j >= LED_COUNT){ break; }
      colors[(loopCount/2 + j + LED_COUNT)%LED_COUNT] = (rgb_color){160 - 20*i, 20*i, (160 - 20*i)*20*i/160};
      j++;
    }
    // transition from green to red over 8 LEDs
    for (int i = 0; i < 8; i++)
    {
      if (j >= LED_COUNT){ break; }
      colors[(loopCount/2 + j + LED_COUNT)%LED_COUNT] = (rgb_color){20*i, 160 - 20*i, (160 - 20*i)*20*i/160};
      j++;
    }
  }
  
  // modify the colors array to overlay the waves of dimness
  // (since the array indices are a function of loopCount, the waves
  // of dimness scroll over time)
  const unsigned char fullDarkLEDs = 10;  // number of LEDs to leave fully off
  const unsigned char fullBrightLEDs = 5;  // number of LEDs to leave fully bright
  const unsigned char cyclePeriod = 14 + fullDarkLEDs + fullBrightLEDs;
  
  // if LED_COUNT is not an exact multiple of our repeating pattern size,
  // it will not wrap around properly, so we pick the closest LED count
  // that is an exact multiple of the pattern period (cyclePeriod) and is not
  // smaller than the actual LED count.
  unsigned int extendedLEDCount = (((LED_COUNT-1)/cyclePeriod)+1)*cyclePeriod;

  j = 0;
  while (j < extendedLEDCount)
  {
    unsigned int idx;
    
    // progressively dim the LEDs
    for (int i = 1; i < 8; i++)
    {
      idx = (j + loopCount) % extendedLEDCount;
      if (j++ >= extendedLEDCount){ return; }
      if (idx >= LED_COUNT){ continue; }
  
      colors[idx].red >>= i;
      colors[idx].green >>= i;
      colors[idx].blue >>= i;      
    }
    
    // turn off these LEDs
    for (int i = 0; i < fullDarkLEDs; i++)
    {
      idx = (j + loopCount) % extendedLEDCount;
      if (j++ >= extendedLEDCount){ return; }
      if (idx >= LED_COUNT){ continue; }
  
      colors[idx].red = 0;
      colors[idx].green = 0;
      colors[idx].blue = 0;
    }
    
    // progressively bring these LEDs back
    for (int i = 0; i < 7; i++)
    {
      idx = (j + loopCount) % extendedLEDCount;
      if (j++ >= extendedLEDCount){ return; }
      if (idx >= LED_COUNT){ continue; }
  
      colors[idx].red >>= (7 - i);
      colors[idx].green >>= (7 - i);
      colors[idx].blue >>= (7 - i);      
    }
    
    // skip over these LEDs to leave them at full brightness
    j += fullBrightLEDs;
  }
}


// ***** PATTERN BrightTwinkle *****
// This function creates a sparkling/twinkling effect by randomly
// picking LEDs to brighten and then fade away.  Possible colors are:
//   white, red, green, blue, yellow, cyan, and magenta
// numColors is the number of colors to generate, and minColor
// indicates the starting point (white is 0, red is 1, ..., and
// magenta is 6), so colors generated are all of those from minColor
// to minColor+numColors-1.  For example, calling brightTwinkle(2, 2, 0)
// will produce green and blue twinkles only.
// When true, the noNewBursts argument changes prevents the generation
// of new twinkles; this can be used for a fade-out effect.
// This function uses a very similar algorithm to the ColorExplosion
// pattern.  The main difference is that the random twinkling LEDs of
// this BrightTwinkle pattern do not propagate to neighboring LEDs.
void brightTwinkle(unsigned char minColor, unsigned char numColors, unsigned char noNewBursts)
{
  // Note: the colors themselves are used to encode additional state
  // information.  If the color is one less than a power of two
  // (but not 255), the color will get approximately twice as bright.
  // If the color is even, it will fade.  The sequence goes as follows:
  // * Randomly pick an LED.
  // * Set the color(s) you want to flash to 1.
  // * It will automatically grow through 3, 7, 15, 31, 63, 127, 255.
  // * When it reaches 255, it gets set to 254, which starts the fade
  //   (the fade process always keeps the color even).
  for (int i = 0; i < LED_COUNT; i++)
  {
    brightTwinkleColorAdjust(&colors[i].red);
    brightTwinkleColorAdjust(&colors[i].green);
    brightTwinkleColorAdjust(&colors[i].blue);
  }
  
  if (!noNewBursts)
  {
    // if we are generating new twinkles, randomly pick four new LEDs
    // to light up
    for (int i = 0; i < 4; i++)
    {
      int j = random(LED_COUNT);
      if (colors[j].red == 0 && colors[j].green == 0 && colors[j].blue == 0)
      {
        // if the LED we picked is not already lit, pick a random
        // color for it and seed it so that it will start getting
        // brighter in that color
        switch (random(numColors) + minColor)
        {
          case 0:
            colors[j] = (rgb_color){1, 1, 1};  // white
            break;
          case 1:
            colors[j] = (rgb_color){1, 0, 0};  // red
            break;
          case 2:
            colors[j] = (rgb_color){0, 1, 0};  // green
            break;
          case 3:
            colors[j] = (rgb_color){0, 0, 1};  // blue
            break;
          case 4:
            colors[j] = (rgb_color){1, 1, 0};  // yellow
            break;
          case 5:
            colors[j] = (rgb_color){0, 1, 1};  // cyan
            break;
          case 6:
            colors[j] = (rgb_color){1, 0, 1};  // magenta
            break;
          default:
            colors[j] = (rgb_color){1, 1, 1};  // white
        }
      }
    }
  }
}


// ***** PATTERN Collision *****
// This function spawns streams of color from each end of the strip
// that collide, at which point the entire strip flashes bright white
// briefly and then fades.  Unlike the other patterns, this function
// maintains a lot of complicated state data and tells the main loop
// when it is done by returning 1 (a return value of 0 means it is
// still in progress).
unsigned char collision()
{
  const unsigned char maxBrightness = 180;  // max brightness for the colors
  const unsigned char numCollisions = 5;  // # of collisions before pattern ends
  static unsigned char state = 0;  // pattern state
  static unsigned int count = 0;  // counter used by pattern
  
  if (loopCount == 0)
  {
    state = 0;
  }
  
  if (state % 3 == 0)
  {
    // initialization state
    switch (state/3)
    {
      case 0:  // first collision: red streams
        colors[0] = (rgb_color){maxBrightness, 0, 0};
        break;
      case 1:  // second collision: green streams
        colors[0] = (rgb_color){0, maxBrightness, 0};
        break;
      case 2:  // third collision: blue streams
        colors[0] = (rgb_color){0, 0, maxBrightness};
        break;
      case 3:  // fourth collision: warm white streams
        colors[0] = (rgb_color){maxBrightness, maxBrightness*4/5, maxBrightness>>3};
        break;
      default:  // fifth collision and beyond: random-color streams
        colors[0] = (rgb_color){random(maxBrightness), random(maxBrightness), random(maxBrightness)};
    }
    
    // stream is led by two full-white LEDs
    colors[1] = colors[2] = (rgb_color){255, 255, 255};
    // make other side of the strip a mirror image of this side
    colors[LED_COUNT - 1] = colors[0];
    colors[LED_COUNT - 2] = colors[1];
    colors[LED_COUNT - 3] = colors[2];
    
    state++;  // advance to next state
    count = 8;  // pick the first value of count that results in a startIdx of 1 (see below)
    return 0;
  }
  
  if (state % 3 == 1)
  {
    // stream-generation state; streams accelerate towards each other
    unsigned int startIdx = count*(count + 1) >> 6;
    unsigned int stopIdx = startIdx + (count >> 5);
    count++;
    if (startIdx < (LED_COUNT + 1)/2)
    {
      // if streams have not crossed the half-way point, keep them growing
      for (int i = 0; i < startIdx-1; i++)
      {
        // start fading previously generated parts of the stream
        fade(&colors[i].red, 5);
        fade(&colors[i].green, 5);
        fade(&colors[i].blue, 5);
        fade(&colors[LED_COUNT - i - 1].red, 5);
        fade(&colors[LED_COUNT - i - 1].green, 5);
        fade(&colors[LED_COUNT - i - 1].blue, 5);
      }
      for (int i = startIdx; i <= stopIdx; i++)
      {
        // generate new parts of the stream
        if (i >= (LED_COUNT + 1) / 2)
        {
          // anything past the halfway point is white
          colors[i] = (rgb_color){255, 255, 255};
        }
        else
        {
          colors[i] = colors[i-1];
        }
        // make other side of the strip a mirror image of this side
        colors[LED_COUNT - i - 1] = colors[i];
      }
      // stream is led by two full-white LEDs
      colors[stopIdx + 1] = colors[stopIdx + 2] = (rgb_color){255, 255, 255};
      // make other side of the strip a mirror image of this side
      colors[LED_COUNT - stopIdx - 2] = colors[stopIdx + 1];
      colors[LED_COUNT - stopIdx - 3] = colors[stopIdx + 2];
    }
    else
    {
      // streams have crossed the half-way point of the strip;
      // flash the entire strip full-brightness white (ignores maxBrightness limits)
      for (int i = 0; i < LED_COUNT; i++)
      {
        colors[i] = (rgb_color){255, 255, 255};
      }
      state++;  // advance to next state
    }
    return 0;
  }
  
  if (state % 3 == 2)
  {
    // fade state
    if (colors[0].red == 0 && colors[0].green == 0 && colors[0].blue == 0)
    {
      // if first LED is fully off, advance to next state
      state++;
      
      // after numCollisions collisions, this pattern is done
      return state == 3*numCollisions;
    }
    
    // fade the LEDs at different rates based on the state
    for (int i = 0; i < LED_COUNT; i++)
    {
      switch (state/3)
      {
        case 0:  // fade through green
          fade(&colors[i].red, 3);
          fade(&colors[i].green, 4);
          fade(&colors[i].blue, 2);
          break;
        case 1:  // fade through red
          fade(&colors[i].red, 4);
          fade(&colors[i].green, 3);
          fade(&colors[i].blue, 2);
          break;
        case 2:  // fade through yellow
          fade(&colors[i].red, 4);
          fade(&colors[i].green, 4);
          fade(&colors[i].blue, 3);
          break;
        case 3:  // fade through blue
          fade(&colors[i].red, 3);
          fade(&colors[i].green, 2);
          fade(&colors[i].blue, 4);
          break;
        default:  // stay white through entire fade
          fade(&colors[i].red, 4);
          fade(&colors[i].green, 4);
          fade(&colors[i].blue, 4);
      }
    }
  }
  
  return 0;
}

 

Copyright 2013, 2014 SPCoast / John Plocher, released under the MIT License