62. Single Double and Long Press with Interrupts

We have to detect singledouble, and long-press events from the push button Control LED1 and LED2 based on the press events while the main loop is busy with other tasks.
To ensure instant response and non-blocking operation, we use interrupts.

  • Single press: Toggle LED1
  • Double-press: Toggle LED2
  • Long press (over 1 second): Turn off both LED1 and LED2

Interrupts 

  • External Interrupt: Detects button press instantly.
  • Timer Interrupt: Defines timing windows for press-type detection (single, double, long).

Detecting the Type of Button Press

To differentiate between singledouble, and long presses, specific timing windows are used.

Timing Windows for button press detection are as follows 

  • 50 – 300 ms → Single press 
  • 50 – 300 ms + second press within 500 ms → Double press
  • > 800 ms → Long press

Approach

  1. Start a timer for 500 ms when the first press is detected.
  2. Monitor all presses within this 500 ms window.

After 500 ms (button released):

  • 1 press → Single Click
  • 2 or more presses → Double/Multiple Click

After 500 ms (button still held):

  • Continue monitoring for another 500 ms.
  • If the button remains pressed for a total of ~1 second, register as a Long Press.

Debouncing Logic

When a mechanical button is pressed, it often produces unwanted rapid ON/OFF transitions (bounces) due to contact vibration.
To ensure a clean and stable reading, debouncing logic introduces a short time delay before validating the press.

Process

  1. Detect button press → start debounce timer (typically 50 ms).
  2. After 50 ms, check if the button is still pressed.
  3. If yes → register it as a valid press and increment the count.
  4. If the signal fluctuates during this period, the timer resets and restarts the 50 ms delay.

Thus, a button press is confirmed only after a stable 50-ms signal is observed.

Hardware Setup

  • Connect two LEDs with appropriate current-limiting resistors to limit the current to 10 mA.
  • One push-button configured to provide clean HIGH/LOW levels (with pull-up or pull-down).

So, by selecting a proper resistor, LED, and push-button switch correctly, we can implement the task.

Below are the solutions to the given task using different microcontrollers

  1. STM32
  2. ESP32
  3. Arduino UNO

Code


#define LED1_PIN 5    // Pin for LED1
#define LED2_PIN 6    // Pin for LED2
#define BUTTON_PIN 2  // Pin for the button

volatile bool isT1running = false;  // Flag to indicate if Timer1 is running
volatile bool isT2running = false;  // Flag to indicate if Timer2 is running
volatile int clicksCount = 0;       // Counter for button clicks
volatile int t1Counter = 0;         // Counter for Timer1 compare events
volatile int t2Counter = 0;         // Counter for Timer2 compare events

void setup() {
  pinMode(LED1_PIN, OUTPUT);          // Set LED1 pin as output
  pinMode(LED2_PIN, OUTPUT);          // Set LED2 pin as output
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // Set button pin as input with pull-up resistor

  // Configure Timer1 for a 500ms timeout
  TCCR1A = 0;              // Set Timer1 to Normal mode
  TCCR1B = 0;              // Stop Timer1
  OCR1A = 31249;           // Set compare value for 500ms
  TCNT1 = 0;               // Reset Timer1 counter
  TIMSK1 = (1 << OCIE1A);  // Enable Timer1 Compare Match A interrupt

  // Configure Timer2 for debounce logic
  TCCR2A = (1 << WGM21);   // Set Timer2 to CTC mode
  TCCR2B = 0;              // Stop Timer2
  OCR2A = 255;             // Set compare value for debounce duration
  TCNT2 = 0;               // Reset Timer2 counter
  TIMSK2 = (1 << OCIE2A);  // Enable Timer2 Compare Match A interrupt

  // Configure external interrupt for the button (INT0)
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), ISR_INT0, FALLING);

  // Enable global interrupts
  sei();
}

void loop() {
  while (true) {
    //In this loop Microcontroller is busy in monitoring and performing important tasks constantly.
  }
}

// Interrupt Service Routine for button press (INT0)
void ISR_INT0() {
  // If Timer1 is not running and no clicks are counted yet, start Timer1
  if (!isT1running && clicksCount == 0) {
    TCCR1B = (1 << WGM12) | (1 << CS12);  // Start Timer1 
    isT1running = true;
  }

  // Start Timer2 for debounce logic if it's not running
  if (!isT2running) {
    TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);  // Start Timer2
    isT2running = true;
  }
}

// Interrupt Service Routine for Timer2 (Debounce logic)
ISR(TIMER2_COMPA_vect) {
  t2Counter++;
  if (t2Counter >= 3) {  // After debounce duration (~3 cycles)
    if (digitalRead(BUTTON_PIN) == LOW && isT1running) {
      clicksCount++;  // Increment click count if button is still pressed
    }
    TCCR2B = 0;           // Stop Timer2
    isT2running = false;  // Reset debounce flag
    t2Counter = 0;        // Reset Timer2 counter
  }
}

// Interrupt Service Routine for Timer1 (Click detection logic)
ISR(TIMER1_COMPA_vect) {
  t1Counter++;

  if (t1Counter == 1) {  // First timeout (500ms after button press)
    if (clicksCount > 1) {
      digitalWrite(LED2_PIN, !digitalRead(LED2_PIN));  // Toggle LED2 on double-click
      t1Counter = 3;                                   // Skip further checks
    } else if (digitalRead(BUTTON_PIN) == HIGH && clicksCount == 1) {
      digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));  // Toggle LED1 on single click
      t1Counter = 3;                                   // Skip further checks
    }
  } else if (t1Counter == 2) {  // Second timeout (long press detected)
    if (digitalRead(BUTTON_PIN) == LOW && clicksCount == 1) {
      digitalWrite(LED1_PIN, LOW);  // Turn off LED1
      digitalWrite(LED2_PIN, LOW);  // Turn off LED2
    }
    t1Counter = 3;  // Ensure Timer1 stops
  }

  if (t1Counter >= 3) {   // Stop Timer1 after logic completion
    TCCR1B = 0;           // Stop Timer1
    isT1running = false;  // Reset Timer1 running flag
    clicksCount = 0;      // Reset click counter
    t1Counter = 0;        // Reset Timer1 counter
  }
}

Code Explanation

1. Global Variables

  • isT1running and isT2running: Flags to track if Timer1 and Timer2 are active.
  • clicksCount: Counts the number of button clicks.
  • t1Counter and t2Counter: Counters for Timer1 and Timer2 interrupts loops. 

2. setup() Function

  • Button ConfigurationBUTTON_PIN is set as an input with a internal pull-up resistor.
  • Timer1 Configuration:
    • Configured for a 500ms timeout using a 16MHz clock and a prescaler of 256.
    • Compare value OCR1A is set to 31249 for 500 ms.
  • Timer2 Configuration:
    • Configured for debounce logic using a prescaler of 1024.
    • Compare value OCR2A is set to 255 for 16 ms.
  • External Interrupt:
    • INT0 is configured to trigger on the falling edge of the button press.
  • Global Interrupts: Enabled using sei().

3. loop() Function

  • The loop() performing important tasks and all the logic is handled by interrupts.

4. Interrupt Service Routines (ISRs)

Button Press ISR (ISR_INT0)

  • Start Timer1 if it is not already running and button-clicks = 0.
  • Used to detect single-click, double-click, and long-press events.
  • Start Timer2 if it is not already running.
  • Used for debouncing a button press.

Timer2 ISR (Debounce Logic)

  • Debounce Logic:
    • After 3 cycles (debounce duration), the button state is checked.
    • If the button is still pressed, clicksCount is incremented.
    • Timer2 is stopped, and its flags are reset.

Timer1 ISR (Click Detection Logic)

  • First Timeout (500ms):
    • If clicksCount > 1, it's a double-click: toggle LED2.
    • If clicksCount == 1 and the button is released, it's a single click: toggle LED1.
  • Second Timeout (1000ms):
    • If the button is still pressed and clicksCount == 1, it's a long press: turn off both LEDs.
  • Stop Timer1:
    • After completing the logic, Timer1 is stopped, and all flags are reset.

Output

Hardware Setup

Arduino-Hardware-setup

Video

Submit Your Solution

Note: Once submitted, your solution goes public, helping others learn from your approach!