Making a push button behave like an SPST?

I was wondering if it would be possible to use interrupts to make a push button (+ pull up), which otherwise acts as a momentary switch, act like a regular wall SPST switch - stays ON after being pushed once, toggles to OFF after being pushed the next time, and so on. I want the state read from the button to control an LED for now. I have the following code:

int ledPin = 13;
int interruptPin = 2;
volatile bool ledState;

void isr()
{
    ledState = !ledState; // toggle state of LED
}

void setup() 
{
    pinMode(interruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(interruptPin), isr, FALLING);
    pinMode(ledPin, OUTPUT);
    ledState = LOW;
}

void loop() {
    digitalWrite(ledPin, ledState);  
}

My reasoning is this: human hands work slow (relative to the MCU's instruction cycles) and can hold down a switch for at least many milliseconds (a few hundred or a few tens if you are being deliberately quick with your fingers), meaning if I check for a LOW then many interrupts will be triggered within that time interval which will repeatedly toggle state, causing the LED to blink and settle finally on an in-deterministic state.

So, I chose to trigger an interrupt only when the voltage to the input pin is FALLING: the pin is pulled up via the internal resistor to HIGH and pressing on the push button shorts it to LOW - so there must be ONLY ONE FALLING EDGE - meaning for a single push on the button, only one interrupt should be generated, regardless of how long the push button is held down for, since. Thus the ISR will be run only once and the state of the LED will be toggled ONCE FOR EVERY PUSH OF THE BUTTON. Then control should return to main where the toggled state will be written, toggling the LED from ON to OFF or OFF to ON.

However what I expected did not happen and the LED is blinking erratically when I push on the button - it is settling down to the last in-deterministic value (on or off) when I let go of the button.
Any help is appreciated. Thanks.

Why do you need an interrupt? Push on push off is standard code for momentary buttons

Look up blink without delay, debounce, state machines

With a millis()-based 'debounce'.

I would use a button library for this, EButton is a very nice one in my opinion.

#include <EButton.h>

EButton button(2);

void click(EButton&) {
  static bool state = LOW;
  state = not state;
  digitalWrite(LED_BUILTIN, state);
}

void setup() {
  button.attachSingleClick(click);
}

void loop() {
  button.tick();
}

[edit]

Here is an interactive example to play with.

you mean with something like:

if (digitalRead(pushButtonPin) == LOW) {
    ledState = !ledState;
} 
digitalWrite(ledPin, ledState);

because then, the button would be held down long enough for the if block to be executed multiple times toggling the state to an indeterministic value. I could add delay but I want to avoid polling or delays so I can only perform calculations in the main loop.

If that is not what you meant, can you link me to some code?

Or you can fix your ISR to debounce.

This ignores all interrupts after one that toggles, for a period of 25 milliseconds:

void isr()
{
    static unsigned long lastTime;
    unsigned long now = millis();

    if (now - lastTime > 25) {
       ledState = !ledState; // toggle state of LED
       lastTime = now;
    }
}

But I srsly woukd not use interrupts in this case where they are absolute not needed.

a7

Okay so from the replies - I take it the main issue is with the switch bouncing? Is that all I need to address and is the 25 millisecond threshold arbitrary or something like a convention?

Use an oscilloscope and measure. 10 - 20 mS usually works fine. Just do some testing on the stuff You want to use.

Absolutely.

You can use the sketch on this page to measure the bounce for the switch you are using

2 Likes

Debouncing a button is arduino 101 so just do a search of this forum or Google and you will get lots of examples. There are button libraries that do it in an abstracted fashion but it is best to understand the concept. In different circumstances it is less needed than others and hardware can effect it, some people hardware debounce with capacitors.

2 Likes

Yes, unfortunately virtually all mechanical switches and push buttons bounce. If you decide to use an interrupt, one approach is to detach (disable) the interrupt inside the ISR. Then in your loop you repeatedly read the button state until it has been released continuously for, say, 250 millis. If at any point it bounces into the pressed state, you just start over counting. And then only when you pass that test, you re-enable the interrupt. So at any point in your loop, you would be in one of two modes - waiting for press, or waiting for release. This method prevents the bouncing from generating interrupts. But of course you can use the same method with polling, so interrupts aren't essential here.

1 Like

There's an app example for that.

You may well choose to use one debouncing button library or another, but it would be good to see real debouncing code and come to an understanding of how it works.

I happened to have made a small example this day from some posted code and added a bit to it on another thread recent.

The debounce interval needed can depend on the switches you are using. In some algorithms, response to a button transition is delayed by one or multiple passages of the debounce constant used. The one I link above does not have that character istic. It reacts immediately the button opens or closes, as would your ISR, and does the debounce in the same way: just ignore anything the switch does in the, say, 20 milliseconds following a closure or opening that was reacted to.

HTH

a7

250mS is a light-year to the uC :grimacing:

https://www.arduino.cc/reference/en/language/functions/interrupts/interrupts/

Some functions will not work while interrupts are disabled, and incoming communication may be ignored. Interrupts can slightly disrupt the timing of code, however, and may be disabled for particularly critical sections of code.

Yes.
As you've found, software debouncing won't prevent the ISR from re-triggering.
Only hardware debouncing can do this.

1 Like

Use an optical switch then instead of a mechanical one.........done.

1 Like

My opinion.
Never use potentially problematic interrupts for human-operated switches.
Avoid interrupts for machine-operated mechanical switches.
Only use interrupts for switches that need millisecond attention, like motor speed/position hall/optical/magnetic sensors.

Use polling for human-operated switches, with a loop time short enough so humans don't notice any delay. Debounce time (if the code needs it) should be handled with millis(), not with delay().
So the loop() doesn't 'hang' waiting for that delay().
Leo..

2 Likes

Perhaps this is just an experiment to see how to use interrupts?
If so then you have achieved your objective, and learnt a lot in the process.

The easy way to make the program work is to add a small capacitor across the switch contacts.

As I explain here

(sorry for repeating that but - 250msec? what kind of switch is THAT?)

If you want to continue the experiment you can
disable the interrupt after its activated,
poll the switch until you get a stable LOW value,
re-enable the interrupt

HOWEVER
interrupts are normally used when you have a background program running continuously
so you could:
move the

digitalWrite(ledPin, ledState);

into the ISR, and

run the blink (or blinkwithoutdelay) example in your loop, to simulate a background task.

Then - maybe - it will help you understand why interrupts should be avoided unless there is a real need and the response can be very rapid.

1 Like

The only advantage I see with interrupts for a mechanical human-operated switch is a multi-person reaction game, to see who bounced the switch first.
Leo..

1 Like

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