How to use multiple buttons and functions?

I’m trying to write a code that can do the following:

I have 6 buttons and 6 functions.

If I press a certain button I want it to start doing something and stop doing what it was doing before.

Example: I press button 1 and it starts function 1, which runs until i press button 3, which stops function 1 and starts function 3, which runs until I press button 6, which stops function 3 and starts function 6, which runs until I press another button...

All of the buttons are momentary buttons, which are pressed and released immediately after.

It is critical that a press on a button stops the running function when pressed, and not waits for the running function to finish.

It is also critical that the function activated by the button runs until another button is pressed.

My functions are all working when run individually, so it's the button, stopping and looping part that bugs me.

I've using the internal INPUT_PULLUP for my buttons so far.

It sounds so simple, and I have tried many versions of button sketches, timing, and other code, but I just cant get my head around the framework. How to make all these parts work together.

It sounds so simple, and I have tried many versions of button sketches, timing, and other code, but I just cant get my head around the framework. How to make all these parts work together.

Without seeing your code, I suspect that your functions are blocking, so you aren't reading the switches when you think you are. I suspect that you'll need to completely rethink and rewrite your sketch to use the state machine concept.

Like Paul, my crystal ball says that some, maybe all, of your functions stop the inputs being read frequently. Can you please give us some examples of the functions ?

PaulS:
Without seeing your code, I suspect that your functions are blocking, so you aren't reading the switches when you think you are. I suspect that you'll need to completely rethink and rewrite your sketch to use the state machine concept.

Thank you both for quick responses.

Been trying to work around this with a dirty soluion using interrupts, but the debouncing is a nightmare, even more because im using different buttons.

I have also looked a little into the state machine concept, and that's going to be a steep learning curve :slight_smile:

I have also looked a little into the state machine concept,

Actually very easy once you stop being scared of it

Let's suppose that the program is required to call function0(), function1() or function2() depending on what is required of it at the current time. There are 3 states. Give them a number and set a variable, let's name it currentState, to the number of the current state.

Then, in the loop() function, use a series of ifs or (my preferred method) switch/case based on the value of currentState so that the required function for the current state is called each time through loop(). Ensure that none of the functions block the free running of the loop() function so no delay()s or while loops in the state functions,

The state functions can do anything you want but must not block execution of the code, and at some time the program will need to change state. This could be because of user input (read this in the loop() function) or maybe the required time in the current state has elapsed (do this using millis() for timing in the state function) or maybe because the actions for the state have been completed (such as having set a series of LEDs to a particular state)..

When it is time to change state set the currentState variable to the target state and set up any initial conditions for the new state such as saving the current value of millis() if the target state involves timing. The next time through loop() the code for the new state will start to be executed and so it goes on.

Have a look at the non-blocking code in Several Things at a Time and in the more extensive Planning and Implementing a Program.

Note how each function runs very briefly and returns to loop() so the next one can be called. And there may be dozens of calls to a function before it is actually time for it to do anything.

...R

Okay need to take a step back to get it right.

To be specific, what i want my buttons to do are show different patterns on addressable leds - WS2812B. Ive been using the FastLED library before with great succes.

Until then, it all went well, and I found the Pushbutton library to have the functions i needed and a good debounce.
I also got the functions using the FastLED library to work, and the buttons to load them.
I the ran into the two crucial problems: that a press on a button stops the running function when pressed, and that the function activated by the button runs until another button is pressed. None of which happens.

This is my original code:

#include <Pushbutton.h>        //include Pushbutton library
#include "FastLED.h"           //include FastLED library, which drives the led-strip

#define NUM_LEDS 4             //number of leds on the strip
#define DATA_PIN 6             //led data pin
CRGB leds[NUM_LEDS];           // An array of leds

#define BUTTON1 2              //button1 pin
#define BUTTON2 3              //button2 pin
#define BUTTON3 4              //button3 pin

Pushbutton pbutton1(BUTTON1);  //create button1
Pushbutton pbutton2(BUTTON2);  //create button2
Pushbutton pbutton3(BUTTON3);  //create button3

void setup() {
 
 Serial.begin(9600);           //initialise serial port
 FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  //set up the leds
}

void loop() {


   if (pbutton1.getSingleDebouncedPress()){      //if button1 is pressed
     Serial.println("Button1");                  //serial monitor shows button1 pressed
       Strobe(0xff, 0xff, 0xff, 10, 50, 1000);   //execute Strobe function with parameters (1,2,3: color, 4: how many flashes, 5: flash delay, 6: end pause)
   }
 
   if (pbutton2.getSingleDebouncedPress()){      //if button2 is pressed
     Serial.println("Button2");                  //serial monitor shows button2 pressed
       RGBLoop();                                //execute RGBLoop function
   }

   if (pbutton3.getSingleDebouncedPress()){      //if button2 is pressed
     Serial.println("Button3");                  //serial monitor shows button3 pressed
       //some function
   }

}

//functions used by FastLED functions and library

void showStrip() {
  FastLED.show();
}

void setPixel(int Pixel, byte red, byte green, byte blue) {
  leds[Pixel].r = red;
  leds[Pixel].g = green;
  leds[Pixel].b = blue;
}

void setAll(byte red, byte green, byte blue) {
 for(int i = 0; i < NUM_LEDS; i++ ) {
   setPixel(i, red, green, blue); 
 }
 showStrip();
}


//functions executed by the buttons

void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause){
 for(int j = 0; j < StrobeCount; j++) {
   setAll(red,green,blue);
   showStrip();
   delay(FlashDelay);
   setAll(0,0,0);
   showStrip();
   delay(FlashDelay);
 }

delay(EndPause);
}


void RGBLoop(){
 for(int j = 0; j < 3; j++ ) { 
   // Fade IN
   for(int k = 0; k < 256; k++) { 
     switch(j) { 
       case 0: setAll(k,0,0); break;
       case 1: setAll(0,k,0); break;
       case 2: setAll(0,0,k); break;
     }
     showStrip();
     delay(3);
   }
   // Fade OUT
   for(int k = 255; k >= 0; k--) { 
     switch(j) { 
       case 0: setAll(k,0,0); break;
       case 1: setAll(0,k,0); break;
       case 2: setAll(0,0,k); break;
     }
     showStrip();
     delay(3);
   }
 }
}

So from what you said it's the delays in the functions, that gives me problems?

Also why doesn't the functions keep looping when called in loop?

Also i've been trying different state machines, but not been able to solve how to use multiple buttons. Maybe pressing one button will take you to specific states and pressing another button takes you to different states?

1 Like

There is one way to deal with this that doesn't involve unrolling all your code into a state machine.

When a sketch is in delay(), it calls a function named 'yield'. You can use that function to check buttons.

// an enum is a better alternative than 
// using a load of named constants
// for this type of thing

enum WhichFunction {
  NOTHING,
  STROBE,
  RGB_LOOP
} whichFunction = NOTHING;

void yield() {
  if(dpbutton1.getSingleDebouncedPress()) {
    whichFunction = STROBE;
  }
  else if(pbutton2.getSingleDebouncedPress()) {
    whichFunction = RGB_LOOP;
  }
}

void loop() {
  // always call this in case the rest of the loop doesn't have any delays in it
  // without this, the buttons won't be checked if the state is NOTHING
  yield();
  switch(whichFunction) {
  case NOTHING: break;
  case STOBE: strobe(); break;
  case RGB_LOOP: rgb_loop(); break;
  }
}

void RGBLoop(){
 for(int j = 0; j < 3; j++ ) {
   // Fade IN
  // this shows one way of doing it
   for(int k = 0; k < 256 && whichFunction==RGB_LOOP; k++) {
     switch(j) {
       case 0: setAll(k,0,0); break;
       case 1: setAll(0,k,0); break;
       case 2: setAll(0,0,k); break;
     }
     showStrip();
     delay(3); // this call to delay may cause whichFunction to be changed
   }

   // Fade OUT
  // this shows another way of doing it 
   for(int k = 255; k >= 0 ; k--) {
     switch(j) {
       case 0: setAll(k,0,0); break;
       case 1: setAll(0,k,0); break;
       case 2: setAll(0,0,k); break;
     }
     showStrip();
     delay(3);  // this call to delay may cause whichFunction to be changed
     if(whichFunction!=RGB_LOOP) return;
   }
 }

  // tell loop to call this function once and once only
  whichFunction = NOTHING;
}

Personally, I'd use classes to organise your behaviours and the buttons for them - but that's a whole 'nother topic :slight_smile: .

Delta_G:
With yield it isn't going to be all that much more responsive. You might not miss the button press, but it is still going to finish the entire animation before it reacts to it.

With the extra checks I put into those loops, tt's not going to finish the animation, it will just finish the delay itself before exiting.

Delta_G:
OK, I didn't see that you added something to the for loop. Still, that seems really hacky compared to just writing it in a non-blocking way.

Oh yeah - totally. I don't do this myself. The only reason I'd consider it is if a sketch already has delays all the way though it and we don't want to do a total rewrite. The stuff I write myself is state machines all the way.

Thank you for all of your suggestions. It's taking some time to try it all out, and read up on different things. But at least now I know what my code does and which approaches can be taken. As I'm quite rusty with my old java, from around 2005, so it takes a little, to write and test, but I will get back when I have some more code for you to read :slight_smile:

So I have tried to get down to basics, and this is my code so far.

int ledpin1 = 13;      //pin for yellow led
int ledpin2 = 12;      //pin for green led
int pushbutton1 = 3;   //pin for button 1
int pushbutton2 = 4;   //pin for button 2

//variables for timer 1 (yellow led blink)
unsigned long timeNow1 = 0;   
unsigned long timePrev1 = 0;
unsigned int timeWait1 = 100;

//variables for timer 2 (green led blink)
unsigned long timeNow2 = 0;
unsigned long timePrev2 = 0;
unsigned int timeWait2 = 100;

boolean LEDstatus1 = LOW;   //holds the state of yellow led
boolean LEDstatus2 = LOW;   //holds the state of green led

//variable to hold the cases
enum whichFunction {BLINKGREEN, BLINKYELLOW}
whichFunction = BLINKYELLOW;


void setup() 
{
 pinMode(ledpin1,OUTPUT);
 pinMode(ledpin2,OUTPUT);
 pinMode(pushbutton1, INPUT_PULLUP);
 pinMode(pushbutton2, INPUT_PULLUP);
}


void loop() {

   if (digitalRead(pushbutton1) == LOW)    //reading the state of button 1
   {
       
       whichFunction = BLINKGREEN;     //select case BLINKGREEN
   }
   
   if (digitalRead(pushbutton2) == LOW)    //reading the state of button 2
   {
       
       whichFunction = BLINKYELLOW;    //select case BLINKYELLOW
   }

   switch(whichFunction)     //switch to select the function corrosponding to the case
   {      
     case BLINKGREEN:
        blinkTheGreenLED();
        break;
        
     case BLINKYELLOW:
        blinkTheYellowLED();
        break;
   }

}

//function to blink the yellow led
void blinkTheYellowLED()
{
   timeNow1 = millis();
   if (timeNow1 - timePrev1 >= timeWait1 )    
   {   
         timePrev1 = timeNow1;   
         if (LEDstatus1 == LOW) { LEDstatus1 = HIGH; } else { LEDstatus1 = LOW; }   
         digitalWrite(ledpin1, LEDstatus1);
   }
}

//function to blink the green led
void blinkTheGreenLED()
{
   timeNow2 = millis();
   if (timeNow2 - timePrev2 >= timeWait2 )    
   {   
         timePrev2 = timeNow2;   
         if (LEDstatus2 == LOW) { LEDstatus2 = HIGH; } else { LEDstatus2 = LOW; }   
         digitalWrite(ledpin2, LEDstatus2);
   }
}

First question. Is this a state machine? or even two state machines running parallel?

This sort of works for my needs, except for a few things.

When I push a button the corrosponding blink starts, but the first function is not turned off.

I have tried a function with delays, and that does not work. The whole function needs to finish before another button can be pushed. This makes me think that I have created two codes running at "the same time" instead of actually changing the state of the machine. Or that I still need to make the functions "non blocking" even using a state machine?

Good progress on writing non blocking code.

I think the issue is that you can switch cases when the other led is in a on condition. There is nothing to turn it off.

You can add code to each case to turn the other led off or alternatively you can turn the unwanted led off an the button press.

int ledpin1 = 13;      //pin for yellow led
int ledpin2 = 12;      //pin for green led
if (digitalRead(pushbutton1) == LOW)    //reading the state of button 1
   {
       
       whichFunction = BLINKGREEN;     //select case BLINKGREEN
       digitalWrite(ledpin1,LOW);
   }
   
   if (digitalRead(pushbutton2) == LOW)    //reading the state of button 2
   {
       
       whichFunction = BLINKYELLOW;    //select case BLINKYELLOW
       digitalWrite(ledpin2,LOW);
   }

I would clarify the variable names to use yellow and green and not use the 1 and 2, especially since now pushbutton1 is selecting led2

Another approach:

A *state change *to active on any button updates the color select variable and a function is called which uses that value to select an LED. Using a function eliminates a lot of duplication - since you only need one LED at a time to blink.

// set up array of LEDs indexed to colorValue

check buttons
if(any button state changed to active){
  reset all LEDs
  reset timer
  update whichLED 
}
// new function to blink one and only one selected LED

blinkLED(whichLED)

void blinkLED(colorValue){
run timer
if(timer done){
  set LED[colorValue] opposite to LED[colorValue]
  reset timer
}

spangsebarnet:
When I push a button the corrosponding blink starts, but the first function is not turned off.

Yup. One of the parts of a state machine is that you may (and in this case, do) need to do things on a state machine transition. In formal state machine diagrams, there might be an 'onEntry/onExit' box, or the arrows might have guard conditions.

So this bit:

  if (digitalRead(pushbutton1) == LOW)    //reading the state of button 1
  {
       whichFunction = BLINKGREEN;     //select case BLINKGREEN
  }

If it was fully coded out, would be more like:

needs to be

  // work out what state we need to be in, put it in nextFunction

  if (digitalRead(pushbutton1) == LOW)    //reading the state of button 1
  {
       nextFunction = BLINKGREEN;     //select case BLINKGREEN
  }
  if (digitalRead(pushbutton2) == LOW)    //reading the state of button 1
  {
       nextFunction = BLINKRED;     //select case BLINKGREEN
  }

  if(nextFunction != whichFunction) {
    // need to perform a state transition

    // first, do whtever needs to be done to exit the current state
    switch(whichFunction) {
      case BLINKGREEN: turn off the green; break;
      case BLINKRED: turn off the red; break;
    }

    whichFunction = nextFunction;

    // now do whatever needs to be done to start the new function.

    switch(whichFunction) {
      case BLINKGREEN: turn on the green; timePrev1 = millis(); break;
      case BLINKRED: turn on the red; timePrev2 = millis(); break;
    }
  }

  // and now, having handled any necessary state transition, we do the
  // regular stuff for managing ongoing state opertaion

If you want to overengineer this, you'd make an abstract superclass:

class State {
public:
  virtual void setup() = 0;
  virtual void loop() = 0;
  virtual void onEntry() = 0;
  virtual void onExit() = 0;
};

A 'led blinker' class

class LedBlinker : public State {
  const byte pin;
  uint32_t ms;
public:
  LedBlinker(byte pin) : pin(pin) {}
  void setup() { pinMode(pin, OUTPUT); }
  void loop() { blink the blinky light }
  void onEntry() { led on, start timer as at now}
  void onExit() { led off }
}

And a couple of instances

LedBlinker red(4);
LedBlinker green(5);

The main loop code would 'think' entirely in terms of dealing with states:

State& currentState;

Since references must always be set, you'd create an 'init' state as well, for before anything started blinking.

I mean - its unnecessary, but you could do it. Heck, you could even make the state machine itself a class with setup and loop methods, allowing you to create state machines simply by declaring them. The payoff of that degree of overengineering is that it becomes simple to change the sketch and make it do something extra, or different. Overkill for arduino projects.

It is important that states are exited and entered with the system correctly configured. Look at the REDLED case in this example code and note the sequence of commands that are executed when the time comes to change state. Some are needed to exit the current state cleanly, such as turning off the LED and other are needed for clean entry to the next state, such as saving the current time.

/*
 * State machine example
 * Requirement :
 * Blink one LED at a steady rate whilst turning on a series of LEDs for periods that are
 * different for each LED
 *
 * The single blinking LED is implemented using the BlinkWithoutDelay principle so that its
 * operation does not slow down or stop the operation of the remainder of the program
 *
 * Control of the other 3 LEDs is controlled using a "State Machine".
 * The program is in one of 3 states during which one of the LEDs is illuminated for
 * a period of time.  Again timing is implemented using millis().  Once the period for the
 * current state is over the program moves onto the next state in the series.
 *
 * NOTE : this is not necessarily the best way to write a program to meet the requirements
 * but it is written this way to illustrate the use of a "State Machine".  The Serial monitor
 * is used for feedback as to what is happening in the program so it can be tested without LEDs
 * Instead of turning an LED on for a period in each state any appropriate non blocking code for the current
 * state could be run such as testing for user input and changing state if it is detected.
 */

enum ledStates    //give the states names (actually numbers 0 to 3) to make reading the code easier
{
  REDLED,
  GREENLED,
  BLUELED
};

byte currentState;
const byte blinkLedPin = 3;
const byte redLedPin = 5;
const byte greenLedPin = 6;
const byte blueLedPin = 9;
const byte sequenceLedPins[] = {redLedPin, greenLedPin, blueLedPin};
unsigned long sequencePeriods[] = {1500, 3500, 5500};
const byte NUMBER_OF_LEDS = sizeof(sequenceLedPins) / sizeof(sequenceLedPins[0]);
unsigned long currentTime;
unsigned long blinkLedStartTime;
unsigned long sequenceLedStartTime;
unsigned long blinkLedPeriod = 300;

void setup()
{
  Serial.begin(115200);
  for (int led = 0; led < NUMBER_OF_LEDS; led++)    //set pinMode()s for LEDs
  {
    pinMode(sequenceLedPins[led], OUTPUT);
    digitalWrite(sequenceLedPins[led], HIGH); //turn all sequence LEDs off initially
  }
  pinMode(blinkLedPin, OUTPUT);
  digitalWrite(blinkLedPin, HIGH);  //turn off the blinkLed initially
  digitalWrite(sequenceLedPins[currentState], LOW);    //turn on the sequence LED for initial state
  currentState = REDLED;    //start in this state
  reportState();
}

void loop()
{
  currentTime = millis();   //used throughout loop() for consistent timing
  //let's start with the single blinking LED
  if (currentTime - blinkLedStartTime >= blinkLedPeriod)  //time to change the single LED state
  {
    digitalWrite(blinkLedPin, !digitalRead(blinkLedPin));
    blinkLedStartTime = currentTime;
    Serial.println(F("\tBLINK"));
  }
  //now we need to check which state we are in and run the appropriate code for it
  //note that by using millis() for timing we keep loop() running freely
  //so that we can blink the single LED
  //when the priod for the current state ends we set the entry conditions for the next state,
  //change the current state so that next time through the code for the next state is executed
  switch (currentState)
  {
    case REDLED:
      //when the priod for the current state ends we set the entry conditions for the next state,
      //change the current state so that next time through the code for the next state is executed
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = GREENLED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
    //
    case GREENLED:
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = BLUELED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
    //
    case BLUELED:
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = REDLED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
  }
}

void reportState()
{
  Serial.print(F("Now in state "));
  Serial.print(currentState);
  Serial.print(F(" for "));
  Serial.print(sequencePeriods[currentState]);
  Serial.println(F(" milliseconds"));
}