Help with code for animating a LED strip

Hello,

I need some help with upgrading this code to do what I need it to do, which is to control an LED strip to behave like this:

  1. While the input signal from tilt sensor is HIGH: slowly keep increasing the amount of LEDs turned on for as long as the signal is HIGH (or until all the LEDs are on, at which point keep them all ON)
  2. While the input signal is LOW: slowly keep decreasing the amount of LEDs turned on for as long as the signal is LOW (or until all the LEDs are off, at which point keep them all OFF)

I’ve almost got it working, only that when the Arduino gets a HIGH signal it continues to light the LEDs until every one is on, no matter if the signal stops midway. It should stop increasing when the signal stops and start decreasing, and start increasing again when the signal starts.

This is a video which shows how it works right now:

And the code for this:

#include <FastLED.h>

#define LED_PIN     13
#define COLOR_ORDER GRB
#define CHIPSET     WS2812B
#define NUM_LEDS    30

#define BRIGHTNESS  2
#define FRAMES_PER_SECOND 120

bool gReverseDirection = false;

CRGB leds[NUM_LEDS];



void setup() {
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( BRIGHTNESS );
  pinMode(2, INPUT); //set pin2 as INPUT
  digitalWrite(2, HIGH);//set pin2 as HIGH
  Serial.begin(9600);
}


void loop() {
  int digitalVal = digitalRead(2);

  if (HIGH == digitalVal) {
    for (int dot = 0; dot < NUM_LEDS; dot++) {
      leds[dot] = CRGB::Blue;
      FastLED.show();
      delay(50);
      Serial.print(digitalVal);
    }
  }
  else {
    for (int dot2 = NUM_LEDS - 1; dot2 >= 0; dot2--) {
      leds[dot2] = CRGB::Black;
      FastLED.show();
      delay(50);

    }

  }
}

Any help would be much appreciated.

Janez

Don't use a for-loop.

void loop()
{
  static int dot;

  int digitalVal = digitalRead(2);

  if(HIGH == digitalVal && dot != NUM_LEDS)
  {
    leds[dot++] = CRGB::Blue;
    FastLed.show();
    delay(50);
  }
}

Just to show how it can be done; not tested but should do the first part of your existing loop.

Thank you sterretje, I’ve got this to work, but at the same time I’ve broken some other things (the rest of the strip is lighting up, even though I’ve defined NUM_LEDS as only 30, and after a few seconds of operation it stops working altogether and the serial monitor starts to update really slowly)

I’ll try to debug a bit more, though I’m not very skilled at it. :slight_smile:

If you want to add some more of your expertise to this code, feel free ofcourse:

#include <FastLED.h>

#define LED_PIN     13
#define COLOR_ORDER GRB
#define CHIPSET     WS2812B
#define NUM_LEDS    30

#define BRIGHTNESS  2
#define FRAMES_PER_SECOND 120

bool gReverseDirection = false;

CRGB leds[NUM_LEDS];

int inPin = 2;

void setup() {
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( BRIGHTNESS );
  pinMode(inPin, INPUT); //set pin2 as INPUT
  digitalWrite(inPin, HIGH);
  Serial.begin(9600);
}


void loop()
{
  static int dot;


  int digitalVal = digitalRead(2);

  if (HIGH == digitalVal && dot < NUM_LEDS)
  {
    leds[dot++] = CRGB::Blue;
    FastLED.show();
    delay(50);
    Serial.print(digitalVal);
  }
  else {

    leds[dot--] = CRGB::Black;
    FastLED.show();
    delay(75);
  }
}

How about this (compiles and updates serial monitor but not able to test the LEDs…)

#include <FastLED.h>

#define LED_PIN     13
#define COLOR_ORDER GRB
#define CHIPSET     WS2812B
#define NUM_LEDS    30

#define BRIGHTNESS  2
#define FRAMES_PER_SECOND   120
#define UPDT_INTERVAL       50ul

bool gReverseDirection = false;
const byte pinSensor = 2;

const char *szIsHigh = "HIGH\n";
const char *szIsLow = "LOW\n";
char *pszState;

CRGB leds[NUM_LEDS];

void setup() 
{
    FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness( BRIGHTNESS );
    
    pinMode(pinSensor, INPUT_PULLUP); //set pin2 as INPUT
        
    Serial.begin(9600);
    
}//setup


void loop() 
{
    static byte
        dot = 0;      
    static unsigned long
        timeDot = 0;
    unsigned long
        timeNow;

    timeNow = millis();
    if( (timeNow - timeDot) >= UPDT_INTERVAL )
    {
        timeDot = timeNow;
        
        if( digitalRead( 2 ) == HIGH )
        {
            if( dot < NUM_LEDS-1 )
                leds[dot++] = CRGB::Blue;
            pszState = (char *)szIsHigh;
                
        }//if
        else
        {
            if( dot > 0 )
                leds[dot--] = CRGB::Black;
            pszState = (char *)szIsLow;
        }//else
        
        FastLED.show();
        Serial.print( pszState );
        
    }//if
    
}//loop

Maybe only print state upon change?

One problem with the code in reply #2 is that the dot variable can go negative and you're accessing leds outside the array.

If your strip is more than 30 leds but you only control 30, it's possible that at power-up leds light up with (possibly random) colors.

#include <FastLED.h>

#define LED_PIN     13
#define COLOR_ORDER GRB
#define CHIPSET     WS2812B
#define NUM_LEDS    30

#define BRIGHTNESS  2
#define FRAMES_PER_SECOND   120
#define UPDT_INTERVAL       50ul

bool gReverseDirection = false;
const byte pinSensor = 2;

const char *pszLabel[ 2 ][ 4 ] = { "LOW\n", "HIGH\n" };
byte pszState, prevPszState;

CRGB leds[NUM_LEDS];

void setup() 
{
    FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness( BRIGHTNESS );
    
    pinMode(pinSensor, INPUT_PULLUP); //set pin2 as INPUT
        
    Serial.begin(115200);  // change serial monitor to match, faster serial empties the output buffer quicker.
    
}//  end of void setup()


void loop() 
{
    static byte
        dot = 0;      
    static unsigned long
        timeDot = 0;

    if( (millis() - timeDot) >= UPDT_INTERVAL )
    {
        timeDot += UPDT_INTERVAL;  // even if the trigger is caught late, interval time does not creep.
                              // credit Robin2 for that one       
        if( digitalRead( 2 ) == HIGH )
        {
            if( dot < NUM_LEDS-1 )
                leds[dot++] = CRGB::Blue;
            pszState = 1;
                
        }//if
        else
        {
            if( dot > 0 )
                leds[dot--] = CRGB::Black;
            pszState = 0;
        }//else
        
        FastLED.show();
        if ( pszState != prevPszState )
        {
          Serial.print( pszLabel[ pszState ] );
          prevPszState = pszState;
        } 
        
    }//if
    
}        //  end of void loop()

PS, I have a button debounce that does timed polling like this but in 500 micros intervals.

Blackfin:
How about this (compiles and updates serial monitor but not able to test the LEDs...)

This works perfectly, thank you!

There are a few minor tweaks I'd like to add to this:

  • to have a way to individually set the timing of the growing blue and diminishing black animations; as I see they are currently both controlled at the same time with UPDT_INTERVAL...
    I'm trying to see how to do this but I can't seem to figure it out.

  • right now the first LED on the strip is lit up constantly, I tried to change this part of the code

if ( dot > 0 )
leds[dot--] = CRGB::Black;

to:

if ( dot >= 0 )
leds[dot--] = CRGB::Black;

but that just resulted in everything being constantly black.

So... a little bit more help would be much appreciated. :slight_smile:

sterretje:
One problem with the code in reply #2 is that the dot variable can go negative and you're accessing leds outside the array.

If your strip is more than 30 leds but you only control 30, it's possible that at power-up leds light up with (possibly random) colors.

Right, I didn't think of that... Also, the animation in that code was constantly looping - even when it filled up all 30 of the LEDs it would virtually continue to fill them, so when the state change went to LOW, the Black-- would start in different places, depending where the "ghost loop" was at the time.
This are a bit redundant thoughts, but they help me to learn. :slight_smile:

EDIT:
@GoForSmoke
The line where it prints to serial monitor was throwing errors... When I deleted it the code works, the same as Blackfin's. Not sure where the problem was.

Janezek:
Hello,

I need some help with upgrading this code to do what I need it to do, which is to control an LED strip to behave like this:

  1. While the input signal from tilt sensor is HIGH: slowly keep increasing the amount of LEDs turned on for as long as the signal is HIGH (or until all the LEDs are on, at which point keep them all ON)
  2. While the input signal is LOW: slowly keep decreasing the amount of LEDs turned on for as long as the signal is LOW (or until all the LEDs are off, at which point keep them all OFF)

Is the tilt sensor capable of analog output?

Would a dial do?

Dial center makes no change. That is analog 511. Higher makes dots increase, faster with higher and lower than 511 makes dot decrease, faster with lower.

Instead of dots always increasing at 20 per second, perhaps vary dots/sec by tilt value?

For the input I only need a HIGH and a LOW, the tilt sensor is there because of the way I want the human to interact with it (specifically: sensing when someone blows on the tilt sensor, which is mounted on a spring which tilts when blown upon and bounces back the instant the human stops blowing - I've got this sensing part working perfectly)

I would like to manually set the speed of the increase and decrease and then leave it set up like that. Ideally I'd like it to decrease slower than it increases...

Try this to address both issues. An even cooler thing to do would be to make the “leading” LED(s) decay or grow in brightness rather than just switching “on” or “off”…

#include <FastLED.h>

#define LED_PIN     13
#define COLOR_ORDER GRB
#define CHIPSET     WS2812B
#define NUM_LEDS    30

#define BRIGHTNESS  2
#define FRAMES_PER_SECOND   120

#define ATTACK      50ul
#define DECAY       75ul    //50% longer to decay than attack

const byte pinSensor = 2;

const char *szIsHigh = "HIGH\n";
const char *szIsLow = "LOW\n";
char *pszState;

CRGB leds[NUM_LEDS];

void setup() 
{
    FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness( BRIGHTNESS );
    
    pinMode(pinSensor, INPUT_PULLUP); //set pin2 as INPUT
        
    Serial.begin(9600);
    
}//setup


void loop() 
{
    static byte
        dot = 0;      
    static unsigned long
        timeDot = 0;
   static unsigned long
        timeDelay = ATTACK;
    unsigned long
        timeNow;

    timeNow = millis();
    if( (timeNow - timeDot) >= timeDelay )
    {
        timeDot = timeNow;
        
        if( digitalRead( pinSensor ) == HIGH )
        {
            timeDelay = ATTACK;
            leds[dot] = CRGB::Blue;
            if( dot < NUM_LEDS-1 )
                dot++;
            pszState = (char *)szIsHigh;
                
        }//if
        else
        {
            timeDelay = DECAY;
            leds[dot] = CRGB::Black;
            if( dot > 0 )
                dot--;
            pszState = (char *)szIsLow;
            
        }//else
        
        FastLED.show();
        Serial.print( pszState );
        
    }//if
    
}//loop

Thank you again Blackfin! I'll try that out when I get home.

You are right, that would be way cooler and beyond the effect I dared to try achieving...

Do you maybe have an example bit of code for this effect?

When going up, if blueness == 250, set blueness to 0 and currdot++ so you begin fading in the next LED

Similarly, when going down, you subtract 25 from blueness; when it hits zero, currdot-- etc.

(Partial code follows; copy paste and erase or comment-out old bits)

#define ATTACK      5ul
#define DECAY       10ul

void loop()
{
    static byte
        currdot = 0,
        blueness = 0x00;     
    static unsigned long
        timeDot = 0;
   static unsigned long
        timeDelay = ATTACK;
    unsigned long
        timeNow;

    timeNow = millis();
    if( (timeNow - timeDot) >= timeDelay )
    {
        timeDot = timeNow;
       
        if( digitalRead( pinSensor ) == HIGH )
        {
            timeDelay = ATTACK;
            leds[currdot] = CRGB(0x00,0x00,blueness);
            if( blueness < 250 )
                blueness += 25;                
            else
            {
                blueness = 0;
                if( currdot < NUM_LEDS-1 )
                    currdot++;
                else
                    blueness = 250;
                
            }//else
            
            //pszState = (char *)szIsHigh;
               
        }//if
        else        
        {
            timeDelay = DECAY;
            leds[currdot] = CRGB(0x00,0x00,blueness);
            if( blueness > 0 )
                blueness -= 25;                
            else
            {
                blueness = 250;
                if( currdot > 0 )
                    currdot--;
                else
                    blueness = 0;
                
            }//else
            
            //pszState = (char *)szIsLow;
               
        }//if
               
        FastLED.show();
        //Serial.print( pszState );
       
    }//if
   
}//loop

Wow! It's on another level, the animation is so more smooth...

Thank you!

Also I like the way your code is written, a bit different than what I'm used to seeing, though I can't say for certain why, I think you define more variables in the beginning of the code and then call on them in the loop, as opposed to using more defining straight in the loop?

Are you following a specific "school of thought", can you recommend some resources where you learned?

Thank you again, I love the way it works now. :slight_smile:

Don't do what I do; I was never formally trained in C or C++ save a couple of classes 35 years ago in engineering school. What I do is habit formed over 30 years of hunting and pecking.

Well, it certainly seems like you know what you're doing now.

If I can be so bold to trouble you with one more thing... I've been studying your code and I can't figure out what this does and how:

timeNow = millis();
  if ( (timeNow - timeDot) >= timeDelay )
  {
    timeDot = timeNow;

Can you help me out once more?
Thank you again for everything so far!

Janezek:
Well, it certainly seems like you know what you’re doing now.

If I can be so bold to trouble you with one more thing… I’ve been studying your code and I can’t figure out what this does and how:

timeNow = millis();

if ( (timeNow - timeDot) >= timeDelay )
  {
    timeDot = timeNow;




Can you help me out once more?
Thank you again for everything so far!

Sure. Look at loop() in the bigger context:

void loop()
{
    ...

    timeNow = millis();
    if( (timeNow - timeDot) >= timeDelay )
    {
        timeDot = timeNow;
       
        <do stuff>

       
    }//if
   
}//loop

The idea of this logic is to control the repetition rate at which “” happens.

Remember that loop() is being executed super-fast – it may be called hundreds of times every millisecond. In order to regulate when is executed, we need to be keeping track of elapsed time, only acting when a certain amount of it has gone by since the last time was executed.

We keep track of the “last” time with the variable timeDot and the current or “now” time with timeNow. Consider timeDot as a signpost we stick in the ground as we’re walking. Our walking motion away from that signpost is the passage of time, expressed here by the return value of millis() (i.e. timeNow). As we walk away from the stationary signpost we constantly measure where we are now relative to that signpost and, when we’re far enough, we act (i.e. run ).

The key to repetition is to erase that old signpost and put a new one down “here”. That’s what “timeDot = timeNow” does. Now, each time we check the elapsed time we’re comparing it to this new signpost and this process continues for as long as the processor is running. In this way is executed at regular intervals.

Another key point is that the processor merely peeks at the time spread between “then” and “now”; if it’s not time to it’s free to do any number of other things. This “non-blocking” code is what everyone talks about when they say “don’t use delay().”

That was very helpful for my, not very used to program logic, brain.
Thank you again, I feel like I am in an open-source goal-oriented learning hub. Which I guess is exactly what this forum actually is.

When I tinker a bit with the code and try to delete the TimeNow and TimeDot lines the UPDT_INTERVAL loses its function and I have to introduce delay before FastLed.show to get a similar effect. Though I understand why delay can be a bad practice...

Sorry for the ramble, thanks again for everything. I'm so excited with how it works now!

When your sketch is doing more than 1 thing at a time, you need non-blocking code.

Timing is in examples because it's very useful, buttons and debounce after blinking multiple leds.

But whether it is time or a pin change or a variable value makes an expression true; all are events.

When nothing blocks, responsive event-driven code becomes possible.

Inside of void loop()
{
if ( timer elapsed ) do_thing0();
if ( read_pin(xpin) == LOW ) do_thing1();
if ( read_pin(xpin) == HIGH ) do_thing2();
if ( flagVariable > 0 ) do_thing3();
...
if ( eventn ) do_thingn();
}

Functions thing0() to thingn() are tasks that run when triggered in fast running loop().

The tasks may share information by variables, a finite state machine can use a switch-case structure to put all the tasks into one set of braces. State machines make processing by steps without blocking easy.

You learn to make code that can be entered and run under all conditions of the job. So you track the process state (state machines) and run the case code that matches. The code may change the state, when it has run enough times or finally got serial input and next time the machine runs it runs the new state-case code.

Don't use loops inside of your tasks unless they are very small or necessary. Make the task run every time that loop() does and process 1 element of that array each time instead of looping through possibly very many complex ops. The task function will have to make and maintain any index(es) that say a for-next does. All task code should run in short steps if possible.

You task functions are wheels that turn. Void loop() is the big wheel that turns the little wheels, don't block the big wheel!

See the 1st 2 addresses below, the Nick Gammon tutorials.

Wonderful explanation GoForSmoke! This sounds like basic programming "hygiene", which is probably learned in machine programming 101, but for someone like me who tinkers with examples on a for-a once in a while-project-basis, it's very helpful to get some structured theory in every now and again.

I see it would help me a lot to learn in a more structured way along with learning by doing, so I've got myself these two books; Arduino Cookbook and Arduino Applied, and I'll check out the tutorials as well.

Thank you as well, and I'm sure it will be a lot easier for the next person who will be googling for similar solutions and finds this thread.