user1499: I want to have an interrupt function executed whenever a button is pressed.
The button is connected to pin 2 and the GND. Therefore, the pin is turned to LOW whenever the button is pressed. In addition to that, proper debouncing should be used
As a proof of concept, the interrupt function should just toggle the BUILTIN_LED whenever the button is pressed.
I have tried many different approaches but i cannot make it work.
This is my last iteration:
bool led_status = LOW; // current state of output pin
int buttonState; // the current reading from the input pin
int lastButtonState = HIGH;  //the previous reading from the input pin

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

int button_switch = 2; // external interrupt pin
bool initialisation_complete = false; // inhibit any interrupts until initialisation is complete



// ISR for handling interrupt triggers arising from associated button switch
// 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:
void button_interrupt_handler()
{
  //static long int elapse_timer;
  
  if (initialisation_complete == true) //only able to run the ISR when arduino has finished initialization
  {
    // new interrupt so okay start a new button read process -
    // now need to wait for button release plus debounce period to elapse
    // this will be done in the button_read function

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

    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:
    if ( (millis() - lastDebounceTime) > debounceDelay)
    {
      if (reading != buttonState) // if the button state has changed:
      {
        buttonState = reading;
        if (buttonState == HIGH) // only toggle the LED if the new button state is HIGH
        {
          led_status = !led_status;
        }
      }
    }
    digitalWrite(LED_BUILTIN, led_status); //set the LED
    lastButtonState = reading; // save the reading. Next time through the loop, it'll be the lastButtonState:
  }
} // end of button_interrupt_handler


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(button_switch, INPUT_PULLUP); //no res = change to INPUT_PULLUP
  attachInterrupt(digitalPinToInterrupt(button_switch), button_interrupt_handler, FALLING); //no res = change to FALLING
  digitalWrite(LED_BUILTIN, led_status);
  initialisation_complete = true; // open interrupt processing for business
}

void loop()
{
  //do nothing  
}

I am not sure what I am doing wrong or why this does not work.
EDIT: When I press the button, nothing happens at all.

user60431: In almost any run of the ISR it will read the button state as LOW, because the ISR is triggered by the falling edge. The time from the falling edge to the reading is very short, about microseconds, and therefore a level change is improbable.
As others already suggest, you can make the ISR "deaf" for some time after the accepted falling edge. You can do this inside the ISR. The approach is to accept subsequent triggers only if they are long enough after the former one.
Please be aware that some buttons bounce even when released. You might experience a trigger in such cases.
Anyway, do not base a decision on the reading of button state after its falling edge.

The most simple solution to read buttons is a cyclic timer with a period of several milliseconds. Take a sample each 50ms or so, and detect the button change in software. You don't even need to add debouncing code, if the period is longer than the bouncing time of your button.
Since this timer will commonly trigger an ISR, you have already an interrupt as source of the button state for other parts of your software.
If you want an extra interrupt, trigger some unused interrupt in software.

user31043: Here is a non-blocking debouncing solution which is suitable also for electrically noisy environments. For example where there are long wires to the buttons which could pick up spurious signals. In this case, you cannot rely on short pulses being the direct result of button activity and must check that the button state has been stable for X ms before accepting it. It also handles potential bounces on the release of the button. It uses a simple finite state machine approach and does not use interrupts.
It can be tried also in a simulator: https://wokwi.com/projects/351021820324872788

const uint8_t ledPin = 13 ;
const uint8_t button_switch = 2;  // the button is wired between this pin and ground (LOW side)
const unsigned long debounceDelay = 100 ;    // the debounce time; increase if the output flickers

enum ButtonState  { PENDING_PRESS, IN_PRESS, PENDING_RELEASE} ;
ButtonState buttonState = ButtonState::PENDING_PRESS ;
uint32_t stateEnteredAtMs = millis() ;


void setup() {
  Serial.begin(115200);
  pinMode( ledPin, OUTPUT) ;
  pinMode( button_switch, INPUT_PULLUP) ;
  
}

void loop() {
  uint32_t ms = millis() ;
  static uint32_t lastButtonCheckAtMs = 0 ;

  if( ms - lastButtonCheckAtMs > 10 ) {

    switch ( buttonState ) {
 
     case ButtonState::PENDING_PRESS : {
        if ( digitalRead(button_switch)  == LOW ) {
          buttonState = ButtonState::IN_PRESS ;
          stateEnteredAtMs = ms ;
        }
        break ;
     }

     case ButtonState::IN_PRESS : {
        if ( digitalRead(button_switch) == HIGH )  {  
          if ( ms - stateEnteredAtMs < debounceDelay ) {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_PRESS ;
          } 
          else {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_RELEASE ;
            digitalWrite( ledPin, ! digitalRead(ledPin)) ;  // toggle led
          }
        }
        break ;
     }

     case ButtonState::PENDING_RELEASE : {
        if ( ms - stateEnteredAtMs > debounceDelay ) {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_PRESS ;
          } 
        break ;
     }
    }
    lastButtonCheckAtMs = ms ;
  }
}



1499: <p>I want to have an interrupt function executed whenever a button is pressed.
The button is connected to <code>pin 2</code> and the <code>GND</code>. Therefore, the pin is turned to <code>LOW</code> whenever the button is pressed. In addition to that, proper debouncing should be used
As a proof of concept, the interrupt function should just toggle the <code>BUILTIN_LED</code> whenever the button is pressed.</p>
<p>I have tried many different approaches but i cannot make it work.</p>
<p>This is my last iteration:</p>
<pre><code>bool led_status = LOW; // current state of output pin
int buttonState; // the current reading from the input pin
int lastButtonState = HIGH;  //the previous reading from the input pin

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

int button_switch = 2; // external interrupt pin
bool initialisation_complete = false; // inhibit any interrupts until initialisation is complete



// ISR for handling interrupt triggers arising from associated button switch
// 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:
void button_interrupt_handler()
{
  //static long int elapse_timer;
  
  if (initialisation_complete == true) //only able to run the ISR when arduino has finished initialization
  {
    // new interrupt so okay start a new button read process -
    // now need to wait for button release plus debounce period to elapse
    // this will be done in the button_read function

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

    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:
    if ( (millis() - lastDebounceTime) &gt; debounceDelay)
    {
      if (reading != buttonState) // if the button state has changed:
      {
        buttonState = reading;
        if (buttonState == HIGH) // only toggle the LED if the new button state is HIGH
        {
          led_status = !led_status;
        }
      }
    }
    digitalWrite(LED_BUILTIN, led_status); //set the LED
    lastButtonState = reading; // save the reading. Next time through the loop, it'll be the lastButtonState:
  }
} // end of button_interrupt_handler


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(button_switch, INPUT_PULLUP); //no res = change to INPUT_PULLUP
  attachInterrupt(digitalPinToInterrupt(button_switch), button_interrupt_handler, FALLING); //no res = change to FALLING
  digitalWrite(LED_BUILTIN, led_status);
  initialisation_complete = true; // open interrupt processing for business
}

void loop()
{
  //do nothing  
}
</code></pre>
<p>I am not sure what I am doing wrong or why this does not work.</p>
<p>EDIT: When I press the button, nothing happens at all.</p>
 60431: <p>In almost any run of the ISR it will read the button state as LOW, because the ISR is triggered by the falling edge. The time from the falling edge to the reading is very short, about microseconds, and therefore a level change is improbable.</p>
<p>As others already suggest, you can make the ISR &quot;deaf&quot; for some time after the accepted falling edge. You <em>can</em> do this inside the ISR. The approach is to accept subsequent triggers only if they are long enough after the former one.</p>
<p>Please be aware that some buttons bounce even when released. You might experience a trigger in such cases.</p>
<p>Anyway, do not base a decision on the reading of button state after its falling edge.</p>
<hr />
<p>The most simple solution to read buttons is a cyclic timer with a period of several milliseconds. Take a sample each 50ms or so, and detect the button change in software. You don't even need to add debouncing code, if the period is longer than the bouncing time of your button.</p>
<p>Since this timer will commonly trigger an ISR, you have already an interrupt as source of the button state for other parts of your software.</p>
<p>If you want an extra interrupt, trigger some unused interrupt in software.</p>
 31043: <p>Here is a non-blocking debouncing solution which is suitable also for electrically noisy environments. For example where there are long wires to the buttons which could pick up spurious signals. In this case, you cannot rely on short pulses being the direct result of button activity and must check that the button state has been stable for X ms before accepting it. It also handles potential bounces on the release of the button. It uses a simple finite state machine approach and does not use interrupts.</p>
<p>It can be tried also in a simulator: <a href="https://wokwi.com/projects/351021820324872788" rel="nofollow noreferrer">https://wokwi.com/projects/351021820324872788</a></p>
<pre><code>
const uint8_t ledPin = 13 ;
const uint8_t button_switch = 2;  // the button is wired between this pin and ground (LOW side)
const unsigned long debounceDelay = 100 ;    // the debounce time; increase if the output flickers

enum ButtonState  { PENDING_PRESS, IN_PRESS, PENDING_RELEASE} ;
ButtonState buttonState = ButtonState::PENDING_PRESS ;
uint32_t stateEnteredAtMs = millis() ;


void setup() {
  Serial.begin(115200);
  pinMode( ledPin, OUTPUT) ;
  pinMode( button_switch, INPUT_PULLUP) ;
  
}

void loop() {
  uint32_t ms = millis() ;
  static uint32_t lastButtonCheckAtMs = 0 ;

  if( ms - lastButtonCheckAtMs &gt; 10 ) {

    switch ( buttonState ) {
 
     case ButtonState::PENDING_PRESS : {
        if ( digitalRead(button_switch)  == LOW ) {
          buttonState = ButtonState::IN_PRESS ;
          stateEnteredAtMs = ms ;
        }
        break ;
     }

     case ButtonState::IN_PRESS : {
        if ( digitalRead(button_switch) == HIGH )  {  
          if ( ms - stateEnteredAtMs &lt; debounceDelay ) {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_PRESS ;
          } 
          else {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_RELEASE ;
            digitalWrite( ledPin, ! digitalRead(ledPin)) ;  // toggle led
          }
        }
        break ;
     }

     case ButtonState::PENDING_RELEASE : {
        if ( ms - stateEnteredAtMs &gt; debounceDelay ) {
            stateEnteredAtMs = ms ;
            buttonState = ButtonState::PENDING_PRESS ;
          } 
        break ;
     }
    }
    lastButtonCheckAtMs = ms ;
  }
}

</code></pre>
