The ESP32 has 26 digital pins which can be used to trigger the execution of a function using an external interrupt. An interrupt is a process that is triggered asynchronously by an external event. Interrupts make it possible to detect an event in real time while leaving the microcontroller’s processor to do other tasks.
The use of external interrupts simplifies the programming of events.
The ESP32 also has 4 Timers which allow you to program the triggering of alarms. Everything is explained in detail in this tutorial
Install the ESP-IDF SDK for ESP32 on IDE Arduino and PlatformIO
If you are new to ESP32 development boards you must first install the ESP-IDF development kit. Here are two tutorials to get started depending on your code editor
Follow the instructions in this tutorial for the Arduino IDE
And this one for PlatformIO (ideally with VSCode)
Introduction to interrupts (external)
The classic way to program when you want to trigger an event using a digital input is to test its value regularly in the loop (). Depending on its value, we execute the code corresponding to the state
void loop() {
// Read the value of the switch
button_state = digitalRead(PIN_BUTTON);
// if the button is pressed, changes the state of the output to HIGH, otherwise LOW
if (button_state == HIGH) {
// Switch on the LED
digitalWrite(PIN_LED, HIGH);
} else {
// Switch off the LED
digitalWrite(PIN_LED, LOW);
}
}
This way of programming is suitable most of the time but poses problems in the following cases:
- We want to slow down the loop loop with a delay()
- Sleep and wake it up when an external event occurs. For example, motion detection, pressing a button or a touch key …
This is where interrupts are useful.
As the interrupt is triggered from an event external to the processors, it is an external interrupt.
ESP32 GPIO pins compatible with interrupts
Interrupts only work with digital inputs. The digital inputs that can trigger an interrupt are circled in the diagram below.
Be careful, however, because some pins go high (HIGH) or emit PWM signals at start-up or during reset. Other pins are used by the system to access flash memory or upload the program.
Do not use pins colored orange or red. Your program might behave unexpectedly while using these.
GPIO pin | Digital input | Note |
0 | PULL UP | Sends a PWM signal at startup. |
1 | TX | Debug output at startup |
2 | ✓ | Connected to the on-board LED |
3 | ✓ | Takes the HIGH state at startup |
4 | ✓ | |
5 | ✓ | Sends a PWM signal at startup |
6 | ✖ | Used for SPI flash memory |
7 | ✖ | Used for SPI flash memory |
8 | ✖ | Used for SPI flash memory |
9 | ✖ | Used for SPI flash memory |
10 | ✖ | Used for SPI flash memory |
11 | ✖ | Used for SPI flash memory |
12 | ✓ | Fail to start if in PULLUP mode |
13 | ✓ | |
14 | ✓ | Sends a PWM signal at startup |
15 | ✓ | Sends a PWM signal at startup |
16 | ✓ | |
17 | ✓ | |
18 | ✓ | |
19 | ✓ | |
21 | ✓ | |
22 | ✓ | |
23 | ✓ | |
25 | ✓ | |
26 | ✓ | |
27 | ✓ | |
32 | ✓ | |
33 | ✓ | |
34 | ✓ | |
35 | ✓ | |
36 | ✓ | |
39 | ✓ |
Data collected from official documentation.
Configure the digital input of the ESP32
To be able to read on a digital input of the GPIO, you must first configure the pin as an input using the pinMode() function like this
pinMode(pin, mode);
The pinMode method has two arguments
- The pin number
- The mode
3 modes are available to configure the spindle as a digital input
- INPUT Depending on the input device used, the circuit must be adapted. For example, when a Momentary Switch type push button is used, a pull-up or pull-down resistor must be integrated into the circuit. Without a pull-up or pull-down resistor, the ESP32 input is floating. The logic level is undefined which can lead to erratic operation of the program. For example not detecting the pressure on a button or on the contrary detecting that it is pressed when it is not.
- INPUT_PULLUP We use the pulling resistance (PULL UP) of the ESP32
- INPUT_PULLDOWN We use the pull-down resistor (PULL DOWN) of the ESP32
To read a digital input – like a button – we use the digitalRead() function, which takes the GPIO pin number as argument.
digitalRead(pin);
Use the previous diagram and table to choose the GPIO pins for your project.
How to add an external interrupt to an ESP32 project?
Then we assign a function that will be executed as soon as an event is detected on the spindle using the attachInterrupt (GPIO, FUNCTION, MODE) method. The method requires 3 arguments
- GPIO The pin that triggers the event. This is the pin that we configured previously
- FUNCTION the function to execute when an event occurs
- MODE trigger mode
5 trigger modes are possible
- LOW to trigger the interrupt whenever the spindle is low (LOW)
- HIGH to trigger the interrupt whenever the pin is high (HIGH)
- CHANGE to trigger the interrupt whenever the pin changes state. For example when it goes from HIGH to LOW or LOW to HIGH
- FALLING for when the spindle goes from HIGH to LOW. This is the detection of the rising edge.
- RISING to trigger when the spindle goes from LOW to HIGH. This is the detection of the falling edge.
Declare the function to be executed in the IRAM (IRAM_ATTR)
The execution of a function called by an interrupt is blocking, ie it is necessary to wait for the end of its execution so that the rest of the code can continue.
Usually the code is executed directly on the flash memory of the development board. It is possible to move the function to the internal RAM of the ESP32 which is much faster.
To do this, simply place the IRAM_ATTR attribute just before the name of the function like this
void IRAM_ATTR myfastfunction(){
...
}
This is not mandatory and you can also test it yourself with the code offered at the end of the tutorial. However, it is strongly recommended to place in the RAM of the ESP32 all the functions called by the interrupts for a real project.
Example for triggering an interrupt using a switch
Now let’s move on to a concrete example. The number of times the user presses a push button (momentary switch) is counted using an interrupt. As soon as the counter reaches 5 clicks, an LED is lit for 5 seconds using a Timer then the interruption is released.
There are many solutions to trigger an external event:
- A push button
- A motion detector (PIR)
- a radar
- A tactile button (a simple copper wire on the ESP32)
Whatever equipment is used, we will always receive a LOW / HIGH type signal. So whatever your project, the code will remain perfectly the same
Here we will be using a push button. It is equipped with a return spring which returns the button to the LOW state as soon as it is released.
Circuit
The push button is connected to digital input 4. The LED is connected to output 32.
You can test the operation of the program in the absence of a pull resistor (PULL_UP). You can test using the ESP32’s internal pull-up resistor (PULL_UP) or add a resistor to the circuit (10kΩ for example).
The LED must be protected by a resistor, the value of which depends on the output voltage and current of the pin (3.3V – 40mA) and the maximum supply voltage of the LED.
You can use this calculator to determine the required resistance value for your circuit.
Supply Voltage in Volt
Forward Tension in Volt
Avertissement: la tension directe ne doit pas dépasser la tension d'alimentation.
Current in mA
Resistor calculated in Ω
Estimated Power in W
|
This calculator is used to determine the resistance required to drive one or more LEDs connected in series from a voltage source at a specified current level. Note. It is preferable to supply the circuit with a nominal power between 2 and 10 times the calculated value in order to avoid overheating.
|
|
|
And buy a resistor assortment
Upload the Arduino code of the project
Create a new sketch on the Arduino IDE or a new PlatformIO project.
On the Arduino IDE you can remove the first line #include <Arduino.h> .
#include <Arduino.h> // Filtre anti-rebond (debouncer) #define DEBOUNCE_TIME 250 volatile uint32_t DebounceTimer = 0; // Pin to which the button, PIR motion detector or radar is connected #define PIN_BUTTON 4 uint32_t button_count = 0; // LED to indicate the end of the program #define PIN_LED 32 #define DELAY_LED 2000 // Uncomment to not put the function in the RAM of the ESP32 //void buttonpressed() { // The function is placed in the RAM of the ESP32. void IRAM_ATTR buttonpressed() { if ( millis() - DEBOUNCE_TIME >= DebounceTimer ) { DebounceTimer = millis(); button_count += 1; Serial.printf("Button has been pressed %u times\n", button_count); } } void setup() { Serial.begin(115200); pinMode(PIN_BUTTON, INPUT_PULLDOWN); attachInterrupt(PIN_BUTTON, buttonpressed, RISING); // Configure LED output pinMode(PIN_LED, OUTPUT); } void loop() { //Detach interruption after 5 clicks if ( button_count >= 5) { detachInterrupt(PIN_BUTTON); Serial.println("Interrupt Detached!"); // reset click counter to avoid re-enter here button_count = 0; // Lights up the LED to indicate that the interrupt is detached digitalWrite(PIN_LED, HIGH); delay(DELAY_LED); digitalWrite(PIN_LED, LOW); } }
After uploading the program, open the serial monitor and do a RESET of the board to follow the progress
Button has been pressed 1 times
Button has been pressed 2 times
Button has been pressed 3 times
Button has been pressed 4 times
Button has been pressed 5 times
Interrupt Detached!
PlatformIO configuration for a LoLin D32
Here is an example platformio.ini configuration file for a LoLin D32 Pro development board
[env:lolin_d32_pro]
platform = espressif32
board = lolin_d32_pro
framework = arduino
monitor_speed = 115200
Explanation of the code
On initialization (setup function ), we declare that the input is a digital input. The internal pull-down resistor of the ESP32 is used which saves a resistance in the circuit. We attach the button to an interrupt. We execute the buttonpressed function as soon as we press the button by detecting the rising edge (RISING).
pinMode(PIN_BUTTON, INPUT_PULLDOWN);
attachInterrupt(PIN_BUTTON, buttonpressed, RISING);
If we execute the function as is as soon as we press the button, we will have multiple executions which can cause slowdown problems or unnecessary requests on a remote server.
To do this, it suffices to wait a certain time before executing the function, this operation is called Debounce.
There are complex libraries to do this, but here it is unnecessary. All you have to do is initialize a DEBOUNCE_TIME variable which contains the timestamp of the last execution. The time elapsed since the last pass in the buttonpressed function must be at least equal to the bounce time. Here it is set at 250ms at the start of the program. You can adjust the value according to your electronics.
To be sure that the function will be executed in priority and quickly, it is placed in the RAM of the ESP32 using the argument IRAM_ATTR.
The function is rudimentary and just increments a counter.
void IRAM_ATTR buttonpressed() {
if ( millis() - DEBOUNCE_TIME >= DebounceTimer ) {
DebounceTimer = millis();
button_count += 1;
}
}
The interruption is detached after 10 clicks on the button.
if ( button_count >= 10) {
detachInterrupt(PIN_BUTTON);
// reset click counter to avoid re-enter here
button_count = 0;
}
Updates
5/10/2020 Publication of the article
- ESP32, GPIO pins and associated functions. I/O, PWM, RTC, I2C, SPI, ADC, DAC
- M5Stack Atomic GPS. ESP32 TinyGPS++ tracker, GPX export on SD card, visualization on Google Maps or VSCode
- How to store data on a micro SD card. Arduino code compatible ESP32, ESP8266
- Getting started Arduino. Receive commands from the serial port (ESP32 ESP8266 compatible)
- C++ functions print•println•printf•sprintf for Arduino ESP32 ESP8266. Combine•format → serial port