Arduino and Esp32 Interrupts (ISR)

Attaching Interrupt to a GPIO Pin

In Arduino IDE, we use a function called attachInterrupt() to set an interrupt on a pin by pin basis. The recommended syntax looks like below.

attachInterrupt(GPIOPin, ISR, Mode);

This function takes three parameters:

GPIOPin – Sets the GPIO pin as an interrupt pin, which tells the ESP32 which pin to monitor.

ISR – Is the name of the function that will be called every time the interrupt is triggered.

Mode – Defines when the interrupt should be triggered. Five constants are predefined as valid values:

LOWTriggers interrupt whenever the pin is LOW
HIGHTriggers interrupt whenever the pin is HIGH
CHANGETriggers interrupt whenever the pin changes value, from HIGH to LOW or LOW to HIGH
FALLINGTriggers interrupt when the pin goes from HIGH to LOW
RISINGTriggers interrupt when the pin goes from LOW to HIGH

Detaching Interrupt from a GPIO Pin

You can optionally call detachInterrupt() function when you no longer want ESP32 to monitor a pin. The recommended syntax looks like below.


Interrupt Service Routine

Interrupt Service Routine is invoked when an interrupt occurs on any GPIO pin. Its syntax looks like below.

void IRAM_ATTR ISR() {

ISRs in ESP32 are special kinds of functions that have some unique rules most other functions do not have.

  • The interrupt service routine must have an execution time as short as possible, because it blocks the normal program execution.
  • Interrupt service routines should have the IRAM_ATTR attribute, according to the ESP32 documentation

What is IRAM_ATTR?

By flagging a piece of code with the IRAM_ATTR attribute we are declaring that the compiled code will be placed in the Internal RAM (IRAM) of the ESP32.
Otherwise the code is placed in the Flash. And flash on the ESP32 is much slower than internal RAM.
If the code we want to run is an interrupt service routine (ISR), we generally want to execute it as quickly as possible. If we had to ‘wait’ for an ISR to load from flash, things would go horribly wrong.

struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;

Button button1 = {18, 0, false};

void IRAM_ATTR isr() {
  button1.numberKeyPresses += 1;
  button1.pressed = true;

void setup() {
  pinMode(button1.PIN, INPUT_PULLUP);
  attachInterrupt(button1.PIN, isr, FALLING);

void loop() {
  if (button1.pressed) {
      Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;

  //Detach Interrupt after 1 Minute
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 60000) {
    lastMillis = millis();
	Serial.println("Interrupt Detached!");

touch interrupt

int threshold = 40;
bool touch1detected = false;
bool touch2detected = false;

void gotTouch(){
 touch1detected = true;

void gotTouch1(){
 touch2detected = true;

void setup() {
  delay(1000); // give me time to bring up serial monitor
  printf("\n ESP32 Touch Interrupt Test\n");
  touchAttachInterrupt(T2, gotTouch, threshold);
  touchAttachInterrupt(T3, gotTouch1, threshold);

void loop(){
    touch1detected = false;
    Serial.println("Touch 1 detected");
    touch2detected = false;
    Serial.println("Touch 2 detected");

timer interrupt


volatile int interrupts;
int totalInterrupts;

hw_timer_t * timer = NULL;

void IRAM_ATTR onTime() {

void setup() {


	// Configure Prescaler to 80, as our timer runs @ 80Mhz
	// Giving an output of 80,000,000 / 80 = 1,000,000 ticks / second
	timer = timerBegin(0, 80, true);                
	timerAttachInterrupt(timer, &onTime, true);    
	// Fire Interrupt every 1m ticks, so 1s
	timerAlarmWrite(timer, 1000000, true);			
void loop() {
	if (interrupts > 0) {

note: portEnter_CRITICAL is expand for vTaskEnterCritical