Single Button Input Controlling Multiple Outputs

Hello All!!! I've been playing with this code for some time and still have not found a way to manage to get 1 input to control multiple outputs based on StateChange of the button. In english what I'm trying to achieve...

  1. 1st Press (LED1 == HIGH)
  2. 2nd Press (LED1 and LED2 == HIGH)
  3. 3rd Press or 5000 milliseconds (LED1 and LED2 == LOW) (back to start)
    - Was also hoping for a timeout function after 2nd Press that would loop back to start. So either 3rd press or timeout of 5000 milliseconds would restart the loop.

My research has taken me to "Counting Button Presses" "Different Cases according to which press we're on" and Just state change in general because it is such a short loop.

I think the simplicity of the program is eating at my complexity lol.

Thank you all so much in advance for helping me understand what direction to take!

#define LED_1_PIN 8
#define LED_2_PIN 9
#define BUTTON_PIN 1

unsigned long debounceDuration = 50; // millis
unsigned long lastTimeButtonStateChanged = 0;
byte lastButtonState = HIGH;

void setup(){
  pinMode(LED_1_PIN, OUTPUT);
  pinMode(LED_2_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop(){

  unsigned long timeNow = millis();
  if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
   byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
        
        digitalWrite(LED_1_PIN, HIGH);
}

 else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
          byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
        
        digitalWrite(LED_1_PIN, HIGH);
        digitalWrite(LED_2_PIN, HIGH);
}

else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
   byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
 
  digitalWrite(LED_1_PIN, LOW);
  digitalWrite(LED_2_PIN, LOW);
 }
}
}
}
}
}
}
}

This Set-Up is turning the LED on with the 1st PRESS, but NOT turning both on with 2nd PRESS, and NOT resetting with 3rd PRESS. I haven't jumped into the 5000 milllis yet... just trying to get a grasp before adding the timeout function.

Thanks again all!

First things first.

#define BUTTON_PIN               1

If this is an Arduino UNO that you are using, pin 1 is used for TX communication.

Are you aware of this ?

Hello Larry,

Yes, I've just been building this sketch digitally on tinkercad and used the first slot, I will switch that to PIN 2 right now! Thank you!

You have not sent use the complete sketch, will please reattach ?

#define LED_1_PIN 8
#define LED_2_PIN 9
#define BUTTON_PIN 2

unsigned long debounceDuration = 50; // millis
unsigned long lastTimeButtonStateChanged = 0;
byte lastButtonState = HIGH;

void setup(){
  pinMode(LED_1_PIN, OUTPUT);
  pinMode(LED_2_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop(){

  unsigned long timeNow = millis();
  if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
   byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
        
        digitalWrite(LED_1_PIN, HIGH);
}

 else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
          byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
        
        digitalWrite(LED_1_PIN, HIGH);
        digitalWrite(LED_2_PIN, HIGH);
}

else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
   byte buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState != lastButtonState) {
      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
 
  digitalWrite(LED_1_PIN, LOW);
  digitalWrite(LED_2_PIN, LOW);
 }
}
}
}
}
}
}
}

give this a look over..

#define LOOP_STATE_STOPPED 0
#define LOOP_STATE_STARTED 1
#define LED_1_PIN 8
#define LED_2_PIN 9
#define BUTTON_PIN 2
//state machine var
int loopState = LOOP_STATE_STOPPED;

//start out on last state
//will flip to 0 first run..
byte currentState = 2;//leave at 2 or world ends..
//var for button debouncing..
unsigned long lastPress;
int intervalDebounce = 250;
//vars for auto reset..
unsigned long lastReset;
int intervalReset = 5000;


void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_1_PIN, OUTPUT);
  pinMode(LED_2_PIN, OUTPUT);
  Serial.println("Ready..");
}

void loop() {

  unsigned long now = millis();

  //debounce button read..
  if (now - lastPress >= intervalDebounce) {
    if (!digitalRead(BUTTON_PIN)) {
      if (loopState == LOOP_STATE_STOPPED) {
        lastPress = millis();
        loopState = LOOP_STATE_STARTED;
        //increment state on each press..
        currentState++;
        //setup for auto reset
        if (currentState == 1) lastReset = millis();
        //roll over if needed..
        if (currentState > 2) currentState = 0;//
      }
    }
  }

  // only enter state machine if loop is started..
  if (loopState == LOOP_STATE_STARTED) {
    //state machine using a switch case
    switch (currentState) {
      //first press
      case 0: { 
          Serial.println("State 0");
          digitalWrite(LED_1_PIN, HIGH);
          digitalWrite(LED_2_PIN, LOW);
          loopState = LOOP_STATE_STOPPED;
          break;
        }
      //second press
      case 1: { 
          Serial.println("State 1");
          digitalWrite(LED_1_PIN, HIGH);
          digitalWrite(LED_2_PIN, HIGH);
          loopState = LOOP_STATE_STOPPED;
          break;
        }
      //third press
      case 2: {
          Serial.println("State 2");
          digitalWrite(LED_1_PIN, LOW);
          digitalWrite(LED_2_PIN, LOW);
          loopState = LOOP_STATE_STOPPED;
          break;
        }
    }
  }

//timed auto reset routine
  if (currentState == 1) {
    if (millis() - lastReset >= intervalReset) {
      currentState = 2;
      Serial.println("Auto-State 2");
      digitalWrite(LED_1_PIN, LOW);
      digitalWrite(LED_2_PIN, LOW);
    }
  }

}

have fun.. ~q

2 Likes

maybe a bit simpler

# define LED_1_PIN 8
# define LED_2_PIN 9
# define BUTTON_PIN 2

byte lastButtonState;
int  state;

unsigned long msecLast;

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    switch (state) {
    case 0:
        digitalWrite (LED_1_PIN, LOW);
        digitalWrite (LED_2_PIN, LOW);
        break;

    case 1:
        digitalWrite (LED_1_PIN, HIGH);
        break;

    case 2:
        digitalWrite (LED_1_PIN, HIGH);
        digitalWrite (LED_2_PIN, HIGH);

        msecLast = msec;
        state = 3;
        break;

    case 3:
        if (msec - msecLast >= 5000)  {
            Serial.println ("timeout");
            state = 0;
        }
        break;
    }

    byte buttonState = digitalRead (BUTTON_PIN);
    if (lastButtonState != buttonState) {
        lastButtonState = buttonState;
        delay (40);     // debounce

        if (LOW == buttonState)  {
            if (4 == ++state)
                state = 0;

            Serial.println (state);
        }
    }
}

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

    pinMode (LED_1_PIN, OUTPUT);
    pinMode (LED_2_PIN, OUTPUT);

    pinMode (BUTTON_PIN, INPUT_PULLUP);
    lastButtonState = digitalRead (BUTTON_PIN);
}
2 Likes

Thank you so much LarryD, qubits-us, and gcjr for the quick and mega helpful responses. I will dissect all 3 options and learn the functionality between them!! So many different ways to eat an apple haha

// or the world ends... :wink: lol

Happy Wednesday Everyone!!

:face_with_spiral_eyes: Damn, we need a way to flag we are doing something :woozy_face: or working of an example.

#define PUSHED                   LOW
#define RELEASED                 HIGH

#define LEDon                    HIGH
#define LEDoff                   LOW

#define heartbeatLED             13
#define LED_1_PIN                8
#define LED_2_PIN                9
#define BUTTON_PIN               2

byte pushCounter               = 0;
byte lastButtonStatus          = RELEASED;

//timing stuff
unsigned long heartbeatTime;
unsigned long checkSwitchesTime;
unsigned long lastTimeButtonStateChanged;

//********************************************^************************************************
void setup()
{
  pinMode(heartbeatLED, OUTPUT);
  
  pinMode(LED_1_PIN, OUTPUT);
  pinMode(LED_2_PIN, OUTPUT);
  
  pinMode(BUTTON_PIN, INPUT_PULLUP);

} //END of   setup()


//********************************************^************************************************
void loop()
{
  //************************************************              T I M E R  heartbeatLED
  //is it time to toggle heartbeat LED ?
  if (millis() - heartbeatTime >= 500ul)
  {
    //restart this TIMER
    heartbeatTime = millis();

    //toggle the LED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //************************************************              T I M E R  checkSwitchesTime
  //is it time to read the switches ?
  if (millis() - checkSwitchesTime >= 50ul)
  {
    //restart this TIMER
    checkSwitchesTime = millis();

    checkSwitches();
  }

  //************************************************              T I M E R  5 seond
  //is it time to turn off the LEDs ?
  if (pushCounter == 2 && millis() - lastTimeButtonStateChanged >= 5000ul)
  {
    pushCounter = 0;

    digitalWrite(LED_1_PIN, LEDoff);
    digitalWrite(LED_2_PIN, LEDoff);
  }

  //************************************************
  //other non blocking code goes here
  //************************************************

} //END of   loop()


//********************************************^************************************************
void checkSwitches()
{
  byte status;

  //*******************************************                       BUTTON_PIN
  status = digitalRead(BUTTON_PIN);

  if (lastButtonStatus != status)
  {
    //update to the new value
    lastButtonStatus = status;

    // button has been released ?
    if (pushCounter == 0 && status == PUSHED)
    {
      digitalWrite(LED_1_PIN, LEDon);

      pushCounter++;
    }

    else if (pushCounter == 1 && status == PUSHED)
    {
      digitalWrite(LED_2_PIN, LEDon);

      pushCounter++;

      //restart the TIMER
      lastTimeButtonStateChanged = millis();
    }

    else if (pushCounter == 2 && status == PUSHED)
    {
      digitalWrite(LED_1_PIN, LEDoff);
      digitalWrite(LED_2_PIN, LEDoff);

      pushCounter = 0;
    }
  }

} //END of   checkSwitches()

1 Like

Well @LarryD put his heart into it, so. :wink:

In that one, the if statements can be simplified a bit. It also leaves it more switch/case ready:

    // button has been released ?
    if (status != PUSHED)
        return;    // nope, nothing to do just now

// button action!

    if (pushCounter == 0)
    {
      digitalWrite(LED_1_PIN, LEDon);

      pushCounter++;
    }

    else if (pushCounter == 1)
    {
      digitalWrite(LED_2_PIN, LEDon);

      pushCounter++;

      //restart the TIMER
      lastTimeButtonStateChanged = millis();
    }

    else if (pushCounter == 2)
    {
      digitalWrite(LED_1_PIN, LEDoff);
      digitalWrite(LED_2_PIN, LEDoff);

      pushCounter = 0;
    }
  }

Multiple solutions are worth it every time. It is fascinating to see you guys' habits and style.

a7

2 Likes

Yes, I have a cat here with no skin :scream: .

1 Like

Thank you Alto777 for the additional information! You all are fantastic!

and yes, absolutely fascinating!!!!

I read @ErebusZT's code for the first time late in the thread. Just now, that is to say.

With all the solutions offered, no one took a look to see explained why the original sketch did not work. There was a coherent attempt to do this a right way. It seemed. What wrong?

Autoformating the code makes something unusual visible.

With printing added, it easy to put you finger on the code and see what is happening. And why it goes wrong. Just trace the flow. Naturally is is all entirely logical.

It's a good thing you stopped to ask, as you just might have the talent to fix something like this so it works. Def worth not doing.

The essence of all solutions offered is to remember what you are up to, so you can step along to the next thing. "Remembering" is done with the use of state variables, not, as was attempted, the structure of the code.

You can see that this code structure approach, even if it could be hammered into functioning, would not scale well. A few more steps and you would have a nightmare. Forget about counting to 42.

See it in the wokwi.

Wokwi_badge OP's code


The other solutions on offer can be copy/pasted over the source code in the simulator as all sketches used the same hardware configuratoin.


// https://wokwi.com/projects/374000430676422657
// https://forum.arduino.cc/t/single-button-input-controlling-multiple-outputs/1161323

# define LED_1_PIN 8
# define LED_2_PIN 9
# define BUTTON_PIN 2

unsigned long debounceDuration = 50; // millis
unsigned long lastTimeButtonStateChanged = 0;
byte lastButtonState = HIGH;

void setup() {
  Serial.begin(115200);
  Serial.println("I can see!\n");

  pinMode(LED_1_PIN, OUTPUT);
  pinMode(LED_2_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {

  unsigned long timeNow = millis();
  if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
    byte buttonState = digitalRead(BUTTON_PIN);

    if (buttonState != lastButtonState) {


      lastTimeButtonStateChanged = timeNow;
      lastButtonState = buttonState;
      if (buttonState == HIGH) { // button has been released
        Serial.println(" // button has been released ONE");
        digitalWrite(LED_1_PIN, HIGH);
      }

      else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
        byte buttonState = digitalRead(BUTTON_PIN);

        if (buttonState != lastButtonState) {
          lastTimeButtonStateChanged = timeNow;
          lastButtonState = buttonState;
          if (buttonState == HIGH) { // button has been released

            Serial.println(" // button has been released TWO");
            digitalWrite(LED_1_PIN, HIGH);
            digitalWrite(LED_2_PIN, HIGH);
          }

          else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
            byte buttonState = digitalRead(BUTTON_PIN);

            if (buttonState != lastButtonState) {
              lastTimeButtonStateChanged = timeNow;
              lastButtonState = buttonState;
              if (buttonState == HIGH) { // button has been released

                Serial.println(" // button has been released THREE");
                digitalWrite(LED_1_PIN, LOW);
                digitalWrite(LED_2_PIN, LOW);
              }
            }
          }
        }
      }
    }


  }
}

If you find yourself over on the right hand side of the screen deep in a statement dozens of lines long, it is probably a sign you are on the wrong track.

a7

1 Like

the code is written such that the the test for a subsquent button press is within the condition for the previous button "press", as if the code stops running and is waiting the the next button press or timer expiration

this may not be obvious from the improper formatting in the OP but is when properly formatted

#define LED_1_PIN 8
#define LED_2_PIN 9
#define BUTTON_PIN 2

unsigned long debounceDuration = 50; // millis
unsigned long lastTimeButtonStateChanged = 0;
byte lastButtonState = HIGH;

void setup(){
    pinMode(LED_1_PIN, OUTPUT);
    pinMode(LED_2_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);
}


void loop(){

    unsigned long timeNow = millis();
    if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
        byte buttonState = digitalRead(BUTTON_PIN);

        if (buttonState != lastButtonState) {
            lastTimeButtonStateChanged = timeNow;
            lastButtonState = buttonState;
            if (buttonState == HIGH) { // button has been released

                digitalWrite(LED_1_PIN, HIGH);
            }

            else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
                byte buttonState = digitalRead(BUTTON_PIN);

                if (buttonState != lastButtonState) {
                    lastTimeButtonStateChanged = timeNow;
                    lastButtonState = buttonState;
                    if (buttonState == HIGH) { // button has been released

                        digitalWrite(LED_1_PIN, HIGH);
                        digitalWrite(LED_2_PIN, HIGH);
                    }

                    else if (timeNow - lastTimeButtonStateChanged > debounceDuration) {
                        byte buttonState = digitalRead(BUTTON_PIN);

                        if (buttonState != lastButtonState) {
                            lastTimeButtonStateChanged = timeNow;
                            lastButtonState = buttonState;
                            if (buttonState == HIGH) { // button has been released

                                digitalWrite(LED_1_PIN, LOW);
                                digitalWrite(LED_2_PIN, LOW);
                            }
                        }
                    }
                }
            }
        }
    }
}

this code structure appears obviously wrong and suggests a lack of understanding of how code interacting with external devices works ... or maybe just an oversight because of the improper formatting

each event -- a button state change or timer expiration -- must be separately tested for within loop()

1 Like

Yes. As we saw in @alto777's post lucky #13 above.

The code can be analysed and flunked just by reading it, but running it in the simulator with but minimum printing installed, to see the flow, is instructive, the output

I can see!

 // button has been released ONE
 // button has been released ONE
 // button has been released ONE
 // button has been released ONE
 // button has been released ONE
 // button has been released ONE

would have steered the OP to look a bit closer at the way the flow is coded. Where sooner later the reason that every button press catches ONE and not the others becomes obvious.

I don't think I've gotten anything to work, and to work by doing what I thought I coded, without feedback beyond observation of the functioning sketch.

Such feedback is easily installed with printing, a classic technique what is very effective and easy. Some sketches will need to not print, for reasons. In those cases, LEDs, a frequency meter, a logic analyser or maybe even an oscilloscope represent the guns getting bigger.

In some cases, even bigger toys need to be brought out. Not so much these kinds of projects in the little toys we play with.

a7

1 Like

Hello All,

LarryD, my knight in shining armor, has solved the quest! BIG THANK YOU!

So I've been testing the codes and suggestions physically over the last 2 days and have the same nuances occurring with the other 2 iterations.
Upon Startup and Beginning of the loop, both LED's are off.
First Press works like a charm
2nd Press works like a charm


Here is where things are getting hairy lol.

Upon 3rd press in qubits-us and gcjr we are getting an inconsistency of getting back to the off state. Both versions will work majority of the time when physically changing the state, but getting different results when allowing the "timeout" reset to occur. Namely, not going back into an OFF position, but rather into the first state or button press.

Qubits-us version will actually cycle through the states if the button is pressed and held. But the timeout feature always resulted in "state 0" being on after timeout. Even though the code is stating "currentState=2 and LED1+2 LOW"

Gcjr's version worked in a manner that was intended. But somehow (debounce I'm guessing) would 50/50 result in either going back to the off state or to the first state. And this was with a physical or timeout reset.

LarryD, Thank you so much again! I've been comparing the codes, tweaking, and testing it in physical application... and for the love of skinned cats and apples, cannot understand the links I am missing.

So, I will humbly ask, for obvious educational purposes. In the 3 depictions, being compared to each other, where is the link between the cycle looping back to the "first button press" without there actually being a press.

And, LarryD, in your version. I can remain on the button for as long as I want, with no effect to the next state, except for the timeout... how is this possible here and not in the others.

I appreciate the criticism and knowledge, thanks all again for taking the time! Hope everyone has a great weekend! :slight_smile:

The sim below is the version I got by picking things I liked from all the previous solutions. I fed those snippets as well as the complete solutions and a brief prosaic description of the functionality to an AI I trained on 50K lines of my own Arduino code, and asked it to write a sketch to specification using my style.

I post the link to make your (whomever) experiments easy. Any complete sketch can be copied from here and pasted over my code in the sim. All use the same hardware configuration.

I hadn't noticed that the @qubits-us version will actually cycle. It does. I never saw it because I didn't test well enough to see it. I think

int intervalDebounce = 250;

in that code will be more like the period the button is ignored. I know I am usually off the button in that time, if I am just clicking through a cycle. This behaviour is by design or code error. I'm too lazy to look further. If you get off the button, you fine.

This does not agree with my observations. If you would cut and paste Gcjr's version into the sim over my code, you could eliminate the possibility of a wiring or hardware issue at your end.

Gcjr's version is compact, but I had trouble convincing myself just by reading it that it would work. Because the way a phantom state is used to wait out the timeout period or be left by button press and the different ways the state gets modified.

Both Qubits-us and @LarryD, and "I" too, separated the timeout code from the cycling code, which makes it a bit easier to see how the complete functionality is achieved, and easier to modify or turn off the timeout feature.


Wokwi_badge YA version, use for hardware


// https://wokwi.com/projects/374000220931822593
// https://forum.arduino.cc/t/single-button-input-controlling-multiple-outputs/1161323

# define led1   8
# define led2   9
# define theButton  2

# define debounceTime 25
unsigned char buttonState;
unsigned long buttonTimer;
unsigned char buttonCycle;

# define timeoutTime 3777
unsigned long timeoutTimer;

# define PRESST   LOW
# define ON       HIGH
# define OFF      LOW

# define heartLED             13
# define heartBit   0x80

void setup()
{
  Serial.begin(115200);
  Serial.println("Jello Whirled!\n");

  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);

  pinMode (theButton, INPUT_PULLUP);
  buttonState = digitalRead(theButton) == PRESST;
}

void loop()
{
  unsigned long now = millis();

  digitalWrite(heartLED, now & 0x200 ? HIGH : LOW);

  switch (buttonCycle) {
  case 0:
    digitalWrite (led1, LOW);
    digitalWrite (led2, LOW);
    break;

  case 1:
    digitalWrite (led2, HIGH);
    break;

  case 2:
    digitalWrite (led1, HIGH);
    digitalWrite (led2, HIGH);
    break;
  }

  if (now - timeoutTimer > timeoutTime && buttonCycle == 2) {
    buttonCycle = 0;
    timeoutTimer = now;   // don't really need to, cuts spam though.
  }

// too soon to even look at button again?
  if (now - buttonTimer < debounceTime) return;

// no let's look to see if it got pressed
  unsigned char button = digitalRead(theButton) == PRESST;

  if (buttonState != button) {
    buttonState = button;
    buttonTimer = now;    // haha, is also timeoutTimer, but
    timeoutTimer = now;

// button got pressed!
    if (button) {
      buttonCycle++;
      if (buttonCycle >= 3) buttonCycle = 0;
    }
  }
}

HTH

a7

if something looks like it should work (with an experienced eye) and doesn't, it's more educational to understand exactly why it doesn't work than to just abandon that approach.

this also helps develop debugging and testing skills and one simple fix may be all that's needed

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