Pages

31 October 2015

FastLED series: back and forth

This is a continuation from the last post on FastLED. We got as far as moving a "dot" back and forth on the strip… what could we do this time? How about adding a "trail" to the dot, a dimming section LEDs to give our dot some character.

I'll just walk through various ways I played with doing this, as I learned some nice things. Intuitively, I started by thinking of modifying the code at the end of the last post:

void loop() 
{

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[dot] = CRGB::Blue;
    FastLED.show();
    
    // clear this led for the next time around the loop
    leds[dot] = CRGB::Black;
    
    delay(30);
  }

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[NUM_LEDS - dot] = CRGB::Blue;
    FastLED.show();
    
    // clear this led for the next time around the loop
    leds[NUM_LEDS - dot] = CRGB::Black;
    
    delay(30);
  }
}

If we wanted the same dot, but with a trail, what are we saying? Let's look at what the array will hold, imagining that the moving dot has a brightness of 3, and the "trail" decreases in brightness behind it. Our time sequence might look like this:
|---+---+---+---+---+---+---+---+---|
| 3 |   |   |   |   |   |   |   |   |
|---+---+---+---+---+---+---+---+---|

|---+---+---+---+---+---+---+---+---|
| 2 | 3 |   |   |   |   |   |   |   |
|---+---+---+---+---+---+---+---+---|

|---+---+---+---+---+---+---+---+---|
| 1 | 2 | 3 |   |   |   |   |   |   |
|---+---+---+---+---+---+---+---+---|

|---+---+---+---+---+---+---+---+---|
|   | 1 | 2 | 3 |   |   |   |   |   |
|---+---+---+---+---+---+---+---+---|

|---+---+---+---+---+---+---+---+---|
|   |   | 1 | 2 | 3 |   |   |   |   |
|---+---+---+---+---+---+---+---+---|

|---+---+---+---+---+---+---+---+---|
|  |   |   | 1 | 2 | 3 |   |   |   |
|---+---+---+---+---+---+---+---+---|

We need some way to identify our initial dot location, set that dot to full bright, and in the next frame, decrease its brightness by some amount while setting the next pixel to full on, and so on. Intuitively, I started by thinking of something like this:

int lead_dot = 0;

void loop() 
{

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[dot].b = leds[dot].b - 80;
    if(leds[dot].b < 0) { leds[dot].b = 0; }
  
  }
  
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + 1;
  if(lead_dot == NUM_LEDS) { lead_dot = 0; }

  FastLED.show();

  delay(50);

}

That might seem reasonable, or at least it did to me, but it doesn't work at all! I used a Serial.print() statement to look at the value of leds[dot].b in the for loop to see what was going on, and noticed that the values for .b never go negative! So my check for a negative value never resulted in a poitive hit and the leds just stayed blue with a little bit of flickering. It was a good lesson learned; if you wanted to know if you were subtracting too much, the time to check is before you do the subtraction; otherwise the value will roll over. Here was my updated method after figuring that out:

int lead_dot = 0;

void loop() 
{
  for(int dot = 0; dot < NUM_LEDS; dot++)
  {
    if(leds[dot].b > 80) { leds[dot].b = leds[dot].b - 80; }
    
    else { leds[dot].b = 0; }
  
  }
  
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + 1;
  if(lead_dot == NUM_LEDS) { lead_dot = 0; }
  
  FastLED.show();

  delay(50);

}

That works pretty slick! As we go down the line, we simply subtract 80 (~1/3) of the full bright value for the blue channel if we know we'll get a desired result, otherwise we just set it to 0. Then we set the leading pixel full blue, set it to use the next pixel on the following loop, make sure we roll back to 0 at the end of the strip, and push the data.

What if you wanted to make the tail longer or shorter? You'd change the 80 value above to subtract to something else, taking more/less steps to reach 0. One function I stumbled on in the FastLED wiki was fadeToBlackBy(). That sounds perfect. You just pass it a value in the form of n/256 and it fades the brightness by that amount. We can use this method, now:

int lead_dot = 0;

void loop() 
{
  
  for(int dot = 0; dot < NUM_LEDS; dot++)
  {
    leds[dot].fadeToBlackBy(64);
  }
  
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + 1;
  if(lead_dot == NUM_LEDS) { lead_dot = 0; }
  
  FastLED.show();

  delay(50);

}

Now we don't have to do any math in the for loop to make sure we're actually dimming the trailing pixels vs. rolling over. When fiddling with the first code, I wasn't checking to see if lead_dot had reached the end of the strip (I didn't have the if() statement shown above). I got really weird results and posted to the FastLED Google+ community for help. I got some great tips from Andrew Tuline, which I started playing with. Andrew's comment was:
I hate counting up and counting down a strip in order to move pixels. I also hate that about as much as using delays (especially when I've got button polling routines to run).
He gave a modified example using the functions beatsin8() and EVERY_N_MILLISECONDS, so I looked into those. EVERY_N_MILLISECONDS is a way to run something every nth millisecond without using the delay() function. The effect is the same, but it uses a check to the clock (elapsed milliseconds) to determine if it should run the code or not. This is different than delay which literally instructs the processor to sit there idly for n milliseconds. This is important if you're reading a sensor in your project. An Arduino won't be able to read the sensor during delay() phases, so your sampling rate will go way down. Encoders are a good example of where you can't deal with delays since you have to constantly check the pin signals.

In any case, you basically just wrap whatever you want to run periodically inside of EVERY_N_MILLISECONS(N) { ... } to accomplish the same as delay(n):


EVERY_N_MILLISECONDS(50)
{

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
  
    leds[dot].fadeToBlackBy(64);
    
  }
  
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + 1;
  if(lead_dot == NUM_LEDS) { lead_dot = 0; }
  
  FastLED.show();

}

Andrew also posted code using a different form of fadeToBlackBy, namely fadeToBlackBy(leds, NUM_LEDS, amount_to_fade). Interesting! So the function can act on an entire array and on n LEDS, fading by some value. Now we don't even need a for loop at all:

EVERY_N_MILLISECONDS(50)
{

  fadeToBlackBy(leds, NUM_LEDS, 64);
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + 1;
  if(lead_dot == NUM_LEDS) { lead_dot = 0; }
  
  FastLED.show();

}

What if we want the dot to go back and forth? I though of moving to an increment that could go positive or negative once we reached the end of the strip, like this:

int delta = 1;

...

void loop() {

EVERY_N_MILLISECONDS(50)
{

  fadeToBlackBy(leds, NUM_LEDS, 64);
  leds[lead_dot].b = 255;
  
  lead_dot = lead_dot + delta;
  if(lead_dot == NUM_LEDS | lead_dot == 0) { delta = -delta; }
  
  FastLED.show();

}

}

That works, though Andrew's comments pointed me in another direction. He used a function called beatsin8(), so I had a look at that in the FastLED 3.1 docs and found this definition:
LIB8STATIC uint8_t beatsin8 ( accum88  beats_per_minute,
uint8_t  lowest = 0,
uint8_t  highest = 255,
uint32_t  timebase = 0,
uint8_t  phase_offset = 0 
)  

beatsin8 generates an 8-bit sine wave at a given BPM, that oscillates within a
given range.

So, sounds like we provide a beats per minute, a range for the output values, and it returns the proper number to oscillate between that range at the desired rate. Let's have a go!

int bpm = 40;

...

void loop()
{

  leds[lead_dot].b = 255;
  lead_dot = beatsin8(bpm, 0, NUM_LEDS);
  FastLED.show();
  
  EVERY_N_MILLISECONDS(20)
  {

    fadeToBlackBy(leds, NUM_LEDS, 64);

  }

}

The speed is definitely not the same (but could easily be tweaked by changing bpm), but the effect is perfect! Can we see what's going on? How about we collect the value of lead_dot every millisecond to see how it's achieving a sin wave with a frequency of 1/40 sec (40 BPM was passed to the function). Doing that yielded this plot:



So as the loop runs, it's simply holding lead_dot for different amounts of time before incrementing/decrementing in order to approximate a sin wave. Pretty nifty! How about our fading? I tried to collect data on all of the LEDs at each update, but it appears that the Serial.print() command might be affecting the timing of the operations. The lights were very spread out and "glitchy." From asking on the Google+ community, apparently printing takes a lot of time. I just resolved to collect every 5 milliseconds and deal with the fact that the lights wouldn't actually work but hoped the data would show reasonably well what's going on:


void loop()
{

  lead_dot = beatsin8(bpm, 0, NUM_LEDS);
  leds[lead_dot].b = 255;

  EVERY_N_MILLISECONDS(5)
  {

    fadeToBlackBy(leds, NUM_LEDS, 64);  
    FastLED.show();

    int now = millis();

    for(int i = 0; i < NUM_LEDS; i++)
    {

      Serial.print(i);
      Serial.print(",");
      Serial.print(leds[i].b);
      Serial.print(",");
      Serial.println(now);

    }

  }

}

And here's what you get (I narrowed it down to just the first pixels):


We can see each pixel spike to 255 and then dim down. Milliseconds later, the pixel that peaked is much lower and the subsequent one is being set to 255. Let's zoom in on just one peak:



I suspect I'm not capturing the data fast enough, or it might be how beatsin8 works, but some of the LEDs don't seem to appear on both sides (they should light up as lead_led returns from the far end, passes through them to the first, and then through them again. It's also possible that based on my ampling rate, somehow the colors have identical values and the lines are simply on top of each other. In any case, I thought it was pretty cool to be able to see the overlapping peaks/trailing values.
To make it more representative of a strand of LEDs, I arranged the data into a tile plot. Think of each column as the LED strip at a given time. Moving from left to right, we can watch the state of the set brightness:


Again, I think there's something wrong with the timing and what printing is doing since the transitions are a bit irregular and several pixels appear to jump to 255 at the same time, but the gist is definitely there! We get a brightness spike that propagates down the line and fades out. Nifty.

Looking around at the other fast math functions offered by FastLED, I noticed cubicwave8, quadwave8, triwave8, and sin8. We can try those out, but need to set the angle and map it to my led strip:

int lead_dot = 0;
int angle = 0;

void loop() 
{
  
  EVERY_N_MILLISECONDS(3)
  {

    uint8_t lead_dot = map(cubicwave8(angle), 0, 255, 0, NUM_LEDS - 1);
    angle = angle + 1;
    leds[lead_dot].b = 255;

    fadeToBlackBy(leds, NUM_LEDS, 32);
  }

  FastLED.show();

}

Give that a whirl and substitute in thevarious waveforms to see the effect. I'll leave it there for this post – happy lighting!

21 October 2015

FastLED series: getting started

Intro

Welcome to my attempt to contribute to the amazing FastLED library with some tutorials and explanations. The library is growing quickly, and I've seem some posts lately asking about more documentation/examples so I thought I'd take a stab at it. Honestly, I'm probaby the completely wrong person for the job, as I just started using it myself… but why not?! I've found that one of the best ways to learn something is to teach it.

I only have WS2812B LEDs and an Arduino, so anything I write will use them and I won't be any help on other platforms/strip types. If you have questions, I'd highly recommend joining the FastLED Users Google+ community. They're helpful, fun, and it's a great place to see peoples' projects and learn more.

Note: I really do mean I'm an extreme novice, so please comment if I got something wrong or you think my wording/explanations could be better.

Alright, here goes…


Setup

To get started, you'll need an LED strip and a microcontroller of some sort. I'm using an Arduino Uno and a strip of WS2812B LEDS. I used some 12V 5050 RGB strips in a previous project and these same WS2812B strips for a more recent one. I vastly prefer the latter, as they only require 5V (no MOSFETs required) and one data line (vs. one per channel, so 3 for a 12V strip). Also, the WS2812Bs are individually addressable, so any pixel can be any color. While I'm sure there's people using FastLED for these, that seems like a fringe use for such a capable library.

In any case, here's my setup:



Pretty simple. I just soldered wires to the three connections on the LED strip (make sure you get the Din side!) and connected them to the Arduino. Per the Adafruit guide, I also soldered a 1000 uF capacitor between the 5V and GND pads (negative lead on the GND side) and have a resistor between pin 3 and the data wire (green in my setup). I haven't had any bad experiences not using this, but I'm trying to follow best practices here :)

That's it from the hardware side, at least for this demo. You can do all sorts of things like dimentional matrices, but we'll just stick to a strip for now. Moving on to code, might as well start by piggy-backing on FastLEDs own wiki.

To get going, I'd recommend using git to grab the v3.1 version of FastLED. It appears that this branch is going to receive the abundance of new features, and it already contains a lot of neat stuff not in the v2.x variants (maybe even 3.0?). If you're not familiar with git, it might be easiest to click "Download ZIP" from the FastLED 3.1 github page and extract it into your Arduino library folder (My Documents\Arduino\Libraries on Windows). I use Linux, so my process is to open a terminal and do:

$ cd ~/Arduino/libraries
$ git clone https://github.com/FastLED/FastLED.git

Now you can create a new .ino file with your IDE of choice (I've really been loving Sublime Text) and insert the starter code from the FastLED wiki:
#include "FastLED.h"

// fast led constants
#define DATA_PIN    3        // change to your data pin
#define COLOR_ORDER GRB      // if colors are mismatched; change this
#define NUM_LEDS    28       // change to the number of LEDs in your strip

// change WS2812B to match your type of LED, if different
// list of supported types is here:
// https://github.com/FastLED/FastLED/wiki/Overview
#define LED_TYPE    WS2812B

// this creates an LED array to hold the values for each led in your strip
CRGB leds[NUM_LEDS];

void setup()
{
  
  // the wiki features a much more basic setup line:
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);

}

That code will initialize your FastLED object. You're defining how many pixels you have (let's call this n), what pin to send the data over, creating an array n long so you can store each led's color values in it, and then telling FastLED about said array for future control.


Doing stuff

The heart of any Arduino sketch happens in the loop() function, which runs whatever you tell it over and over. The wiki already explains some basic functions, but let's go through them anyway. The first thing to understand is that your LED strip is treated as an array. Maybe you're more familiar with the term "vector" from another language, or can visualize a single row of cells in a spreadsheet. Your LEDs are treated like this (10 LED strip):

|---+---+---+---+---+---+---+---+---+---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---+---+---+---+---+---+---+---+---+---|

Each "slot" (also called an index) contains the values for that LED. There are 10 of them, but array numbering starts with 0, so things are always 1 "off." Since we initialized this array to store the values as leds[NUM_LEDS], this means you created an array with NUM_LEDS "slots" (28 in my case if you check the code above). If you've not worked with arrays before, it will really help to keep the above in mind as we play around with the library.

On that note, we can go to the first example! To set an RGB color on an LED, we need to set the data in it's slot in the array to the color we want. FastLED has a ton of pre-defined RGB colors to choose from, accessed by CRGB::color_name. We'll set the first LED to red like so:

void loop() 
{

  leds[0] = CRGB::Red;
  Serial.println(leds[0]);

}

If you run that… nothing happens! The above is simply storing a color value in the 0th slot of the leds array. From skipping ahead on the Wiki, we can see that each value in the leds array contains values for red, green, and blue:

CRGB has three one-byte data members, each representing one of the three red,
green, and blue color channels of the color. There is more than one way to
access the RGB data; each of these following examples does exactly the same thing:

  // The three color channel values can be referred to as
  // "red", "green", and "blue"...
  leds[i].red   = 255;
  leds[i].green = 0;
  leds[i].blue  = 0;

  // ...or, using the shorter synonyms "r", "g", and "b"...
  leds[i].r = 255;
  leds[i].g = 0;
  leds[i].b = 0;

  // ...or as members of a three-element array:
  leds[i][0] = 255;  // red
  leds[i][1] = 0; // green
  leds[i][2] = 0; // blue

So, our leds array is actually a multidimensional storage system for the color channels of each pixel. Can we test this? We can initialize a serial connection to the Arduino so we can print out values as we play around:

void setup()
{

// keep whatever else you have, and add:
Serial.begin(9600);

}
Then, let's modify our example above like so:
void loop()
{

  leds[0] = CRGB::Red;

  Serial.print("red = ");
  Serial.print(leds[0].r);
  Serial.print(", blue = ");
  Serial.print(leds[0].g);
  Serial.print(", blue = ");
  Serial.println(leds[0].b);

  delay(500);

}

Run the sketch and start your serial monitor. You'll see these lines spitting out:
red = 255, blue = 0, blue = 0
red = 255, blue = 0, blue = 0
red = 255, blue = 0, blue = 0

It works! We set the red value of leds[0] to 255 (100%, as each color can take a range of 0-255). Now, to send that data to the strip and show the result, you add FastLED.show() to the code. This takes the current values of leds (all NUM_LEDS slots of data) and sends it out to the strip. Since we didn't touch the other array values, only the first led will do anything.

void loop()
{

  leds[0] = CRGB::Red;
  FastLED.show();

  delay(500);

}

Cool:



Well, that was fun… how do I stop that light from shining in my eyes? You need to set the value of leds[0] back to 0. Technically, we only changed leds[0].r, but the way shown in the wiki to turn off an LED is to set it to black:
void loop()
{

  leds[0] = CRGB::Black;
  FastLED.show();

  delay(500);

}

The fun is all in the changing of pixels. You can set one on, and turn it off, so the natural next step is to do that again and again:

void loop()
{

  leds[0] = CRGB::Red;
  FastLED.show();

  delay(500);

  leds[0] = CRGB::Black;
  FastLED.show();

  delay(500);

}

How about moving the pixel? You're familiar with the idea of an array, so what we want is something like this, shown as snapshots in time, with the * representing the pixel that's on:

|---+---+---+---+---|
| * |   |   |   |   |
|---+---+---+---+---|

|---+---+---+---+---|
|   | * |   |   |   |
|---+---+---+---+---|

|---+---+---+---+---|
|   |   | * |   |   |
|---+---+---+---+---|

|---+---+---+---+---|
|   |   |   | * |   |
|---+---+---+---+---|

|---+---+---+---+---|
|   |   |   |   | * |
|---+---+---+---+---|

How might you do that? To do this, we need to set the 0th pixel to some color, show it, then turn it off and set the 1st pixel to a color, turn it off, etc. Using the excellent wiki again:

void loop() 
{

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[dot] = CRGB::Blue;
    FastLED.show();
    
    // clear this led for the next time around the loop
    leds[dot] = CRGB::Black;
    
    delay(30);
  }
}

If you're not familiar with loops, that might seem a bit confusing. A for() loop focuses on some variable specific to the loop (dot in this case), does something while it's in a certain range of values (less than the number of LEDs we have), and does something after each time it runs (dot++ means, "add one to the current value of dot each time).

Walking through the loop step by step we find:
  • dot is initialized to 0 in the loop definition
  • set leds[0] to blue
  • push the data (turn on the first pixel)
  • set leds[0] to black (but note that the data isn't pushed out yet)
  • wait 30 milliseconds
  • dot++ is now executed, so dot becomes 1
  • set leds[1] to blue
  • push the data (the first pixel was set to black the last time, so only the second pixel will show as blue)
  • set the second pixel to black, wait 30 ms, add 1 to dot
The for() loop will stop as soon as the condition dot < NUM_LEDS is not met. In my case, this means as long as dot is less than 28, it will run. Arrays starting at 0 is handy in this case since the values are 0-27. Thus, once the loop runs for dot equal to 27, one will be added and the loop will exit.
Give it a try and hopefully you find that movement satisfying!



The dot keeps moving because once the for() loop is done, you're also inside of the main loop() function, so the for() loop just starts again. The variables in a for() loop are temporary, so they just get re-initialized each time as dot = 0. What if you wanted the dot to come back? Think through what you'd want to have happen, perhaps starting with the end of the loop we used above:
  • the 27th pixel will be blue
  • you want to turn on the 26th pixel instead of starting back over at 0, and turn off the 27th pixel
  • and so on
You could write the loop in "reverse," like this:

for(int dot = NUM_LEDS - 1; dot > -1; dot--)

I find this a bit hokey since you have to tweak dot to odd values. You've defined some nice constants, and the array starts at the wonderful number, zero, but to go in reverse one has to fiddle. Can we use dot just as it is, increasing from zero -> 27? As you think about arrays, sometimes it can be helpful to lay out what your loop variable is compared to what value you want to change:

| dot:   |  0 |  1 |  2 | ... | 26 | 27 |
|--------+----+----+----+-----+----+----|
| array: | 27 | 26 | 25 | ... |  1 | 0  |

Can you see it? You can use math inside of the index brackets ([ ]) when setting values, so what if we wrote the return loop like this:

void loop() 
{

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[dot] = CRGB::Blue;
    FastLED.show();
    
    // clear this led for the next time around the loop
    leds[dot] = CRGB::Black;
    
    delay(30);
  }

  for(int dot = 0; dot < NUM_LEDS; dot++)
  { 
    
    leds[NUM_LEDS - dot] = CRGB::Blue;
    FastLED.show();
    
    // clear this led for the next time around the loop
    leds[NUM_LEDS - dot] = CRGB::Black;
    
    delay(30);
  }
}



Pretty neat! I think I'll call it good for now, and hope to continue on writing about using FastLED in the near future.

20 October 2015

Henderizer v2, accelerometer controlled LED dance suit (2 of 2)

I wanted to create a separate post with some of the specific details for the nerds out there. The circuit was quite simple, as I mentioned in Part 1:

- pin 9: LED strip data in
- pin A4: SDA of adxl345
- pin A5: SDL of adxl345
- 5V connected to adxl345 and LED strip
- GND connected to adxl and LED strip

Here's some close ups. The four wires you see are for the adxl345. On finished boards, there would be 5 more coming into the board: LED ground/power/data and battery pack positive and ground. The back is just some jumpers to read the encoder button get power/ground.




On that note, I was originally going to use an LM8705 to scoot down a 4 x AA battery pack to 5V for the LED strips (I'd read you can damage them at more than 5V). Then, I stumbled on the mention of powering a standalone Arduino through the LM8705 with 7 - 16V. I was only giving it 6V. I'm not really sure what happens at less than 7V, but after reading around more, I opted to just solder a jumper in my 4 x AA pack between the first slot negative and the second slot negative so that I'd get 4.5V from three instead.

Other than that, I used a prototype solder board (5 x 7cm cut in half) for everything and other than the above components, I just had to re-create the fairly straightforward Arduino on a breadboard.

Here's the parts I ended up using:


component source price qty total
atmega328p eBay 2.00 1 3.31
16 MHz crystal mouser 0.30 1 0.30
22pF cap amazon 0.091 2 0.18
28 pin socket mouser 0.77 1 0.77
adxl345 ebay 3.26 1 3.26
pcb ebay 1.09 1 1.09
battery holder ax-man 1.50 1 1.50
rotary encoder mouser 1.44 1 1.07
encoder knob mouser 0.46 1 0.46
22ga solid wire amazon 22.00 - 0
26ga stranded wire amazon 22.00 0.05 1.10
WS2112b 60 LED/m RGB strips eBay 39.99 0.2 8.00
belt clip ax-man 0.50 1 0.5
total 21.54

I have to give a plug for tubelight, the eBay vendor I used for the LED strips. I found them to have the lowest cost ws2812b strips, period. So much so, that I contacted them after making my purchase (and about triple checking the specs) to make sure I was getting what I thought. I even included a link to the same item (but for much more) to verify the products were identical. They were, indeed.

I placed my order about 2mos in advance since the shipping window was huge. The day before the last day of the shipping estimate, tracking still said "origin is preparing shipment." Eeks. I took this to mean that they were still in China. I contacted tubelight and they DHL'd me a second set (I ordered 4 x 5m reels!) free. They asked if I was willing to resend on my second set (if and when it came) to other US buyers to save them having to re-send them. I was, but ultimately just bought the second set as well since my wife loves the idea of making some neat Christmas lights with them this year.

In any case, I simply insert the story as I was blown away that they'd go through that trouble and cost to make sure I hit the project deadline. Check them out!

The code is still a bit of a work in progress, but if you'd like to take a look, go for it!

#include "FastLED.h"
#include "Wire.h"
#include "Encoder.h"


// adxl device for wire.h
#define DEVICE (0x53)

// must use fastLED 3.1: https://github.com/FastLED/FastLED/tree/FastLED3.1
// fast led constants and initialize
#define DATA_PIN    9
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    56
CRGB leds[NUM_LEDS];


// adxl setup
byte _buff[6];
char POWER_CTL = 0x2D;  //Power Control Register
char DATA_FORMAT = 0x31;
char DATAX0 = 0x32; //X-Axis Data 0
char DATAX1 = 0x33; //X-Axis Data 1
char DATAY0 = 0x34; //Y-Axis Data 0
char DATAY1 = 0x35; //Y-Axis Data 1
char DATAZ0 = 0x36; //Z-Axis Data 0
char DATAZ1 = 0x37; //Z-Axis Data 1
uint8_t howManyBytesToRead = 6;

// encoder initialization
Encoder enc(3, 4);
int enc_switch = 2;

int readings[50];
int read_index = 0;
int totals = 0;;
int rolling_ave = 0;
int old_rolling_ave = 0;


// global variables
int bright = 200;
int cutoff = 40;
int mode = 0;

// rainbow_equal
int last = 0;
int top = 0;
double next = 0;
double len = 3;

// spinner
double all_time = 1;
double spin = 0;
int mean_x = 0;
int mean_y = 0;
int mean_z = 0;

int max_x = 200;
int max_y = 200;
int max_z = 200;

double which = 0;

// enc
int last_time;
int last_press;
int press;
int maxed = 200;

int x;
int y;
int z;

double count = 0;
int last_pop = 0;


void setup() {
  
  delay(2000);
  Serial.begin(9600); 

  // tell FastLED about the LED strip configuration
  FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalLEDStrip).setDither(bright < 255);
  FastLED.setBrightness(bright);

  pinMode(2, INPUT_PULLUP);

  // join i2c bus (address optional for master)
  Wire.begin();        
  
  // Put the ADXL345 into +/- 4G range by writing the value 0x01
  // to the DATA_FORMAT register.
  // Think 0x00 = 2g, 00x1 = 4g, 00x2 = 8g, and 00x3 = 16g
  writeTo(DATA_FORMAT, 0x0B);

  //Put the ADXL345 into Measurement Mode by writing 0x08 to POWER_CTL register.
  writeTo(POWER_CTL, 0x08);
  
}

  
void loop()
{
  //read the acceleration data from the ADXL345
  readFrom(DATAX0, howManyBytesToRead, _buff); 

  // each axis reading comes in 10 bit resolution, ie 2 bytes.
  // Least Significat Byte first!!
  // Thus we are converting both bytes in to one int
  
  x = (((int)_buff[1]) << 8) | _buff[0];   
  y = (((int)_buff[3]) << 8) | _buff[2];
  z = (((int)_buff[5]) << 8) | _buff[4];

  // the next section takes a constant average of the accelerometer
  // and subtracts it from the current reading. this means that
  // whatever orientation you put it in, it will tend towards readings of 
  // zero for all axes. I found the adxl's I got to have quite varied
  // baselines, so this made sure that all of the suits would record an impulse
  // based on motion "away from however you were positioned last."
  mean_x = (x + mean_x) / 2;
  mean_y = (y + mean_y) / 2;
  mean_z = (z + mean_z) / 2;
  
  x = abs(x - mean_x);
  y = abs(y - mean_y);
  z = abs(z - mean_z);
  // I logged some motion and figured out the top end was around 250
  top = constrain(x + y + z, 0, 250); //constrain(max(x, max(y, z)), 0, 225);
  // I found that a moving average might be helpful to remove super close
  // instances of multiple accelerations above the threshold. I may tweak
  // it some more, but it definitely helped smooth things out
  readings[read_index] = top;
  read_index = read_index + 1;
  
  totals = 0;

  for(int i = 0; i < 60; i++){

    totals = readings[i] + totals;

  }

  rolling_ave = totals / 60;

  if(read_index > 59)
  {

    old_rolling_ave = rolling_ave;
    read_index = 0;

  }

  
  // for now, just the button is read. push it and it cycles the mode
  // and flashes a color based on which one you're setting.
  // eventually I'd like a long-press to set things like the acc cutoff
  if(!digitalRead(enc_switch)) {
    
      leds[0] = CHSV(mode * (255/4) + 1, 255, 150);
      FastLED.show();
      delay(500);
      mode = mode + 1;
      if(mode > 3) { mode = 0;}
      leds[0] = CHSV(mode * (255/3) + 1, 255, 150);
      FastLED.show();
      delay(1000);  
    }

  switch(mode)
  {
    case 0:                                               
      rainbow_equal();
      break;
      
    case 1:
      sparkler();
      break;
      
    case 2:
      spinner();
      break;

    case 3:
      rainbow_fade();
      break;
      
  }


}


void rainbow_equal()
{

  //top = max(x, max(y, z));
  //FastLED.setBrightness(200);

  if(len > 1) {

  len = len - (.012 * len);
  bright = map(len, 1, NUM_LEDS / 2, 10, 150);
  FastLED.setBrightness(bright);

}

if(top > cutoff) {

  last = len;

  len = map(top, cutoff, maxed, 8, NUM_LEDS / 2);

  if(last > len) {

    len = last + 3;

    if (len > NUM_LEDS / 2) {len = NUM_LEDS / 2;}

  }
}

next = next + 1;

int start = NUM_LEDS - len;

fill_rainbow(leds, len, next, 3);
fill_rainbow(leds + start, len, next, 3);
FastLED.show();
FastLED.clear();


next = next + 1;

delay(4);
}

void spinner()
{

  //next = 0;

  if(spin > 4) {

    spin = pow(spin, 0.996);
    bright = map(spin, 5, 35, 50, 255);
    
    next = next + spin;
    FastLED.setBrightness(bright);

  }


  else {
    
    next = next + 2;
  }

  //top = max(x, y); 

  if(top > cutoff & spin < 15) {

    spin = map(top, cutoff, maxed, 8, 35);
    bright = map(top, cutoff, maxed, 70, 200);

    next = next + spin;
    FastLED.setBrightness(bright);
    
  }

  if(next > 255) {

    next = next - 255; 

  }

  fill_rainbow(leds, NUM_LEDS / 2, next, 15);
  fill_rainbow(leds + (NUM_LEDS / 2), NUM_LEDS / 2, 255 - next, 15);
  //fill_rainbow(leds + 30, 15, next, 15);
  //fill_rainbow(leds + 45, 15, 255 - next, 15);
  FastLED.show();

  delay(3);

}

void sparkler() 
{


  FastLED.setBrightness(200);

  //top = max(x, max(y, z));

  int num = constrain(map(top, cutoff, maxed, 20, 200), 1, 100);

   while(num > 0) {

     int i = random8(0, NUM_LEDS - 1);                                           
     if (i < NUM_LEDS) leds[i] = CHSV(random8(), random8(50,255), random8(10, 200));
     num = num - 1;
   }
   
   for (int j = 0; j < NUM_LEDS; j++) leds[j].fadeToBlackBy(random8(20, 60));
   
   LEDS.show();                                                
   
   delay(50);

}


void rainbow_fade()
{


if(top > cutoff & rolling_ave > old_rolling_ave)
{


  count = map(top, cutoff, maxed, 50, 500);

  if(last > count) { count = last + 5; }

  if(count > 500) { count = 500; }

  if(millis() - last_pop > 750){
  
  last_pop = millis();

  if(x > y)
  {

    if(x > z)
    {

      if(y > z)
      {
        
        next = 0 + (y/x * 10);

      }
    
      else
      {

        next = 160 + (z/x * 10);

      }

    } 

    else
    {

      next = 160 + (x/z * 10);

    }

  }
      

  else
  {

    if(x > z)
    {

      next = 96 - (x/y * 10);      

    }    

    else
    {

      next = 96 + (z/y * 10);

    }

  }

}
  

}

last = count;

bright = constrain(map(count, 3, 500, 10, 225), 10, 225);

/*
if(count > last)
{

  FastLED.setBrightness(bright);

  fill_rainbow(leds, NUM_LEDS, next, 150 / (count + 1));
  FastLED.show();
  FastLED.clear();
  delay(50);

}
*/

if(count > 3)
{

  count = pow(count, 0.987);

}

else { count = 0; bright = 0; }


FastLED.setBrightness(bright);

fill_rainbow(leds, NUM_LEDS, next + 200/count, 1); //constrain(50 / (count + 1), 1, 20));
FastLED.show();
FastLED.clear();

delay(2);

}

////////// begin accelerometer reader //////////

void writeTo(byte address, byte val) {
  Wire.beginTransmission(DEVICE); // start transmission to device 
  Wire.write(address);             // send register address
  Wire.write(val);                 // send value to write
  Wire.endTransmission();         // end transmission
}

// Reads num bytes starting from address register on device in to _buff array
void readFrom(byte address, int num, byte _buff[]) {
  Wire.beginTransmission(DEVICE); // start transmission to device 
  Wire.write(address);             // sends address to read from
  Wire.endTransmission();         // end transmission

  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from device

  int i = 0;
  while(Wire.available())         // device may send less than requested (abnormal)
  { 
    _buff[i] = Wire.read();    // receive a byte
    i++;
  }

  Wire.endTransmission();         // end transmission
}

////////// end accelerometer reader //////////

void fadeLEDs(int fadeVal, int which){
  for (int i = 0; i < which; i++){
    leds[29 - i].fadeToBlackBy( fadeVal );
    leds[30 + i].fadeToBlackBy( fadeVal );

  }
}

19 October 2015

Henderizer v2, accelerometer controlled LED dance suit (1 of 2)

I just wrapped up a mostly successful endeavor of creating a second version of my original accelerometer-controlled LED dance suit for a friend's wedding. Actually, make that creating 8 of them. The goal was 10, but unfortunately a garage project from hell (mostly being a slave to it until I had a roof back on it) got me a late start and I fell short. 8/10 was still enough to make a nice splash at the wedding, though! Here's how it turned out :)



And here's a slightly more "controlled" demo of the various modes (one I added after the wedding). I love the look of the strips under a shirt. Diffusing the light is definitely the way to go in my opinion.



Originally, I planned on using suspenders, but couldn't figure out a way to deal with pinning an inflexible LED strip reliably to the stretchy straps. I feared they would either get pulled on too much, or when someone bent over the LED strip would "bulge" out due to the slack. Near the deadline, I had an idea to use magnets to pin the strip to an undershirt, with nuts or washers attached the back of the strip and a rare earth magnet holding them from inside the shirt. I had cases 3D printed, but was so ridiculously down to the wire (pun intended) on the build that my wife literally just hot glued the boards to the battery pack between the ceremony and reception. Here they are in their day-of hot glued glory:

Top view showing solder prototype board and adxl345 attached to case

Belt clip ripped out of a cell phone belt case from Ax-Man, a local surplus store

I plan to finish all of the suits, and the final product will look like this with the cases I designed/printed up (the adxl345 accelerometer even fits inside):




Here's a view of the two part case, showing the base plate which allows the board to simply "nest" in the four corner frames, and the top will cinch down in it and attach to the base plate with screws. This lets me epoxy the baseplate to the battery pack, insert the board, and screw on the case which can still be removed if any repairs are needed.



In terms of components and code, there's details in Part 2, but the gist is:
- adxl345 accelerometer for detecting motion
- ws2812b individual LED strips (60/m density)
- rotary encoder to change modes/adjust variables (like acceleration threshold)
- standalone arduino built on a prototype solder board
- FastLED library used for effects, along with studying any code samples I could scrounge up around the web. I especially found the work/demos of Mark Kriegsman to be super helpful (and amazing).

Compared to the last suit, the board design/circuit was soooo much simpler since I used 5V individual addressable LED strips. No MOSFETs, no per-channel PWM pins needed. What a dream to work with. That said, soldering all of those up was still quite the task! Huge thanks to my work colleague/friend Ken Meyer who heard I was under the gun and came over and soldered like a machine for 4hrs on his vacation day. I never, ever, ever would have made it where I got without him!

The state of things after a long Friday of soldering


I also have to thank my lovely wife, Amanda, for running around like a madman (madwoman?) with me during the final days, picking up batteries, screws, digging through Ax-Man bins looking for something cheap with a belt hook, etc. She also ran the hell out of her hot glue gun to make this all happen.

I've really enjoyed these and will likely build something quite a bit more complex down the road. For now... I'm taking a break!

Part 2 features a list of materials and the code.

12 July 2015

An Arduino/accelerometer controlled LED dance suit (2 of 3)

This is part 2 of a three part series on an LED light dance suit (harness?). It's Arduino-based, and the lights are controlled by the motion of an accelerometer.
- Part 1: motivation, result, and parts list
- Part 2 (this post): making the harness, board, and enclosure
- Part 3 (coming): code and further ideas/plans
----------

This post will cover making the suit, board and enclosure. It's not a full instructable style explanation. I'll just show some details of the project for any who want to try and replicate something from it, or are just curious how it went together.


The harness
I'll start here, as it's the cruder of the components. I looked at a couple of thrift shops for suspenders, figuring it's the sort of item that surely gets donated but rarely purchased. Both that I checked didn't have any! I decided to just make something instead and picked up 3m of nylon webbing. I didn't want a stretch material, as the LED strips don't stretch and it might trash whatever attachment mechanism I used.

I would never do it this way again, but lacking a better idea at the time, I opted to sew a couple of loops around both sides of the LED surface mount packages. Horrible idea: cumbersome, ridiculously time consuming, not even a great hold, and likely prone to coming loose/stretching/breaking. In any case, this is what I did:


If and when I do this again, I'll opt for hook and loop of some sort, an idea I picked up at this awesome project.


For the harness, I wanted to add something besides just suspender straps, so I came up with the square in the middle (at the time was thinking sort of an Iron Man or Tron look):



For the suspenders and center piece, there are only two types of intersections. Nothing special, but just wanted to illustrate how I sewed these together.

1) Angled pieces that "bridge" from the center square to the edges
  • Simply put the angled piece behind the main side straps to hide the cut edge on the backside.
  • You end up with a parallelogram intersection, and I stitched around all four sides
  • I cut the back piece flush with the strap edge and singed it with a lighter
  • I'll also note here that anywhere I had any length of wires to run between LED pieces, I poked holes through the webbing and ran them on the backside as shown to hide them. A little seemed tasteful (like in the center square), but I didn't think long runs would look as good.


2) Angled bridge to corners of center square
  • I first constructed the square, just sizing it so that it would be long enough for a 3-light strip on each side. Overlapped at corners and sewed all four edges of the square intersection
  • Next, I lined up the bridge pieces with the square and "pinned" them to the square with a single pass of needle/thread. This way they could rotate, but were still attached.
  • I put on the suspender portion and held up the square/bridge assembly in the mirror. When it looked good (centered, all pieces reasonably taught), I used pins to hold the intersections in place and then stitched them permanently.
  • I started by hanging it at the top of the shoulders, verifying fit, and then attaching the bottom to pieces after putting it all back on


I ran my sewing machine by turning the wheel manually. It was definitely time-consuming and annoying, but my thread would break otherwise. I tried playing with the tensions, as this seemed to be the most likely candidate from reading online, but couldn't get it right. I can sew through a single ply of a cotton shirt scrap, so I think it might just be cramming that needle/eyelet through 3 layers of beefy webbing.

Just a quick shot of the attachment between the back and front attached to elastic/adjustment buckle/hardware:

 


I wanted two circuits of LEDs to eventually allow for toggling just the side straps or the center by themselves, an I wanted to run as little wire as possible behind the straps (more bulk, rigid section...). Here's playing around with some different patterns:

 


I ended up using the following. Above, I was trying really hard to keep parallel/series runs equal as I wasn't sure if it would affect the brightness at the beginning/end of each run. After fiddling a bit, I don't think it matters for something this short (30 LEDs on side straps, 24 use in center array):


The dotted line represents a run of wires from the power source behind the strap, linking up to the first diagonal "bridge" piece that supplies the center section. The dots represent soldered jumper wires between sections.


The board
I wanted the board to look super clean and well-laid out. I was also pretty aggressive on size, shooting for really small. This meant quite a lot of effort to optimize all the component placements so they would 1) fit and 2) look cool (hopefully). I was going to do this in software, but just didn't find it that easy. I looked at Eagle, perhaps kicad and another open source application, and fritzing.

Fritzing was great for components, as it has a great pre-populated library, but I really didn't care for it's connection drawing and part sizing/placement/orientation. If you try it, you'll see what I mean.

  • Components are always at an angle (not a top-down view), so they take up a ton of room and hide anything behind them.
  • Trying to draw connections with all right angle bends (remember, I'm trying to keep this neat looking), is really tedious. If you move things, you might lose a connection/intersection between sections of your bent path. Then you have to reconnect them.
  • You can't seem to make right angle bends if you're not actually on a PCB or breadboard template.


In the end, I just took to drawing prototype layouts on graph paper, using each grid as a 0.1" space between solder holes on the board. This was great; I was able to quickly try a lot of different layouts until I found what I liked best:

 
Trying to keep MOSFETs near their associated pins

Having MOSFETs against bottom long edges

Who knows...


The final design, with audibles called afterward for the encoders/switch


You can see that initially I was trying to just put those MOSFETs near the pins that controlled them, however the layout I ended up with was more tidy with respect to having to cross wires over each other. I can't recall if I really planned it or not, but the MOSFETs off to the side gives me space for the nice encoder/switch placement in the lid. They protrude a good deal below the lid, and the MOSFETs would have made it impossible to get them in there.

The DIN connectors were quite challenging to wire up. Lots of patience required! I knew I needed to clear the MOSFETs with the cover, and needed a little space below the board for wires and clearance between it and the back plate. I also knew I wanted the enclosure ~1/16 - 1/8" bigger than the board. I used some scrap wood cut to allow for these dimensions to hold the connector at the right height while I bent up little jumper wires to solder in. Here's the connectors soldered and in the scrap wood:

 


Here's the near-final board soldered up (just no encoders/switch at this point):


The bottom isn't quite as pretty...


At this point, things between the board and enclosure became a bit symbiotic, as I couldn't place the encoders/switch without knowing how high the enclosure walls/lid were going to be, where the holes in the lid would be placed, etc. But I couldn't drill holes in the lid without knowing how the board would sit, constrained/hanging by those connectors. Since I'm focused on the board here, I'll just let you know the above happened and that this was the final wiring:


Note the red electrical tape on the switch and far encoder. You might not be able to see it, but there's also a small piece on the top of the 16MHz crystal. Things got pretty tight, and I didn't want to risk inadvertently jumping neighboring MOSFETs together via the metal on the encoder/switch, or solder lugs on the bottom of the switch via the metal case of the crystal. I wanted to use clear electrical tape spray, but I was getting close at this point and just didn't feel like taking the time to spray a puddle somewhere an dab it on with a Q-tip...


The enclosure
This was probably the most fun, and best result of the project. I was originally targeting an Altoids tin as my enclosure. After making some progress on the board and realizing how big it would turn out, particularly in height and length with the connectors, I started having second thoughts. Plus, the tin just felt a bit too flexible on the top and bottom for something like a knob or switch. I bought a couple of cans of sardines, as I recalled those to be pretty rigid containers... either my childhood memory is bad, or they've cut costs on sardine cans over the past ~15yrs. They were pretty flexible, too.

Then I started thinking wood. Wood has it's perks in that it's easy to work with and available. The main downside is that it's so heavy/thick compared to other materials. I could have planed or sanded mine down, but that amount of work just wasn't worth it. The scrap I had around was some Goncalo Alves that I picked up from the amazing scrap bins kept at my lumberyard: Forest Products Supply. I hadn't had an opportunity to use it for a cribbage board, so I decided now was the time. And wow was it more beautiful than I ever expected.

You saw the prototype ends above, which I used to make the final pieces as well. I found it was easier to cut the recess first, then cut the piece to length. I used simple mitered corners, but probably would have used rabbeted joints if I had a router table. I glued everything up and clamped it with rubber bands. Worked like a champ.

I drilled holes for the top and back covers, and drilled/tapped them with a #4-40 threads. I did this before finishing, and even before some rough work, so that I could pin them together and sand everything at once to keep it all aligned. Sort of like this, but without the board in there:


I hunted around for some time trying to find some really small machine thread inserts that I could use to avoid using machine threads directly into wood. No real luck. They make inserts, but I was amazed at how thick the walls were! In other words, you'd need a relatively huge hole in the wood to receive the male threads of the adapter, just to let you attach a teeny little 4-40 machine screw. Somewhere along the way, I picked up a tip to drill the hole, coat it with super glue, and then tap it (possibly doing a second super glue coating and re-tapping). This is what I did, and the theory is that the wood soaks up the super glue to give it some hard/toughness and not chip out via tapping or simply inserting the screws.

Here's the sides/back before finishing:


For finish, I opted for some automotive 2-part clear coat. I wanted a super shiny, mirror-like finish. Trying to read about doing this is quite tough. There are a lot of opinions about using automotive clear on wood, and in both directions. I asked a former automotive paint rep, and he said he'd been using clear on wood for years. Good enough for me!

This was right after painting (clear is still tacky):



For the lid, I googled things like "clear coat on polycarbonate" or "clear coat on acrylic" and found a thread claiming that ~600-800 scratches would disappear once cleared. This is critical, as you don't get good adhesion to polymer without scuffing, but scuffing clear polymer makes it look cloudy (as shown above). I wanted a nice design of some sort on the cover, so needed to paint it, and thus scuff it. But if the scuffs didn't come out with the clear, it would all be for nothing.

I took my chance and masked a pattern I came up with just fiddling around. I wanted it to look sort of electronic, but also to resemble an equalizer/music impulse looking thing. I was pretty happy with the result. After I pulled the masking tape from the red, I cleared over everything:



Then came wet sanding and buffing. Paint collects around any hard edges/corners, so you need to wet sand and flatten it out. I did that with 2000 grit WetOrDry, followed by 3000 and 5000 grit 3M Trizact, followed by steps 1-3 of the 3M Perfect-It buffing system. Turned out amazing, and here's the final finish on the wood:



Everything still looks pretty good, but it's a month later and compare the above to the pictures from part 1:


See the little dots in the reflection of my ceiling lights? That's the wood grain showing as a texture, and I think it's the result of continued solvent evaporation from the clear coat. They say clear coats take a good 30 days to full harden and do their thing. I'm thinking if shrinking is involved, it's conforming to the wood grain more, causing it to show.

This view is good to point out the clear elastic cord stuff I stole from my kids' necklace making kit to use for belt loops. It's just threaded in/out of those four holes and tied on the inside. The four tiny holes with nothing in them were meant to mount the board to the bottom using some polymer spacers between the metal plate and board. In the end, the holes were off and the board is held plenty well as a result of the 17 wires soldering it to the connector plugs, which are screwed to the box.


Odds and ends
I started with the sorts of 9/15-pin connectors I could find at the surplus store and just hacked away until I liked them. For example, here's the blue 15-pin connector that's on the suit itself:

Cutting off tabs and sanding


I also wanted to keep the encoders and switch straight/square with respect to the lid edges while tightening on them from the top. But I couldn't hold the nuts on the inside of the box for the connector plugs with the lid on... how to accomplish both attaching the board and the encoders/switch!?

I took to scuffing a washer/nut for each connector screw (4 sets) as well as the wood around the hole from the inside. Then I popped in the board, put screws through the outside connector mounts, and actually glued the washer/nut to the wood on the inside with a 2-part polyurethane. After I knew it was cured, I removed the screws. Now I could blindly attach the board from only the outside without having to hold the nut on the inside, allowing me to pre-attach the lid to the board.

Here's a shot where you can see the washer/nuts glued to the inside of the box on the far connector through the lid:



And from the outside it's just a couple of button head cap screws:



Notice how I have a 9-pin connector for the power side, but only use 3 pins during use (GND, +6V, and +12V). You might also notice from the pics of the bare board that there are more than three wires soldered to it. Well, to flash an atmega328 on a board, you need to connect an Arduino with it's chip removed to some of the pins on the standalone board. For a breadboard, this is easy enough, but I surely didn't want to have a bunch of loose jumper wires permanently attached to the board to program it. Or solder/de-solder connections each time.

I used the spare pins to give me access to the reset and TX/RX pins of the atmega328. When I want to program it, I use some jumpers where I have 18ga leads (about the size of the DIN pins) soldered to 22ga wires (Arduino's header size). I thought this was pretty clever :) These yield the following setup for programming (yes, the soldered connections are now heat shrinked!):



And that's it for the build. Next, we're on to the code and some future ideas/plans. Check out part 3 (coming soon) if you're interested!

An Arduino/accelerometer controlled LED dance suit (1 of 3)

This is part 1 of a three part series on an LED light dance suit (harness?). It's Arduino-based, and the lights are controlled by the motion of an accelerometer.
- Part 1 (this post): motivation, result, and parts list
- Part 2: making the harness, board, and enclosure
- Part 3 (coming): code and further ideas/plans
----------

Background and motivation
For my birthday, my sister-in-law asked for any gift wishes, and I promptly provided information for the MicroPython. I'd been drooling over it every since it's Kickstarter campaign, and I had just missed the backing deadline when I first found out about it. I'd begun to dabble with the Arduino, and have wanted to learn python anyway, so I thought it would be an awesome second microcontroller for the arsenal. In any case, I felt it was a bit much for a birthday gift and gave plenty of freedom not to buy it... but I did get it -- thanks Rachel :)

I loved that the MicroPython came with an accelerometer built in, and used an RGB LED to make a little "thank you" video after receiving it:



After that, I started messing with a cutoff value for the accelerometer. In this way, it would respond to an impulse above the cutoff, but wouldn't be lit all the time (due to gravity alone setting it off). I added in a fade over time as well to create pulses (so every, say, 30 milliseconds, it would subtract a value from the current brightness). It was quite mesmerizing... the night I finished it, I turned off all my first floor lights, camped out on the couch with headphones in, and rocked out staring at an LED for probably an hour while listening to various bass-heavy songs. Totally worth it!

At that point, it was already dawning on me that this could be pretty cool for dancing, and I conceived the idea to make a suit/suspenders/harness of some sort. I was trying to do this on a budget, so I settled on a basic 12V RGB LED strip (30 per meter); these are not individually addressable, which means I can have the entire strip set to any color, but all of the LEDs will be that color. Using addressable LEDs would allow for an insane amount of options/patterns/behavior and also only require 5V (just a 4 x AA pack), but the strips are maybe 3-5x the non-addressable kind. I wasn't ready to splurge yet... or perhaps my wife just wasn't ready for me to splurge yet :)


The result
Rather than go through all the details, I figured it's probably more fun to just see the result so here you go!

the suit and power supply (8 x AA battery pack)


 
the enclosure


And a video of me flailing around a bit with the suit on:




Parts list
Having built my first Arduino on a breadboard for my daughter's robot project, I knew I'd do the same here (smaller, and I'm not giving up my only Arduino!). I ended up using two circuit of LEDs, both of which require 12V and PWM signals x 3 channels. Power to come through MOSFETs since the Arduino can't supply the necessary voltage or current (you can read more on this at Adafruit's excellent tutorial). I also wanted some ability to control the LED behavior on the fly, so I tossed in some rotary encoders and a switch. Everything is powered via an 8 x AA battery pack (with a split after 4 AAs to power the Arduino with 6V and another positive output lug after all 8 AAs to provide 12V to the strips). 

Here's the parts I ended up using:

component source price qty total
atmega 328p digikey 3.31 1 3.31
16 MHz crystal digikey 0.30 1 0.30
irlb8721 mosfet digikey 0.73 6 4.38
22pF cap amazon 0.091 2 0.18
lm7805 regulator digikey 0.56 1 0.56
10uF cap digikey 0.33 2 0.66
28 pin socket digikey 0.72 1 0.72
adxl 345 ebay 5.00 1 5.00
pcb ebay 0.93 1 0.93
battery holder ax-man 1.50 1 1.50
3-way toggle switch ax-man 1.50 1 1.50
rotary encoder mouser 1.44 3 4.32
encoder knob mouser 0.46 3 1.38
rgb 5M led strip amazon 17.99 0.5 9.00
9-pin plug (male) ax-man 0.50 1 0.50
9-pin plug (female) ax-man 0.50 1 0.50
15-pin plug (male) ax-man 0.50 1 0.50
15-pin plug (female) ax-man 0.50 1 0.50
total 35.74


Some notes/comments:
  • I know when I bought mine in April, I thought ~$18 for 5 meters was a good price... magically they all appear to be ~$10 now. No idea how I would have missed saving 50% if they were that much a few months ago!
  • I used the IRLB8721 MOSFET based on reading at Adafruit, but if you google around there are plenty of other options. Since I used two separate circuits of LEDs (to make them toggle-able), I needed 6. If you just use one run of LEDs, you only need 3.
  • Originally, I was using the adxl335, but found that I easily maxed out the +/- 3g limit, so I ended up getting the adxl345, which requires one less wire and can be programmed to 2/4/8/16 g's for the upper limit.
  • I used a bunch of 10k's for the encoders and switch, and one 2.2k for the switch, and the cost isn't factored in above. I bought a kit from Amazon (so more of an investment/tool I just have around), and they're so cheap I just consider them as not worth listing above.
  • I didn't count the 22ga solid and stranded hook-up wire I used for reasons similar to the resistors. I bought a bunch of it and consider it just inherent to a project and not adding that much cost.
  • I also bought nylon webbing, thread, and suspender clips from Hancock Fabrics
  • For the enclosure, I used spare wood laying around from cribbage boards I've made and a piece of scrap metal for the back (costs aren't counted). The polycarbonate cover was perhaps $10 from a hardware store and I used ~1/4 of the sheet.
  • An Arduino is needed (obviously), for sending flashing the bootloader and code to the atmega328 on the board. I don't count this as it's more of a "tool" and not actually consumed in the project, at least in my case.
  • Optional: a 12V power supply. I used a female 2.1x5mm jack with terminals where I could insert a couple wires, which I soldered to the LED strips. This way, I didn't have to futz with a battery pack. Small convenience, just wanted to mention it.
Regarding total costs, the components are listed above with links to where I purchased things. The harness was another $30 (webbing + hardware), but if I did it again I might use some ~$5 junk suspenders off of Ebay for my starting point. The hardware alone cost me that much at Hancock Fabrics.

The enclosure is up to you... as stated, I used mostly what I had around, and just had to purchase the plastic top. I could have just used the same scrap metal, but the cheap cost of the polycarbonate sheeting seemed worth having it be see through. The screws and related hardware might cost ~$5.

For equipment, here's what I recall using:
  • electronics gear: soldering iron, wire cutter/strippers, flush cutters, helping hands, a nail set (like a little center punch) as a heat sink and to hold wires until the solder cooled, multimeter, heat shrink tubing, electrical tape
  • harness: sewing machine, needle/thread
  • misc: small backcut fine-toothed saw, file, sandpaper, paint gun/paint, 4-40 round head cap screws/nuts/washers, miter saw, 3" pneumatic buffer and compounds, paint/equipment needed for decoration, some good glue (I used a 2 part polyurethane adhesive from 3M, but I believe it's discontinued). Epoxy would probably work fine.
Alright, see part 2 for the build!