Using Pushbutton to Toggle Between LED Patterns

I am trying to use a pushbutton to toggle between a series of different blinking LED patterns. My abbreviated code is below.

The issue I have is that my various patterns take several seconds to execute and I only perform a digitalRead once per loop (at the beginning). Therefore, if I am in the middle of a light pattern and press the pushbutton to activate a new pattern, my input is not picked up. I have to press and hold the button while the first LED pattern finishes before I can register that the pushbutton is being pressed.

Is there a better way to structure the code so that I can toggle between light patterns, even if I am in the middle of executing a given pattern?

I'm still new to Arduino and programming so if there is a concept or tutorial you can point me to, I'd appreciate it.

Thanks in advance.

Samir

int switchState1 = 0;
int mode = 0;

void setup() {
  // put your setup code here, to run once:
}

void LEDPattern0() {
	...
}

void LEDPattern1() {
	...
}

void LEDPattern2() {
	...
}

void loop() {
  // put your main code here, to run repeatedly:
  
  switchState1 = digitalRead(2);
  if ( switchState1 == HIGH ) {
    if ( mode == 0 ) {
      mode = 1;
    } else if ( mode == 1 ) {
      mode = 2;
    } else if ( mode == 2 ) {
      mode = 0;
    }
  }
  if (mode == 0) {
    LEDPattern0();
  }
  if (mode == 1) {
    LEDPattern1();
  }
  if (mode == 2) {
    LEDPattern2();
  }
  delay(2000);
} // end loop()

Post the code for one of your patterns - I expect it uses delay.

You might consider making a generic function that does any pattern and pass it an array of structs that define a particular pattern you want to display,

Then you'll want to do the pattern timing using millis and check for a keypress after each change so you can abort and move on to a new pattern.

Thanks for the quick response. You are correct that I am using the delay() function. An updated version of the example code is below. As you can see, I have to time the pressing of the push button so that it occurs right at the end of each loop in order for the digitalRead() function to pick it up.

I have not used millis() but will look into it.

#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24 // Popular NeoPixel ring size

// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int switchState1 = 0;
int mode = 0;

void setup() {
  // put your setup code here, to run once:
  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(255); // Set BRIGHTNESS 0 to 255
}

void LEDPattern0() {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(255, 0, 0));
    pixels.show();
    delay(50);
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(50);
  }
}

void LEDPattern1() {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 255, 0));
    pixels.show();
    delay(50);
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(50);
  }
}

void LEDPattern2() {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 255));
    pixels.show();
    delay(50);
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(50);
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  
  switchState1 = digitalRead(2);
  if ( switchState1 == HIGH ) {
    if ( mode == 0 ) {
      mode = 1;
    } else if ( mode == 1 ) {
      mode = 2;
    } else if ( mode == 2 ) {
      mode = 0;
    }
  }
  if (mode == 0) {
    LEDPattern0();
  }
  if (mode == 1) {
    LEDPattern1();
  }
  if (mode == 2) {
    LEDPattern2();
  }
  delay(2000);
} // end loop()

consider this data driven approach

#define Button  A1
#define LED     10

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12, 13 };
#define N_LEDS  sizeof(pinsLed)

// -----------------------------------------------------------------------------
// patterns
enum  { None = 0, A = 1, B = 2, C = 4, D = 8 };

#define All (A|V|B|C|D)

struct Patterns_s {
    int             on;
    int             off;
    unsigned long   msec;
    int             next;
} patterns [] = {
    { A,        All,     500,  1 },  // 0
    { None,     A,       500, -1 },

    { B,        All,     500,  1 },  // 2
    { D,        B,       500, -1 },

    { None,     All,     500,  1 },  // 4
    { A,        None,    500,  1 }, 
    { A|B,      None,    500,  1 },
    { A|B|C,    None,    500,  1 },
    { None,     A,       500,  1 },
    { None,     A|B,     500, -5 },
};

int patIdx = 0;

// index into patterns for each pattern
byte patList [] = { 0, 2, 4 };
#define N_PATS  sizeof(patList)

int patListIdx = 0;

// -----------------------------------------------------------------------------
void
loop ()
{
    static byte butState = Off;
           byte but      = digitalRead (Button);

    if (butState != but)  {
        butState = but;

        if (On == but)  {
            // advance to next pattern
            patListIdx = (patListIdx+1) % N_PATS;
            patIdx     = patList [patListIdx];
        }

        delay (10);     // debounce
    }


    // process a pattern
    Patterns_s  *p = & patterns [patIdx];

    static unsigned long msecLst = 0;
           unsigned long msec    = millis ();

    // wait until next interval
    if ( (msec - msecLst) > p->msec)  {
        msecLst = msec;

        // set LEDs off
        for (unsigned i = 0; i < N_LEDS; i++)
            if (p->off & (1 << i))
                digitalWrite (pinsLed [i], Off);

        // set LEDs on
        for (unsigned i = 0; i < N_LEDS; i++)
            if (p->on & (1 << i))
                digitalWrite (pinsLed [i], On);

        // advance to next step in pattern
        patIdx += p->next;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    pinMode (Button, INPUT_PULLUP);

    for (unsigned i = 0; i < N_LEDS; i++)  {
        digitalWrite (pinsLed [i], Off);
        pinMode      (pinsLed [i], OUTPUT);
    }
}
1 Like

You can also consider a millis() based approach with no delays:

#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24 // Popular NeoPixel ring size

// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

const uint8_t pinSw = 2;

int switchState1 = 0;
int mode = 0;
uint8_t lastSw;

void setup() 
{
    // put your setup code here, to run once:
    pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
    pixels.setBrightness(255); // Set BRIGHTNESS 0 to 255

    lastSw = digitalRead( pinSw );
}

void loop() 
{
    static uint32_t
        timeSwitch;
    uint32_t 
        timeNow = millis();
    
    //read the switch every 50mS
    if( (timeNow - timeSwitch) >= 50ul )
    {        
        timeSwitch = timeNow;
        //read the switch
        switchState1 = digitalRead(pinSw);
        //is it different than last read?
        if( switchState1 != lastSw )
        {            
            //yes; save this new value as "last"
            lastSw = switchState1;
            //if it has gone high bump the mode value 0, 1, 2, 0, 1, ... with each press
            if( switchState1 == HIGH )
            {
                mode++;
                if( mode == 3 )
                    mode = 0;
                    
            }//if
            
        }//if
        
    }//if

    //call LED pattern every loop; millis() timing will do the 50mS/50mS timing in that function
    LEDPattern();
    
} // end loop()

void LEDPattern( void )
{
    static bool             //these variables are static because we want to remember them when we come back again
        bState = false;
    static uint32_t
        timeLED;
    static uint8_t
        idx = 0;
    uint32_t
        timeNow = millis();

    //strip is updated every 50mS
    if( (timeNow - timeLED) < 50ul )
        return;

    timeLED = timeNow;
        
    switch( bState )
    {
        case    false:
            //LED is off now; set the color based on the value of mode...
            uint8_t r = (mode == 0) ? 255:0; //this is like "if( mode == 0 ) r = 255; else r = 0;"
            uint8_t g = (mode == 1) ? 255:0;
            uint8_t b = (mode == 2) ? 255:0;        
            pixels.setPixelColor(idx, pixels.Color(r, g, b));
            //and then transition to 'on' state
            bState = true;
            
        break;

        case    true:
            //LED is on now; turn it off...
            pixels.setPixelColor(idx, pixels.Color(0, 0, 0));
            //and go back to 'off' state
            bState = false;

            //bump to next LED. When done all, circle back to zero
            idx++;
            if( idx == NUMPIXELS )
                idx = 0;
        break;
        
    }//switch            

    //update the strip
    pixels.show();
    
}//LEDPattern

(Compiles, not tested...)

1 Like

My tutorial on How to write Timers and Delays in Arduino has a non-blocking led sequence example.
Combine it with the tasks approach from
Multi-tasking in Arduino

1 Like

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.