Deviant pixels

Submitted by oliver on Sun, 09/02/2018 - 22:30

Dark winter evenings are so much nicer with some colourful light effects! The idea behind this one is that a bunch of neopixels randomly change colour in unison, but not exactly: each one deviates slightly from a given, randomly chosen "master" colour. Here is how (with only two neopixels, for simplicity):

Example timing diagram

Some points on the time axis explained:

  • At the start, all neopixels have the same colour, or hue angle (current = 0). set_target runs for the first time in the setup function. It sets a master target colour and an individual target colour that each neopixel will have reached at point A. The target colour for pixel 1 is slightly above, and the target colour for pixel 2 is way below the master target colour. The upper and lower deviation limit is set by, well, deviation_limit.
  • Between start and point A, pixel 2 reaches its target before pixel 1. It sits there until pixel 1 also reaches its target.
  • Point A: go_target returns true which triggers set_target to run (for the second time).
  • Between point A and point B: this time, pixel 1 reaches its target before pixel 2.
  • Point B: go_target returns true again and set_target runs for the third time. This time, the deviation of both pixels is negative.
  • Point D (now): pixel 2 has already reached its target, pixel 1 is still on its way...

Notice that the length of the intervals varies depending on the distribution of previous and next target colours.

Code

/**
 * Deviant pixels
 * neopixels randomly changing colour, not quite in unison
 * 23.12.2014 - 02.09.2018 - oliver walkhoff
 */


/* debugging */
// #define DEBUG // uncomment this line for debugging

/* libraries */
#include <Adafruit_NeoPixel.h>

/* hardware */
int neo_pin = 3; // digital pin for neopixel strip
const byte neo_pixels = 8; // number of neopixels on strip

/* behaviour */
int delay_ms = 30; // try 20 ... 50
int deviation_limit = 80; // try 20 ... 80

/* global variables */
int master; // master target colour
int target[neo_pixels]; // target colour for each pixel
int current[neo_pixels]; // current colour of each pixel

/* initialise neopixel strip */
Adafruit_NeoPixel strip = Adafruit_NeoPixel(neo_pixels, neo_pin, NEO_GRB + NEO_KHZ800);


void setup() {
  // all pixels 'off'
  strip.begin();
  strip.show();
  
  // set initial current colour of each pixel
  for (int i = 0; i < neo_pixels; i++) {
    current[i] = 0;
  }

  // set initial target colour of each pixel
  set_target();

  // serial
  #ifdef DEBUG
    Serial.begin(9600);
  #endif
}


void loop() {
  if (go_target()) {
    set_target();
  }

  strip.show();
  delay(delay_ms);
}


// approach target colour
bool go_target() {
  bool ready = true;

  for (int i = 0; i < neo_pixels; i++) {
    strip.setPixelColor(i, wheel((current[i]) & 255));

    if (target[i] > current[i]) {
      current[i] += 1;
      ready = false;
    } else if (target[i] < current[i]) {
      current[i] -= 1;
      ready = false;
    }
  }

  return ready; // true if all pixels have reached their target colour
}


// set new target colour
void set_target() {
  int deviation;

  master = random(255);

  #ifdef DEBUG
    Serial.print("master: ");
    Serial.println(master);
  #endif

  for (int i = 0; i < neo_pixels; i++) {
    deviation = random(-1 * deviation_limit, deviation_limit);
    target[i] = master + deviation;

    #ifdef DEBUG
      Serial.print("deviation: ");
      Serial.print(deviation);
      Serial.print(" target: ");
      Serial.println(target[i]);
    #endif
  }
}


// from Adafruit:
// input a value 0 to 255 to get a color value.
// the colours are a transition r - g - b - back to r.
uint32_t wheel(byte wheel_pos) {
  if (wheel_pos < 85) {
   return strip.Color(wheel_pos * 3, 255 - wheel_pos * 3, 0);
  } else if (wheel_pos < 170) {
   wheel_pos -= 85;
   return strip.Color(255 - wheel_pos * 3, 0, wheel_pos * 3);
  } else {
   wheel_pos -= 170;
   return strip.Color(0, wheel_pos * 3, 255 - wheel_pos * 3);
  }
}
Five deviant pixels

Five deviant pixels in a box, behind a milky acrylic plate.