I tried to follow the answer here: https://arduino.stackexchange.com/a/18545/51302

Unfortunately I can only get the interrupt to go once (on serial monitor is the time of that first interrupt), then nothing. I know this has already been answered but I don't understand the mistake and would appreciate help.

What I'm trying to do: get a button to set an interrupt to trigger foo and thus write the steps on the monitor.

I've left remarks to try and explain what I try to do, so it would be easier to understand what I left out or used incorrectly. Thanks.

#define IRF520 A2
#define BUTTON 3
static unsigned long last_press = 0;
static bool button_state = HIGH;             //pull up resistor
volatile bool interrupt_flag = 0;            //when the interrupt fires, button press or noise
bool is_pressed = false;                     //after checking that the button was really pressed
bool was_pressed = false;                    //last state of the button
unsigned long button_pause_duration = 20;        //pause required to accept press in ms
unsigned long interrupt_time=0;

void button_interrupt() {
  interrupt_flag = true;                      //can be a false alarm
  Serial.print("interrupt time: ");         //this is the onlt step that works

void setup() {
  pinMode(IRF520, OUTPUT); //A2 controls IRF520
  pinMode(BUTTON, INPUT_PULLUP);  //pin D2 and D3 are sutible for interrupts
  attachInterrupt(digitalPinToInterrupt(BUTTON), button_interrupt, FALLING);

void debounce_button() {
  if (digitalRead(BUTTON) != button_state) {
    button_state = digitalRead(BUTTON);         //if button changed state remember the new state
  if (button_state == LOW) {
    last_press = millis();
    Serial.print("last_press was updated to: ");
  } else {
    last_press = 0;
    interrupt_flag = false;
  if ( last_press && (millis() - last_press) > button_pause_duration) {
    last_press = 0;     //set to 0 for the next check
    is_pressed = true;   //button was really pressed
    Serial.print("last_press was set to 0 and is_pressed to true - BUTTON WAS PRESSED at: ");

unsigned int foo(bool is_pressed) {
  if (is_pressed) {
    Serial.print("foo is active, MOSFET does its thing");

void loop() {
    Serial.print("the button is now being pressed: ");
  • 2
    do not use serial.print() inside the interrupt handler ... set the interrupt flag and save the value of millis() ... do the serial.print() inside loop()
    – jsotola
    Jul 1, 2019 at 22:55
  • Edited the question, tried it but it doesn't solve the problem.
    – Toma
    Jul 1, 2019 at 23:24
  • the && in the if statement looks wrong ... what are you trying to do?
    – jsotola
    Jul 2, 2019 at 1:46
  • I check that the timing is good and that last_press is not 0. because it is 0 for the cases I ignore and if it is not 0, along with the other condition, ist a real event.
    – Toma
    Jul 2, 2019 at 4:48
  • In general, it's a bad idea to use interrupts for bouncing buttons. And interrupts are not able to do debug printouts. Jul 2, 2019 at 10:10

3 Answers 3


So, I grabbed a Mega and several pushbutton switches (one of which turned out to be a push-on, push-off, much to my bemusement), and ginned up a small Arduino program to illustrate an interrupt-driven button debounce technique. Please feel free to point out any and all errors ;-)

Here is a small program to debounce any momentary contact switch. The Mega interrupt line (pin 2, interrupt 0) triggers on any low-to-high or high-to-low transition. the interrupt service routine (ISR) simply sets a flag to TRUE and exits. If multiple interrupts occur before the loop() function can service the flag, the second and subsequent ISR calls do nothing (well, they set the flag, but since it was already set...). The loop() code looks for a set flag, and if it finds one, generates a 50uSec pulse on a selected pin, delays 100mSec, resets the flag, and exits. As long as the switch's contact bounce activity ceases before the 100mSec delay expires, one and only one pulse per transition will be generated. YMMV.

Small program to demonstrate switch debounce technique using an interrupt
The ISR is called for low-to-high and high-to-low transitions, and may get
called multiple times per button press.  However, the code in loop() ensures
that each transition produces one and only one pulse per transition, assuming
the typical 10-20 mSec max bounce duration.

Note that if the line 'delayMicroseconds(50); //used to show multiple bounces'
is used instead of the line 'delay(100);//ignore bounces for 100mSec', then 
multiple contact bounces will each generate their own 50uSec pulse.  This is 
useful for O'scope diagnosis if you happen to have one.

#include <elapsedMillis.h>
#include <PrintEx.h> //allows printf-style printout syntax

StreamEx mySerial = Serial; //added for printf-style printing

//pin assignments
const int LED_PIN = 13;
const int BUTTON_PIN = 2;
const int BUTTON_STATE_PIN = 3;

bool buttonState = 0;
bool blinkState = 0;

// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool Interrupt = false;     // indicates whether MPU interrupt pin has gone high
void BtnInterrupt() 
    Interrupt = true;

// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {
    // configure pins
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_STATE_PIN, OUTPUT); //scope monitor pin
    digitalWrite(BUTTON_STATE_PIN, LOW);

    buttonState = digitalRead(BUTTON_PIN);
    mySerial.printf("Initial button state is %s\n", buttonState == LOW ? "LOW" : "HIGH");
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), BtnInterrupt, CHANGE);

// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() 
    if (Interrupt)
        digitalWrite(BUTTON_STATE_PIN, HIGH);
        digitalWrite(BUTTON_STATE_PIN, LOW);
        //delayMicroseconds(50); //used to show multiple bounces
        delay(100);//ignore bounces for 100mSec
        Interrupt = false;

Here are two O'scope screen grabs. One shows the pulse train generated by a single button transition with a 50uSec delay, and the other shows the same switch with the 100mSec delay. In both photos, the horizontal time scale is 500uSec/cm

enter image description here

enter image description here

  • Amazing answer. This deserves more likes!
    – Tono Nam
    Jul 24, 2021 at 1:37
  • this works really fine, thanks!
    – bardulia
    Oct 6, 2023 at 14:03

I think the problem is that you only trigger the interrupt when the button is pressed (interrupt FALLING)

Therefore, there is no opportunity to call debounce when the button is released.

Therefore, there is no way to change the button state variable back to HIGH (I.e. when the button is released).

To verify the hypothesis or gain some other insights, I suggest trying to put a debug statement in your loop similar to the following

  If (millis () >= debugTime) {
       DebugTime = millis() + 1000:   // output every second...
       Serial.print("value of button state is: ");
      // and a few other variables.

I will leave it to you to properly declare debugTime (unsigned long) and choose a few other key variables (maybe interrupt flag) to print in the debug message block.

And i think you already have done so, but also delete the print statements in your ISR.


The problem is that you need to check whether the last interrupt was far enough in the past that the output can be considered stable. The most obvious solution to me is to have the ISR record the time it was called, and the main loop can simply check if that time is sufficiently before the current time. The comparison is very cheap, and doesn't require a delay. The "same record interrupt time and then compare" can cover multiple buttons on MCUs like the ATTINY85 which uses a single shared ISR. The disambiguation etc. resides outside the ISR.

Your Answer

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

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