Sparkling Belt

The belt shown above is actually worn around the waist, inside the belt loops of a pair of pants, but for the video I chose to display it on the floor, where the patterns can be more easily seen.

It is made from a one-meter-long strip of 144 NeoPixel LEDs, connected to an Arduino Pro Mini, and a 1200 milliampere-hour lithium polymer battery.

The strip comes with connectors at each end. The female connector is where the data goes in. The male connector at the other end is so that a string of these can be linked together. Since we are not going to be linking this up to any more of them, we cut off the male connector, and solder it to the Arduino, so that the computer can be snapped into place. Our other option would be to cut off both connectors, and solder the Arduino to the wires that used to connect to the female connector.

The white wire is soldered to the Arduino Pro Mini's ground. I would normally have said "ground pin", but in this case we are not soldering any pins into the holes in the circuit board.

The red wire is connected to the VCC hole. This is where the 3.7 volt power from the battery will enter the computer.

The green wire is the data wire, and it is soldered to the A3 hole. That is all the soldering we need to do on the computer board.

The strip of LEDs conveniently also has an extra pair of power and ground wires already attached. This allows us to simply tin the ends of the wires with our soldering iron to stiffen them, and then plug the wire ends directly into the battery connector. We do this for testing, but we will want to solder on a more professional battery connector later. Since the power and ground wires are connected to the same color wires that go into the Arduino, connecting the battery powers both the LED strip and the Arduino at the same time.

At this point, we need to program the Arduino. Normally, we would solder a row of header pins to the row of holes shown at the bottom of the photo, and use an FTDI USB-to-Serial board with female connector to plug into that row of header pins. But we have a trick that avoids that step. We use an FTDI board with male pins, and push those male pins into the holes in the Arduino board. By holding the pins in place by hand, we can program the Arduino, and then remove the FTDI board when we're done. This leaves a cleaner, smaller Arduino board, without pins that can get caught in the fabric pocket we will later slip it into.

If you have an FTDI board that has a female connector, you just use the header pins that we would have soldered to the Arduino, but without the soldering. We just insert them into the FTDI board and we now have a male FTDI board.

The program to make pretty patterns in the lights is a little long (the whole thing is here), so I will break it down into pieces here.

To start with, we need to include the header for the Adafruit NeoPixel library:

#include <Adafruit_NeoPixel.h>

Next, we define some constants using the enum keyword. Defining the constants this way is much better than using the preprocessor and #define statements, because it allows the compiler to do better type checking. It is better than using a const int, since no extra storage is used.

enum { PIN = A3, NUMPIXELS = 144, FULLY_SATURATED = 255 };

We define the PIN constant to match where we soldered the NeoPixel data input (the hole marked A3 on the Arduino). We define the number of pixels in our string. In this case we have 144 LEDs in our one meter long string. Other densities are available (for less money) that have 60 LEDs per meter, or 30.

Next, we define two structures RgbColor and HsvColor. Working with colors in the Reg, Green, Blue color space is inconvenient, but that is how the LEDs are constructed (each of the 144 lights consist of a red, a green, and a blue LED, along with a little computer chip to control them). We would rather work in a more convenient color space, such as the Hue, Saturation, and Brightness (HSV) space. In HSV we can talk about which color we want to end up with, rather than which colors it is made up of. We can control the brightness or the saturation of the color without changing the hue, or vice-versa.

Thus, the next part of the program is a routine that can convert from the HSV color space into the RGB color space:

RgbColor HsvToRgb( HsvColor hsv ) { ... }

I won't go into detail here on how the conversion is performed, since that is not the point of this project. You can read the code, but the best way to understand it is to read any of the excellent discussions of HSV to RGB conversion available elsewhere on the Internet.

The setup() routine looks like this:

Adafruit_NeoPixel pixels = Adafruit_NeoPixel( NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800 );

void
setup( void )
{
  pixels.begin();
}

We define the object pixels to set up the library. The NeoPixel constructor needs to know how many pixels in our string, and which pin we use to talk to them, as well as whether their controller chip expects the data to be Green, then Red, then Blue, or Red, then Green, then Blue, and how fast to send the data (our string uses GBR and 800 kiloHertz). We call pixel.begin() to initialize the NeoPixel library.

Next, we define our first pattern routine, the train. The train lights up 10 LEDs in a row, all the same color. Every time it is called, it moves the train along by one position, and increments the color, thus gradually changing the color of the train from red (hue 0) through orange, yellow, green, blue, and violet (hue 255) before wrapping back around to red. We use the method setPixelColor() to tell each LED what color to become. Only when we have instructed each LED in the whole sting (all 144 of them), do we send the command to display the new colors, using the method show().

unsigned char hue = 0;
int train_length = 10;
int train_position = 0;

void
train( void )
{
  for( int i = 0; i < NUMPIXELS; i++ )
  {
    int bright = 0;
    if( i > train_position && i < train_position + train_length )
      bright = 255;
    int low = (train_position + train_length) - NUMPIXELS;
    if( i < low )
      bright = 255;
    HsvColor hsv( hue, FULLY_SATURATED, bright );
    RgbColor rgb = HsvToRgb( hsv );
    pixels.setPixelColor( i, pixels.Color( rgb.r, rgb.g, rgb.b ) );
  }
  pixels.show();
  hue++;
  train_position++;
  train_position %= (NUMPIXELS + train_length);
}

The next pattern routine is collide(). It is very similar to train, but it starts two trains running, one at one end of the string, and another at the other end. The two trains collide in the middle, and pass right through one another.

The simplest patter routine is called chaser(). It simply turns on every 20th LED, moving the position over by one every time it is called. It makes some dots that quickly travel around the belt in circles.

The last pattern routine is sparkle(). It picks five LEDs at random to turn on each time it is called. Because it is usually called quite often, it looks to the eye as if there were many more than just five LEDs lit. This effect of human vision is called persistence of vision.

unsigned char stars[5];

void
sparkle( void )
{
  for( unsigned x = 0; x < sizeof stars; x++ )
    stars[x] = random( NUMPIXELS );
  for( int i = 0; i < NUMPIXELS; i++ )
  {
    int bright = 0;
    for( unsigned x = 0; x < sizeof stars; x++ )
      if( stars[x] == i )
        bright = 255;
    hue = random( 255 );
    HsvColor hsv( hue, FULLY_SATURATED, bright );
    RgbColor rgb = HsvToRgb( hsv );
    pixels.setPixelColor( i, pixels.Color( rgb.r, rgb.g, rgb.b ) );
  }
  pixels.show();
}

Finally, we get to the loop() routine, It calls the pattern routines in random order. Half of the time it waits for 50 milliseconds before calling the pattern routine again, so the pattern moves more slowly.

enum
{
  CHASER,
  TRAIN,
  COLLIDE,
  SPARKLE,
  SLOWTRAIN,
  SLOWCHASER,
  SLOWCOLLIDE,
  SLOWSPARKLE,
  THE_END
  };

unsigned count = 0, mode = CHASER;

void
loop( void )
{
  if( mode == CHASER )
    chaser();
  else if( mode == TRAIN )
    train();
  else if( mode == SPARKLE )
    sparkle();
  else if( mode == COLLIDE )
    collide();
  else if( mode == SLOWCHASER )
  {
    chaser();
    count += 9;
    delay( 50 );
  }
  else if( mode == SLOWTRAIN )
  {
    train();
    count += 9;
    delay( 50 );
  }
  else if( mode == SLOWSPARKLE )
  {
    sparkle();
    count += 9;
    delay( 50 );
  }
  else if( mode == SLOWCOLLIDE )
  {
    collide();
    count += 9;
    delay( 50 );
  }
  if( count++ > 1000 )
  {
    mode  = random( THE_END );
    count = 0;
  }
}