Interrupts

Lesson 94: Using detachInterrupt() External Interrupts

Learn how detachInterrupt() disables a registered external interrupt callback for controlled event handling.

Progress indicator

Lesson 94 of 94

Learning Objectives

  • Understand what detachInterrupt() does and how it stops external interrupt callbacks.
  • Learn detachInterrupt() syntax and correct interrupt mapping usage.
  • Understand how to temporarily disable interrupt-based input flow safely.
  • Compare attachInterrupt() and detachInterrupt() as enable/disable pair operations.
  • Avoid common mistakes when detaching or re-attaching interrupts.
  • Build a practical state-machine mindset for controlling interrupt lifecycle safely.

Concept Explanation

What is detachInterrupt()

detachInterrupt() disables a previously attached external interrupt callback.

After detach, trigger events on that interrupt source no longer run the ISR.

It does not remove your variables or reset counters. It only stops future ISR callback execution for that interrupt mapping.

detachInterrupt() Syntax

Basic syntax: detachInterrupt(digitalPinToInterrupt(pin));

Use the same interrupt mapping path you used when calling attachInterrupt().

Using mismatched mapping is a common bug and may leave the expected interrupt still active.

How detachInterrupt() Works

  1. Interrupt callback is unregistered from interrupt source.
  2. Future trigger events no longer invoke the ISR.
  3. Main program continues running without that interrupt callback path.

Any ISR execution already in progress will finish; detach affects new events after the call.

Disabling External Interrupts

detachInterrupt() is useful when you want to stop further ISR updates after a condition is met.

Example: stop counting button edges after reaching a threshold.

This can reduce noise-triggered updates and keep system behavior bounded after target is met.

attachInterrupt() vs detachInterrupt() (Comparison)

  • attachInterrupt(): enable/link callback to interrupt source.
  • detachInterrupt(): disable/unlink callback from interrupt source.

Use them as a controlled enable/disable pair around event-driven logic windows.

Treat them like start/stop controls for one interrupt path, not as a replacement for global interrupt control functions.

When to Use detachInterrupt()

  • Stop event counting after a limit is reached.
  • Temporarily pause interrupt-driven input while running a mode/state.
  • Prevent repeated ISR firing during sensitive phases.
  • Disable unused interrupt paths to simplify runtime behavior.

Real-life example: after counting a fixed number of pulses, detach to freeze the result and prevent extra noise from changing final values.

Example Code

This example detaches button interrupt after 5 events and shows interrupt state in Serial.

const int BUTTON_PIN = 0;
const int LED_PIN = 2;
volatile unsigned long interruptCount = 0;
bool interruptEnabled = true;

void IRAM_ATTR onButtonEdge() {
  interruptCount = interruptCount + 1;
}

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);

  attachInterrupt(
    digitalPinToInterrupt(BUTTON_PIN),
    onButtonEdge,
    FALLING
  );
}

void loop() {
  unsigned long countSnapshot;
  noInterrupts();
  countSnapshot = interruptCount;
  interrupts();

  if (countSnapshot >= 5 && interruptEnabled) {
    detachInterrupt(digitalPinToInterrupt(BUTTON_PIN));
    interruptEnabled = false;
    Serial.println("Interrupt detached after 5 events.");
  }

  digitalWrite(LED_PIN, interruptEnabled ? HIGH : LOW);

  Serial.print("Count: ");
  Serial.print(countSnapshot);
  Serial.print(" | Enabled: ");
  Serial.println(interruptEnabled ? "YES" : "NO");

  delay(200);
}

Example Code Explanation

  1. ISR increments interruptCount on each configured button edge event.
  2. loop() snapshots volatile count safely in a short critical section.
  3. When count reaches 5, detachInterrupt() is called for button source.
  4. interruptEnabled flag tracks whether callback path is active.
  5. LED/Serial output show current count and enabled/disabled state.
  6. Once detached, extra button edges do not increase interruptCount anymore.
  7. Flag-driven UI output makes state transitions clear during debugging.

What Happens Inside

  1. Hardware trigger fires and ISR updates counter while attached.
  2. Main loop checks count and threshold condition.
  3. detachInterrupt() removes callback mapping for that source.
  4. Future button events no longer run ISR callback.
  5. Main loop continues using last captured values and normal logic flow.
Internal flow: event path attached -> ISR updates data -> condition reached -> detach called -> event path disabled -> system continues with stable final state.

Common Mistakes with detachInterrupt()

  • Detaching wrong interrupt source due to mapping mismatch.
  • Forgetting to track whether interrupt is currently attached.
  • Expecting ISR to keep firing after detachInterrupt() call.
  • Reading shared ISR values without safe snapshot logic.
  • Detaching too early and missing required event data.

Best Practices for detachInterrupt()

  • Detach only when a clear condition/threshold is reached.
  • Track interrupt state with explicit flags.
  • Use digitalPinToInterrupt() consistently for attach/detach calls.
  • Use short critical sections for copying shared data.
  • Add serial logs when debugging interrupt attach/detach transitions.

Try it now

Open the simulator workspace and step through attach/detach interrupt transitions, threshold logic, and post-detach stable behavior.

Run in Simulator