Update: Read the full project description here.

The days are getting shorter. You do not have to fear the dark, this is the right time to create a fancy color changing Xmas decoration. This is a very simple project and is quickly done.

Required Parts

  • “Silver” laminated paper and/or half transparent paper.
  • NeoPixels from Adafruit (Flora RGB smart Neo Pixel version 2).
  • Arduino Uno
  • AC/DC power supply 9V, >500mA
  • Thin multi conductor cable with 4 wires, or 3 wires plus shield.
  • Eyelets and screws with nuts

Create the Spheres

To create the spheres, first cut a number of stripes with equal length and width. I personally used stripes 35cm long and 1.5cm wide.

xmas-stripes

Stack 8 stripes and put a hole in the middle of the stripes and two holes at the end of the stripes.

xmas-make-hole

 

Next I put an eyelet into the middle and assemble all stripes using the eyelet.

xmas-make-eye

Next I rotate the stripes until all are evenly distributed and have the same angle. Then I bend them up, starting with the top one and use another eyelet to connect the stripes using the holes at the sides of the stripes.

xmas-final-sphere

In the case the eyelet is too short, I simple use a screw with a nut to fasten the stripes.

xmas-screw

The Light

I use the wire to connect all NeoPixels. They need power and have one input and one output line. I solder the NeoPixels to the wire, connect the input and output lines.

The COntroller

As controller I use an Arduino Uno and connect the power of the NeoPixels to the VIN and GND pins and the input of the first NeoPixel to I/O pin seven.

Software

I first created a class Color for all color calculations.

#pragma once

#include <Arduino.h>

#include <Adafruit_NeoPixel.h>

class Color
{
public:
  Color()
  : _r(0.0f), _g(0.0f), _b(0.0f)
  {
  } 

  Color(float r, float g, float b)
  : _r(r), _g(g), _b(b)
  {
    checkLimits();
  }
  
  Color(float h)  // hue
  {
    const int p = h*3.0f;
    const float m = (h*3.0f)-((float)(p));
    switch (p) {
      case 0:
        _r = 1.0f-m;
        _g = m;
        _b = 0.0f;
        break;
      case 1:
        _r = 0.0f;
        _g = 1.0f-m;
        _b = m;
        break;
      case 2:
        _r = m;
        _g = 0.0f;
        _b = 1.0f-m;
        break;
    }
    checkLimits();
  }
  
  Color blendWith(const Color &other, float factor)
  {
    return Color(
      ((_r * (1.0f-factor)) + (other._r * factor)),
      ((_g * (1.0f-factor)) + (other._g * factor)),
      ((_b * (1.0f-factor)) + (other._b * factor)));
  }
  
  uint32_t getColor() const 
  {
    const uint8_t r = (uint8_t)(255.0f * _r);
    const uint8_t g = (uint8_t)(255.0f * _g);
    const uint8_t b = (uint8_t)(255.0f * _b);
    return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b;
  }
 
private:
  void checkLimits() {
    if (_r < 0.0f) _r = 0.0f;
    if (_r > 1.0f) _r = 1.0f;
    if (_g < 0.0f) _g = 0.0f;
    if (_g > 1.0f) _g = 1.0f;
    if (_b < 0.0f) _b = 0.0f;
    if (_b > 1.0f) _b = 1.0f;    
  }
  
private:
  float _r;
  float _g;
  float _b;
};

Then I created a very simple controller code which changes the color very smoothly every two minutes.

#include <Adafruit_NeoPixel.h>
#include "Color.h"

Adafruit_NeoPixel strip = Adafruit_NeoPixel(3, 7, NEO_GRB + NEO_KHZ800);


void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  randomSeed(analogRead(0)|
    (((uint32_t)analogRead(1))<<8)|
    (((uint32_t)analogRead(2))<<16));
  pinMode(13, OUTPUT);
}

Color c1;
Color c2;
Color c3;

void loop() {
  Color new1;
  Color new2;
  Color new3;
  getRandomColor(&new1, &new2, &new3);
  
  // blend the colors
  for (float i = 0.0f; i < 1.0f; i += 0.001) {
    strip.setPixelColor(0, c1.blendWith(new1, i).getColor());
    strip.setPixelColor(1, c2.blendWith(new2, i).getColor());
    strip.setPixelColor(2, c3.blendWith(new3, i).getColor());
    strip.show();
    delay(20);
  }

  // this are the new colors
  c1 = new1;
  c2 = new2;
  c3 = new3;
  strip.setPixelColor(0, c1.getColor());
  strip.setPixelColor(1, c2.getColor());
  strip.setPixelColor(2, c3.getColor());
  strip.show();
  
  // Show the colors for 5 minutes
  for (int i = 0; i < 120; ++i) {
    delay(1000);
    digitalWrite(13, ((i&1)==0)?HIGH:LOW);
  }
}

uint8_t getHueShift(Color *c1, Color *c2, Color *c3, float s1, float s2)
{
    float h1 = (float)(random(1024))/1024.0f;
    float h2 = h1 + s1;
    float h3 = h1 + s2;
    while (h2 > 1.0) h2 -= 1.0;
    while (h3 > 1.0) h3 -= 1.0;
    while (h2 < 0.0) h2 += 1.0;
    while (h3 < 0.0) h3 += 1.0;
    *c1 = Color(h1);
    *c2 = Color(h2);
    *c3 = Color(h3);
}

void getRandomColor(Color *c1, Color *c2, Color *c3)
{
  uint8_t mode = random(12);
  switch (mode) {
    case 0:
    default:
      *c1 = Color(1.0, 1.0, 1.0);
      *c2 = Color(0.8, 0.8, 0.8);
      *c3 = Color(0.6, 0.6, 0.6);
      break;
    case 1:
      getHueShift(c1, c2, c3, 0.0, 0.0);
      break;
    case 2:
      getHueShift(c1, c2, c3, 0.333, 0.666);
      break;
    case 3:
      getHueShift(c1, c2, c3, 0.5, 0.0);
      break;
    case 4:
      getHueShift(c1, c2, c3, 0.2, 0.4);
      break;
    case 5:
      getHueShift(c1, c2, c3, 0.1, 0.2);
      break;
    case 6:
      getHueShift(c1, c2, c3, -0.2, -0.4);
      break;
    case 7:
      getHueShift(c1, c2, c3, -0.1, -0.2);
      break;
    case 8:
      getHueShift(c1, c2, c3, 0.2, 0.0);
      break;
    case 9:
      getHueShift(c1, c2, c3, 0.4, 0.0);
      break;
    case 10:
      getHueShift(c1, c2, c3, -0.2, 0.0);
      break;
    case 11:
      getHueShift(c1, c2, c3, -0.4, 0.0);
      break;
  }
}

Note I am using float for the calculations. Usually this is a bad idea, but the controller is most of the time idle and has plenty of RAM left, so there is no big impact because of this.