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.