Override External Interrupt

I have a device that uses a single External Interrupt (BUTTON_PIN 3).
This is used to Arm/Disarm my device.
A long press (STATE_LONG) of ~3 seconds Arms the device.
A short press (STATE_SHORT) of <1 second Disarms the device.
When a long press is activated, the code in loop flashes the LED 30 times. and the device is Armed.
When a short press is activated, the code in loop flashes the LED once and the device is Disarmed.

The following sketch is to test the Interrupts on BUTTON_PIN 3.
I have not got to the code to “switch” the Arm/Disarm circuit.

What I am attempting to achieve is to be able to “override/stop” the long press from running if a short press is activated.
In other words: the user may unintentionally activate a long press which will Arm the device - and must be able to override/stop the Arming by activating a short press while the long press is running.

Is this possible?
And if so, how do I go about this?

My Code:

//**************************************
// Board: MiniCore ATMega328 / 8MHz Internal
// Variant: 328P
// BOD: Disable
// LTO: Disable
//**************************************

#include <LowPower.h>
#include <EnableInterrupt.h>
#define BUTTON_PIN 3 // ON/OFF

#define STATE_NORMAL 0
#define STATE_SHORT 1
#define STATE_LONG 2

const int LED_STAT = 7;

volatile int  resultButton = 0; // global value set by checkButton()

int counterON = 0;

int ledState;                 // ledState used to set the LED
unsigned long previousMillis;   // will store last time LED was updated
long OnTime = 250;     // milliseconds of on-time
long OffTime = 750;    // milliseconds of off-time

void setup() {
  Serial.begin(9600);
  Serial.println("BOOT");

  pinMode(LED_STAT, OUTPUT);
  digitalWrite(LED_STAT, LOW);

  pinMode(BUTTON_PIN, INPUT);
  enableInterrupt(BUTTON_PIN, checkButton, CHANGE);
  Serial.println(F("Button pin initialized"));

  Serial.println(F("System ON"));

  // Flash LED one to indicate ON
  digitalWrite(LED_STAT, HIGH);
  delay(500);        // ...for 1/2 sec
  digitalWrite(LED_STAT, LOW);

  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}


void loop() {

  int longButton = 0;
  int count = 0;

  while (true) {
    switch (resultButton) {

      case STATE_NORMAL: {
          break;
        }

      case STATE_SHORT: {
          Serial.println("DISARMING DEVICE");
          digitalWrite(LED_STAT, HIGH);
          delay(3000);        // ...for 3 sec
          digitalWrite(LED_STAT, LOW);
          resultButton = STATE_NORMAL;
          LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
          break;
        }

      case STATE_LONG: {
          Serial.println("ARMING DEVICE");

          // Flash LED for 30 counts
          while (counterON <= 30) {
            unsigned long currentMillis = millis();
            if ((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
            {
              ledState = LOW;  // Turn it off
              previousMillis = currentMillis;  // Remember the time
              digitalWrite(LED_STAT, ledState);  // Update the actual LED

              counterON++;
              Serial.println("on");
              Serial.print("number of ON pulses: ");
              Serial.println(counterON);
            }
            else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
            {
              ledState = HIGH;  // turn it on
              previousMillis = currentMillis;   // Remember the time
              digitalWrite(LED_STAT, ledState);   // Update the actual LED
            }
          }

          counterON = 0;

          Serial.println(F("System Armed"));
          delay(100);
          longButton++;
          resultButton = STATE_NORMAL;
          LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
          break;
        }
    }
  }
}


void checkButton() {
  /*
    This function implements software debouncing for a two-state button.
    It responds to a short press and a long press and identifies between
    the two states. Your sketch can continue processing while the button
    function is driven by pin changes.
  */

  //  detachInterrupt(BUTTON_PIN);

  const unsigned long LONG_DELTA = 2000ul;               // hold seconds for a long press
  const unsigned long DEBOUNCE_DELTA = 30ul;        // debounce time
  static int lastButtonStatus = LOW;                                   // HIGH indicates the button is NOT pressed
  int buttonStatus;                                                                    // button atate Pressed/LOW; Open/HIGH
  static unsigned long longTime = 0ul, shortTime = 0ul; // future times to determine is button has been poressed a short or long time
  boolean Released = true, Transition = false;                  // various button states
  boolean timeoutShort = false, timeoutLong = false;    // flags for the state of the presses

  buttonStatus = digitalRead(BUTTON_PIN);                // read the button state on the pin "BUTTON_PIN"
  timeoutShort = (millis() > shortTime);                          // calculate the current time states for the button presses
  timeoutLong = (millis() > longTime);

  if (buttonStatus != lastButtonStatus) {                          // reset the timeouts if the button state changed
    shortTime = millis() + DEBOUNCE_DELTA;
    longTime = millis() + LONG_DELTA;
  }

  Transition = (buttonStatus != lastButtonStatus);        // has the button changed state
  Released = (Transition && (buttonStatus == LOW)); // for input pullup circuit

  lastButtonStatus = buttonStatus;                                     // save the button status

  if ( ! Transition) {                                                                //without a transition, there's no change in input
    // if there has not been a transition, don't change the previous result
    resultButton =  STATE_NORMAL | resultButton;
    return;
  }

  if (timeoutLong && Released) {                                      // long timeout has occurred and the button was just released
    resultButton = STATE_LONG | resultButton;       // ensure the button result reflects a long press
  } else if (timeoutShort && Released) {                          // short timeout has occurred (and not long timeout) and button was just released
    resultButton = STATE_SHORT | resultButton;     // ensure the button result reflects a short press
  } else {                                                                                  // else there is no change in status, return the normal state
    resultButton = STATE_NORMAL | resultButton; // with no change in status, ensure no change in button status
  }
}

You seem to have gone out of your way to make things complicated. Why use an interrupt to detect a button press in the first place when you could do it by polling the input ?

The code in this link illustrates how to program for different button clicks.

In other words: the user may unintentionally activate a long press which will Arm the device - and must be able to override/stop the Arming by activating a short press while the long press is running.

I don't understand this.

If the user is in the middle of a long press and wants to change his mind all he has to do is release the button and then it won't be a long press.

...R

As Bob says, you should not use interrupt for button presses. Interrupts are for things that happen unexpectedly and need to be processed very quickly. It is also very difficult to debounce a button input with a interrupt.

This is not the answer to your question but something to get you started. This sample code debounces 4 (easy to change) buttons inputs. Modify it to do what you need.

/* Simple button debounce for 4 buttons. Increments a count and sends the updated count to the serial monitor once per button press */
/* Tested on an Uno */
/* Connect simple push to make buttons between 0V and pin 2, 0V and pin 3, 0V and pin 4 and between 0V and pin 5 */

#define noOfButtons 4     //Exactly what it says; must be the same as the number of elements in buttonPins
#define bounceDelay 20    //Minimum delay before regarding a button as being pressed and debounced
#define minButtonPress 3  //Number of times the button has to be detected as pressed before the press is considered to be valid

const int buttonPins[] = {2, 3, 4, 5};      // Input pins to use, connect buttons between these pins and 0V
uint32_t previousMillis[noOfButtons];       // Timers to time out bounce duration for each button
uint8_t pressCount[noOfButtons];            // Counts the number of times the button is detected as pressed, when this count reaches minButtonPress button is regared as debounced 
uint8_t testCount[noOfButtons];             //Test count, incremented once per button press

void setup() {
  uint8_t i;
  uint32_t baudrate = 115200;
  Serial.begin(baudrate);
  Serial.println("");
  Serial.print("Serial port connected: ");
  Serial.println(baudrate);
  for (i = 0; i < noOfButtons; ++i) {
    pinMode(buttonPins[i], INPUT_PULLUP);
    Serial.print("Testcount ");
    Serial.print(i);
    Serial.print(" = ");
    Serial.println(testCount[i]);
  }
}

void loop() {
  debounce();
  delay(10);     //Your other code goes here instead of this delay. DO NOT leave this delay here, it's ONLY for demonstration.
}

void debounce() {
  uint8_t i;
  uint32_t currentMillis = millis();
  for (i = 0; i < noOfButtons; ++i) {
    if (digitalRead(buttonPins[i])) {             //Input is high, button not pressed or in the middle of bouncing and happens to be high
        previousMillis[i] = currentMillis;        //Set previousMillis to millis to reset timeout
        pressCount[i] = 0;                        //Set the number of times the button has been detected as pressed to 0
      }
    else {
      if (currentMillis - previousMillis[i] > bounceDelay) {
        previousMillis[i] = currentMillis;        //Set previousMillis to millis to reset timeout
        ++pressCount[i];
        if (pressCount[i] == minButtonPress) {
          ++testCount[i];
          Serial.print("Button ");
          Serial.print(i);
          Serial.print(" testcount = ");
          Serial.println (testCount[i]);
        }
      }
    }
  }
}

UKHeliBob: You seem to have gone out of your way to make things complicated. Why use an interrupt to detect a button press in the first place when you could do it by polling the input ?

The system is in a sleep state (LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);) The Interrupt wakes the system

Robin2: The code in this link illustrates how to program for different button clicks. I don't understand this.

If the user is in the middle of a long press and wants to change his mind all he has to do is release the button and then it won't be a long press.

...R

Once a long press is activated and the button released, the code runs in the "case STATE_LONG:" for approx 30 seconds. It is during this period - after the long press button is released that I would like to override the interrupt

Declan: The system is in a sleep state (LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);) The Interrupt wakes the system

Then use the interrupt to wake up but hand over to regular code for the user actions. If necessary the interrupt could save the value of millis() to indicate when the button was first pressed.

...R

Declan:
Once a long press is activated and the button released, the code runs in the “case STATE_LONG:” for approx 30 seconds.
It is during this period - after the long press button is released that I would like to override the interrupt

If the thing is running for 30 seconds the interrupt will be old history - an interrupt service routine that lasts for 100 microsecs would be very long.

Just keep polling the buttons during the 30 second interval. Have a look at how it is done in Several Things at a Time

…R

Once a long press is activated and the button released, the code runs in the "case STATE_LONG:" for approx 30 seconds.

None of your states should be blocking. loop() should run freely so that you can do other things as Robin suggests and look at the topic he linked to