{"id":181571,"date":"2025-10-30T14:07:19","date_gmt":"2025-10-30T14:07:19","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=181571"},"modified":"2026-01-28T11:59:40","modified_gmt":"2026-01-28T11:59:40","slug":"esp32-gpio-interrupts-arduino","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/esp32-gpio-interrupts-arduino\/","title":{"rendered":"ESP32 GPIO Interrupts with Arduino IDE"},"content":{"rendered":"\n<p>Learn how to configure and handle interrupts with the ESP32 board to detect and respond to changes on its input GPIOs. We&#8217;ll build a project example using a pushbutton and another one using a PIR Motion Sensor.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 GPIO Interrupts with Arduino IDE\" class=\"wp-image-181889\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?w=1920&amp;quality=100&amp;strip=all&amp;ssl=1 1920w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?resize=1536%2C864&amp;quality=100&amp;strip=all&amp;ssl=1 1536w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p>Setting an ESP32 Input Pin as an interrupt allows you to quickly detect events (changes in the GPIO state) and react to them by halting the main program and running a callback function (also called an interrupt service routine\u2014ISR). This can be useful to detect the press of a pushbutton, when a sensor passes a certain threshold, when motion is detected, etc.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Table of Contents<\/h2>\n\n\n\n<p>In this tutorial, we&#8217;ll cover the following subjects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"#interrupts-introduction\" title=\"\">Introducing Interrupts<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#what-are-interrupts\" title=\"\">What are Interrupts?<\/a><\/li>\n\n\n\n<li><a href=\"#types-interrupts\" title=\"\">Types of Interrupts<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#interrupts-esp32\" title=\"\">Using Interrupts with the ESP32<\/a><\/li>\n\n\n\n<li><a href=\"#example-1\" title=\"\">Example 1: ESP32 &#8211; Detecting a Button Press with an Interrupt<\/a><\/li>\n\n\n\n<li><a href=\"#example-2\" title=\"\">Example 2: ESP32 with a PIR Sensor &#8211; Detect Motion with an Interrupt<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before proceeding with this tutorial, you should have the ESP32 boards installed in your Arduino IDE. Follow this next tutorial to install the ESP32 on the Arduino IDE, if you haven\u2019t already.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/installing-esp32-arduino-ide-2-0\/\">Installing ESP32 Board in Arduino IDE 2 (Windows, Mac OS X, Linux)<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"interrupts-introduction\">Introducing Interrupts<\/h2>\n\n\n\n<p>Interrupts are useful for making things happen automatically in microcontroller programs and can help solve timing problems. <\/p>\n\n\n\n<p>Interrupts provide mechanisms to respond to external events, enabling your ESP32 boards to react quickly to changes without continuously polling (continuously checking the current value of a pin or variable).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"what-are-interrupts\">What are Interrupts?<\/h3>\n\n\n\n<p>Interrupts are signals that pause the normal execution flow of a program to handle a specific event. When an interrupt happens, the processor stops the execution of the main program to execute a task and then gets back to the main program. That task is also referred to as an <em>interrupt handling routine<\/em> (or <em>interrupt service routine<\/em>, ISR), as shown in the figure below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"992\" height=\"291\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/interrupt.png?resize=992%2C291&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"How interrupts work\" class=\"wp-image-81979\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/interrupt.png?w=992&amp;quality=100&amp;strip=all&amp;ssl=1 992w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/interrupt.png?resize=300%2C88&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/interrupt.png?resize=768%2C225&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 992px) 100vw, 992px\" \/><\/figure><\/div>\n\n\n<p>In summary, with interrupts, you don\u2019t need to constantly check the current value of a pin. With interrupts, when a change is detected, a callback function is triggered.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"types-interrupts\">Types of Interrupts<\/h3>\n\n\n\n<p>There are different types of interrupts: external interrupts (hardware-based) and timer interrupts (software interrupts).<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>External Interrupts<\/strong>: triggered by external signals and detected on the ESP32 GPIOs, such as a button press or a sensor reading\u2014this is hardware-based and associated with a specific GPIO pin. When the state of a pin changes, it will trigger the interrupt service routine (ISR). We&#8217;ll focus on these in this tutorial.<\/li>\n\n\n\n<li><strong>Timer Interrupts (software timers)<\/strong>: initiated based on time intervals, enabling periodic actions\u2014this is software-based. <a href=\"https:\/\/randomnerdtutorials.com\/esp32-freertos-software-timers-interrupts\/\" title=\"\">You can learn more about software interrupts with the ESP32 in this tutorial<\/a>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"interrupts-esp32\">Using Interrupts with the ESP32<\/h2>\n\n\n\n<p>To set an interrupt in the Arduino IDE, you use the&nbsp;<strong>attachInterrupt()<\/strong>&nbsp;function, that accepts as  arguments: the GPIO pin, the name of the function to be executed, and the mode:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>attachInterrupt(GPIO, callback_function, mode);<\/code><\/pre>\n\n\n\n<p>This instruction should be added to the <span class=\"rnthl rntliteral\">setup()<\/span> of your Arduino code.<\/p>\n\n\n\n<p>Let&#8217;s take a look at the arguments you should pass to that function.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">GPIO Interrupt<\/h3>\n\n\n\n<p>The first argument of the <span class=\"rnthl rntliteral\">attachInterrupt()<\/span> function is the GPIO number where we&#8217;ll detect the change.  For example, if you want to use GPIO 27 as an interrupt, you can use:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>digitalPinToInterrupt(27)<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclgreen\">With an ESP32 board, all the pins that can act as inputs can be set as interrupts.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Callback Function<\/h3>\n\n\n\n<p>The second argument of the <span class=\"rnthl rntliteral\">attachInterrupt()<\/span> function is the name of the function that will be called when the interrupt is triggered.<\/p>\n\n\n\n<p>Now, there are a few important rules you should be aware of when defining your ISR (callback function).<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The ISR should not return anything.<\/li>\n\n\n\n<li>ISRs should be as short and fast as possible because they halt the normal execution of the code.<\/li>\n\n\n\n<li>They should have the <span class=\"rnthl rntliteral\">ARDUINO_ISR_ATTR<\/span> attribute, so that they run in the ESP32 Internal RAM and not in Flash. IRAM access is much faster, which is critical for ISRs to run reliably without timing issues or crashes during interrupts.<\/li>\n\n\n\n<li>Variables that are used inside ISRs and throughout the code should preferably be <span class=\"rnthl rntliteral\">volatile<\/span>. This prevents the compiler from caching values in registers (and skipping memory access), so reads\/writes always access the actual memory location and reflect unexpected changes caused by the interrupt.<\/li>\n<\/ol>\n\n\n\n<p>Here&#8217;s an example of an ISR so that you can check its syntax:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void ARDUINO_ISR_ATTR my_callback() {\n    \/\/ Any code you want to run\n}<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclgray\">Another important thing about ISRs is that you should keep their code as fast and simple as possible and avoid things like complex operations, writing to the Serial Monitor, or using <span class=\"rnthl rntliteral\">delay()<\/span>. Instead, you should use a flag or counter to indicate that the interrupt happened, and then handle whatever you need to do in the main code or <span class=\"rnthl rntliteral\">loop()<\/span> section.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Mode<\/h3>\n\n\n\n<p>The third argument is the mode. There are 5 different modes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span class=\"rnthl rntliteral\">LOW<\/span>: to trigger the interrupt whenever the pin is LOW;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">HIGH<\/span>: to trigger the interrupt whenever the pin is HIGH;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">CHANGE<\/span>: to trigger the interrupt whenever the pin changes value &#8211; for example, from HIGH to LOW or LOW to HIGH;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">FALLING<\/span>: for when the pin goes from HIGH to LOW;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">RISING<\/span>: to trigger when the pin goes from LOW to HIGH.<\/li>\n<\/ul>\n\n\n\n<p>The following picture will help you better understand the different trigger modes.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"750\" height=\"438\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/interrupt-modes.png?resize=750%2C438&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Interrupt modes\" class=\"wp-image-169637\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/interrupt-modes.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/interrupt-modes.png?resize=300%2C175&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Attach an Interrupt with Arguments<\/h3>\n\n\n\n<p>Besides the <span class=\"rnthl rntliteral\">attachInterrupt()<\/span> function, you can alternatively use the <span class=\"rnthl rntliteral\">attachInterruptArg()<\/span> function instead. The function <span class=\"rnthl rntliteral\">attachInterruptArg()<\/span> is used to attach the interrupt to the defined pin using argumentsthis means you can pass arguments to the callback function.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>attachInterruptArg(uint8_t pin, void callback_function, void * arg, int mode);<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span class=\"rnthl rntliteral\">pin<\/span> defines the GPIO pin number.<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">callback_function<\/span> set the callback function.<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">arg<\/span> pointer to the interrupt arguments.<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">mode<\/span> set the interrupt mode.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Detaching\/Disabling an Interrupt from a GPIO Pin<\/h3>\n\n\n\n<p>When you don&#8217;t want the ESP32 to no longer monitor the pin, you can call the <span class=\"rnthl rntliteral\">detachInterrupt()<\/span> function and pass as argument the GPIO pin.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>detachInterrupt(digitalPinToInterrupt(interruptPin));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"example-1\">Example 1: ESP32 &#8211; Detecting a Button Press with an Interrupt<\/h2>\n\n\n\n<p>In this section, we&#8217;ll create a simple example to detect a button press using an interrupt. We&#8217;re using a pushbutton in this example, but you can use a sensor with a threshold, like a PIR Motion Sensor, for example (we&#8217;ll take a look at that later in this guide).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"421\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-with-pushbutton.jpg?resize=750%2C421&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 with a pushbutton on a breadboard\" class=\"wp-image-181807\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-with-pushbutton.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-with-pushbutton.jpg?resize=300%2C168&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Parts Required<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noopener\" title=\"\">ESP32 Board<\/a> (model of your choice)<\/li>\n\n\n\n<li>1x <a href=\"https:\/\/makeradvisor.com\/tools\/pushbuttons-kit\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Pushbutton<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Jumper Wires<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Breadboard<\/a><\/li>\n<\/ul>\n\n\n<p>You can use the preceding links or go directly to <a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\">MakerAdvisor.com\/tools<\/a> to find all the parts for your projects at the best price!<\/p><p style=\"text-align:center;\"><a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2017\/10\/header-200.png?w=1200&#038;quality=100&#038;strip=all&#038;ssl=1\"><\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Wiring Diagram<\/h3>\n\n\n\n<p>For this example, you need to connect a pushbutton to GPIO 18. We won&#8217;t use any resistors for the pushbutton because we&#8217;ll use the ESP32 internal pull-up resistors. Wire one lead of the pushbutton to GPIO 18 and the other one to GND, as shown in the diagram below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"719\" height=\"792\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-pushbutton-schematic-diagram_bb.png?resize=719%2C792&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 connected to a pushbutton - schematic diagram\" class=\"wp-image-181811\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-pushbutton-schematic-diagram_bb.png?w=719&amp;quality=100&amp;strip=all&amp;ssl=1 719w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-pushbutton-schematic-diagram_bb.png?resize=272%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 272w\" sizes=\"(max-width: 719px) 100vw, 719px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntclblue\">Alternatively, you can use any other suitable GPIO as long as you modify the code. Make sure to check our <a href=\"https:\/\/randomnerdtutorials.com\/esp32-pinout-reference-gpios\/\" title=\"\">ESP32 Pinout Guide<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code<\/h3>\n\n\n\n<p>Upload the following code to your ESP32. It detects the pushbutton presses and prints the number of presses in the Serial Monitor.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-gpio-interrupts-arduino\/\n*********\/\n#include &lt;Arduino.h&gt;\n\n\/\/ Global variables for the button\nconst uint8_t buttonPin = 18;\nvolatile int32_t counter = 0;\nvolatile bool pressed = false;\n\n\/\/ Interrupt Service Routine (ISR)\nvoid ARDUINO_ISR_ATTR buttonISR() {\n  counter++;\n  pressed = true;\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  pinMode(buttonPin, INPUT_PULLUP);\n  attachInterrupt(buttonPin, buttonISR, RISING);\n  Serial.println(&quot;Press the button on GPIO 18.&quot;);\n}\n\nvoid loop() {\n  if (pressed) {\n    Serial.print(&quot;Button pressed &quot;);\n    Serial.print(counter);\n    Serial.println(&quot; times.&quot;);\n    pressed = false;\n  }\n  delay(10);\n}<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP32_Interrupts\/Interrupt_Pushubtton_No_Debounce.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Let&#8217;s take a look at how the code works so that you can understand how to use interrupts in your code.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Pushbutton Global Variables<\/h4>\n\n\n\n<p>First, define global variables for the pushbutton. We&#8217;re defining the <span class=\"rnthl rntliteral\">buttonPin<\/span> as a const variable because it won&#8217;t change throughout the code. The <span class=\"rnthl rntliteral\">counter<\/span> variable will count the number of pushbutton presses. Finally, <span class=\"rnthl rntliteral\">pressed<\/span> is a boolean variable that will indicate whether the button was pressed or not. It starts as <span class=\"rnthl rntliteral\">false<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ Global variables for the button\nconst uint8_t buttonPin = 18;\nvolatile uint32_t counter = 0;\nvolatile bool pressed = false;<\/code><\/pre>\n\n\n\n<p>Notice that <span class=\"rnthl rntliteral\">counter<\/span> and <span class=\"rnthl rntliteral\">pressed<\/span> are <span class=\"rnthl rntliteral\">volatile<\/span> variables because they will be used inside the ISR and also throughout the code (in the <span class=\"rnthl rntliteral\">loop()<\/span>).<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Interrupt Service Routine<\/h4>\n\n\n\n<p>Define the interrupt service routine, which is the callback function that will run when the interrupt is triggered. In this case, the function is called <span class=\"rnthl rntliteral\">buttonISR()<\/span> and it has the <span class=\"rnthl rntliteral\">ARDUINO_ISR_ATTR<\/span> attribute so that the function runs on the ESP32 IRAM as we&#8217;ve seen previously.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void ARDUINO_ISR_ATTR buttonISR() {<\/code><\/pre>\n\n\n\n<p>In this function, we increase the <span class=\"rnthl rntliteral\">counter<\/span> variable and set the <span class=\"rnthl rntliteral\">pressed<\/span> variable to <span class=\"rnthl rntliteral\">true<\/span>, indicating that a button press happened.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>counter++;\npressed = true;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">setup()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">setup()<\/span>, initialize the Serial Monitor for debugging purposes.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void setup() {\n  Serial.begin(115200);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Setting an Interrupt<\/h4>\n\n\n\n<p>Set the interrupt pin first as an input with an internal pull-up resistor as follows.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>pinMode(buttonPin, INPUT_PULLUP);<\/code><\/pre>\n\n\n\n<p>Set the pin as an interrupt and assign it a callback function with <span class=\"rnthl rntliteral\">RISING<\/span> mode (this means the interrupt will be triggered when the interrupt pin goes from LOW to HIGH\u2014when the pushbutton is pressed).<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>attachInterrupt(buttonPin, buttonISR, RISING);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">loop()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">loop()<\/span>, we check whether the pushbutton was pressed. If it was pressed, we print how many times it was pressed so far.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void loop() {\n  if (pressed) {\n    Serial.print(\"Button pressed \");\n    Serial.print(counter);\n    Serial.println(\" times.\");<\/code><\/pre>\n\n\n\n<p>In the end, we set it to <span class=\"rnthl rntliteral\">false<\/span> again, so that we can print when there&#8217;s a new press.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>pressed = false;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Testing the Example<\/h3>\n\n\n\n<p>Upload the code to your ESP32. After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 RST button so that it starts running the code.<\/p>\n\n\n\n<p>Press the pushbutton.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"421\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Pressing-Pushbutton.jpg?resize=750%2C421&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Pressing a pushbutton connected to the ESP32\" class=\"wp-image-181806\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Pressing-Pushbutton.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Pressing-Pushbutton.jpg?resize=300%2C168&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>See the number of presses increasing in the Serial Monitor.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"666\" height=\"326\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_no_debouncing.png?resize=666%2C326&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32: detecting pushbutton presses with an interrupt without debouncing - results on the Serial Monitor\" class=\"wp-image-181653\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_no_debouncing.png?w=666&amp;quality=100&amp;strip=all&amp;ssl=1 666w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_no_debouncing.png?resize=300%2C147&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 666px) 100vw, 666px\" \/><\/figure><\/div>\n\n\n<p><strong>Issue<\/strong>: notice that when you press the pushbutton, sometimes it catches more presses than it should. This is an issue related to mechanical buttons called mechanical bouncing. This can be solved via software or via hardware.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"476\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/button-bounce.png?resize=750%2C476&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"debounce a pushbutton\" class=\"wp-image-169643\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/button-bounce.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/button-bounce.png?resize=300%2C190&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>This happens because the electrical contacts inside the button connect and disconnect very quickly before reaching a steady state, which will cause the system to register multiple press events, causing an inaccurate count.<\/p>\n\n\n\n<p>We&#8217;ll show you how you can add debouncing to your code to prevent false pushbutton presses.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Detecting a Button Press with an Interrupt (with Debouncing)<\/h3>\n\n\n\n<p>The following code is similar to the previous one, but includes the lines of code required for debouncing the pushbutton.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-gpio-interrupts-arduino\/\n*********\/\n#include &lt;Arduino.h&gt;\n\n\/\/ Global variables for the button\nconst uint8_t buttonPin = 18;\nvolatile uint32_t counter = 0;\nvolatile bool pressed = false;\n\n\/\/ For debouncing the pushbutton\nconst unsigned long DEBOUNCE_DELAY = 50;  \/\/ in milliseconds\nvolatile unsigned long lastPressTime = 0;\n\n\/\/ Interrupt Service Routine (ISR)\nvoid ARDUINO_ISR_ATTR buttonISR() {\n  unsigned long now = millis();\n  if (now - lastPressTime &gt; DEBOUNCE_DELAY) {\n    counter++;\n    pressed = true;\n  }\n  lastPressTime = now;\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  pinMode(buttonPin, INPUT_PULLUP);\n  attachInterrupt(buttonPin, buttonISR, HIGH);\n  Serial.println(&quot;Press the button on GPIO 18.&quot;);\n}\n\nvoid loop() {\n  if (pressed) {\n    Serial.print(&quot;Button pressed &quot;);\n    Serial.print(counter);\n    Serial.println(&quot; times.&quot;);\n    pressed = false;\n  }\n  delay(10);\n}\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP32_Interrupts\/Interrupt_Pushubtton_Debounce.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>In this code, we&#8217;ve added variables to handle debouncing for the pushbutton. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ For debouncing the pushbutton\nconst unsigned long DEBOUNCE_DELAY = 50;  \/\/ in milliseconds\nvolatile unsigned long lastPressTime = 0;<\/code><\/pre>\n\n\n\n<p><span class=\"rnthl rntliteral\">DEBOUNCE_DELAY<\/span> defines the minimum time required between button presses to register a valid event (preventing false triggers from mechanical bounce). The 50 milliseconds should be enough. If you continue to have false positives, increase the debounce time.<\/p>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">lastPressTime<\/span> saves the time of the last button press.<\/p>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">buttonISR()<\/span> function, before considering a valid pushbutton press, we first check if at least 50 milliseconds have passed since the last press. We get the time that has passed since the program started with <span class=\"rnthl rntliteral\">millis()<\/span> (in milliseconds) and save it in the <span class=\"rnthl rntliteral\">now<\/span> variable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>unsigned long now = millis();\nif (now - lastPressTime &gt; DEBOUNCE_DELAY) {<\/code><\/pre>\n\n\n\n<p>If yes, we consider that we have a valid button press and we increase the <span class=\"rnthl rntliteral\">counter<\/span> variable and set the <span class=\"rnthl rntliteral\">pressed<\/span> variable to <span class=\"rnthl rntliteral\">true<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>counter++;\npressed = true;<\/code><\/pre>\n\n\n\n<p>After that, update the <span class=\"rnthl rntliteral\">lastPressTime<\/span> with the current time.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>lastPressTime = now;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Testing the Example<\/h4>\n\n\n\n<p>Now, if you test this new example, you&#8217;ll see you no longer get false positive presses (if you do, increase the debounce delay in your code).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"676\" height=\"315\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_with_debouncing.png?resize=676%2C315&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32: detecting pushbutton presses with an interrupt with debouncing - results on the Serial Monitor\" class=\"wp-image-181714\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_with_debouncing.png?w=676&amp;quality=100&amp;strip=all&amp;ssl=1 676w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/Pushbutton_interrupt_with_debouncing.png?resize=300%2C140&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 676px) 100vw, 676px\" \/><\/figure><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"example-2\">Example 2: ESP32 with a PIR Sensor &#8211; Detect Motion with an Interrupt<\/h2>\n\n\n\n<p>As we mentioned previously, you can also detect events caused by sensors that change their output state when they reach a certain threshold, like for example a PIR Motion sensor.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/PIR-Motion-Sensors.jpeg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"PIR Motion Sensors: AM312 and HC-SR501\" class=\"wp-image-181715\" style=\"width:750px;height:auto\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/PIR-Motion-Sensors.jpeg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/PIR-Motion-Sensors.jpeg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><figcaption class=\"wp-element-caption\">Two of the most popular motion sensors used by hobbyists<br>electronics projects: mini PIR motion sensor (AM312) and PIR motion sensor<br>(HC-SR501).<\/figcaption><\/figure><\/div>\n\n\n<p>These sensors output a <span class=\"rnthl rntliteral\">HIGH<\/span> signal when movement is detected, or a <span class=\"rnthl rntliteral\">LOW<\/span> signal if no movement is detected. The digital output from the PIR sensor can be read by an ESP32 GPIO pin, allowing you to program specific actions based on the detected motion status. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Example Overview<\/h3>\n\n\n\n<p>We\u2019ll create a simple example that will light up an LED when motion is detected. After learning how it works, the same way of thinking can be applied to useful applications, such as sending an email or triggering an alarm.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"135\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/pir-motion-sensor-with-micropython-esp32-esp8266.png?resize=750%2C135&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 with a PIR Motion Sensor Example Overview\" class=\"wp-image-82033\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/pir-motion-sensor-with-micropython-esp32-esp8266.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/03\/pir-motion-sensor-with-micropython-esp32-esp8266.png?resize=300%2C54&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>Here&#8217;s how the example works:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The sensor detects motion.<\/li>\n\n\n\n<li>The ESP32 detects this event.<\/li>\n\n\n\n<li>It prints in the Serial Monitor that motion was detected.<\/li>\n\n\n\n<li>It turns on an LED for 20 seconds.<\/li>\n\n\n\n<li>During those 20 seconds, we don&#8217;t print anything else to the Serial Monitor.<\/li>\n\n\n\n<li>After those 20 seconds and if motion was not detected, we turn off the LED, print a message to the Serial Monitor indicating that motion has stopped.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Parts Required<\/h3>\n\n\n\n<p>For this example, you need the following parts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noopener\" title=\"\">ESP32 DOIT DEVKIT V1 Board<\/a> or other model of your choice<\/li>\n\n\n\n<li>1x <a href=\"https:\/\/makeradvisor.com\/tools\/mini-hc-sr505-pir-motion-sensor\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Mini PIR motion sensor (AM312)<\/a> or <a href=\"https:\/\/makeradvisor.com\/tools\/pir-motion-sensor-hc-sr501\/\" target=\"_blank\" rel=\"noopener\" title=\"\">PIR motion sensor (HC-SR501)<\/a><\/li>\n\n\n\n<li>1x <a href=\"https:\/\/makeradvisor.com\/tools\/3mm-5mm-leds-kit-storage-box\/\" target=\"_blank\" rel=\"noopener\" title=\"\">5mm LED<\/a><\/li>\n\n\n\n<li>1x <a href=\"https:\/\/makeradvisor.com\/tools\/resistors-kits\/\" target=\"_blank\" rel=\"noopener\" title=\"\">220 Ohm resistor<\/a> (or similar value)<\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" title=\"\">Jumper wires<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Breadboard<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Circuit Diagram<\/h3>\n\n\n\n<p>PIR sensors have a GND, VCC, and a data line. Connect the GND to the ESP32 GND, VCC to 3.3V, and the data line to an available GPIO. We\u2019ll use the following pins:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>PIR Sensor<\/strong><\/td><td><strong>GND<\/strong><\/td><td><strong>VCC<\/strong><\/td><td><strong>Data<\/strong><\/td><\/tr><tr><td><strong>ESP32<\/strong><\/td><td>GND<\/td><td>3.3V<\/td><td>GPIO 27<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>For this circuit, the LED is connected to GPIO 26, and the PIR motion sensor data pin is connected to GPIO 27. We\u2019ll use the AM312 PIR motion sensor that works with 3.3V. The following figure shows the AM312 PIR motion sensor pinout.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/04\/AM312-PIR-Motion-Sensor.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"AM312 PIR Motion Sensor Pinout labeled\" class=\"wp-image-130668\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/04\/AM312-PIR-Motion-Sensor.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/04\/AM312-PIR-Motion-Sensor.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>You can use the following diagram as a reference to wire your circuit.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"567\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?resize=1024%2C567&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 connected to a PIR motion sensor and an LED\" class=\"wp-image-68486\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?resize=1024%2C567&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?resize=300%2C166&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?resize=768%2C426&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?resize=750%2C415&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/07\/pir_esp32_interrupts.jpg?w=1274&amp;quality=100&amp;strip=all&amp;ssl=1 1274w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntclgray\">The <a href=\"https:\/\/makeradvisor.com\/tools\/mini-hc-sr505-pir-motion-sensor\/\" target=\"_blank\" rel=\"noopener noreferrer\">Mini AM312 PIR Motion Sensor<\/a>&nbsp;used in this project operates at 3.3V. However, if you&#8217;re using another PIR motion sensor like the&nbsp;<a href=\"https:\/\/makeradvisor.com\/tools\/pir-motion-sensor-hc-sr501\/\" target=\"_blank\" rel=\"noopener noreferrer\">HC-SR501<\/a>, it operates at 5V. You can either&nbsp;<a href=\"https:\/\/randomnerdtutorials.com\/modifying-cheap-pir-motion-sensor-to-work-at-3-3v\/\" target=\"_blank\" rel=\"noopener noreferrer\">modify it to operate at 3.3V<\/a>&nbsp;or simply power it using the Vin pin.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code<\/h3>\n\n\n\n<p>After wiring the circuit, upload the following code to your ESP32.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-pir-motion-sensor-interrupts-timers\/\n  ESP32 GPIO Interrupts with Arduino IDE: https:\/\/RandomNerdTutorials.com\/esp32-gpio-interrupts-arduino\/\n*********\/\n#include &lt;Arduino.h&gt;\n\n\/\/ Set GPIOs for LED and PIR Motion Sensor\nconst uint8_t led = 26;\nconst uint8_t motionSensor = 27;\n\n\/\/ Timer: Auxiliary variables\nunsigned long now;\nvolatile unsigned long lastTrigger = 0;\nvolatile bool startTimer = false;\n\nbool printMotion = false;\n\nconst unsigned long timeSeconds = 20 * 1000UL;  \/\/20 seconds in milliseconds\n\nvoid ARDUINO_ISR_ATTR motionISR() {\n  lastTrigger = millis();\n  startTimer = true;\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  pinMode(motionSensor, INPUT_PULLUP);\n  attachInterrupt(motionSensor, motionISR, RISING);\n\n  \/\/ Set LED to LOW\n  pinMode(led, OUTPUT);\n  digitalWrite(led, LOW);\n}\n\nvoid loop() {\n  now = millis();\n\n\/\/ Turn LED on immediately on new trigger\n  if (startTimer &amp;&amp; !printMotion) {\n    digitalWrite(led, HIGH);\n    Serial.println(&quot;MOTION DETECTED!!!&quot;);\n    printMotion = true;\n  }\n\n\/\/ Turn off the LED after timeout\n  if (startTimer &amp;&amp; (now - lastTrigger &gt; timeSeconds)) {\n    Serial.println(&quot;Motion stopped...&quot;);\n    digitalWrite(led, LOW);\n    startTimer = false;\n    printMotion = false;\n  }\n}\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP32_Interrupts\/Interrupts_PIR_Motion.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Let&#8217;s take a quick look at the code to better understand how it works.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Defining Variables<\/h4>\n\n\n\n<p>We start by defining the pins for the LED and PIR Motion sensor. Adjust if you&#8217;re using different pins:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const uint8_t led = 26;\nconst uint8_t motionSensor = 27;<\/code><\/pre>\n\n\n\n<p>Create variables to track the duration of the LED in the on state. The <span class=\"rnthl rntliteral\">now<\/span> variable saves the current time (time elapsed since the program has started), the <span class=\"rnthl rntliteral\">lastTrigger<\/span> saves the last time motion was detected, and the <span class=\"rnthl rntliteral\">startTimer<\/span> is a boolean variable to indicate whether the timer to turn on the LED is currently running or not.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>unsigned long now;\nvolatile unsigned long lastTrigger = 0;\nvolatile bool startTimer = false;<\/code><\/pre>\n\n\n\n<p>We also have another variable to keep track whether the <em>Motion Detected<\/em> text was already printed to the Serial Monitor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>bool printMotion = false;<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">timeSeconds<\/span> variable saves how long we want the LED on after motion is detected. You can adjust according to your preferences.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const unsigned long timeSeconds = 20 * 1000UL;  \/\/20 seconds in milliseconds<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">motionISR()<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">motionISR()<\/span> will run when motion is detected. We save the current time on the <span class=\"rnthl rntliteral\">lastTrigger<\/span> variable to keep track when motion was detected, and we set the <span class=\"rnthl rntliteral\">startTimer<\/span> variable to <span class=\"rnthl rntliteral\">true<\/span> to indicate it&#8217;s time to start the timer to turn on the LED.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void ARDUINO_ISR_ATTR motionISR() {\n  lastTrigger = millis();\n  startTimer = true;\n}<\/code><\/pre>\n\n\n\n<p>We&#8217;ll then handle these variables in the <span class=\"rnthl rntliteral\">loop()<\/span> to do the tasks we want.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">setup()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">setup()<\/span>, set the motion sensor as an interrupt on <span class=\"rnthl rntliteral\">RISING<\/span> mode (when motion is detected, the sensor sets its output pin to <span class=\"rnthl rntliteral\">HIGH<\/span>).<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>pinMode(motionSensor, INPUT_PULLUP);\nattachInterrupt(motionSensor, motionISR, RISING);<\/code><\/pre>\n\n\n\n<p>And set the LED as an <span class=\"rnthl rntliteral\">OUTPUT<\/span> and set it to <span class=\"rnthl rntliteral\">LOW<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ Set LED to LOW\npinMode(led, OUTPUT);\ndigitalWrite(led, LOW);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">loop()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">loop()<\/span>, we&#8217;re constantly getting the current time and saving it in the <span class=\"rnthl rntliteral\">now<\/span> variable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>now = millis();<\/code><\/pre>\n\n\n\n<p>Then, we check whether the LED timer has started and if the motion message has not already been printed. If these conditions are met, we turn the LED on, print a message to the Serial Monitor, and set the <span class=\"rnthl rntliteral\">printMotion<\/span> variable to <span class=\"rnthl rntliteral\">true<\/span>, because we have now printed the <em>Motion Detected<\/em> message to the Serial Monitor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>if (startTimer &amp;&amp; !printMotion) {\n  digitalWrite(led, HIGH);\n  Serial.println(\"MOTION DETECTED!!!\");\n  printMotion = true;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">loop()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">loop()<\/span>, we&#8217;re also checking if, since the <span class=\"rnthl rntliteral\">startTimer<\/span> has started, 20 seconds have passed since the last trigger. If 20 seconds have passed since the last trigger, we turn off the LED, and set the <span class=\"rnthl rntliteral\">startTimer<\/span> and <span class=\"rnthl rntliteral\">printMotion<\/span> variables to <span class=\"rnthl rntliteral\">false<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>if (startTimer &amp;&amp; (now - lastTrigger &gt; timeSeconds)) {\n  Serial.println(\"Motion stopped...\");\n  digitalWrite(led, LOW);\n  startTimer = false;\n  printMotion = false;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Testing the Example<\/h4>\n\n\n\n<p>Upload the code to your ESP32 board. Open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button so that it starts running the code.<\/p>\n\n\n\n<p>Move your hand in front of the PIR sensor. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"496\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-detect-moton-with-PIR.jpeg?resize=750%2C496&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Moving my hand in front of the PIR motion sensor connected to the ESP32\" class=\"wp-image-181800\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-detect-moton-with-PIR.jpeg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-detect-moton-with-PIR.jpeg?resize=300%2C198&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>The LED should turn on, and a message is printed in the Serial Monitor saying <span class=\"rnthl rntliteral\">\u201cMOTION DETECTED!!!\u201d<\/span>. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"691\" height=\"306\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-Interrupts-Serial-Monitor.png?resize=691%2C306&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 PIR Motion Sensor with Interrupts - Messages on Serial Monitor\" class=\"wp-image-181798\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-Interrupts-Serial-Monitor.png?w=691&amp;quality=100&amp;strip=all&amp;ssl=1 691w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-Interrupts-Serial-Monitor.png?resize=300%2C133&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 691px) 100vw, 691px\" \/><\/figure><\/div>\n\n\n<p>After 20 seconds, the LED should turn off (if motion was not detected meanwhile).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"428\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-and-LED.jpeg?resize=750%2C428&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 on a breadboard connected to an LED and a PIR motion sensor\" class=\"wp-image-181799\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-and-LED.jpeg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-PIR-Motion-Sensor-and-LED.jpeg?resize=300%2C171&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>Now that you understand how to use interrupts to detect motion with a PIR motion sensor, you can easily adjust the code to do any useful tasks instead of controlling an LED. You can, for example, send notifications to your email or smartphone to indicate that motion was detected.<\/p>\n\n\n\n<p>We have a tutorial with seven different ways to send notifications with the ESP32 that you can explore:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/send-notifications-esp32\/\" title=\"\">7 Different Ways to Send Notifications with the ESP32<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>In this tutorial, we covered how to use interrupts with the ESP32 to detect changes on its GPIOs. Instead of having to constantly poll the state of a GPIO, we can use interrupts. Our code will run normally, and when an interrupt is detected, a callback function (ISR) will run.<\/p>\n\n\n\n<p>We covered an example using a pushbutton, and another one using a PIR motion sensor, but this can be applied to many other sensors that have an output pin that changes its state when reaching a certain threshold. For an in-depth guide on using a PIR motion sensor with the ESP32, you can check this tutorial:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-pir-motion-sensor-interrupts-timers\/\" title=\"\">ESP32 with PIR Motion Sensor using Interrupts and Timers<\/a><\/li>\n<\/ul>\n\n\n\n<p>For tutorials and guides about other sensors, you can check out our compilation of guides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-guides-sensors-modules\/\">ESP32: 29 Free Guides for Sensors and Modules<\/a><\/li>\n<\/ul>\n\n\n\n<p>If you also want to learn how to use interrupts with the ESP32 using MicroPython firmware, check these guides instead:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-interrupts-esp32-esp8266\/\">MicroPython: Interrupts with ESP32 and ESP8266<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-timer-interrupts-ep32-esp8266\/\">MicroPython: Timer Interrupts with the ESP32\/ESP8266<\/a><\/li>\n<\/ul>\n\n\n\n<p>We hope you&#8217;ve found this guide useful. To learn more about the ESP32, check out our resources:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/learn-esp32-with-arduino-ide\/\">Learn ESP32 with Arduino IDE (eBook)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-esp32\/\">All our ESP32 Projects and Guides<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to configure and handle interrupts with the ESP32 board to detect and respond to changes on its input GPIOs. We&#8217;ll build a project example using a pushbutton and &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"ESP32 GPIO Interrupts with Arduino IDE\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/esp32-gpio-interrupts-arduino\/#more-181571\" aria-label=\"Read more about ESP32 GPIO Interrupts with Arduino IDE\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":5,"featured_media":181889,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[276,281,277,299,264],"tags":[],"class_list":["post-181571","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-esp32","category-esp32-project","category-esp32-arduino-ide","category-0-esp32","category-project"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/10\/ESP32-Interrupts.jpg?fit=1920%2C1080&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/181571","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=181571"}],"version-history":[{"count":35,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/181571\/revisions"}],"predecessor-version":[{"id":187937,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/181571\/revisions\/187937"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/181889"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=181571"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=181571"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=181571"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}