Button + Interrupt

Please take a look at the below simple code for ISR button detection,

image

>> Wokwi Simulator <<

#define buttonPin 2
#define ledPin    5
bool reset;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);   // Button Pin Mode
  pinMode(ledPin, OUTPUT);            // LED Pin Mode
  pinMode(LED_BUILTIN, OUTPUT);       // LED_BUILTIN Pin Mode
  attachInterrupt(digitalPinToInterrupt(buttonPin), ISR_BUTTON, FALLING); // Attached Interrupt with edge mode
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // Turn LED_BUILTIN On
  digitalWrite(ledPin, HIGH);       // Turn LED On
  delay(1000);                      // Delay 1000 ms
  digitalWrite(LED_BUILTIN, LOW);   // Turn LED_BUILTIN Off
  digitalWrite(ledPin, LOW);        // Turn LED Off
  delay(1000);                      // Delay 1000 ms
}

void yield() {
  if (reset) {                      // Check Reset state
    digitalWrite(ledPin, LOW);      // Turn LED Off
    reset = false;                  // Clear reset state
  }
}

void ISR_BUTTON() {
  reset = true;                     // Set reset state
}

@microbeaut, you should ask a moderator to move this to its own thread where it can get exclusive attention.

It is a misuse of an interrupt to use it for a human activated event. Polling in a decently tight loop() will not miss button presses. Using an interrupt to catch a button press because of delay()s on the code is a crutch at best. Well written code does not need or use interrupts for that purpose.

1 Like

@moderator: move this post together with the post of user @microbeaut

This is a demo-code that shows

  1. how to organise code in functions where each function does ONE thing
  2. how to do timing in a non-blocking way
  3. how to convert a momentary push-button into a toggle-switch
/*
  Arduino Forum
  Topics:       Delay() in main loop preventing ISR button detection
  Sub-Category: Programming Questions
  Category:     Using Arduino
  Link:         https://forum.arduino.cc/t/delay-in-main-loop-preventing-isr-button-detection/1148422/14
*/

const byte buttonPin = 2;
const byte pressed   = LOW;
const byte unPressed = HIGH;

const byte ledPin    = 5;

unsigned long myBlinkTimer;
unsigned long myDebounceTimer;

boolean myLedState = LOW;
boolean ledpinActive = true;

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");

  digitalWrite(ledPin, LOW);
  digitalWrite(LED_BUILTIN, LOW);

  pinMode(buttonPin, INPUT_PULLUP);   // Button Pin Mode
  pinMode(ledPin, OUTPUT);            // LED Pin Mode
  pinMode(LED_BUILTIN, OUTPUT);       // LED_BUILTIN Pin Mode
}


void loop() {
   
  ledpinActive = GetToggleSwitchState(buttonPin); 

  // check if 1005 milliseconds have passed by
  if ( TimePeriodIsOver(myBlinkTimer,1005) ) {
    // when REALLY 1005 milliseconds HAVE passed by
    if (myLedState == LOW) {
      myLedState = HIGH; // invert from LOW to HIGH
    }
    else {
      myLedState = LOW; // invert from HIGH to LOW
    }
  }

  digitalWrite(LED_BUILTIN, myLedState); // switch LED On/Off

  // check if ledpin shall be switched on/off
  if (ledpinActive == true) {
    // only in case ledpinActive 
    digitalWrite(ledPin, myLedState); // switch LED on/off
  }
  else {
    digitalWrite(ledPin, LOW); // keep LED off
  }
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}


bool GetToggleSwitchState(byte pinNumber) {
  // "static" makes variables persistant over function calls
  static bool toggleState     = false;
  static bool lastToggleState = false;

  static byte buttonStateOld = unPressed;
  static unsigned long buttonScanStarted  =  0;
  unsigned long buttonDebounceTime = 50;
  static unsigned long buttonDebounceTimer;

  byte buttonStateNew;

  if ( TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
    // if more time than buttonDebounceTime has passed by
    // this means let pass by some time until
    // bouncing of the button is over
    buttonStateNew = digitalRead(pinNumber);

    if (buttonStateNew != buttonStateOld) {
      // if button-state has changed
      buttonStateOld = buttonStateNew;
      if (buttonStateNew == unPressed) {
        // if button is released
        toggleState = !toggleState; // toggle state-variable
      } // the attention-mark is the NOT operator
    }   // which simply inverts the boolean state
  }     // !true  = false   NOT true  is false
  //       !false = true    NOT false is true
  return toggleState;
}

best regards Stefan

Is your function good enough for toggle with debounce?
If you keep a debouncing delay of 50 milliseconds, How does your function work when sometime the bounce is more than 50 ms? For example, the bounce is 250 ms.

If your switch has 1/4 second of bounce, get a new switch!

5 Likes

I agree.
Thank you for your advice.

With the function as it is right now the function would interpret the bouncing as normal push

As in coding you have to specify almost everything you could change the number

unsigned long buttonDebounceTime = 50;

to 400

unsigned long buttonDebounceTime = 400;

but as a consequence you would have to hold down the button for at least 401 milliseconds
to get the normal reaction. Without knowing that, this particular button beeing extremely bouncy a user would be astonished about the unreliable / unpredictable function of the button.

And there would be no way in software that would solve this problem better in this application.

The only situation where such a super-bouncy button could still be used would be a state-machine where you have one state waiting for the button to be pressed down as the starting-signal for something and the following-up step is executed at least longer than the bounce-time of the button to avoid an unintended restart.

best regards Stefan

Hello microbeaut

As already mentioned the interrupt technology isn´t needed to read a button state via an arduino.

The most common design is to scan the port pin connected to a button.

As base for this scanner the BlinkWithOutDelay example used as timer and StateChangeDetection example can be used.

All these examples can be found in the IDE.

Give them a try. Play around and learn to gain the knowledge.

Have a nice day and enjoy coding in C++.

1 Like

You should declare the common variable (i.e. reset) as volatile.

Basically by declaring the variable as volatile, yiu are telling the compiler that the content of the variable can change at random and unpredictable times. This allows the compiler to cater for that situation when optimising the generated machine code.

However, in your example it probably wont make much difference, but it is a useful habit to acquire if you plan to write code that shares variable with ISRs. There are several other techniques you need to learn - especially if you use data that is larger than what a single machine instruction can process (e.g. a 32 bit value) as a shared volatile variable.

1 Like

Instead of changing the number, why don't you improve the function by detecting rising and falling edges?

Agree that a debounce function can be done by using the state machine.
Without the state machine, please take a look at the below code to do the button debounce,

Debounce Time: 10 ms
Bounce Generator: 50 ms (can be adjusted in the simulator)

// Define the input button pin
#define buttonPin 2

// Define the pin of the LED to be used to display the status of the toggle.
#define ledPin    5

// Define the debounce delay for button debouncing
#define TIME_DEBOUNCE 10UL    // 10 milliseconds

// Define the blink delay for LED blinking
#define TIME_BLINK 500UL      // 500 milliseconds

// Button structure/object with debounce function
typedef struct {
  uint8_t pin;                // Button pin number
  bool pullup;                // Pullup flag
  bool state;                 // Button state
  bool input;                 // Input State
  unsigned long startTime;    // Start Time

  void mode(uint8_t pinNumber, uint8_t inputMode);  // Button mode function
  bool read();                                      // Button read function
} button_t;

// Declare the button structure/object.
button_t button;

// Declare the toggle variable.
bool toggle;

void setup() {

  // Setup the button mode (INPUT or INPUT_PULLUP)
  button.mode(buttonPin, INPUT_PULLUP);

  // Setup LED pin mode for toggle state.
  pinMode(ledPin, OUTPUT);

  // Setup LED pin mode for blinking status.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // Reading the button press state to toggle the toggle variable
  if (button.read()) toggle = !toggle;

  // Set the LED output with a toggle state.
  digitalWrite(ledPin, toggle);

  // Set the LED output with a blinking state.
  digitalWrite(LED_BUILTIN, Blink());
}

/*
  Function: Blink
  Para:     -
  Return:   -
*/
bool Blink() {
  // Declare the blink static variable.
  static bool blink = true;

  // Declare the start time static variable
  static unsigned long startTime;

  // Save the current time in milliseconds.
  unsigned long currTime = millis();

  //Return the blink state if the elapsed time is less than the blink delay.
  if (currTime - startTime < TIME_BLINK) return blink;

  // The elapsed time is longer than or equal to the blink delay.
  // Reset the start time
  startTime = currTime;

  // Toggle the blink state
  blink = !blink;

  // Return the blink state
  return blink;
}

/*
  Function: read
  Para:     pinNumber: buttton pin number
            inputMode: Button pin mode
  Return:   pressed state
*/
void button_t::mode(uint8_t pinNumber, uint8_t inputMode) {
  // Set the PIN into the pin variable.
  pin = pinNumber;

  // pullup = false when the inputMode is INPUT mode.
  // pullup = true when the inputMode is INPUT_PULLUP mode.
  pullup = inputMode == INPUT_PULLUP;

  // Initial button state with pullup state in release condition
  // INPUT mode, release = false.
  // INPUT_PULLUP mode, relase = true.
  state = pullup;

  // Set button pin mode
  pinMode(pin, inputMode);
}

/*
  Function: read
  Para:     -
  Return:   pressed state
*/
bool button_t::read() {
  // Save previous reading input to a local variable.
  bool prevInput = input;

  // Read the state of the button into the input variable.
  input = digitalRead(pin);

  // If the input does not change the state, return false.
  // Return false as release state.
  if (input == state) return false;

  // The input is different from the button state.
  // Save the current time in milliseconds.
  unsigned long currTime = millis();

  // If the input changes due to press/release or noise.
  // Reset the start time
  if (input != prevInput) startTime = currTime;

  // If the elapsed time is less than the debounce delay.
  // Return false as release state.
  if (currTime - startTime < TIME_DEBOUNCE) return false;

  // The input changed state longer than or equal to debounce delay.
  // The button state takes the actual current input state.
  state = input;

  // Return pressed one scan time.
  // INPUT mode: pressed, input = true and pullup = false.
  // INPUT_PULLUP mode: pressed, input = false and pullup = true.
  return state == !pullup;
}

button_t::read() Timing Diagram

Thank you for your advice :heart:

Thank you for your advice.

1 Like

Debounce is not always necessary. For instance a limit switch or emergency shut down should respond to the first indication of a state change. Switch bounce is not relevant.

I agree with you on instance function/action. The button shall stop a motor and shutdown a process when pressed (or wire break).
Post #3, implement a function for the toggle. The toggle state can be an error when the bounce time is more than the debounce time.

Somehow advanced code.
You did not write anything about your programming-knowledge.
Based on your initial posting I was estimating rather low knowledge-level as the program is pretty simple and can be improved with things like volatile-attribute etc.

Your (@microbeaut ) code in post # 10 is somehow advanced.
The code in post # 10 uses structs that include functions.

Did you write this code yourself?
You added comments to the code. These comments explain a part of the things.
But the comments do not explain the basic principle how structs work and how the relation between the struct and the member-functions work.

This makes it hard to understand for beginners.
If a beginner would like to modify the given code for a different thing I estimate he would have a hard time with a lot of experimenting what to modify in which way.
There is quite a good amount of implicit knowledge that a programmer must have to understand it in detail and to modify it.

My suggestion to this is:
either: hide-away all the details the same way all the details how a digitalWrite() hides away all the low-level-stuff by putting everything into a library and then write a documentation or intensive commented demo-codes.
or
use simpler code and add plenty of comments to make it easy to understand.

From reading through your code I see no improvements. I guess this is mainly is caused by my own limited knowledge. If you can explain the advantages please do so.
best regards Stefan

Yes, I did.

My English is not too good.

Please try to test the simulator, then replace all with your code (debounce 10ms, bounce generator 50 ms)

I have no comment on your TimePeriodIsOver. But you do the debounce without pressing the button or checking any rising or falling edge—you interval run TimePeriodIsOver then check the button state. I think you know what it means.

When I test your function with a five-second bounce, the ledpinActive will toggle, which it shouldn't because you claim that you have your debounce. Please take a look at the simulator for your toggle switch state.

image

You can also run the simulator on post #10 to compare your result.
Or you can also run the simulator with the ezButton library to compare with your function.
NOTE: I tried to update a comment for you to understand the button_t::read function.

I hope you can learn how to rewrite your debounce code from the button_t::read and button_t::read() Timing Diagram.

μB

Tuning in late, sry. I like your diagram, but I have said elsewhere that switches are a special case and that

  • open switches do not capriciously close and

  • closed switches do not capriciously open

that is to say that a press bounces but will ultimately settle closed and a release bounces but will settle open. If your switches do either, get new switches.

So the pattern I use immediately reports the event of being pressed or released indicated by the first beginnings of the transition, then ignores the switch for the bounce period.

Your debouncing is more robust and might be employed to deglitch a signal where there are transitions outside those around human acts.

Like a noisy digital signal from a receiver.

Some switches might open briefly, such as an alarm switch on a window, what could very briefly open do to rattling from the wind or a truck dribing by.

if the debounce period has not elapsed, return.

if the button differs from the internal state
     raise the appropriate flag (press or release event)
     change the internal state and
     start the lockout debounce period    

I've dressed it up many ways, including going all C++ on it to the very limited extent of my ability with the features it has that C does not.

Usually though it's as simple as the pseudocode above.

This is the update method of a half-baked C++ button object, it is meant to be called at the top of the loop function for all button objects:

  void update()
  {
//    unsigned long now = millis();  // usually I have a global "now"

    if (now - lastTime < DEBOUNCE) return;
  
    unsigned char raw = !digitalRead(thePin);   // 1 = pressed, button pulled up.

    if (state != raw) {
      if (raw) iPress = 1;
      else iRelease = 1;

      state = raw;
      lastTime = now;
    }
  }

In the wokwi, I cheat and turn off the simulated bounce.

IRL I have used a small capacitor and debounce thereby in hardware. Not very often, but it works well and affords instant switch down recognition and a small constant delay on switch up reporting.

I don't like any method that delays switch down reporting, and I don't like techniques that take a non-constant delay to report.

I use interrupts when they are unavoidable. I can't recall using one for a pushbutton.

a7

1 Like

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