Zbotic Logo Zbotic Logo
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

  • Shop
  • About Us
  • Contact Us
  • Reseller
  • Blogs
020 69134444
1800 209 0998
[email protected]
Help Desk
Facebook Twitter Instagram Linkedin YouTube
Zbotic Logo Zbotic Logo
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

All departments
  • 3D Print Service
  • 3D Printer
  • Batteries & Chargers
  • Development Boards
  • Drone Parts
  • EBike parts
  • Sensor Modules
  • Electronic Components
  • Electronic Modules
  • IoT and Wireless
  • Mechanical Parts and Workbench Tools
  • Motors & Drivers & Pumps & Actuators
  • DIY and Robot Kits
  • Show more
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
Return to previous page
Home IoT & Smart Home

ESP32 Interrupt Handling: GPIO Interrupt Tutorial and Code

ESP32 Interrupt Handling: GPIO Interrupt Tutorial and Code

March 11, 2026 /Posted byJayesh Jain / 0

ESP32 interrupt handling GPIO tutorial code is one of the most searched topics among Indian hobbyists and IoT developers. When you need your ESP32 to respond instantly to a sensor trigger, button press, or external signal without constantly polling in a loop, hardware interrupts are the answer. This tutorial covers everything you need to know — from the basic theory of interrupts to writing production-ready ISR (Interrupt Service Routine) code with proper debouncing, using the Arduino IDE or ESP-IDF framework.

Table of Contents

  1. What Are Interrupts and Why Use Them?
  2. ESP32 GPIO Pins and Interrupt Support
  3. Using attachInterrupt() in Arduino IDE
  4. ISR Best Practices: volatile, IRAM_ATTR, and Flags
  5. Debouncing Interrupts in ESP32
  6. Real-World Example: Rotary Encoder and PIR Sensor
  7. ESP-IDF GPIO Interrupt Configuration
  8. Frequently Asked Questions

What Are Interrupts and Why Use Them?

In embedded programming, an interrupt is a hardware or software signal that tells the CPU to immediately pause its current task and execute a special function called an Interrupt Service Routine (ISR). Once the ISR completes, the CPU returns to whatever it was doing before.

The alternative to interrupts is polling — repeatedly checking a pin’s state inside your loop(). Polling wastes CPU cycles and can miss fast events if your loop has blocking code like delay() or long communication calls. Interrupts solve this elegantly.

Key benefits of using interrupts on ESP32:

  • React to external events in microseconds, not loop-cycle delays
  • Free up CPU for other tasks (critical for Wi-Fi and BLE background operations)
  • Reduce power consumption in sleep modes
  • Essential for time-sensitive protocols like rotary encoders and pulse counting

The ESP32 supports interrupts on all GPIO pins, which is a significant advantage over Arduino Uno (which only has 2 interrupt pins) and makes it ideal for complex IoT projects built and sold across India.

Ai Thinker NodeMCU-32S-ESP32 Development Board

Ai Thinker NodeMCU-32S-ESP32 Development Board – IPEX Version

A reliable ESP32 development board with all GPIO pins broken out, perfect for experimenting with interrupt-driven projects. Widely used across India for IoT prototyping.

View on Zbotic

ESP32 GPIO Pins and Interrupt Support

The ESP32 has 34 programmable GPIO pins, and all of them support external interrupts. However, a few important points apply in practice:

  • GPIO 34–39 are input-only; they cannot source current or drive loads, but they work perfectly as interrupt inputs from sensors.
  • GPIO 6–11 are connected to the internal flash memory and should never be used as interrupt or general-purpose pins.
  • GPIO 0, 2, 5, 12, 15 have boot-time constraints — avoid attaching interrupts to these if your project is sensitive to boot sequence.
  • Safe, fully usable interrupt GPIO pins: 4, 13, 14, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 39.

ESP32 supports five interrupt modes (trigger types) for each GPIO:

Mode Constant Description
RISING Trigger on LOW→HIGH transition
FALLING Trigger on HIGH→LOW transition
CHANGE Trigger on any edge (both RISING and FALLING)
ONLOW Level interrupt — fires while pin is LOW (use carefully)
ONHIGH Level interrupt — fires while pin is HIGH (use carefully)

Using attachInterrupt() in Arduino IDE

The Arduino framework for ESP32 provides the familiar attachInterrupt() function. Here is the complete syntax:

attachInterrupt(digitalPinToInterrupt(pin), ISR_function, mode);

Let’s build a minimal working example — a button on GPIO 27 toggles an LED on GPIO 2 using an interrupt:

const int LED_PIN = 2;
const int BUTTON_PIN = 27;

volatile bool buttonPressed = false;

void IRAM_ATTR handleButtonPress() {
  buttonPressed = true;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN),
                  handleButtonPress,
                  FALLING);
}

void loop() {
  if (buttonPressed) {
    buttonPressed = false;
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    Serial.println("Button pressed - LED toggled");
  }
}

Breaking down this code:

  • volatile bool buttonPressed — the volatile keyword tells the compiler not to cache this variable in a register. This is mandatory for any variable shared between the main loop and an ISR.
  • IRAM_ATTR — places the ISR function in IRAM (Internal RAM) instead of flash. Flash reads can be slow or blocked during cache miss; IRAM executes instantly. Always use this attribute for ISRs on ESP32.
  • INPUT_PULLUP — activates the internal pull-up resistor. Button connects GPIO to GND, so pressing gives a FALLING edge.
  • The ISR only sets a flag. The actual work (Serial.println, digitalWrite) happens in loop().

ISR Best Practices: volatile, IRAM_ATTR, and Flags

Writing good ISRs is critical for system stability. The ESP32 is a multi-core SoC running a real-time operating system (FreeRTOS), which adds extra complexity. Follow these golden rules:

Rule 1: Keep ISRs Short and Fast

Never call blocking functions inside an ISR — no Serial.print(), no delay(), no WiFi calls, no Wire (I2C) transactions. These functions are not ISR-safe and will cause a watchdog timer reset (WDT crash) or hard fault.

Rule 2: Use the Flag Pattern

Set a volatile boolean or increment a volatile uint32_t counter in the ISR. Process the action in your main loop(). This is the safest, most portable pattern:

volatile uint32_t pulseCount = 0;

void IRAM_ATTR countPulse() {
  pulseCount++;
}

// In loop():
if (pulseCount > 0) {
  noInterrupts();
  uint32_t count = pulseCount;
  pulseCount = 0;
  interrupts();
  // Process count safely here
}

Rule 3: Use noInterrupts()/interrupts() for Atomic Access

On ESP32, 32-bit variable reads/writes are atomic on a single core, but if you use 64-bit values or multi-byte structures, wrap the copy operation with portENTER_CRITICAL() / portEXIT_CRITICAL() (the FreeRTOS-safe version of noInterrupts).

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR myISR() {
  portENTER_CRITICAL_ISR(&mux);
  // modify shared variable
  portEXIT_CRITICAL_ISR(&mux);
}

// In main code:
portENTER_CRITICAL(&mux);
// read shared variable
portEXIT_CRITICAL(&mux);
AC 220V Security PIR Human Body Motion Sensor

AC 220V Security PIR Human Body Motion Sensor Detector

Connect a PIR sensor output to a GPIO interrupt pin on your ESP32 to build a real-time motion detection alarm system — no polling required.

View on Zbotic

Debouncing Interrupts in ESP32

Mechanical buttons and switches suffer from “bounce” — when pressed, the contact physically bounces multiple times in 5–50 milliseconds, generating many spurious interrupt triggers instead of one. This is one of the most common issues beginners face with ESP32 GPIO interrupts.

Software debouncing inside the ISR (using millis-based check):

const unsigned long DEBOUNCE_MS = 50;
volatile unsigned long lastInterruptTime = 0;
volatile bool buttonFlag = false;

void IRAM_ATTR handleButton() {
  unsigned long now = millis();
  if (now - lastInterruptTime > DEBOUNCE_MS) {
    buttonFlag = true;
    lastInterruptTime = now;
  }
}

Note: calling millis() inside an ISR is technically safe on ESP32 Arduino (it reads a hardware timer register), but it is not recommended in very tight timing contexts. A cleaner approach uses esp_timer_get_time() which returns microseconds and is ISR-safe:

volatile int64_t lastTrigger = 0;

void IRAM_ATTR debouncedISR() {
  int64_t now = esp_timer_get_time();
  if (now - lastTrigger > 50000) { // 50ms in microseconds
    buttonFlag = true;
    lastTrigger = now;
  }
}

Hardware debouncing is more reliable: add a 0.1µF ceramic capacitor between the GPIO pin and GND, along with a 10kΩ pull-up resistor. The RC low-pass filter smooths out bounce spikes before they reach the GPIO. This is the recommended approach for production products sold in the Indian market where environmental noise can be higher.

Real-World Example: Rotary Encoder and PIR Sensor

Let’s build a real-world project: an ESP32-based security system that uses a PIR sensor interrupt to trigger an alarm, combined with a rotary encoder for menu navigation — all interrupt-driven.

#define PIR_PIN    34
#define ENC_CLK    32
#define ENC_DT     33
#define BUZZER_PIN 25

volatile bool motionDetected = false;
volatile int encoderPos = 0;
volatile int lastEncA = HIGH;

void IRAM_ATTR pirISR() {
  motionDetected = true;
}

void IRAM_ATTR encoderISR() {
  int a = digitalRead(ENC_CLK);
  int b = digitalRead(ENC_DT);
  if (a != lastEncA) {
    encoderPos += (b != a) ? 1 : -1;
    lastEncA = a;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIR_PIN, INPUT);
  pinMode(ENC_CLK, INPUT_PULLUP);
  pinMode(ENC_DT,  INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(PIR_PIN),
                  pirISR, RISING);
  attachInterrupt(digitalPinToInterrupt(ENC_CLK),
                  encoderISR, CHANGE);
}

void loop() {
  if (motionDetected) {
    motionDetected = false;
    digitalWrite(BUZZER_PIN, HIGH);
    Serial.println("ALERT: Motion Detected!");
    delay(1000);
    digitalWrite(BUZZER_PIN, LOW);
  }

  static int lastPos = 0;
  if (encoderPos != lastPos) {
    Serial.print("Encoder: "); Serial.println(encoderPos);
    lastPos = encoderPos;
  }
}

This pattern is used in many commercial IoT products shipped within India — home automation controllers, industrial counters, and smart door lock systems all rely on interrupt-driven GPIO handling like this.

30Pin ESP32 Expansion Board

30Pin ESP32 Expansion Board with Type-C USB and Micro USB

This expansion board breaks out all 30 GPIO pins of your ESP32 with labelled connectors, making it easy to prototype interrupt-driven multi-sensor projects.

View on Zbotic

ESP-IDF GPIO Interrupt Configuration

If you are working with ESP-IDF (Espressif’s official C/C++ SDK) instead of the Arduino framework, the interrupt API is slightly different but more powerful and flexible:

#include "driver/gpio.h"

#define GPIO_INPUT_PIN  GPIO_NUM_27
#define ESP_INTR_FLAG_DEFAULT 0

static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg) {
  uint32_t gpio_num = (uint32_t) arg;
  xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}

void app_main(void) {
  gpio_config_t io_conf = {
    .intr_type    = GPIO_INTR_NEGEDGE,
    .mode         = GPIO_MODE_INPUT,
    .pin_bit_mask = (1ULL << GPIO_INPUT_PIN),
    .pull_down_en = 0,
    .pull_up_en   = 1
  };
  gpio_config(&io_conf);

  gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));

  gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
  gpio_isr_handler_add(GPIO_INPUT_PIN,
                       gpio_isr_handler,
                       (void*) GPIO_INPUT_PIN);

  uint32_t io_num;
  while (1) {
    if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
      printf("GPIO[%lu] interrupt triggeredn", io_num);
    }
  }
}

Using a FreeRTOS queue (xQueueSendFromISR) is the cleanest IDF pattern. The ISR pushes the GPIO number into the queue, and a dedicated task receives it — decoupling interrupt handling from processing completely. This approach is especially useful in multi-core ESP32 projects where you want the processing task to run on Core 1 while Core 0 handles Wi-Fi/BLE.

Ai Thinker ESP32-C3-01M Wi-Fi + BLE Module

Ai Thinker ESP32-C3-01M Wi-Fi + BLE Module

A compact ESP32-C3 module ideal for interrupt-driven IoT nodes — supports GPIO interrupts and runs ESP-IDF natively at a very affordable price point in India.

View on Zbotic

Frequently Asked Questions

Can I use delay() inside an ESP32 ISR?

No, you should never call delay() inside an ISR. delay() relies on FreeRTOS tick interrupts and other background tasks, which are blocked while an ISR runs. Using delay() in an ISR will cause a watchdog timer reset and crash your ESP32. Always use the flag pattern — set a variable in the ISR and handle the delay in loop().

Why does my ESP32 reset or crash when I add an interrupt?

The most common causes are: (1) missing IRAM_ATTR on the ISR function — the function is in flash and causes a cache exception when called during a flash read; (2) calling non-ISR-safe functions like Serial, WiFi, or Wire inside the ISR; (3) a stack overflow if the ISR is too complex. Always keep ISRs minimal and use the flag/queue pattern.

How many GPIO interrupts can ESP32 handle simultaneously?

The ESP32 can handle interrupts on all GPIO pins simultaneously through a single shared GPIO interrupt service, which then dispatches to individual handlers. Practically, you can have dozens of interrupt-enabled pins active at the same time. There is a limit on the number of installed ISR service handlers (default 32), but this is rarely reached in normal projects.

What is the difference between RISING and CHANGE modes?

RISING triggers the ISR only when the pin transitions from LOW to HIGH (0V to 3.3V). FALLING triggers only on HIGH to LOW. CHANGE triggers on both transitions. For a button with INPUT_PULLUP, use FALLING for a single trigger per press. Use CHANGE for rotary encoders where you need to detect both edges to determine direction.

Is attachInterrupt() safe to call from setup() on ESP32?

Yes, attachInterrupt() is safe to call from setup() and even from within loop(). You can also remove an interrupt with detachInterrupt(digitalPinToInterrupt(pin)) at any time. This is useful for disabling interrupts temporarily during critical sections or when reconfiguring a pin’s mode.

Ready to Start Building?

Get all the ESP32 modules, sensors, and accessories you need for your interrupt-driven projects at Zbotic — India’s trusted electronics components store. Fast shipping across Mumbai, Delhi, Bangalore, Hyderabad, Pune, and all major Indian cities.

Shop ESP32 & IoT Components

Tags: arduino interrupt, ESP32, ESP32 ISR, ESP32 Tutorial, GPIO interrupt
Share Post
  • Facebook
  • Linkedin
  • Whatsapp
ESP32 HTTP Web Server: Control...
blog esp32 http web server control gpio from browser page 595556
blog ble mesh network smart lighting control with esp32 595559
BLE Mesh Network: Smart Lighti...

Related posts

Svg%3E
Read more

IoT Home Insurance Sensor Kit: Leak, Smoke, and Motion

April 1, 2026 0
Table of Contents IoT and Home Insurance Water Leak Detection Smoke and Fire Detection Motion and Intrusion Sensing Building the... Continue reading
Svg%3E
Read more

IoT Pet Tracker: GPS Collar with Geofencing Alerts

April 1, 2026 0
Table of Contents Introduction and Overview Hardware Components Required GPS Module Integration with ESP32 Cloud Platform Setup Real-Time Tracking Dashboard... Continue reading
Svg%3E
Read more

IoT Aquaponics Controller: Fish and Plant Automation

April 1, 2026 0
Table of Contents The Water Monitoring Challenge in India Sensor Technologies for Water Building the Sensor Node Data Transmission and... Continue reading
Svg%3E
Read more

IoT Composting Monitor: Temperature and Moisture Tracking

April 1, 2026 0
Table of Contents Why Temperature Monitoring Matters Sensor Selection Guide Hardware Assembly and Wiring Firmware Development Cloud Data Logging Alert... Continue reading
Svg%3E
Read more

IoT Beehive Monitor: Weight, Temperature, and Humidity

April 1, 2026 0
Table of Contents Why Monitor Beehives Weight Measurement System Temperature and Humidity Sensing Building the Monitor Data Analysis for Bee... Continue reading

Add comment Cancel reply

Your email address will not be published. Required fields are marked

Facebook Twitter Instagram Pinterest Linkedin Youtube

Get the latest deals and more.

Download on Google Play Download on the App Store

Call us: 020 69134444 / 1800 209 0998

Monday - Saturday 09:30 AM - 06:00 PM
For Technical Supports Email: [email protected]
For Sales / Enquiries Email: [email protected]

  • My Account

    • Cart

    • Wishlist

    • Checkout

    • My Orders

    • Track Order

    • My Account

  • Information

    • FAQs

    • Blogs

    • Career

    • About Us

    • Contact Us

    • Payment Options

  • Policies

    • Privacy Policy

    • Terms & Conditions

    • GST Input Tax Credit

    • Shipping Return Policy

    • E-Waste Collection Points

    • Our Sitemap

© Zbotic.in is registered trademark of Moxie Supply Pvt Ltd – All Rights Reserved
Login
Use Phone Number
Use Email Address
Not a member yet? Register Now
Reset Password
Use Phone Number
Use Email Address
Register
Already a member? Login Now