2 pushbutton on / off

Hi all, I'm new here and maybe this was already discussed.
I want to control an output, which can be switched on by 2 buttons and only be switched off by the opposite button. So, left on right off, right on left off.

Alferwards I need this to select 2 different loops, off loop 1, on loop 2
Thx

Maybe these would help:

quite often

here's one approach; look it over

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

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

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

How many buttons are there? Do you mean to say you have two buttons, one which should turn on the output, and another which should turn it off?

When you turn on the output, you could at the same time set a flag, which here is just a variable that will hold two values.

I suggest zero and one. So once you have your buttons working, if these lines are next

  if (thatVariable == 0) loop1();

  if (thatVariable == 1) loop2();

will handle the other part, executing either one function or the other, here just named loop1 and loop2.

You can come up with a better name than thatVariable.

a7

Maybe I can suggest some logic?
With both buttons using INPUT_PULLUP and wired to GND
so that LOW is down and HIGH is up...

If the output is OFF and either button changes from HIGH to LOW,

that button becomes the ON button, the output turns ON and only if the other button changes from HIGH to LOW does the output turn OFF.

Perhaps for testing, use the Arduino onboard led as the output?

That leaves details to fill in but does it describe what you want?

My suggestion is to set up global byte variables for:

output pin number
output state

output previous ON button == 255
output current ON button == 255
these two are to allow complex over time output actions taken

input pins numbers
input pins previous read and current read states, that start HIGH
make these variables arrays, if you add buttons you make the arrays bigger and the code won't need to grow.
number of inputs
input pin array index, start with 0

input pin pressed first to make output ON

And then in void loop

read one button, the index button into the index current state. If the index previous state == HIGH and the index current state == LOW, the index button was pressed.

If the button was pressed then
if output On button == 255, it becomes the index
else
if the ON button is the same button, ignore the press
else
the ON button has to be a different index, make it 255

set the index previous state = index current state

if index < number of inputs, index++, else index = 0

That is the input code, the output code in void loop is

If previous ON == 255 and current ON != 255
set output HIGH or begin complex output actions
else
if previous ON != 255 and current ON == 255
stop the output

set previous ON = current ON

finished, the input and output parts run over and over in loop()

This will give you code that you can modify to do more.

Only 2 buttons in total, both can turn on, and once turned on, only the opposit button can switch off

:+1:

OK, clear enough.

In addition to tracking whether the output is on or off (and should run loop1() or lop2() as required), you will have to also remember which button was responsible for turning it on, so you can test the buttons that are pressed subsequently to see if it is the other button now, and should turn the output off.

tracking and remembering stuff is the roll for variables. Here again without looking further I recommend conceptually assigning zero (0) to a button and one (1) to the other, and setting a variable like whichButton appropriately when a button is pressed and you are turning something on.

Then you can check, when you are looking for turning off presses, whether such a press is coming from the right button.

You will probably run into a little issue called contact bounce, a period of indeterminate reading of a button as it either opens or closes. This can be handled in many ways. A cheap and easy and low rent way to do this is place a delay(20); as one of the top level statements in you loop() function, so you are only looking at the switches like 50 times a second, infrequently enough so that contact bounce will not be seen - you look slow enough and never even see it.

With bouncing, when you press the right button to turn off the output, a bounce may almost instantly look like you are pressing the button again, which as I understand it woukd turn the thing right back on so fast you might never even see it was off...

a7

Perhaps a picture is worth one thousand words.

A simulation of a button that IS being pressed versus a button that WAS pressed.

sketch.ino for wokwi
byte button2pin = 2, button3pin = 3;          // configure button pins
unsigned long button3counter, button2counter; // configure button-press counters

unsigned long timer, timeout = 50;            // used in readButton2()
bool currentButtonState, lastButtonRead;      // used in readButton2()

void setup() {
  Serial.begin(115200);
  pinMode(button2pin, INPUT_PULLUP); // pin to button (n.o.) button to gnd
  pinMode(button3pin, INPUT_PULLUP);
  Serial.println("Press button 3 for \"IS BEING\" pressed.\nPress button 2 for \"WAS\" pressed.");
}

void loop() {
  readButton2(); // read the "was pressed" button in a stand-alone function
  readButton3(); // read "is pressed" button
}

void readButton2() {
  bool currentButtonRead = digitalRead(button2pin);             // read button pin

  if (currentButtonRead != lastButtonRead) {                    // if button pin changes...
    timer = millis();                                           // ...start a timer
    lastButtonRead = currentButtonRead;                         // ... and store current state
  }

  if ((millis() - timer) > timeout) {                           // if button change was longer than debounce timeout
    if (currentButtonState == HIGH && lastButtonRead == LOW) {  // ... and State is "NOT pressed" while Button "IS PRESSED"
      //==================================================
      digitalWrite(LED_BUILTIN, HIGH);                          // The button was pressed...
      showButton(button2pin, ++button2counter);;                // Put actions or function calls here
      digitalWrite(LED_BUILTIN, LOW);
      //==================================================
    }
    currentButtonState = currentButtonRead;                     // update button state
  }
}

void readButton3() {
  if (!digitalRead(button3pin)) { // button is pressed
    digitalWrite(LED_BUILTIN, HIGH); // LED on
    showButton(button3pin, ++button3counter); // increase button count and print
  }
  else(digitalWrite(LED_BUILTIN, LOW)); // LED off
}

void showButton(byte button, unsigned long count) {
  Serial.print("Button ");
  Serial.print(button);                        // print button pin
  Serial.print(button == 3 ? " IS " : " WAS"); // if "3" print "IS", else print "WAS"
  Serial.print(" pressed ");
  Serial.print(count);                         // print button press count
  if (count > 1)
    Serial.println(" times.");
  else
    Serial.println(" time.");
}
diagram.json for wokwi
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": 0, "left": 0, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -51.4,
      "left": 153.6,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn2",
      "top": -51.4,
      "left": -28.8,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo2",
      "top": -96,
      "left": -57.6,
      "attrs": { "text": "Button 3 Bounces\nshows 'IS' pressed" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo1",
      "top": -96,
      "left": 115.2,
      "attrs": { "text": "Button 2 DE-bounced\nshows 'WAS' pressed" }
    }
  ],
  "connections": [
    [ "nano:GND.2", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:2", "btn1:1.l", "green", [ "v0" ] ],
    [ "nano:GND.2", "btn2:2.r", "black", [ "v0" ] ],
    [ "nano:3", "btn2:1.r", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}

Nice. Maybe increase the baud rate to the more modern 115200 where the difference is more dramatic.

Wow. That makes a more realistic difference in bounces.

Maybe a more "visual" way can help to find a solution.
Train comes in from the left, triggers the crossing that was blinking white to blinking red and stays like that until the train passes a trigger on the right. And this action has to work also from the right to the left with the same triggers.

I looked at @xfpd's nice demonstartion of "is" versus "was" pressed. I offer this alternate drop in version of the readButton2() function ("was" pressed). It is a bit different but has the same elements:

A means for ignoring or riding over bouncing coupled with a test for a change of state, with actions taken when the button becomes pressed and when it gets released.

I found I had no need for currentButtonState, but the function maintains it identically (or is it inverted) to what @xpfd's would do.

Oops! readButton3() uses the build in LED, so my hope to see it toggle with each button2 press is in vain.


# define PRESST LOW

void readButton2() {
  if (millis() - timer < timeout) return;                       // too soon to look at button again

  bool currentButtonRead = digitalRead(button2pin) == PRESST;   // read button pin. true if pressed

  if (currentButtonRead != lastButtonRead) {                    // button has changed
    if (currentButtonRead) {
      digitalWrite(LED_BUILTIN, HIGH);                          // Rising edge: The button became pressed...
      button2counter++;
      showButton(button2pin, button2counter);                   // Put actions or function calls here
    }
    else {
      digitalWrite(LED_BUILTIN, LOW);                           // Falling edge: The button got released...
                                                                // Put actions or function calls here
    }

    currentButtonState = currentButtonRead;                     // update button state for whomever

    lastButtonRead = currentButtonRead;                         // remember the button
    timer = millis();                                           // and when you read it
  }
}

Nice. That works for my brain. Do we assume there is no need to worry about multiple trains?

But your problem is simplified by this new description. If there is only one train at a time, either switch going pressed (or whatever) should turn the signal on, and either should turn it off.

So maybe the original description should be followed as it has the challenge of figuring out if a switch should do anything (same switch can't turn off the signal).

a7

I should have noted that... because I tested it in every way... and failed back to not toggling.

I know you said you're new to this all what is the reason you want to do this project is it an assignment or just for yourself? I definately can show you how to do it. Your thinking loop statement but it's if/if else you want.

The problem is also in the detection, 1 train gives multiple input pulses, and the lenght and speeds of each train is also different so it would be difficult to work with a delay time. Once the input is high it should stay high. On the exit side the train then gives also multiple off pulses. There I could maybe work with a delaytime of 1min to set the system back to idle state

This is something I want to try for myself. I can make it with simple electronics but want to give this programming a try

something like this is usual handled by recognizing an event and capturing a timestamp and waiting some period of time since that timestamp to recognize the absence of the event. The reoccurnce of the event before the time expires maintain the condition.

const byte PinInp = A1;

const unsigned long MsecPeriod = 1000;
      unsigned long msecT;

bool cond;

// -----------------------------------------------------------------------------
void
loop (void)
{
    unsigned long msec = millis ();
    if (cond && msec - msecT >= MsecPeriod)  {
        cond  = false;
        Serial.println ("clr");
    }

    if (LOW == digitalRead (PinInp))  {
        msecT = msec;
        if (! cond)
            Serial.println ("set");
        cond  = true;
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin (9600);
    pinMode (PinInp, INPUT_PULLUP);
}

First I thought this could work with two "buttons" with a long debounce time. But then I thought debouncing is no problem, just retrigger a timer on exit as often as the contact closes and after the last contact + some "waiting time" fire the final event and reopen the crossing. It doesn't matter if a short or a long train is passing. 5 seconds after the last axel has passed, the crossing will be opened.

The example should work and be reusable for several crossings (no matter what kind of crossing you need, blinking LEDs,moving a servo etc...).
Just define the two contacts and two functions to operate your hardware for each crossing in your layout

the code
/*
    train crossing activated either from east or west
    https://forum.arduino.cc/t/2-pushbutton-on-off/1293385/7

    2024-08-21 by noiasca
    code in thread
*/

class Crossing {
  protected:
    const uint8_t pinA;     // contact low active
    const uint8_t pinB;     // contact low active
    const uint32_t delayOpen {5000};  // time after the last axcel has passed to open the crossing
    enum State {IDLE, FROM_A, FROM_B, WAIT_A, WAIT_B} state = IDLE;  // the states for the internal state machine
    uint32_t previousMillis = 0;     // time management
    using Callback = void(*)(void);
    Callback fctOpen, fctClose;      // callback functions for open/close the crossing

  public:
    Crossing(const uint8_t pinA, const uint8_t pinB, Callback fctClose, Callback fctOpen) : pinA(pinA), pinB(pinB), fctOpen(fctOpen), fctClose(fctClose) {}

    // to be called in setup
    void begin() {
      pinMode(pinA, INPUT_PULLUP);
      pinMode(pinB, INPUT_PULLUP);
    }

    // the FSM to be called in loop
    void update() {
      
      switch (state) {
        case IDLE:
          if (digitalRead(pinA) == LOW) {
            state = FROM_A;
            fctClose();
          }
          else if (digitalRead(pinB) == LOW) {
            state = FROM_B;
            fctClose();
          }
          break;
        case FROM_A:
          if (digitalRead(pinB) == LOW) {
            previousMillis = millis();
            Serial.println("first axel passed");
            state = WAIT_A;
          }
          break;
        case WAIT_A :
          if (digitalRead(pinB) == LOW) {
            previousMillis = millis();
          }
          if (millis() - previousMillis > delayOpen) {
            fctOpen();
            state = IDLE;
          }
          break;
        case FROM_B:
          if (digitalRead(pinA) == LOW) {
            Serial.println("first axel passed");
            previousMillis = millis();
            state = WAIT_B;
          }
          break;
        case WAIT_B :
          if (digitalRead(pinA) == LOW) {
            previousMillis = millis();
          }
          if (millis() - previousMillis > delayOpen) {
            fctOpen();
            state = IDLE;
          }
          break;
      }
    }
};

constexpr uint8_t lightPin {4};   // just as demo - this crossing as only a steady light 

void block() {
  Serial.println("what ever needed to block that crossing");
  digitalWrite(lightPin, HIGH);
}

void unblock() {
  Serial.println("what ever needed to unblock that crossing");
  digitalWrite(lightPin, LOW);
}

Crossing crossing(2, 3, block, unblock);  // a crossing with two contacts and two function what to do to block or unblock 

void setup() {
  Serial.begin(115200);
  crossing.begin();  // start the contact/button
}

void loop() {
  crossing.update();  
}
//

Wow, thanks, yes, that's it !
Now I just only need to implement the 2 blinking/fading loops of the lights

what patterns / fade / blink patterns (intervals) do you need during which phase (open/close)?