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. If you prefer, the NodeMCU board can also be bought at Amazon.


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
This entry was posted in ESP8266 and tagged , , , , , , . Bookmark the permalink.

5 Responses to ESP8266: External interrupts

  1. Pingback: ESP8266: Detecting rain drops | techtutorialsx

  2. Pingback: ESP8266: DS3231 1Hz Square wave generator | techtutorialsx

  3. Pingback: ESP8266: DS3231 Alarm when seconds match | techtutorialsx

  4. Jay Sprenkle says:

    Nice post! Thanks for writing it. Do you know if the 8266 has support for atomic operations? In case an interrupt interrupts my interrupt handler… 😉

    Liked by 1 person

    • antepher says:

      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

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s