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. It is a very simple project and is is quickly done. Either build the decoration for yourself or create the perfect gift for your family or friends.

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

Optional Parts

  • Insulating Tape
  • Stripe Boards
  • Crimp Connectors
  • Crimp Housings
  • Connectors

Create the Spheres

To create the spheres, 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 each a hole at the end of the stripes. You can use a special tool as shown, or just a common office hole puncher. Make sure you have eyelets of the right size.

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-stripe-positions

The result should look like this:

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 Electronics

Make the Shield and Distributor

I start by creating a shield for the Arduino Uno. First I plug pin headers into the connectors of the Arduino board, put the stripe board on top and solder all headers to the board. Next I cut all stripes from the middle of the board and solder a four pin connector to the top side of the stripe board. Now I connect the VIN, GND and pin 7 with the four pin connector, using VIN as leftmost pin, GND as second pin and the signal from pin 7 of the Arduino ad third pin of the connector. The fourth pin is not connected, this prevents any damage if the connector is plugged in upside down.

xmas-1

To prevent any accidents, I protect the shield using insulating tape.

xmas-2

Next I solder the distributor. I use a small stripe board with four connectors on it. One connector is the input from the Arduino shield, the other three connectors go to the spheres. For the input I use the same connection schema as on the Arduino shield to allow any direction for the cable. For the sphere connectors I use, from left to right, VCC, Input, Output, GND.

xmas-4

Again, I protect the bottom and edges of the board using insulation tape.

xmas-3

Now it’s time to create the cable. I use crimps and housings from Molex to create the connectors. Its actually very simple: You solder the end of the wires to the crimps and push them into the housing. They snap in the housing with a click and can not be removed anymore. Normally the crimps would be connected mechanically, using a special tool to press the wings of the crimp to the wire. Soldering is fast and easy and does not need any special tool.

I use insulation tape to make sure there is no place for shortcuts.

xmas-5

Next a really important part: Testing. I test each board and check if each connection works and also test if any pins which should not be connected are not. This is very important and I actually found a connection which should not be there, because I forgot to cut a stripe.

xmas-6

Connect the NeoPixel

As wire I use a special shielded signal cable with three wires inside (Sensocord-M/D-UL from kabeltronik). I prepare the end of the wire by cutting the insulation away and drill the shield into a wire. I put back a piece of the removed outer insulation to the shield wire.

xmas-7

I use the flora RGB smart Neo Pixel, which has a round board because it is especially made to sew it on textile.

xmas-8

I start with the ground wire and solder it to the NeoPixel.

xmas-9

Next I fasten the other wires and solder them to the board.

xmas-10

It should look like this.

xmas-11

And again, add insulation tape.

xmas-12

Now I put a spacer behind the pixel for two reasons: First to add some weight to the sphere and second to control the direction of the NeoPixel. It should point straight down.

xmas-13

I pull the wire through the eyelet of the sphere.

xmas-14

Make sure the NeoPixel points straight down.

xmas-15

Now I add insulation tape to the wire on top to fasten the wire and the NeoPixel in the sphere.

xmas-16

Next I create the connector. First soldering crimps to all wires, put them into the housing and add insulation tape.

xmas-17

xmas-18

xmas-19

Yay! A first test if everything works so far.

xmas-20

Perfect!

xmas-21

Now I repeat this for two additional spheres.

Software

You find the documented source code on GitHub: https://github.com/LuckyResistor/XmasLights

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;
};

This class has a constructor which takes only one float. It creates a color using a color from the “color wheel”. The value has to be from 0.0 to 1.0, where 0.0 is red, 0.33 green and 0.66 blue. The interesting thing about selecting a color from the “color wheel” is that you can easily select matching or contrasting colors.

Next 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 very 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.

In the setup() method I first initialize the library for the NeoPixels, then use the input from unconnected analog pins to initialize the random number generator. If you omit this step, you will always get the same “random” numbers after each start.

In the loop I call getRandomColor() to get a new set of random colors. Then I blend the current colors slowly into the new colors. This takes about 20 seconds. After this I simple wait for two minutes. Note I use the delay function in a loop. There is a problem if you try to use the delay function with really large values. Sometimes the delay will never end — this is probably an issue in the Arduino library.

The getRandomColor() function first creates a random number to decide the color setup. One case is more or less white, but the other cases create colors with a given “shift”. This is a value which is added or subtracted from the original color value. For example getHueShift(c1, c2, c3, 0.0, 0.0) will generate three times the same color, but getHueShift(c1, c2, c3, 0.5, 0.0) will make the middle sphere to have the “opposite” color in the color wheel, which is the contrast color (e.g. red and green).

The getHueShift() first select a initial random color, then applies the shift and assign then to the variables c1, c2 and c3.

This code tries to generate random colors, but in a beautiful combination. If you test the code, you will see that you always get nice color combinations which have a harmonic look.

Conclusion

Creating color changing spheres is simple and does not require much work. But the created light patterns on the wall and in the room are really beautiful and look really professional.