ESP8266: External interrupts

The objective of this post is to explain how to handle external interrupts with the ESP8266.


Introduction

The objective of this post is to explain how to handle external interrupts with the ESP8266, which will be a very useful tool when working with sensors.

In a more formal definition, interrupts are events or conditions that cause the microprocessor or microcontroller to stop the execution of the task that it is performing, work in a different task temporarily and come back to the initial task [1]. This behavior is summarized in figure 1.

esp8266-interrupts

Figure 1 – Handling of interrupts.

Interrupts can be external or internal. In our case, we are going to work with external interrupts, meaning that they are caused by an event external to our ESP8266. The trigger for those interrupts will be a change in a value of a digital input pin of the microcontroller.

External interrupts are very useful because they allow us to be able to receive data from sensors in a microcontroller without constantly asking the sensor if it has new data. In other words, interrupts allow us to avoid polling.

Naturally, when an interrupt occurs, we need to handle it in a interrupt service routine (ISR) which corresponds to the function that executes when the interrupt happens.

It is a good practice to design interrupt service routines as small as possible [2], so the processor gets back to the execution of the main program. So, we should not do blocking calls or handle communication, for example, inside an ISR. The best approach is to signal the main code, using for example a flag or a counter, to indicate that the interrupt has happened. Then, the main code should have the logic necessary to handle it.

In our case, we will define a very simple handling function that detects a falling edge of the digital signal in one of the GPIOs of the ESP8266 and signals the main code to print a message to the serial port.

All the tests shown here were performed on a NodeMCU board, which you can find here at eBay for less than 5 euros.


The code

We will start our code by declaring some global variables. The first one will be the pin where the interrupt will be triggered. Interrupts may be attached in all the GPIOs of the ESP8266, except for the GPIO16 [3].

For this example, we will use GPIO13. Please take in consideration that, in the NodeMCU board, the pins of the microcontroller don’t map to he pins of the board. The correct mapping can be seen here and, as described, the GPIO13 will map to the D7 pin of the board.

We will also declare 2 counters. The first one will be the variable used to communicate between the ISR and the main program. So, for each interrupt, the code in the ISR will increment the counter, being a very fast routine.

Thus, for the main program, this counter represents the total number of interrupts already attended by the ISR and to which the main code has yet to perform an action (in our case, printing a message to the serial port).

The second counter will hold the total number of interrupts that have occurred in the program, so it’s only incremented. This will be used for us to include in the message printed to the serial port.

Check the definition of this 3 variables bellow.

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

Note that the counter that is incremented by the ISR is declared as volatile. This article explains very well why we need to use the keyword volatile in such case. But basically, we need to declare a variable as volatile when it can be changed unexpectedly (as in an ISR), so the compiler doesn’t remove it due to optimizations [4].

On the setup function, we will begin the serial connection, so we are able to send a message when the interrupt occurs.

Since the interrupt is triggered when a falling edge in the signal of the GPIO13 is detected, then the corresponding pin needs to be declared as an input pin. Besides that, we will use the input pull-up mode of the pin, so we know that it will be in a known state when no input is connected to it.

Otherwise, when we have an input digital pin without an input signal connected to it, it will detect random changes due to electrical noise [5], which we wan’t to avoid so no false interrupts are triggered. You can read more about pull-up resistors here.

The declaration of the pin as input is done with the pinMode function, which receives as first argument the pin and as second argument the mode.

pinMode(interruptPin, INPUT_PULLUP);

Finally, we attach the interrupt to the pin with the attachInterrupt function. It receives as first argument the interrupt number, as second argument the interrupt service routine, and as third the interrupt mode.

In the first argument, we will use the digitalPinToInterrupt function, which receives as input the interrupt pin, to translate the actual digital pin to the specific interrupt number [6].

As the second argument, we will pass the handling function we will define latter. This function must take no parameters and return nothing [6].

As third argument, we can pass one of the 3 supported interrupt types: CHANGE, RISING and FALLING [7]. In our case, we wan’t to detect a falling edge in the signal, so we pass FALLING.

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

The handleInterrupt will be our routine, which we must define. So, as said, this routine only increments the value of the interrupts counter.

void handleInterrupt() {
  interruptCounter++;
}

Remember that this is the counter used to communicate between the ISR and the main code, and not the global interrupts counter.

Now we will finally define our main loop function. The code will only check if the interruptCounter is greater than zero. If it is, it decrements the interrupt counter, indicating that the interrupt has been handled by the main code. Then, it increments the global counter and prints a message to the serial port.

void loop() {

  if(interruptCounter>0){

      interruptCounter--;
      numberOfInterrupts++;

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

}

You can check the full code bellow.

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

void setup() {

  Serial.begin(115200);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

void handleInterrupt() {
  interruptCounter++;
}

void loop() {

  if(interruptCounter>0){

      interruptCounter--;
      numberOfInterrupts++;

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

}


Testing the code

After uploading the code to the ESP8266, just open the serial console. Then, the easiest way to generate interrupts without any additional hardware is to connect and disconnect the ground pin of the ESP8266 to the GPIO with the interrupt routine configured. Be carefull to avoid connecting the ground pin to the wrong GPIO.

With this method, the voltage will change between ground and VCC and, when the falling edge occurs, the interrupt will trigger the execution of our function.

You should get something similar to figure 2 in the serial console.

esp8266-external-interrupts

Figure 2 – Output of the interrupts test program.

Note that it is normal that many messages are printed when we touch the two wires connecting the GPIO and the GND pins, indicating that multiple interrupts have occurred. This happens because of the transitory regimen that occurs when the two wires touch, so there is a period of time where the signal is bouncing between GND and VCC, before it stabilizes.

This is a common problem called bouncing and is very common, for example, when we use contact switches in our projects. Naturally, there are techniques to mitigate this phenomena, but they are outside the scope of this introductory post.


Technical details

  • ESP8266 libraries: v2.3.0


References

[1] https://en.wikibooks.org/wiki/Microprocessor_Design/Interrupts

[2] http://www.embedded.com/design/programming-languages-and-tools/4397803/Interrupts-short-and-simple–Part-1—Good-programming-practices

[3] https://github.com/esp8266/Arduino/blob/master/doc/reference.md

[4] http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

[5] https://www.arduino.cc/en/Tutorial/DigitalPins

[6] https://www.arduino.cc/en/Reference/AttachInterrupt

[7] https://github.com/esp8266/Arduino/blob/master/doc/reference.md

Advertisements

14 Replies to “ESP8266: External interrupts”

    1. Thanks 🙂

      I’m not sure about atomic operations, but one option is to disable interrupts during the execution of your interrupt handler and then re-enabling them.

      You have here the description of this operation for the Arduino boards:
      https://www.arduino.cc/en/Reference/noInterrupts

      I haven’t tested it on the ESP8266 (just on regular Arduinos) but by doing a quick search on the github page of the ESP libraries I could find references to these functions, so I think they are implemented:
      https://github.com/esp8266/Arduino/search?utf8=%E2%9C%93&q=+noInterrupts%28%29%3B

      If you have the opportunity to test it, please let me know if it works. If I have some time, I will try to test it too and post the results.

      Nevertheless, check if you really need to worry about that. In the example given here, even if the interrupt handler (the code in the main loop) is interrupted by an interrupt, after the corresponding ISR ends, it will resume its normal execution where it was stopped. That’s why ISRs should be designed to run as fast as possible, so your “normal” code resumes execution as fast as possible.

      Since we are using a counter, even if an interrupt interrupts our handler, it will be stored and in the next iteration of the main loop it will be handled. You can even change the if (interruptHandler >0) by while(interruptHandler>0), so it will be handled in the same main loop execution.

      Hope this helps 🙂

      Liked by 1 person

    1. Hi! Thank you very much 🙂

      Well detecting key presses in a button correctly in a microcontroller is actually a very interesting problem, not so trivial.

      If you have tried this code with a regular press button, you most likely have experienced that multiple interrupts were triggered when you pressed the button.

      This behavior is caused by an effect called bouncing. The article below has a very good explanation about it but, in short, bouncing results from the metal pieces that should make contact when you press the but not doing it instantaneously. Thus, there is a transitory effect where the input pin will receive a signal that will be changing between High and Low, which will trigger multiple interrupts.
      https://www.allaboutcircuits.com/technical-articles/switch-bounce-how-to-deal-with-it/

      An easy way to solve this is on software. when you detect the first interrupt, you log the time in a variable with the millis function. Then, you define a bouncing interval in a another variable and for each new interrupt, you check if the current time is inside your bouncing interval. If it is, you just ignore that interrupt.

      There’s no magic formula for this bouncing interval, so you will need to play around with it for you button.

      After that interval, when you detect a new interrupt, the user is no longer pressing the button, so you can check how much time as passed since the first time you registered the time, and you know if the user has pressed the button for 3 seconds or not.

      If he has pressed for more than 3 seconds, then just call the function you want.

      Note that you will need to use the CHANGE value for triggering the interrupt, to know both when the user presses and releases the button.

      This is roughly what you need to do. Of course that you may need some adjustments, but I think this sets you on the right track.

      Best regards,
      Nuno Santos

      Like

    1. Hi!

      The pins that are labeled on the NodeMCU board don’t map to the ones of the ESP8266 microcontroller.

      You can check at the article below the mappings between the pins labeled on the board and the ones from the microcontroller.
      https://techtutorialsx.com/2017/04/02/esp8266-nodemcu-pin-mappings/

      Please note that there are some different versions of the NodeMCU for selling, so the pin mappings of yours may differ.

      Best regards,
      Nuno Santos

      Like

  1. Hi, I don’t know much about code but I need a code that set interrupt on monthly once(once in every month for 2 years) with the help of rtc and the relay is turned off and this interrupt should be reset through web browser. I am giving washing machine on monthly rent so on a particular date machine should be stopped by relay upon payment received it should start. So interrupt should happen independently by checking rtc for date and month but it should be reset through web browser after getting confirmation from payment gateway which will generate API for the payment received in the customer login page of my website. You can contact me at bjp9886@gmail.com

    Liked by 1 person

    1. Hi!

      Unfortunately I don’t have any tutorial that does everything you need. But here are some guides that hopefully can help you in the right track.

      Alarms with DS3231 RTC:
      https://techtutorialsx.com/2017/02/04/esp8266-ds3231-alarm-when-seconds-match/

      Setting HTTP webserver on the ESP8266:
      https://techtutorialsx.com/2018/01/01/esp8266-arduino-asynchronous-http-web-server/

      Serving HTML from the ESP32 (did not test it on the ESP8266 but it should work):
      https://techtutorialsx.com/2017/12/09/esp32-arduino-asynchronous-http-webserver-simple-html/

      Note however that what you are trying to do has some complexity, and may be difficult if you are inexperienced. Furthermore, if you are using it for a real application scenario and not just for fun, then you need to make sure your code works reliably and your hardware is robust for long term operation.

      Hope this helps.

      Best regards,
      Nuno Santos

      Like

  2. Hi @antepher!

    Thanks for your tutorial.

    I have a issue with my project when using FALLING edge interrupt. It triggers once on downside and once going to high.

    The same circuit I’m using, works fine with RISING interrupt.
    Analasing on osciloscopy, the sinal goes UP and DOWN just fine (3.15v HIGH and 0V LOW). I just saw some noise when it’s UP (inside the HIGH range).

    I also did a debouncing routine to avoid get noise when using physical switches.

    static unsigned long last_interrupt_time1 = 0;
    void canal1Interrupt()
    {
    unsigned long interrupt_time1 = millis();

    if (interrupt_time1 – last_interrupt_time1 > 100) // If interrupts come faster than 100ms, assume it’s a bounce and ignore
    {
    numInterrupt1++;
    Serial.println(“\n CANAL 1: “); Serial.println(numInterrupt1);
    //arrayCanalInterrupt1[numInterrupt1 – 1] = numInterrupt1;
    }
    last_interrupt_time1 = interrupt_time1;
    }

    Can you help with some advice on this?

    Liked by 1 person

    1. Hi,

      You’re welcome 🙂

      Quick initial question, is the function you have there your Interrupt Service Routine?

      If that’s the case, ISRs should run as fast as possible, so I would recommend against printing stuff to serial on an ISR.

      It’s weird that it is not working for falling since your handling function doesn’t seem to depend on the direction of the signal transition.

      Do you have access to some equipment that can generate a clean signal without bouncing, to discard any issue with your debouncing code? Then you could test if, for a clean signal, it also gives you problems with falling edges.

      Also, is it deterministic or sometimes you get the problem and others don’t? It may be related to some racing condition (although it’s weird if it only happens for the falling edge case).

      Nonetheless, when we access variables concurrently between an ISR and the main loop, we need to be careful to not leave those variables in an inconsistent state.

      As far as I’m aware, ESP8266 integer operations are atomic in terms of machine instructions, which means if you simply increment / decrement a variable it should be fine (I could not find a better source, but from the MicroPython implementation they mention that: http://docs.micropython.org/en/v1.9.3/esp8266/reference/isr_rules.html#use-of-python-objects)

      Nonetheless, if you start doing some more complex sync between the ISR and the main loop. there may be some problems.

      It can also be some transitory bug that is setting the interrupt to CHANGE instead of FALLING when you set it to FALLING. To check for this, I would ask around the GitHub page of the Arduino core.

      Hope this helps getting you in the right path and if you find the cause let us know 🙂

      Best regards,
      Nuno Santos

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s