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!

9 comments:

  1. you've got a missing open brace in your 3rd code example above, in the first for-loop's first line.

    for(int dot = 0; dot < NUM_LEDS; dot++)

    instead of

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

    ReplyDelete
  2. @anonymous: Good catch, and corrected. Found the same error in the 4th block and fixed that too. Thanks!

    ReplyDelete
    Replies
    1. 'tis I should be thanking you. I started with Adafruit libraries and their NeoPixel products with simple loops and single animation sequences run in series but I've been wanting to implement more pizzazz, with less code, more efficient code and overlapping, more complex animations. Your blog entries helped me get up to speed faster with FastLED and I hope you will be making more and soon.

      If you can recommend other sources of information on FastLED or animation examples that demonstration more of the "undocumented" features of the library, I'd appreciate it. I've been reading over the FastLED documentation, wiki and the Google+ group already.

      Thanks again and keep up the good work,

      Scott

      Delete
    2. Hi Scott -- wow, thanks! Happy that this could help others, and I started on the series because of a similar request over on the Google+ group (basically to have examples of lesser documented features). I'm pretty new myself to FastLED, so just took a stab at basic stuff.

      Feel free to list some other things you'd want to see and I'd be happy to try something. I know these came up in the post I'm thinking of: fadeToBlackBy, fill_rainbow/fill_gradient, EVERY_N_MILLISECONDS.
      - https://plus.google.com/116225765652479729260/posts/aXGJq4WidcV

      The places you listed are the only ones I know of myself. The post above suggests it's just a byproduct of a busy development team focused on features for their actual work, which leaves some documentation by the wayside for now...

      Delete
    3. Good stuff. Thanks! Implementing in my project tonight. :)

      Delete
  3. Brilliant tutorial! Excellent research!

    ReplyDelete
  4. I really appreciated your two tutorials on FastLED. It's a shame you seemed to have stopped publishing articles on your blog. I wish you success in whatever else it is you're doing!

    ReplyDelete
    Replies
    1. Hi Pietro: glad you liked them, and indeed... I'm sorry I haven't written more as well! Not sure where the time goes, but it's just seemed quite busy as of late.

      I'd gladly take suggestions on something else you'd like to see. At one point I thought about going through some of the fill_* functions (e.g. fill_rainbow), beatsin*, or some other family. Anything you'd like to see?

      Delete
  5. Great tutorials!!!! The way you stepped through everything really helped. I've been out of C++ for over a decade and just getting back into it. This helped big time!!! I'd like to learn some of the Fill functions like (rainbow, color palette, etc) If you have any links or a direction I could head, that would be greatly appreciated!!! Keep up the awesome work!

    ReplyDelete

formatting help: <i>italics</i>, <b>bold</b>