Delay help with neopixel strip

Hello, I'm currently working on a school project using a neopixel strip to light up an interactive map of Lewis' and Clark's expedition path. I don't know much about arduino or C++ so I had a friend come help me program and do all the coding. Everything works well, except that when you press the button, there is a long delay before it does anything the next time you press the button. Or in other words, if you press the button, it will light up and do it's thing, but then if you try and click the button again to move onto the next light sequence, it won't do anything, and you have to spam press the button a bunch of times before it works. Does anyone have any idea why this could be? I don't know what I'm doing very well, so explaining it simply or just revising the code for me would be greatly appreciated!


#include <Keyboard.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN     3
#define LED_COUNT   100
#define BUTTON_PIN  2

int clickCount = 0;
bool buttonWasPressed = false;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  Serial.begin(9600);

  strip.begin();
  strip.clear();
  strip.show();
  strip.setBrightness(250); // adjust brightness
}

void loop() {
  int btn = digitalRead(BUTTON_PIN);

  // Detect button press (LOW means pressed)
  if (btn == LOW && !buttonWasPressed) {
    buttonWasPressed = true;
    clickCount++;
    Serial.print("Click count: ");
    Serial.println(clickCount);
    delay(10);
  }
  if (btn == HIGH) {
    buttonWasPressed = false; // reset when released
  }

  // Run different loops depending on clickCount
  if (clickCount == 0) {
    // Strip stays off
    strip.clear();
    strip.show();
  }
  else if (clickCount == 1) {
    // Example: light pixel 9 red
    strip.clear();
    strip.setPixelColor(9, strip.Color(255, 0, 0));
    strip.show();
  }
  else if (clickCount == 2) {
    // Example: chase from pixel 14 down to 0
    for (int i = 8; i >= 3; i--) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }    
    strip.setPixelColor(2, strip.Color(255, 0, 0));
    strip.show();
  }
  else if (clickCount == 3) {
    // Example: fill pixels 0–29 one at a time
    for (int i = 1; i >= 0; i--) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }
    for (int i = 34; i<= 37; i++) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }    
    strip.setPixelColor(38, strip.Color(255, 0, 0));
    strip.show();
  }else if (clickCount == 4){
    for (int i = 39; i<= 46; i++) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }   
    strip.setPixelColor(47, strip.Color(255, 0, 0));
    strip.show();
  } else if (clickCount == 5){
    for (int i = 48; i<= 51; i++) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }   
    strip.setPixelColor(52, strip.Color(255, 0, 0));
    strip.show();
  }else if (clickCount==6){
    for (int i = 53; i<= 61; i++) {
      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();
      delay(500);
    }   
    strip.setPixelColor(62, strip.Color(255, 0, 0));
    strip.show();
  }else if(clickCount==7){
    strip.clear();
    for (int i = 62; i>= 34; i--) {
      strip.setPixelColor(i, strip.Color(0, 0, 255));
      strip.show();
      delay(500);
    }  for (int i =0; i <=9; i++){
      strip.setPixelColor(i, strip.Color(0, 0, 255));
      strip.show();
      delay(500);
    }
    clickCount ++; 
    }
  else{
  void(* resetFunc) (void) = 0; // declare reset function at address 0
  int clickCount = 0;
  bool buttonWasPressed = false;  
  resetFunc();  
              // call it → restart

  }
}

Someone wrote

which is spot on; I found that looking for @Grumpy_Mike 's old post on reworking neopixel animations to avoid the use of delay. Which I didn't find. Yet.

In the meantime, notice in your sketch where all the delays are… that is why no one nothing pays much attention to your pushbutton except for very brief and infrequent periods.

Google

Arduino finite state machine

or

Arduino two things at once

and familiarize yourself with the "blink without delay" sketch on offer in the arduino IDE.

a7

Was it this one?

// Multiple patterns in a state machine format
// using the Adafruit Neopixel libiary each pattern runs in its own 8 pixel length of the strip
// by Mike Cook Feb 2020

#define PINforControl   4 // pin connected to the small NeoPixels strip
#define NUMPIXELS1      64 // number of LEDs on strip

#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS1, PINforControl, NEO_GRB + NEO_KHZ800);


// first set up the parameters to use in the pattern calling
void blinkOne();
void rainbow();
void rainbowCycle();
void colorWipe(); 
void theaterChaseRainbow();
void int programNumber();
void (*jumpTable[])(void) = void {blinkOne(), rainbow(), rainbowCycle(), colorWipe(), theaterChaseRainbow() };


long unsigned int patternInterval [] = { 500, 40, 20, 200, 5 }; // how often each pattern updates
long unsigned int lastUpdate [] = {millis(), millis(), millis(),millis(),millis()};  // for millis() when last update occurred
boolean patternEnabled [] = {true,true,false,true,true}; // should the pattern be called at all
byte patternState[] = { 0,0,0,0,0 } ; // state machine variable for patterns - this initialises them to zero
/*
// now set up the array of pointers to each pattern
//void (*patternPtrs[]()) = {blinkOne, rainbow, rainbowCycle, colorWipe, theaterChaseRainbow }; //the array of pattern pointers
//volatile uint8_t patternPtrs = 0;
uint16_t patternPtrs = 0;
void (*jumpTable[])() = {dummy, isr1, isr2, isr3};
volatile uint8_t isrSelect = 0;

ISR(TIMER1_COMPA_vect) {
  jumpTable[isrSelect]();
}

void dummy() {
}

void isr1() {
  // Do Stuff
}

void isr2() {
  // Do Differnt Stuff
}
*/
const byte button = 3; // pin to connect button switch to between pin and ground

void setup() {
  /*
  //initialises the array of pattern pointers
  patternPtrs[0] = blinkOne; 
  patternPtrs[1] = rainbow(); //void (*)()
  patternPtrs[2] = rainbowCycle();
  patternPtrs[3] = colorWipe;
  patternPtrs[4] = rainbowCycle;
  */
  strip.begin(); // This initialises the NeoPixel library.
  pinMode(button, INPUT_PULLUP); // change pattern button
}

void loop() {
  for(int i = 0; i<5; i++) { // go through all the patterns and see if it is time to call one
    if(patternEnabled[i] && millis() - lastUpdate[i] > patternInterval[i]){
      lastUpdate[i] = millis();
      callPatterns(i, patternState[i]);
     // jumpTable[programNumber](); // go to pattern routine
    }
  }
}

/*
void callPatterns(int index, byte state) {
 // (*patternPtrs[index])(index,state); //calls the pattern at the index of `index` in the array
//    (*patternPtrs[index]); //calls the pattern at the index of `index` in the array
//  ISR(TIMER1_COMPA_vect) 
 // jumpTable[isrSelect]();
 // void (*patternPtrs)(void) = { blinkOne, rainbow, rainbowCycle, colorWipe, theaterChaseRainbow}

ISR(TIMER1_COMPA_vect);
}
*/

// These are the pattern functions written as a state machine
// this is the Blink program in FastLED's example folder
void blinkOne(int index,byte state) {
  if(state == 0){
    andPixelColour(3, 0x800000); 
    strip.show(); // display
    //patternState[index] = 1; // move on the state machine for the next call
  }
  if(state == 1){
    xorPixelColour(3, 0x800000); 
     strip.show(); // display
     patternState[index] = 0;
   }
   lastUpdate[0] = millis(); // time for finding the next change to the display
  }

void rainbow() { // modified from Adafruit example to make it a state machine
  static uint16_t j=0;
    for(int i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
     j++;
  if(j >= 256) j=0;
  lastUpdate[1] = millis(); // time for finding the next change to the display
}

void rainbowCycle() { // modified from Adafruit example to make it a state machine
  static uint16_t j=0;
    for(int i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
  j++;
  if(j >= 256*5) j=0;
  lastUpdate[2] = millis(); // time for next change to the display
}

void theaterChaseRainbow() { // modified from Adafruit example to make it a state machine
  static int j=0, q = 0;
  static boolean on = true;
     if(on){
            for (int i=0; i < strip.numPixels(); i=i+3) {
                strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
             }
     }
      else {
           for (int i=0; i < strip.numPixels(); i=i+3) {
               strip.setPixelColor(i+q, 0);        //turn every third pixel off
                 }
      }
     on = !on; // toggel pixelse on or off for next time
      strip.show(); // display
      q++; // update the q variable
      if(q >=3 ){ // if it overflows reset it and update the J variable
        q=0;
        j++;
        if(j >= 256) j = 0;
      }
  lastUpdate[3] = millis(); // time for next change to the display   
}

void colorWipe(uint32_t c) { // modified from Adafruit example to make it a state machine
  static int i =0;
    strip.setPixelColor(i, c);
    strip.show();
  i++;
  if(i >= strip.numPixels()){
    i = 0;
    wipe(); // blank out strip
  }
  lastUpdate[4] = millis(); // time for next change to the display
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void andPixelColour(int i, long colour){ // ANDs the colour with what is already there
  strip.setPixelColor(i, colour & strip.getPixelColor(i));
}

void xorPixelColour(int i, long colour){ // Exclusive ORs the colour with what is already there
  strip.setPixelColor(i, colour ^ strip.getPixelColor(i));
}

void orPixelColour(int i, long colour){ // ORs the colour with what is already there
  strip.setPixelColor(i, colour | strip.getPixelColor(i));
}

void wipe(){ // clear all LEDs
     for(int i=0;i<strip.numPixels();i++){
       strip.setPixelColor(i, strip.Color(0,0,0));
       }
}


  //void (*jumpTable[])(void) = {blinkOne(),rainbow(), rainbowCycle(), colorWipe(),theaterChaseRainbow() };
   
ISR(TIMER1_COMPA_vect) {
  jumpTable[programNumber](); // go to pattern routine
  }
   

Not 100% sure it compiles any more due to changes in the C++ compiler, but give it a try.

You might have to restore some of the commented out code.

Yeth. In this post a bit of explanation, and the automatic links are all plausible leads:


I was looking for what may not exist: your more tutorial-orientated exposure of the concepts and that code.

Here's a nice tutorial that covers the state machine idea, it's generalized but the basic ideas are important:


HTH

a7

The Adafruit_NeoPixel library interferes with a millis()-driven button reading and pixel lighting, replacing for() loops. The button seems to need to be held briefly when pressed.

[edit]
Also, original code statements regarding which pixels to color do not match the comments.

It is the practice of using a vectored jump to enter a function, rather than using a case statement that will not work with the modern compiler.

A vectored jump is much more efficient because you only have to set it once to keep calling the same function. So you do not need to have a case statement go through all the possibilities there are every time you want to call a function.

Oddly enough though a vectored jump still works with my Glitch Storm project, to be found at

So this implies I am doing something wrong somewhere, with the LEDs.

"Hey there! That lag you're seeing is almost certainly caused by the delay() function in your else if blocks (like the long delay(500) calls).
The issue is that delay() literally stops the entire program—including the code that checks your button—for that half-second. So if you press the button during that time, the Arduino misses the press entirely.
The best solution is to learn "Blink Without Delay" (using the millis() function). It lets you do timing and animations without blocking the button check. It's a little more complex to code, but it's the core skill for making interactive Arduino projects feel fast and responsive!
Good luck with the project! The map sounds really cool."