Interrupt for button to put device to sleep/wake up

EDIT: I was able to solve the problem (see 2nd code, I've put if(enableSleepButton)
in beggining of sleepNow()), but I am still unsure if this is correct approach. Still seeking for answers!

I am using Arduino Nano and have a button connected to D2. I want to put the device to sleep when this button is pressed - and wake it back up when button is pressed again (this can be done many times).

What I don't understand is how to handle interrupts so they don't fire continuously once set up. Based on documentation, you can only wake up the device with LOW state on pin:

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), sleepNow, LOW);
}

void sleepNow() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sei();
  sleep_cpu();
}

void wakeUp() {
  sleep_disable();
  DBG("IRQ");
}

Problem is that sleepNow() will keep firing while BUTTON_PIN is in LOW state, right? So I tried using multiple interrupts and juggling them around and it's working:

volatile bool enableSleepButton = false;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), sleepNow, RISING); // rising will work while device is not in sleep
}

void loop() {
  if(enableSleepButton) // after adding this, it now works properly - but is the approach correct?
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), sleepNow, RISING); // this re-enables interrupt for sleep mode on button press
}

void sleepNow() {
  if(enableSleepButton)
    return; // device not fully awaken yet, don't allow sleep

  detachInterrupt(digitalPinToInterrupt(BUTTON_PIN)); // prevent further IRQs for sleep
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), wakeUp, LOW); // wakeup only works for LOW; this will wake up the device next time we press the button
  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sei(); - why do I need this? Some people have it, others not. For me the wakeup isn't working without it
  sleep_cpu();
}

void wakeUp() {
  detachInterrupt(digitalPinToInterrupt(BUTTON_PIN)); // detach LOW interrupt now so this function doesn't get called repeatedly while pin is in LOW mode
  sleep_disable();
  DBG("IRQ");
}

Questions:

  • is juggling with interrupts required, or could I just somehow use 1 interrupt inside setup()?
  • why is sei() required for wake-up and why is it omitted from documentation?
  • why is the program hanging after setting the sleep interrupt again in loop() and how to solve this?

Please note I've spent 2 hours reading Gammon's guide to Interrupts, this, this, this, this, and many more threads, so I would really appreciate if someone could guide me and/or correct my code. Thank you

Is there no one who used interrupts and would be willing to explain how to do it properly? It's pretty basic stuff.

Problem is that sleepNow() will keep firing while BUTTON_PIN is in LOW state, right?

It certainly looks like it. Is there a reason why you used FALLING rather than LOW in attachInterrupt() ?

Hi UKHeliBob, thanks for your time and your other threads which I also checked!

I actually used RISING, because AFAIK in this case interrupt will fire of only once, while LOW keeps triggering the interrupt for many times in the few millis that the button is pressed and before it jumps back up (it’s a push button).
This caused my device to go from sleep to wake and back to sleep and so on just on a single button press.

If you check sleepNow() function (this get’s triggered when button is pressed to put device to sleep), you can see I have to detach this interrupt and attach another so the next time I press the button it will call wakeUp() function. But the problem is that it seems that these interrupts keep firing so as soon as device is put to sleep, it wakes back up immediately. I would somehow need to pause all interrupts for i.e. 100ms after some IRQ happened.

I can solve this problem by introducing a volatile bool isPressed and then ignore the interrupt code if this flag is active and i.e. less than 100ms have passed (this is enough time for button to jump back to non-pressed position). But it seems like a really bad solution and I would be interested to see some simple example or just some guidance on how to properly solve this.

Maybe I should use noInterrupts() and interrupts()?