Button switching function

Starting a new topic by request of earlier users. This has been discussed over and over, but I’m struggling to adapt it to my code.

In a nutshell, I want to use a momentary button to switch LED patterns. I’ve built the patters into functions as well as a button state checker to run in the loop according to code here:

I’m having some trouble though because it seems like if the LED loops doin their thing, any and all button presses are ignored. Even trying to print them to the monitor is fruitless.

Mind having a look?

#include <Adafruit_NeoPixel.h>
#define LEDPIN 6 // connect the Data from the strip to this pin on the Arduino
#define NUMBER_PIXELS 3 // the number of pixels in your LED strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMBER_PIXELS, LEDPIN, NEO_GRB + NEO_KHZ800);

unsigned long time;

int prevMillis;
int pause;
int currentMillis;
int buttonState = 0;
int wait = 10; // how long we wait on each color (milliseconds)
int pixels[NUMBER_PIXELS]; //array for lightning
int inPin= 7;
int val=0;
int previous;
int sequenceNum = 0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(inPin, INPUT);
 strip.begin();
//rainbowWheel(); 
}

void loop() {
  currentMillis = millis();
  //Serial.println(buttonState);
  buttonState = digitalRead(7);
  if (digitalRead(7)==HIGH) Serial.println("hi");
  // put your main code here, to run repeatedly:
  delay(60);
  if(buttonState == HIGH && previous == LOW && millis() - time >20){
    time=millis();
    sequenceNum++;
    if (sequenceNum>1) sequenceNum = 0;
  }
  runSequence(sequenceNum);
  previous = buttonState;
}

void runSequence(int seqNum){
  if (millis() - prevMillis > pause) {
    switch (seqNum){
      case 0:
        rainbowWheel();
        break;
      case 1:
        lightning();
        break;
    }
    prevMillis = millis();
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

void rainbowWheel(){
  Serial.println("r");
  while (0==0){
for (int color=0; color<255; color++) {
      for (int i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, Wheel(color));
       }
    strip.show();
    delay(50);
  }
  }
}
void lightning(){
  Serial.println("l");
  while (0==0){
  for (int i=0; i<strip.numPixels();i++){
strip.setPixelColor(i,0,0,pixels[i]);   
  int lightning = random(1,200);
  if (lightning == 4){
    strip.setPixelColor(i,128,128,128);
    strip.show();
    delay(5);
    strip.setPixelColor(i,0,0,0);
    strip.show();
    delay(5);
    strip.setPixelColor(i,128,128,128);
    strip.show();
    delay(5);
    strip.setPixelColor(i,0,0,0);
    strip.show();
    delay(5);
  }
    
    if (pixels[i]%2==0){
      pixels[i]+=2;
    }else{
      pixels[i]-=2;
    }
    if (pixels[i]<1) pixels[i]=2;
    if (pixels[i]>128) pixels[i]=127;
    strip.show();
   }
  delay(30);
}
}

“if the LED loops doin their thing, any and all button presses are ignored”
If that is true, you have programmed it wrong. You simply can’t prevent the pot from boiling over while sitting at the neighbors house watching TV.

You only check for button presses within the Loop function. Within the pattern flashing functions, you Keep flashing while(0==0) which translates to “Flash forever and only return to the Loop and check the push button when eternity Ends”.

You have several Options.

  1. Have the pushbutton Trigger an Interrupt. Interrupts CANNOT be ignored by the CPU! Within the Interrupt Service Routine, Change the flashmode. Within each flashing pattern Loop, Exit the Loop if the Flash pattern is no longer correct. Problem solved.

  2. If you are doing delay within the flashing Loop Patterns, replace it with a function which checks for button changes while waiting instead of simply waiting as the delay does. If the button was pressed, Exit the Flash Routine, Change the Flash mode, and start the appropriate pattern. Problem solved.

Hi, How have you got your button wired?

What is your serial print indicating?

If your switch is between the input and 5V, have you got a 10K resistor between the input and gnd to pull the input down when the button is OFF. Or if your button is between input and gnd, have you got a 10K from input to 5V to pull the input high when the button is OFF.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Thanks.. Tom.. :)

The button here has it's own pull up (came with a sensor kit). My initial button state change tests worked fine when there was nothing going to the LEDs.

I assumed it was a code issue as JaBa suggests.

So, JaBa, if I change the While (0==0){ to While(sequenceNum==1){ without changing the loop, which is already checking the button state, should that do it or is there more to it than that (i.e., your second solution)? Or should I create a standalone function to do the button check and call that at the end of the pattern loops?

My understanding from the thread linked above was that I'd run into problems doing it that way because the buttonstate would just repeat too frequently and it would change the patterns much too quickly.

You only look at the button at the end of the LED sequence. So for it to work you have to hold it down until the sequence ends.

What you need to do is to rewrite your code as a state machine. That means removing all delays and for loops.

So, JaBa, if I change the ....

No. His proposed solution is very poor. You might as well check the button all the time as check the flag an interrupt service routine sets.

Grumpy_Mike: That means removing all delays and for loops.

Lemme see if I got this straight. You're suggesting I empty my loop() and do something like this:

  • create a function to check the button state HIGH/Low with a sequenceNum var
  • create LED pattern functions as while(sequenceNum==x)
  • inside each pattern's loop, call the button check function prior to the delay and do a break before calling the next pattern function

?

For the most part, the delays and the for loops are required to cycle through the light patterns, so I can't remove those without losing the animation.

For the most part, the delays and the for loops are required to cycle through the light patterns, so I can’t remove those without losing the animation.

No that is incorrect you can remove delays and loops and have the animation.

create LED pattern functions as while(sequenceNum==x)

No.

Forget loops you can not use them, as I told you what you need is a state machine.
See my
http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html
Or Robin2’s several things at once

This code is the AdaFruit WS2812b test function rewritten as a state machine, note no for loops or delays. Look out the original AdaFruit code for comparison.

// StrandTest from AdaFruit implemented as a state machine
// pattern change by push button
// By Mike Cook Jan 2016

#define PINforControl   7 // pin connected to the small NeoPixels strip
#define NUMPIXELS1      6 // number of LEDs on second strip

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

unsigned long patternInterval = 20 ; // time between steps in the pattern
unsigned long lastUpdate = 0 ; // for millis() when last update occoured
unsigned long intervals [] = { 20, 20, 50, 100 } ; // speed for each pattern
const byte button = 2; // pin to connect button switch to between pin and ground

void setup() {
  strip.begin(); // This initializes the NeoPixel library.
  wipe(); // wipes the LED buffers
  pinMode(button, INPUT_PULLUP); // change pattern button
}

void loop() {
  static int pattern = 0, lastReading;
  int reading = digitalRead(button);
  if(lastReading == HIGH && reading == LOW){
    pattern++ ; // change pattern number
    if(pattern > 3) pattern = 0; // wrap round if too big
    patternInterval = intervals[pattern]; // set speed for this pattern
    wipe(); // clear out the buffer 
    delay(50); // debounce delay
  }
  lastReading = reading; // save for next time

if(millis() - lastUpdate > patternInterval) updatePattern(pattern);
}

void  updatePattern(int pat){ // call the pattern currently being created
  switch(pat) {
    case 0:
        rainbow(); 
        break;
    case 1: 
        rainbowCycle();
        break;
    case 2:
        theaterChaseRainbow(); 
        break;
    case 3:
         colorWipe(strip.Color(255, 0, 0)); // red
         break;     
  }  
}

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 = millis(); // time for 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 = 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;
  // for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
  //  for (int q=0; q < 3; q++) {
     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 = 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 = millis(); // time for next change to the display
}


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

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);
}

I'm here to learn from those more experienced than I, and the last thing I want to seem is combative, but the code that you posted has several for loops and at least one delay. To be clear, the for loops I have are in the rainbowWheel() and lightning() functions, similar to the way Adafruit's code uses them in the rainbow() and wipe() functions. So when you say there are no for loops or delays, you are contradicting the code you pasted.

I'm not pointing this out to try and seem like a know-it-all, only that my code isn't all that dissimilar from Adafruit's here, which is causing my confusion.

When I get home this evening, I will copy and paste Adafruit's code into my own sketch and test, adding in my own lightning function once I'm confident that works as expected.

OK what I meant was there were no for loops that change the pattern. If you need to fill an array with a lot of stuff you use a for loop. However, unlike the original the for loops have no delays in them. They execute one step in the pattern and then return. The delay is for debouncing the push button.

So sorry for confusing you but no delay is used in the sequencing of the pattern and no for loops are used to progress the pattern. Each call to the pattern function does one step only. The speed of the pattern is governed by how often you call the pattern function, in the gap between calling the functions you are constantly testing the push button. Please read the state machine links.

I’m happy to report that, even though I don’t fully understand why yet, the code now works thanks to Mike’s guidance. I’m going to sit and make an effort to understand why, but for now I need to press on with my project before the Halloween deadline.

Thanks for the help. Karma points for all. Oh, and here’s the working code. I decided to keep the rainbow theater chase too, cuz the pattern looks really good in the globe I printed. Once I’ve got it all assembled, I’m happy to share photos and/or video.

// StrandTest from AdaFruit implemented as a state machine
// pattern change by push button
// By Mike Cook Jan 2016

#define PINforControl   6 // pin connected to the small NeoPixels strip
#define NUMBER_PIXELS      3 // number of LEDs on second strip

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

unsigned long patternInterval = 20 ; // time between steps in the pattern
unsigned long lastUpdate = 0 ; // for millis() when last update occoured
unsigned long intervals [] = { 20, 20, 50, 100 } ; // speed for each pattern
const byte button =7; // pin to connect button switch to between pin and ground
int pixels[NUMBER_PIXELS]; //array for lightning

void setup() {
  strip.begin(); // This initializes the NeoPixel library.
  wipe(); // wipes the LED buffers
  pinMode(button, INPUT_PULLUP); // change pattern button
 //populates pixel array with random blue values for lightning storm
  for (int i=0; i<strip.numPixels();i++){
    int rc = random(0,128);
    pixels[i]=rc;
    }
}

void loop() {
  static int pattern = 0, lastReading;
  int reading = digitalRead(button);
  if(lastReading == HIGH && reading == LOW){
    pattern++ ; // change pattern number
    if(pattern > 2) pattern = 0; // wrap round if too big
    patternInterval = intervals[pattern]; // set speed for this pattern
    wipe(); // clear out the buffer 
    delay(50); // debounce delay
  }
  lastReading = reading; // save for next time

if(millis() - lastUpdate > patternInterval) updatePattern(pattern);
}

void  updatePattern(int pat){ // call the pattern currently being created
  switch(pat) {
    case 0:
        lightning(); 
        break;
    case 1: 
        rainbowCycle();
        break;
    case 2:
        rainbow(); 
        break;     
  }  
}

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 = millis(); // time for 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 = 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;
  // for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
  //  for (int q=0; q < 3; q++) {
     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 = millis(); // time for next change to the display    
}

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

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 lightning(){
  for (int i=0; i<strip.numPixels();i++){
strip.setPixelColor(i,0,0,pixels[i]);   
  int lightning = random(1,200);
  if (lightning == 4){
    strip.setPixelColor(i,128,128,128);
    strip.show();
    delay(5);
    strip.setPixelColor(i,0,0,0);
    strip.show();
    delay(5);
    strip.setPixelColor(i,128,128,128);
    strip.show();
    delay(5);
    strip.setPixelColor(i,0,0,0);
    strip.show();
    delay(5);
  }
    if (pixels[i]%2==0){
      pixels[i]+=2;
    }else{
      pixels[i]-=2;
    }
    if (pixels[i]<1) pixels[i]=2;
    if (pixels[i]>128) pixels[i]=127;
    strip.show();
   }
   lastUpdate = millis();
}