Restart increment of a neopixel stick with a button without delay.

Hi guys,

i am really happy with my new Arduino stuff and playing around with all those pixels :).

I am new to programming and its difficult to understand it, but with every success, I grow in knowledge.

My latest problem now is to restart my loop of incrementing the strip at any time with the push of a button.

I know, that delay(); will stop the code completely and wait till the time passes, but I wrote some code without the use of delay(); and used millis(); instead.

So far my leds ar incrementing through my strip, but I am not able to restart it until it passes though completely.
I read that the while(); part in my code works kinda like a delay(); and that would prevent it from restart until a full cycle.
I used some millis(); code from a different sample so its not like the ones from Blink Without Delay.

I searched so many sites and cannot write another code to increment without a delay, so hopefully you can help me with it.

#include <FastLED.h>

#define NUM_LEDS_PER_STRIP 16
CRGB ledStrip[NUM_LEDS_PER_STRIP];

const int signalButtonPin = 4;
const int ledStripPin = 8;

int buttonState = 0;
int lastButtonState = 0;

int delaySignalAnimation = 100;

unsigned long currentMillis = 0;
unsigned long previousMillis = 0;

void setup() {

  pinMode(signalButtonPin, INPUT_PULLUP);
  pinMode(ledStripPin, OUTPUT);

  FastLED.addLeds<NEOPIXEL, 8>(ledStrip, NUM_LEDS_PER_STRIP);

}

void loop() {

  buttonState = digitalRead(signalButtonPin);

  if (buttonState != lastButtonState) {

    if (buttonState == LOW) {

      fill_solid(ledStrip, NUM_LEDS_PER_STRIP, CRGB::Black);
      for (int i = 0; i < 16; i++) {
        ledStrip[i] = CRGB::Blue;
        FastLED.setBrightness(100);
        FastLED.show();
        currentMillis = previousMillis = millis();
        while (previousMillis + delaySignalAnimation >= currentMillis) {
          currentMillis = millis();
        }

      }

    } else {

      lastButtonState = buttonState;

    }

    fill_solid(ledStrip, NUM_LEDS_PER_STRIP, CRGB::Black);
    FastLED.setBrightness(0);
  }

  FastLED.show();
}

Thanks in advance for you help.

MfG Marvin

lastButtonState = buttonState should always be done, not just when buttonState is HIGH

i think you should use delay() because that's exactly what you're doing. Your're not conditionally doing anything else and the code is somewhat confusing.

i think what you're looking to do is to increment thru the LED strip while checking for a button press. Your code cycles thru the LEDs when the button is LOW

instead of using a for loop, your loop code could perform a led cycle and separately test for a button press.

the code can maintain a global LED index, instead of the for(int i. Loop() tests if delaySignalAnimation has passed and if so, increments the index, tests for wraparound (if ++index >= 16) index = 1, and updates the LED.

regardless of performing an LED cycle, loop() then tests for a button press which force the index to 0

I read that the while(); part in my code works kinda like a delay(); and that would prevent it from restart until a full cycle.

Yes i that way it kinda does, but no not quite, although the solution i will provide is not really going to fix things in the long run. Check out the blink without delay example in the iDE for a better plan.
For now what you could do is during your waiting, test the buttonstate and if it is low, and if so clear the strip and reset the counter.

while (previousMillis + delaySignalAnimation >= currentMillis) {
          currentMillis = millis();
          if (digitalRead(signalButtonPin) == LOW) {
            i=0;
            fill_solid(ledStrip, NUM_LEDS_PER_STRIP, CRGB::Black);
          }
        }

Deva

i see no reason to only check for a button press after an time interval has expired. A button press can be checked for in each and every iteration of loop and only needs to reset the LED index.

it seems that the LEDs should only be updated after a time interval has expired

Hey Deva,

I tryed out you little replacement, its like you said not 100% what I was looking for.
If I press the button and hold it, it starts from the 2nd led and lights up and only if I let go the button the cycle goes thoug. My original code cycles through with the button holding, but that can be because of the while delay.

gcjr, I kinda understand what you mean, but its hard to really imagine a code from it. Sorry for the confusing code I have managed to build :D. I think I soldered something wrong with my button on my permabord. I soldered one pin of the button to a resistor from ground and the other pin to a DIO, so I think thats why it will be LOW if presses.

I think I really need to study that BlinkWithoutDelay code and that Several things at the same time to achieve my goal.

So basicly I need to loop an If till all the leds are on and than exit that with an else for the state of doing nothing else until I press a button again.

i see no reason to only check for a button press after an time interval has expired.

Not checking after a time interval but during the time interval. I am not proud of this solution because it is educationally wrong.

If I press the button and hold it, it starts from the 2nd led and lights up and only if I let go the button the cycle goes thoug.

yeah i know it should probably be i=-1;  // not i=0; i gets incremented at the end of the loop (i++) that would make it 0 for the start of it.
For the letting go of the button you can do the thing with buttonstate, as you do in the original code, if the state has changed and is LOW, execute the reset of i, and change the lastbuttonstate (every time it has changed) You probably do have to do some kind of debounce, since now you are polling nearly all the time instead of just once a loop()

consider

#define N_LED   16

unsigned long msecLst = 0;

void loop() {
    static int  i = 0;

    msec = millis();
    if (msec - msecLst >= msec) {
        msecLst = msec;

        if (N_LED <= ++i)
            i = 0;

        if (0  == i)
            fill_solid(ledStrip, NUM_LEDS_PER_STRIP, CRGB::Black);

        ledStrip[i] = CRGB::Blue;
        FastLED.setBrightness(100);
        FastLED.show();
    }


    buttonState = digitalRead(signalButtonPin);
    if (buttonState != lastButtonState) {
        lastButtonState = buttonState;

        if (buttonState == LOW)
            i = 0;
    }

    FastLED.show();
}

duplicate post

consider

educationally much better !

Hey gcjr,

I wrote your code with my variables and just to make it simple for now without the button to start the loop and with some changes and an interval now it increment to one after another and loops.

With Devas note to change dot=0; to dot=-1; it starts the first loop at the begining and not from the second. Still not shure why :), does it have something to do with the ++i, because on normal increment code its i++.

I have implement a button with the simple LOW, HIGH states like the Blink without Delay sample i think and it works as I want to be.
Only thing now is, that I want a little black pause after the button has released. I tryed to add the millis code somewhere several times, but got nowhere with it.

Here is the code without the pause:

#include <FastLED.h>

#define NUM_LEDS 16
CRGB ledStrip [NUM_LEDS];

const int ledStripPin = 8;
const int buttonPin = 4;
const int interval = 100;

unsigned long startMillis = 0;
unsigned long lastMillis = 0;

int buttonState = 0;
int lastButtonState = 0;

static int dot = -1;

void setup() {

  pinMode(ledStripPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  FastLED.addLeds<NEOPIXEL, 8>(ledStrip, NUM_LEDS);
  FastLED.show();

}

void loop() {

  startMillis = millis();

  if (digitalRead(buttonPin) == LOW) {

    if (startMillis - lastMillis >= interval) {
      lastMillis = startMillis;

      if (NUM_LEDS <= ++dot)
        dot = 0;

      if (0 == dot)
        fill_solid(ledStrip, NUM_LEDS, CRGB::Black);

      ledStrip[dot] = CRGB::Blue;
      FastLED.setBrightness(1);
      FastLED.show();
    }
  }

  if (digitalRead(buttonPin) == HIGH) {

    dot = -1;

    fill_solid(ledStrip, NUM_LEDS, CRGB::White);
    FastLED.setBrightness(0);
    FastLED.show();

  }

}

Otherwise, thanks for now, we almost did what I want to achive :D.

it starts the first loop at the begining and not from the second. Still not shure why

within a for loop declarationfor (int i = 0; i < 16; i++) the increment is done at the end of the loop, regardless of it's format. and the comparison is always done at the beginning of the loop.
The difference between i++ & ++i is therefore not relevant is this case.
if (NUM_LEDS <= ++dot) here the comparison is done at the beginning of, but ++dot does the increment before the comparison, where dot++ would do the comparison before the increment.
Either way the increment is done at the beginning of the loop, so if you want dot to be '0' at least, then you should start of with it as -1 or ( NUM_LEDS or higher)
The increment is done, it is compared, if to high it is reset to 0.
Consider this

if (digitalRead(buttonPin) == HIGH) {
    dot = -1;
    //fill_solid(ledStrip, NUM_LEDS, CRGB::Black); // what happens when you comment these lines out
    //FastLED.setBrightness(0);  // this line in particular has no purpose, Black is always brightness 0
    //FastLED.show();
  }

isn't this a bit low ?FastLED.setBrightness(1);

Only thing now is, that I want a little pause between the cycles, like implement all leds on, then putt all black wait a bit and start over as long the button is pressed. I tryed to add the millis code somewhere in the button LOW state, but got nowhere with it.

beween every cycle ? or just if the button is pressed ? do you want them staying all Blue or all black for a while or both.
Don't add any extra millis() code, use one spot for the millis, but instead add a conditional extra interval.

Normally i don't support FastLED projects because they allow for the user to write beyond the boundary of the Array, which is something you will have to watch out for here, but i still think some of the things in your sketch are not quite in the right place. Consider moving some parts to individual functions and see which variables can and should be local.

For my testing brightness(1), is enought, i can increase it later, neopixels are bright :D.

The Code should do something lik this:

If I push the button, the cycle should run trough. If I let go, the Strip should be black for some time and then go to the resting color. But It should be able to push the button again and the cycle should start again without the pause completely over. Hope you understand that.

I think i need some more buttonstates than just LOW and HIGH now.

So should I use the Adafruit library for that project, or is it the same like FastLED?

Yes, I know the structure is very bad, because I just put them where they work for that time and not for a 100% correct codestuctur :slight_smile:

So should I use the Adafruit library for that project, or is it the same like FastLED?

They are not the same, but there is basically no need to change. Problem with the FastLED library is that there is no error checking on writing into the LedStrip output buffer, which tends to cause issues for people implementing it for the first time. If all FastLED users would do something like if ((dot>=0) && (dot<NUM_LEDS)) ledStrip[dot] = CRGB::Blue;the issue would not occur, and with Neopixel.h it can not occur either because that is sort of what .setPixelColor() looks like.
For all libraries goes that they come with examples, which are all delay() based and therefore educationally not so good.

I think i need some more buttonstates than just LOW and HIGH now.

I think you need more 'program states', there is an explanation about a 'state machine' in the tutorials section of this forum, have a look at it.

brightness(1), is enought

yes but you only need to set it once, and black is black.
Consider having a function like this

int ShowPixels(bool reset) {  // not the best name
  static int dot = NUM_LEDS;
  if (reset) {
    dot = NUM_LEDS;
    fill_solid(ledStrip, NUM_LEDS, CRGB::Black);
    FastLED.show();
    return;
  }
  if (NUM_LEDS <= ++dot)  {
    dot = 0;
    fill_solid(ledStrip, NUM_LEDS, CRGB::Black);
  }
  ledStrip[dot] = CRGB::Blue;
  FastLED.show();
}

and calling that when the machine is in the correct state.