Attaching Interrupt to a GPIO Pin
In Arduino IDE, we use a function called attachInterrupt()
to set an interrupt on a pin by pin basis. The recommended syntax looks like below.
attachInterrupt(GPIOPin, ISR, Mode);
This function takes three parameters:
GPIOPin – Sets the GPIO pin as an interrupt pin, which tells the ESP32 which pin to monitor.
ISR – Is the name of the function that will be called every time the interrupt is triggered.
Mode – Defines when the interrupt should be triggered. Five constants are predefined as valid values:
LOW | Triggers interrupt whenever the pin is LOW |
HIGH | Triggers interrupt whenever the pin is HIGH |
CHANGE | Triggers interrupt whenever the pin changes value, from HIGH to LOW or LOW to HIGH |
FALLING | Triggers interrupt when the pin goes from HIGH to LOW |
RISING | Triggers interrupt when the pin goes from LOW to HIGH |
Detaching Interrupt from a GPIO Pin
You can optionally call detachInterrupt()
function when you no longer want ESP32 to monitor a pin. The recommended syntax looks like below.
detachInterrupt(GPIOPin);
Interrupt Service Routine
Interrupt Service Routine is invoked when an interrupt occurs on any GPIO pin. Its syntax looks like below.
void IRAM_ATTR ISR() {
Statements;
}
ISRs in ESP32 are special kinds of functions that have some unique rules most other functions do not have.
- The interrupt service routine must have an execution time as short as possible, because it blocks the normal program execution.
- Interrupt service routines should have the
IRAM_ATTR
attribute, according to the ESP32 documentation
What is IRAM_ATTR?
By flagging a piece of code with the IRAM_ATTR
attribute we are declaring that the compiled code will be placed in the Internal RAM (IRAM) of the ESP32.
Otherwise the code is placed in the Flash. And flash on the ESP32 is much slower than internal RAM.
If the code we want to run is an interrupt service routine (ISR), we generally want to execute it as quickly as possible. If we had to ‘wait’ for an ISR to load from flash, things would go horribly wrong.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {18, 0, false};
void IRAM_ATTR isr() {
button1.numberKeyPresses += 1;
button1.pressed = true;
}
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
if (button1.pressed) {
Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
//Detach Interrupt after 1 Minute
static uint32_t lastMillis = 0;
if (millis() - lastMillis > 60000) {
lastMillis = millis();
detachInterrupt(button1.PIN);
Serial.println("Interrupt Detached!");
}
}
touch interrupt
int threshold = 40;
bool touch1detected = false;
bool touch2detected = false;
void gotTouch(){
touch1detected = true;
}
void gotTouch1(){
touch2detected = true;
}
void setup() {
Serial.begin(115200);
delay(1000); // give me time to bring up serial monitor
printf("\n ESP32 Touch Interrupt Test\n");
touchAttachInterrupt(T2, gotTouch, threshold);
touchAttachInterrupt(T3, gotTouch1, threshold);
}
void loop(){
if(touch1detected){
touch1detected = false;
Serial.println("Touch 1 detected");
}
if(touch2detected){
touch2detected = false;
Serial.println("Touch 2 detected");
}
}
timer interrupt
#include<Arduino.h>
volatile int interrupts;
int totalInterrupts;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTime() {
portENTER_CRITICAL_ISR(&timerMux);
interrupts++;
portEXIT_CRITICAL_ISR(&timerMux);
}
void setup() {
Serial.begin(9600);
// Configure Prescaler to 80, as our timer runs @ 80Mhz
// Giving an output of 80,000,000 / 80 = 1,000,000 ticks / second
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTime, true);
// Fire Interrupt every 1m ticks, so 1s
timerAlarmWrite(timer, 1000000, true);
timerAlarmEnable(timer);
}
void loop() {
if (interrupts > 0) {
portENTER_CRITICAL(&timerMux);
interrupts--;
portEXIT_CRITICAL(&timerMux);
totalInterrupts++;
Serial.print("totalInterrupts");
Serial.println(totalInterrupts);
}
}
note: portEnter_CRITICAL is expand for vTaskEnterCritical