ESP32 Arduino: External interrupts

The objective of this post is to explain how to handle external interrupts using the ESP32 and the Arduino core. The tests were performed on a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


Introduction

The objective of this post is to explain how to handle external interrupts using the ESP32 and the Arduino core.

The tests were performed on a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


The setup code

We will start by declaring the pin where the interrupt will be attached on a global variable. Note that depending on your ESP32 board the pin numbering of the ESP32 microcontroller and the one labeled on the board may not match. In the FireeBeetle board, the pin used below (digital pin 25) matches with the one labeled IO25/D2.

const byte interruptPin = 25;

We will also declare a counter that will be used by the interrupt routine to communicate with the main loop function and signal that an interrupt has occurred. Note that this variable needs to be declared as volatile since it will be shared by the ISR and the main code. Otherwise, it may be removed due to compiler optimizations.

volatile int interruptCounter = 0;

Additionally we will declare a counter to keep track of how many interrupts have already occurred globally since the start of the program. So this counter will be incremented each time an interrupt occurs.

int numberOfInterrupts = 0;

Finally, we will declare a variable of type portMUX_TYPE, which we will need to take care of the synchronization between the main code and the interrupt. We will see how to use it later.

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

Moving to the setup function, we start by opening a serial connection, in order to be able to output the results of our program.

Serial.begin(115200);
Serial.println("Monitoring interrupts: ");

Next, since we are going to be working with an external pin interrupt, we need to configure the previously declared pin number as an input pin. To do so we call the pinMode function, passing as argument the the number of the pin and the operating mode.

In order to know the state of the input when no electric signal is applied to our pin, we use the INPUT_PULLUP mode. So, when no signal is applied, it will be at a voltage level of VCC instead of floating, avoiding the detection of non existing external interrupts.

pinMode(interruptPin, INPUT_PULLUP);

Next we attach the interrupt to the pin with a call to the attachInterrupt function. As first argument, we pass the result of a call to the digitalPinToInterrupt function, which converts the pin number used to the corresponding internal interrupt number.

Next we pass the function that will handle the interrupts or, in other words, that will be executed when an interrupt on the specified pin occurs. We will call it handleInterrupt and specify its code later.

Finally we pass the interrupt mode, which basically specifies which type of change in the pin input signal triggers the interrupt. We will use FALLING, which means that the interrupt will occur when a change from VCC to GND is detected on the pin.

attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

The final setup code can be seen below.

const byte interruptPin = 25;
volatile int interruptCounter = 0;
int numberOfInterrupts = 0;

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void setup() {

  Serial.begin(115200);
  Serial.println("Monitoring interrupts: ");

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}


The main loop

Now we will move to the main loop. There we will simply check if our interrupts counter is greater than zero. If it does, it means that we have interrupts to handle.

So, if an interrupt has occurred we first take care of decrementing this interrupts counter, signalling that the interrupt has been detected and will be handled.

Note that this counter approach is better than using a flag since if multiple interrupts occur without the main code being able to handle them all, we will not loose any events. On the other hand if we use a flag and multiple interrupts occur without the main code being able to handle them, then the flag value will keep being set to true in the ISR and the main loop handler will only interpret as if only one has occurred.

Other important aspect to keep in mind is that we should disable interrupts when writing on a variable that is shared with an interrupt. This way we ensure that there is no concurrent access to it between the main code and the ISR.

In the Arduino environment, we usually have the NoInterrupts and Interrupts function to disable and re-enable interrupts. Nonetheless, at the time of writing, these functions were not yet implemented in the ESP32 Arduino core.

So, we perform the decrement of the variable inside a critical section, which we declare using a portENTER_CRITICAL and a portEXIT_CRITICAL macro. These calls both receive as input the address of the previously declared global portMUX_TYPE variable.

if(interruptCounter>0){

      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);

      //Handle the interrupt
}

After taking care of decrementing the counter, we will now increment the global counter that holds the number of interrupts detected since the beginning of the program. This variable doesn’t need to be incremented inside a critical section since the interrupt service routine will not access it.

After that, we will print a message indicating an interrupt was detected and how many interrupts have happened so far. Note that sending data to the serial port should never be done inside an interrupt service routine due to the fact that ISRs should be designed to execute as fast as possible. If you do this, you will most likely run into runtime problems.

This way, in our architecture, the ISR only takes care of the simple operation of signaling the main loop that the interrupt has occurred, and then the main loop handles the rest.

You can check the full main loop code below.

void loop() {

  if(interruptCounter>0){

      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);

      numberOfInterrupts++;
      Serial.print("An interrupt has occurred. Total: ");
      Serial.println(numberOfInterrupts);
  }
}


The interrupt handling function

To finish the code, we will declare our interrupt handling function. As previously mentioned, it will only take care of incrementing the global variable that is used to signalize to the main loop that an interrupt has occurred.

We will also enclose this operation in a critical section, which we declare by calling the portENTER_CRITICAL_ISR and portExit_CRITICAL_ISR macros. They also both receive as input the address of the global portMUX_TYPE variable.

This is needed because the variable we are going to use is also changed by the main loop, as seen before, and we need to prevent concurrent access problems.

Update: The interrupt handling routine should have the IRAM_ATTR attribute, in order for the compiler to place the code in IRAM. Also, interrupt handling routines should only call functions also placed in IRAM, as can be seen here in the IDF documentation. Thanks to Manuato for point this.

The full code for the interrupt handling function is shown below.

void IRAM_ATTR handleInterrupt() {
  portENTER_CRITICAL_ISR(&mux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&mux);
}


The final code

The final source code can be seen below. You can copy and paste it to your Arduino environment to test it.

const byte interruptPin = 25;
volatile int interruptCounter = 0;
int numberOfInterrupts = 0;

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR handleInterrupt() {
  portENTER_CRITICAL_ISR(&mux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&mux);
}

void setup() {

  Serial.begin(115200);
  Serial.println("Monitoring interrupts: ");
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

void loop() {

  if(interruptCounter>0){

      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);

      numberOfInterrupts++;
      Serial.print("An interrupt has occurred. Total: ");
      Serial.println(numberOfInterrupts);
  }
}


Testing the code

To test the code, simply upload it to your ESP32 and open the Arduino IDE serial monitor. The easiest way to trigger interrupts is to use a wire to connect and disconnect the digital pin where the interrupt was attached to GND.

Since the pin was declared as INPUT_PULLUP, then this will trigger a transition from VCC to GND and an external interrupt will be detected. Please be careful to avoid connecting the ground pin to the wrong GPIO and damaging the board.

You should get an output similar to figure 1, which shows the interrupts being triggered and the global counter being printed.

ESP32 Arduino external interrupts.png

Figure 1 – Output of the interrupt handling program.

86 Replies to “ESP32 Arduino: External interrupts”

    1. Hi!

      Thank you very much for pointing that out, the attribute is indeed missing in the interrupt handling routine.

      I will update the tutorial as soon as I can.

      Best regards,
      Nuno Santos

      1. The reason for using the keyword volatile is not because a compiler might optimize it away (indeed, that will only happen if the variable is in scope but not referenced). Volatile is use to insure that a fresh copy of the variable is fetched each time it’s referenced. Otherwise, it could be cached and its value out of sync when the interrupt is called.

    1. Hi!
      Thank you very much for pointing that out, the attribute is indeed missing in the interrupt handling routine.
      I will update the tutorial as soon as I can.
      Best regards,
      Nuno Santos

      1. The reason for using the keyword volatile is not because a compiler might optimize it away (indeed, that will only happen if the variable is in scope but not referenced). Volatile is use to insure that a fresh copy of the variable is fetched each time it’s referenced. Otherwise, it could be cached and its value out of sync when the interrupt is called.

  1. Do you know wich pins are compatible with interrupts? And can you say me were you found this information? Thanks 🙂

    1. All IO Pins can be used as interrupt pins. There are no such limitations to only two Pins like on an Arduino Nano Board.

    2. Hi!

      I typically get my information from a lot of sources, but mainly from previous experience with the Arduino environment, by looking into the examples and source code of the ESP32 libraries and also on IDF documentation.

      I don’t recall in particular from where I’ve seen the interrupts stuff, but most likely i was after searching on the Arduino core libraries.

      I haven’t confirmed, but from this post it seems that all the GPIOs support interrupts:
      https://www.esp32.com/viewtopic.php?t=738

      Best regards,
      Nuno Santos

  2. Do you know wich pins are compatible with interrupts? And can you say me were you found this information? Thanks 🙂

    1. All IO Pins can be used as interrupt pins. There are no such limitations to only two Pins like on an Arduino Nano Board.

    2. Hi!
      I typically get my information from a lot of sources, but mainly from previous experience with the Arduino environment, by looking into the examples and source code of the ESP32 libraries and also on IDF documentation.
      I don’t recall in particular from where I’ve seen the interrupts stuff, but most likely i was after searching on the Arduino core libraries.
      I haven’t confirmed, but from this post it seems that all the GPIOs support interrupts:
      https://www.esp32.com/viewtopic.php?t=738
      Best regards,
      Nuno Santos

  3. Hello, thanks for the great article 😉 I have a doubt, do I still need to put the portMUX lines if the volatile variable is just a bool (named “interruptReceived” or whatever..) that is set to true in the ISR, and checked and set to false in the loop()… ?

    1. Hi!

      Thank you very much for the feedback 🙂

      Since there’s still concurrent access to the variable (both the ISR and the setup write on it), then the best approach it to keep those lines.

      As indicated in the IDF documentation below, those calls also protect from the multi-core concurrent access of the same variable:
      http://esp-idf.readthedocs.io/en/latest/api-guides/freertos-smp.html?highlight=portenter_critical#critical-sections-disabling-interrupts

      Best regards,
      Nuno Santos

    2. As antepher stated, it is in general a good idea to keep those lines in place.
      Since you are doing so little which is executed very quickly by the processor, I don’t think that you’d ever get a problem I’d leave those lines away.
      Better safe than sorry, I guess.

  4. Hello, thanks for the great article 😉 I have a doubt, do I still need to put the portMUX lines if the volatile variable is just a bool (named “interruptReceived” or whatever..) that is set to true in the ISR, and checked and set to false in the loop()… ?

    1. Hi!
      Thank you very much for the feedback 🙂
      Since there’s still concurrent access to the variable (both the ISR and the setup write on it), then the best approach it to keep those lines.
      As indicated in the IDF documentation below, those calls also protect from the multi-core concurrent access of the same variable:
      http://esp-idf.readthedocs.io/en/latest/api-guides/freertos-smp.html?highlight=portenter_critical#critical-sections-disabling-interrupts
      Best regards,
      Nuno Santos

    2. As antepher stated, it is in general a good idea to keep those lines in place.
      Since you are doing so little which is executed very quickly by the processor, I don’t think that you’d ever get a problem I’d leave those lines away.
      Better safe than sorry, I guess.

  5. Hey
    I just tried to program something with interrurpts but have the biggest problem to trigger only one interrupt event. When the edge is slow and clean the esp32 triggers many events. when I reduce the smoothing cap the signal is not clean and the bounce triggers more than one event.

    1. Hi! What is your signal generator? Something mechanic like a button?

      If so, then it is normal and it is due to the bouncing effect. You can debounce it in software, like after receiving an interrupt, you set up the interrupt time and discard all the next interrupts during a given period of time.

      Only after the debounce time passes you start processing another interrupt.

      Note that in this debounce case, the wave is squared but due to the bouncing effect of the mechanical contacts there is a transitory regime where the signal is changing between HIGH and LOW

      If it is your signal source that is not clean (not a square wave), then that is most likely a problem that you need to solve on the hardware side. The interrupts are triggered by rising / falling edges, so a not clean signal will most likely cause the problems you are experiencing.

      Best regards,
      Nuno Santos

  6. Hey
    I just tried to program something with interrurpts but have the biggest problem to trigger only one interrupt event. When the edge is slow and clean the esp32 triggers many events. when I reduce the smoothing cap the signal is not clean and the bounce triggers more than one event.

    1. Hi! What is your signal generator? Something mechanic like a button?
      If so, then it is normal and it is due to the bouncing effect. You can debounce it in software, like after receiving an interrupt, you set up the interrupt time and discard all the next interrupts during a given period of time.
      Only after the debounce time passes you start processing another interrupt.
      Note that in this debounce case, the wave is squared but due to the bouncing effect of the mechanical contacts there is a transitory regime where the signal is changing between HIGH and LOW
      If it is your signal source that is not clean (not a square wave), then that is most likely a problem that you need to solve on the hardware side. The interrupts are triggered by rising / falling edges, so a not clean signal will most likely cause the problems you are experiencing.
      Best regards,
      Nuno Santos

    1. Hi!

      That’s weird, at the time I tested it was working fine.

      Maybe it was a bug introduced in your version of the Arduino core? My recommendation is to pull the latest changes and check if it works ok.

      Additionally, with which input are you resting? If you are using something with mechanical contacts, then you may be experiencing the bouncing effect, which generates a lot of interrupts when you close or open the metallic contacts.

      This can easily be mistaken by the interrupts reacting to a change on the signal, but in reality it is caused by the quick variations in the input electrical signal.

      Best regards,
      Nuno Santos

    1. Hi!
      That’s weird, at the time I tested it was working fine.
      Maybe it was a bug introduced in your version of the Arduino core? My recommendation is to pull the latest changes and check if it works ok.
      Additionally, with which input are you resting? If you are using something with mechanical contacts, then you may be experiencing the bouncing effect, which generates a lot of interrupts when you close or open the metallic contacts.
      This can easily be mistaken by the interrupts reacting to a change on the signal, but in reality it is caused by the quick variations in the input electrical signal.
      Best regards,
      Nuno Santos

  7. Is it possible that Serials functions like .available() .read() or .print() can interfere with the external interupt ?

    1. Hi!

      I’m not aware of any problem and they are not supposed to interfere, but there may be some bug.

      Unless you are calling those functions you mention inside the interrupt handling routine.
      In that case, it will most likely cause you problems since interrupt service routines should run as fast as possible, to return the execution to the “normal” flow. So, no blocking calls should be done inside them.

      Also, many functions cannot be called inside service routines due to interference in lower level primitives. I’m not aware of all the problematic use cases, but I always try to use routine functions to set some variable (a flag or an integer) indicating the interrupt has occurred, and then do the actual handling in the “main” code.

      This approach might save you a lot of debugging for weird problems 🙂

      But what are you experiencing?

      Best regards,
      Nuno Santos

  8. Is it possible that Serials functions like .available() .read() or .print() can interfere with the external interupt ?

    1. Hi!
      I’m not aware of any problem and they are not supposed to interfere, but there may be some bug.
      Unless you are calling those functions you mention inside the interrupt handling routine.
      In that case, it will most likely cause you problems since interrupt service routines should run as fast as possible, to return the execution to the “normal” flow. So, no blocking calls should be done inside them.
      Also, many functions cannot be called inside service routines due to interference in lower level primitives. I’m not aware of all the problematic use cases, but I always try to use routine functions to set some variable (a flag or an integer) indicating the interrupt has occurred, and then do the actual handling in the “main” code.
      This approach might save you a lot of debugging for weird problems 🙂
      But what are you experiencing?
      Best regards,
      Nuno Santos

  9. Hi There!

    I’m starting to use ESP32, but i have not found a clear way to enable “SerialEvent”.

    Do you have some information about that??

    Thank you in advance

    PD: Excellent post! very useful 😀

    1. Hi!

      Thank you very much for the feedback 🙂

      What do you mean by enabling the SerialEvent? Do you want to generate an interrupt when something is received on the serial port?

      Best regards,
      Nuno Santos

  10. Hi There!
    I’m starting to use ESP32, but i have not found a clear way to enable “SerialEvent”.
    Do you have some information about that??
    Thank you in advance
    PD: Excellent post! very useful 😀

    1. Hi!
      Thank you very much for the feedback 🙂
      What do you mean by enabling the SerialEvent? Do you want to generate an interrupt when something is received on the serial port?
      Best regards,
      Nuno Santos

  11. I have tried to create two interrupts on two separate pins.
    Pin 16 works , pin 12 does not.

    What am I missing ?

    #include

    const byte tickInterruptPin = 16; // Connected to pin 3 of RV-1805-C3
    const byte rpmInterruptPin = 12; //

    volatile bool tickInterruptTriggered = false;
    volatile bool revInterruptTriggered = false;

    portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

    void IRAM_ATTR tickInt()
    {
    portENTER_CRITICAL_ISR(&mux);
    tickInterruptTriggered = true;
    portEXIT_CRITICAL_ISR(&mux);
    }

    void IRAM_ATTR rpmInt()
    {
    portENTER_CRITICAL_ISR(&mux);
    revInterruptTriggered = true;
    portEXIT_CRITICAL_ISR(&mux);
    }
    void setup(){
    Serial.begin(115200);
    Serial.println(); Serial.println();
    Serial.println(“Test Interrupts”);
    Serial.println(); Serial.println();

    pinMode(tickInterruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tickInterruptPin), tickInt, FALLING);

    pinMode(rpmInterruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(rpmInterruptPin), rpmInt, FALLING);
    }

    void loop(){

    if( revInterruptTriggered ){
    revInterruptTriggered = false;
    Serial.println(“REV interrupt”);
    }
    if (tickInterruptTriggered) {
    tickInterruptTriggered = false;
    Serial.println(“TICK interrupt”);
    }

    }

  12. I have tried to create two interrupts on two separate pins.
    Pin 16 works , pin 12 does not.
    What am I missing ?
    #include
    const byte tickInterruptPin = 16; // Connected to pin 3 of RV-1805-C3
    const byte rpmInterruptPin = 12; //
    volatile bool tickInterruptTriggered = false;
    volatile bool revInterruptTriggered = false;
    portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
    void IRAM_ATTR tickInt()
    {
    portENTER_CRITICAL_ISR(&mux);
    tickInterruptTriggered = true;
    portEXIT_CRITICAL_ISR(&mux);
    }
    void IRAM_ATTR rpmInt()
    {
    portENTER_CRITICAL_ISR(&mux);
    revInterruptTriggered = true;
    portEXIT_CRITICAL_ISR(&mux);
    }
    void setup(){
    Serial.begin(115200);
    Serial.println(); Serial.println();
    Serial.println(“Test Interrupts”);
    Serial.println(); Serial.println();
    pinMode(tickInterruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tickInterruptPin), tickInt, FALLING);
    pinMode(rpmInterruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(rpmInterruptPin), rpmInt, FALLING);
    }
    void loop(){
    if( revInterruptTriggered ){
    revInterruptTriggered = false;
    Serial.println(“REV interrupt”);
    }
    if (tickInterruptTriggered) {
    tickInterruptTriggered = false;
    Serial.println(“TICK interrupt”);
    }
    }

    1. Hi!

      Were you able to figure out if it was some problem with the soldering?

      I’m not aware of any restriction in using interrupts in pin 12, so I’m curious to know what was the cause.

      Best regards,
      Nuno Santos

    1. Hi!
      Were you able to figure out if it was some problem with the soldering?
      I’m not aware of any restriction in using interrupts in pin 12, so I’m curious to know what was the cause.
      Best regards,
      Nuno Santos

  13. Has the FALLING edge construct been fixed in the attachInterrupt function ?
    Back to the code I posted on Sept 6, I get two interrupts per RPM pulse on a magnetic sensor.

    1. Hi!

      Unfortunately I’m not sure, I’ve not been working with interrupts for a while.

      My suggestion is to ask around the Arduino core GitHub page, they probably can confirm it 🙂

      Best regards,
      Nuno Santos

  14. Has the FALLING edge construct been fixed in the attachInterrupt function ?
    Back to the code I posted on Sept 6, I get two interrupts per RPM pulse on a magnetic sensor.

    1. Hi!
      Unfortunately I’m not sure, I’ve not been working with interrupts for a while.
      My suggestion is to ask around the Arduino core GitHub page, they probably can confirm it 🙂
      Best regards,
      Nuno Santos

  15. I reckon the logic in this tutorial is wrong. One cannot use locks and critical sections of code as a substitute for disabling interrupts.

    Let’s assume you execute line 26 in the sample (lock and enter the critical section). Then an interrupt occurs. Now your interrupt handler will block, waiting for the lock, and your code is deadlocked – neither the interrupt handler nor the main loop can make progress. (And then the Watchdog Timer will bite you!)

    Interrupts can be disabled / masked, which would be the correct thing to do at line 26. Then you won’t need the mux: both the ISR and the main loop run on the same core, on a single thread in the Arduino environment.

    https://www.esp32.com/viewtopic.php?t=2467 tells us how to mask interrupts.

    1. Hi!

      Thanks for your feedback.

      I understand your point but if you read IDF’s documentation, it specifically states (in bold) that disabling interrupts is not a valid protection method against simultaneous access to shared data as it leaves the other core free to access the data even if the current core has disabled its own interrupts:
      https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/freertos-smp.html?highlight=portENTER_CRITICAL_ISR#critical-sections-disabling-interrupts

      It also clearly states (in the same link) that the “enterCritical” macros were design for that protection.

      Note that even the name implies it can be used in interrupts:
      portENTER_CRITICAL_ISR

      I’m not sure how the ESP32 core handles that situation you have mentioned under the hood because in a regular synchronization scenario it would indeed end up in a deadlock.

      But it’s an interesting question to ask, probably in IDF’s GitHub page.

      Note that Arduino’s interrupt example also uses the same approach of critical sections:
      https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino

      Since this example has as contributor me-no-dev, it’s another strong indication that this is the correct way of doing, since he is probably one of the guys that knows more about how the ESP32 works 🙂

      Furthermore, take in consideration that FreeRTOS has specific APIs to handle locks from interrupts:
      https://www.freertos.org/xSemaphoreTakeFromISR.html
      https://www.freertos.org/a00124.html

      Regarding the assumption about the fact that both the Arduino loop and setup run in the same core, that can change in the future without we even notice, since there is no coupling between our code and where those two functions will run. We don’t indicate explicitly if setup and main loop run in a given core, so I would say it’s a much better approach to not rely on that and protect data access anyway.

      Furthermore, it’s completely reasonable that someone using the Arduino core also wants to work with FreeRTOS tasks and multi-core, since it’s really simple to do (FreeRTOS primitives are available in the arduino core)

      This is also why, in this tutorial, I’ve covered the use of critical sections.

      Nonetheless, I’ve not extensively tested all the use cases of synchronization between tasks and interrupts using the critical sections, so I cannot guarantee you that there is no deadlock situation I’m not aware about.

      I can only comment that so far I did not run into any troubles using this approach 🙂

      Did you run into any problem so far using critical sections?

      This is indeed an interesting problematic, so let me know your thoughts on this information, and thanks for contributing with more insight 🙂

      Best regards,
      Nuno Santos

    2. More information:

      https://www.esp32.com/viewtopic.php?t=1703

      It seems like the critical section disables interrupts on the current core (therefore, when the loop enters the critical section, there’s no danger of interrups being called in the same core, so they will not try to get a locked mutex and stay locked forever) and then even if an interrupt in the other core tries to take the mutex, eventually the first core will release it, thus not deadlocking.

      Probably if the first core as a blocking or very big critical section it will cause problems, but it’s a general rule that critical sections should be as short as possible.

      Best regards,
      Nuno Santos

  16. I reckon the logic in this tutorial is wrong. One cannot use locks and critical sections of code as a substitute for disabling interrupts.
    Let’s assume you execute line 26 in the sample (lock and enter the critical section). Then an interrupt occurs. Now your interrupt handler will block, waiting for the lock, and your code is deadlocked – neither the interrupt handler nor the main loop can make progress. (And then the Watchdog Timer will bite you!)
    Interrupts can be disabled / masked, which would be the correct thing to do at line 26. Then you won’t need the mux: both the ISR and the main loop run on the same core, on a single thread in the Arduino environment.
    https://www.esp32.com/viewtopic.php?t=2467 tells us how to mask interrupts.

    1. Hi!
      Thanks for your feedback.
      I understand your point but if you read IDF’s documentation, it specifically states (in bold) that disabling interrupts is not a valid protection method against simultaneous access to shared data as it leaves the other core free to access the data even if the current core has disabled its own interrupts:
      https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/freertos-smp.html?highlight=portENTER_CRITICAL_ISR#critical-sections-disabling-interrupts
      It also clearly states (in the same link) that the “enterCritical” macros were design for that protection.
      Note that even the name implies it can be used in interrupts:
      portENTER_CRITICAL_ISR
      I’m not sure how the ESP32 core handles that situation you have mentioned under the hood because in a regular synchronization scenario it would indeed end up in a deadlock.
      But it’s an interesting question to ask, probably in IDF’s GitHub page.
      Note that Arduino’s interrupt example also uses the same approach of critical sections:
      https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino
      Since this example has as contributor me-no-dev, it’s another strong indication that this is the correct way of doing, since he is probably one of the guys that knows more about how the ESP32 works 🙂
      Furthermore, take in consideration that FreeRTOS has specific APIs to handle locks from interrupts:
      https://www.freertos.org/xSemaphoreTakeFromISR.html
      https://www.freertos.org/a00124.html
      Regarding the assumption about the fact that both the Arduino loop and setup run in the same core, that can change in the future without we even notice, since there is no coupling between our code and where those two functions will run. We don’t indicate explicitly if setup and main loop run in a given core, so I would say it’s a much better approach to not rely on that and protect data access anyway.
      Furthermore, it’s completely reasonable that someone using the Arduino core also wants to work with FreeRTOS tasks and multi-core, since it’s really simple to do (FreeRTOS primitives are available in the arduino core)
      This is also why, in this tutorial, I’ve covered the use of critical sections.
      Nonetheless, I’ve not extensively tested all the use cases of synchronization between tasks and interrupts using the critical sections, so I cannot guarantee you that there is no deadlock situation I’m not aware about.
      I can only comment that so far I did not run into any troubles using this approach 🙂
      Did you run into any problem so far using critical sections?
      This is indeed an interesting problematic, so let me know your thoughts on this information, and thanks for contributing with more insight 🙂
      Best regards,
      Nuno Santos

    2. More information:
      https://www.esp32.com/viewtopic.php?t=1703
      It seems like the critical section disables interrupts on the current core (therefore, when the loop enters the critical section, there’s no danger of interrups being called in the same core, so they will not try to get a locked mutex and stay locked forever) and then even if an interrupt in the other core tries to take the mutex, eventually the first core will release it, thus not deadlocking.
      Probably if the first core as a blocking or very big critical section it will cause problems, but it’s a general rule that critical sections should be as short as possible.
      Best regards,
      Nuno Santos

  17. If the CLK pin of a rotary encoder is connected to pin 25, then as the rotary encoder is moved through one “click” in either direction the “An interrupt has occurred” statement is printed several times with the “Total” incrementing accordingly.
    With a 0.1microF capacitor, the encoder bouncing is reduced as indicated by the print statement occurring two or three times.
    How did you debounce the switch?

    1. Hi!

      Sorry for the delay.

      I’ve never worked with rotary encoders, so I cannot help much unfortunately.

      I did not use a switch, I simply connect a wire between the pin and GND and disconnect, to trigger some interrupts.

      It also suffers from bouncing, which was not covered in this tutorial.

      When I want to debounce a switch, I do it on the software side. Basically, when an interrupt occurs, I act accordingly and register the time when it as occurred (with the micros function).

      On new interrupts, I compare the current time with the time I’ve registered from the previous interrupt and if it is lesser than a “debouncing” interval, I simply discard it and do not act on it.

      Not sure if this applies to your use case, but this is a simple way of debouncing on the software side.

      Hope this helps!
      Best regards,
      Nuno Santos

  18. If the CLK pin of a rotary encoder is connected to pin 25, then as the rotary encoder is moved through one “click” in either direction the “An interrupt has occurred” statement is printed several times with the “Total” incrementing accordingly.
    With a 0.1microF capacitor, the encoder bouncing is reduced as indicated by the print statement occurring two or three times.
    How did you debounce the switch?

    1. Hi!
      Sorry for the delay.
      I’ve never worked with rotary encoders, so I cannot help much unfortunately.
      I did not use a switch, I simply connect a wire between the pin and GND and disconnect, to trigger some interrupts.
      It also suffers from bouncing, which was not covered in this tutorial.
      When I want to debounce a switch, I do it on the software side. Basically, when an interrupt occurs, I act accordingly and register the time when it as occurred (with the micros function).
      On new interrupts, I compare the current time with the time I’ve registered from the previous interrupt and if it is lesser than a “debouncing” interval, I simply discard it and do not act on it.
      Not sure if this applies to your use case, but this is a simple way of debouncing on the software side.
      Hope this helps!
      Best regards,
      Nuno Santos

  19. I have read on other posts that the ESP32 is detecting all RISING and FALLING events as being CHANGE, so people are getting two events per on/off cycle of their sensor. A fault in the standard libraries that handle interrupts, say some. Apparently the attachInterruptArg() function also doesn’t work, though I’ve no idea what you use it for.
    I hope this reassures people, though I doubt it! Somebody, somewhere, is writing a fix, I’d like to think they’d also make interrupts easier to use because I’m old and stupid..
    I’m just trying to stop my ESP32 from resetting on every interrupt…

    1. Hi!

      I’ve never noticed any issue while working with interrupts. Is this a new issue of some more recent version of IDF or an older one?

      Best regards,
      Nuno Santos

  20. I have read on other posts that the ESP32 is detecting all RISING and FALLING events as being CHANGE, so people are getting two events per on/off cycle of their sensor. A fault in the standard libraries that handle interrupts, say some. Apparently the attachInterruptArg() function also doesn’t work, though I’ve no idea what you use it for.
    I hope this reassures people, though I doubt it! Somebody, somewhere, is writing a fix, I’d like to think they’d also make interrupts easier to use because I’m old and stupid..
    I’m just trying to stop my ESP32 from resetting on every interrupt…

    1. Hi!
      I’ve never noticed any issue while working with interrupts. Is this a new issue of some more recent version of IDF or an older one?
      Best regards,
      Nuno Santos

  21. In the tutorial I read these lines:
    “Also, interrupt handling routines should only call functions also placed in IRAM, as can be seen here in the IDF documentation. Thanks to Manuato for point this.”
    And the word ‘here’ is a link to important information.
    But this link doesn’t work for me. It brings me to a general Read the Docs page (RtD). Even after creating an login account on RtD I cannot get to this information.

    Can somebody tell me how I will see this list of functions also placed in IRAM?

    Best regards,

    Jop

Leave a Reply