4

I want to detect if a push button was pressed and released again. So I thought the right approach would be to first wait while the pin yields LOW and then wait while the pin yields HIGH:

void push(int pin) {
  // wait until button was pushed down...
  while (digitalRead(pin) == LOW);
  // ... and released again
  while (digitalRead(pin) == HIGH);
}

That function can be easily reused. The code works fine, but I am just wondering if that solution is considered clean and a good practice.

2
  • You can ask google how they solve this problem in c with microcontrollers. For the search you can use: button debouncing microcontroller or button state change microcontroller. I think the solution above is only good, when you run this function only one time. In this case the state changes (bouncing) will do nothing. For example if you want to count the presses find another solution.
    – user40798
    Dec 29, 2017 at 18:04
  • it only supports one button, so that's kind of weak.
    – dandavis
    Dec 31, 2017 at 1:44

3 Answers 3

4

No, I wouldn't consider that good practice - for one good reason: it's blocking.

Your system can't be doing anything else at all while you're waiting on the button. That may be fine for some situations, but far from ideal for 99% of others.

It is better to remember the state of the button and detect that change of state.

Personally, to make it properly portable, I would implement a class to contain all the state information. Something like:

class Button {
    private:
        bool _state;
        uint8_t _pin;

    public:
        Button(uint8_t pin) : _pin(pin) {}

        void begin() {
            pinMode(_pin, INPUT_PULLUP);
            _state = digitalRead(_pin);
        }

        bool isReleased() {
            bool v = digitalRead(_pin);
            if (v != _state) {
                _state = v;
                if (_state) {
                    return true;
                }
            }
            return false;
        }
};

Then you can do something like:

Button myButton(3);

void setup() {
    myButton.begin();
    Serial.begin(115200);
}

void loop() {
    if (myButton.isReleased()) {
        Serial.println(F("Released"));
    }
}

Every time myButton.isReleased() is called the current button state is compared against the state it was last time it was called. If it's changed then the button must either have been pressed or released. So you remember that change. If it's been released then you return a true, otherwise return a false.

You can now easily have it work with many buttons:

Button myButton(3);
Button myOtherButton(4);

void setup() {
    myButton.begin();
    myOtherButton.begin();
    Serial.begin(115200);
}

void loop() {
    if (myButton.isReleased()) {
        Serial.println(F("Released 1"));
    }
    if (myOtherButton.isReleased()) {
        Serial.println(F("Released 2"));
    }
}
1
  • Thank you, the blocking might clearly be an issue, however not in my specific use case (a reaction time game). I didn't use C++ features yet, but for abstracting a state, it's clearly a good idea. Dec 29, 2017 at 15:15
4

I concur with Majenko, although your code would work, the code is blocking, meaning that your code stops at that point until the button is released again. The remaining code in your loop does not execute until the button is resolved.

A much better method is to check the state of the button in each passing of the loop. If the state changes (ie goes low) you can set a flag. Then when you read that the button is high again and the flag has been set then you execute the appropriate code. For example:

const int buttonPin = 2;     // the number of the pushbutton pin
boolean buttonWasLow = false;         // variable flag for when the pushbutton goes low

void setup() {
    pinMode(buttonPin, INPUT);    // initialize the pushbutton pin as an input:
}

void loop() {
    // read the state of the pushbutton and set a flag if it is low:
    if (digitalRead(buttonPin) == LOW)  {
        buttonWasLow = true;
    }

    // This if statement will only fire on the rising edge of the button input
    if (digitalRead(buttonPin) == HIGH && buttonWasLow)  {
        // reset the button low flag
        buttonWasLow = false;

        // Button event here
    }
}

My favorite way to detect a rising edge of an input is actually much simpler:

void loop() {
    button = digitalRead(buttonPin);

    // If the button is pressed
    if (button && !buttonLast)
    {
        // Button event here
    }

    // Update button flag
    buttonLast = button;
}

user8886193 brings up the very good point about button debounce.

Bouncing is the tendency of any two metal contacts in an electronic device to generate multiple signals as the contacts close or open; debouncing is any kind of hardware device or software that ensures that only a single signal will be acted upon for a single opening or closing of a contact.

I prefer to debounce the pushbutton inputs through hardware by adding a 100nF (or larger) from the input pin to ground. Note that this requires a 10K (or larger) resistance in series with the button circuit in order for the capacitor to charge/discharge.

Alternatively, in software, the most common debounce for beginners is adding a delay such as delay(100);, which would look something like this:

void loop() {
    button = digitalRead(buttonPin);

    // If the button is pressed
    if (button && !buttonLast)
    {
        // Button event here
        delay(100);  // <---- Delay inserted after the button event
    }

    // Update button flag
    buttonLast = button;
} 

This has the downside that it is blocking for 100ms while the delay executes. A better method is to debounce without a delay. One of the built in examples demonstrates button debounce. The main loop is shown below:

void loop() {
    // read the state of the switch into a local variable:
    int reading = digitalRead(buttonPin);

    // check to see if you just pressed the button
    // (i.e. the input went from LOW to HIGH), and you've waited long enough
    // since the last press to ignore any noise:

    // If the switch changed, due to noise or pressing:
    if (reading != lastButtonState) {
        // reset the debouncing timer
        lastDebounceTime = millis();
    }

    if ((millis() - lastDebounceTime) > debounceDelay) {
        // whatever the reading is at, it's been there for longer than the debounce
        // delay, so take it as the actual current state:

        // if the button state has changed:
        if (reading != buttonState) {
            buttonState = reading;

            // only toggle the LED if the new button state is HIGH
            if (buttonState == HIGH) {
                ledState = !ledState;
            }
        }
    }

    // set the LED:
    digitalWrite(ledPin, ledState);

    // save the reading. Next time through the loop, it'll be the lastButtonState:
    lastButtonState = reading;
}
7
  • 1
    I usually put a capacitor next to my push button in order to get a clean up/low signal right away. Jan 1, 2018 at 11:28
  • @sa_leinad could you comment on why you chose the capacitor value (100nF) you did? Feb 20, 2018 at 17:41
  • The values come from the RC timing constant t = RC. You want to aim for a few milliseconds to get any reduction in the button switching noise. A good article is from the Embedded with Elliot blog: hackaday.com/2015/12/09/…
    – sa_leinad
    Feb 22, 2018 at 1:30
  • The voltage across the capacitor rises slowly in spite of the bouncing switch, and increasing the component values will make it even slower to reach the logical high. Component values could be changed about until the ideal charge and discharge rates cause a delay that outlasts the bouncing and a stable interval has passed. - Excerpt from Debouncing switches in hardware and software
    – sa_leinad
    Feb 22, 2018 at 1:36
  • "I usually put a capacitor next to my push button" Yep, a 1uF capacitor across the button. No need to fiddle with code. See Nick Gammon's excellent switch tutorial gammon.com.au/switches that covers all kinds of conceivable situations (high or low, GND or +5V, internal pull-up or pull-down, capacitor debounce or software debounce, etc.) Jul 24, 2018 at 8:33
4

"I usually put a capacitor next to my push button"

A 1uF capacitor across the button is easy, cheap and means no need to fiddle with debouncing codes. See Nick Gammon's excellent switch tutorial that covers almost any conceivable situation when using switches with Arduinos and clones (high or low, GND or +5V, internal pull-up or pull-down, capacitor debounce or software debounce, etc.). In any case, Nick Gammon has many great explanatory examples on his site and where else he comments online.

1
  • 1
    +1 for the link to Nick Gammon's tutorial, it is indeed excellent! Jan 4, 2019 at 15:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.