Specifies a function to call when an external interrupt occurs. Replaces any previous function that was attached to the interrupt.

NOTE: pinMode() MUST be called prior to calling attachInterrupt() to set the desired mode for the interrupt pin (INPUT, INPUT_PULLUP or INPUT_PULLDOWN).

The attachInterrpt() function is not interrupt-safe. Do not call it from within an interrupt service routine (ISR).

Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):

All A and D pins (including TX, RX, and SPI) on Gen 3 devices can be used for interrupts, however you can only attach interrupts to 8 pins at the same time.

Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):

External interrupts are supported on the following pins:


Not supported on the Photon (you can't use attachInterrupt on these pins):

  • D0, A5 (shared with SETUP button)

No restrictions on the Photon (all of these can be used at the same time):

  • D5, D6, D7, A2, WKP, TX, RX

Shared on the Photon (only one pin for each bullet item can be used at the same time):

  • D1, A4
  • D2, A0, A3
  • D3, DAC
  • D4, A1

For example, you can use attachInterrupt on D1 or A4, but not both. Since they share an EXTI line, there is no way to tell which pin generated the interrupt.

But you can still attachInterrupt to D2, D3, and D4, as those are on different EXTI lines.


Not supported on the P1 (you can't use attachInterrupt on these pins):

  • D0, A5 (shared with MODE button)

No restrictions on the P1 (all of these can be used at the same time):

  • D5, D6, A2, TX, RX

Shared on the P1 (only one pin for each bullet item can be used at the same time):

  • D1, A4
  • D2, A0, A3
  • D3, DAC, P1S3
  • D4, A1
  • D7, P1S4
  • A7 (WKP), P1S0, P1S2
  • P1S1, P1S5


Not supported on the Electron/E series (you can't use attachInterrupt on these pins):

  • D0, A5 (shared with MODE button)
  • D7 (shared with BATT_INT_PC13)
  • C1 (shared with RXD_UC)
  • C2 (shared with RI_UC)

No restrictions on the Electron/E series (all of these can be used at the same time):

  • D5, D6

Shared on the Electron/E series (only one pin for each bullet item can be used at the same time):

  • D1, A4, B1
  • D2, A0, A3
  • D3, DAC
  • D4, A1
  • A2, C0
  • A7 (WKP), B2, B4
  • B0, C5
  • B3, B5
  • C3, TX
  • C4, RX

Additional information on which pins can be used for interrupts is available on the pin information page.

attachInterrupt(pin, function, mode);
attachInterrupt(pin, function, mode, priority);
attachInterrupt(pin, function, mode, priority, subpriority);


  • pin: the pin number
  • function: the function to call when the interrupt occurs; this function must take no parameters and return nothing. This function is sometimes referred to as an interrupt service routine (ISR).
  • mode: defines when the interrupt should be triggered. Three constants are predefined as valid values:
    • CHANGE to trigger the interrupt whenever the pin changes value,
    • RISING to trigger when the pin goes from low to high,
    • FALLING for when the pin goes from high to low.
  • priority (optional): the priority of this interrupt. Default priority is 13. Lower values increase the priority of the interrupt.
  • subpriority (optional): the subpriority of this interrupt. Default subpriority is 0.

The function returns a boolean whether the ISR was successfully attached (true) or not (false).


void blink(void);
int ledPin = D1;
volatile int state = LOW;

void setup()
  pinMode(ledPin, OUTPUT);
  pinMode(D2, INPUT_PULLUP);
  attachInterrupt(D2, blink, CHANGE);

void loop()
  digitalWrite(ledPin, state);

void blink()
  state = !state;

You can attach a method in a C++ object as an interrupt handler.

class Robot {
    Robot() {
      pinMode(D2, INPUT_PULLUP);
      attachInterrupt(D2, &Robot::handler, this, CHANGE);
    void handler() {
      // do something on interrupt

Robot myRobot;
// nothing else needed in setup() or loop()

Using Interrupts: Interrupts are useful for making things happen automatically in microcontroller programs, and can help solve timing problems. Good tasks for using an interrupt may include reading a rotary encoder, or monitoring user input.

If you wanted to insure that a program always caught the pulses from a rotary encoder, so that it never misses a pulse, it would make it very tricky to write a program to do anything else, because the program would need to constantly poll the sensor lines for the encoder, in order to catch pulses when they occurred. Other sensors have a similar interface dynamic too, such as trying to read a sound sensor that is trying to catch a click, or an infrared slot sensor (photo-interrupter) trying to catch a coin drop. In all of these situations, using an interrupt can free the microcontroller to get some other work done while not missing the input.

About Interrupt Service Routines: ISRs are special kinds of functions that have some unique limitations most other functions do not have. An ISR cannot have any parameters, and they shouldn't return anything.

Generally, an ISR should be as short and fast as possible. If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have. millis() relies on interrupts to count, so it will never increment inside an ISR. Since delay() requires interrupts to work, it will not work if called inside an ISR. Using delayMicroseconds() will work as normal.

Things you should not do from an ISR:

  • Any memory allocation or free: new, delete, malloc, free, strdup, etc.
  • Any Particle class function like Particle.publish, Particle.subscribe, etc.
  • Most API functions, with the exception of pinSetFast, pinResetFast, and analogRead.
  • delay or other functions that block.
  •, Log.error, etc.
  • sprintf, Serial.printlnf, etc. with a %f (float) value.
  • attachInterrupt and detachInterrupt cannot be called within the ISR.
  • Mutex locks. This includes SPI transactions and I2C lock and unlock.
  • Start an SPI.transaction with DMA.