Introduction: All Professionals Know About This Subject: Interrupt ISR

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

Today, we’ll talk about how to handle external interrupts in ESP32. I consider this subject to be somewhat advanced programming, but it’s nothing incredibly difficult. We’ll first create an example to trigger the interrupt with a button.

When we talk about interruption, it seems rather subjective, right? However, this is actually subroutine. It is like a special function that happens in a very fast time interval.

Step 1: Introduction

As its name implies, "Interrupt", when triggered, "interrupts" the current processor task and then processes the event that caused it.

When we do not use Interrupt, we have to do all event checks in the main loop.

Interrupts are useful for making things happen automatically in microcontroller programs and can help solve time problems.

Step 2: Flow and Interruption

Step 3: How to Set Up an External Interrupt

We have two main functions to configure the interrupt. These are: attachInterrupt and detachInterrupt.

These external interrupts should be used on digital pins.

We’ll provide some more details about them below.

attachInterrupt

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

This call configures the interrupt for a given pin, also indicating which event will trigger the interrupt, and which function will respond to it.

pin: Interruption pin

ISR (Interrupt Service Routine): is a function that will be called when the interrupt occurs.

mode: defines when the interrupt should be triggered. There are four predefined constants with valid values (see below).

detachInterrupt

detachInterrupt(pin);

This call disables the pin interrupt.

There is no return.

Step 4: Interrupt Service Routine (ISR)

ISRs are special types of functions that have some unique limitations that most other functions don’t have. An ISR cannot have any parameters and should not return anything.

  • An ISR should be as short and fast as possible.
  • If your program uses multiple ISRs, only one can be run at a time.
  • millis () use interrupts to count, so it will not increment within an ISR.
  • delay () requires interruption, so it will not work inside an ISR.
  • delayMicroseconds () don’t use interrupts, so they will work normally.

Step 5: MODE

This is set when the interrupt is triggered.

The following are the predefined constants:

  1. LOW: Stops the interruption whenever the pin is low.
  2. CHANGE: triggers interruption whenever the pin changes state.
  3. RISING: triggers the interruption when the pin goes from low to high (LOW> HIGH).
  4. FALLING: triggers the interruption when the pin goes from high to low (HIGH> LOW)
  5. HIGH: triggers interruption whenever the pin is high.

Step 6: Other Important Information

• If you use a variable to pass data between an ISR and the main program, the variable must be set to volatile, thus ensuring that it is updated correctly.

• To retrieve millis () within an ISR, use xTaskGetTickCount ().

• To change the values of the volatile variables, do this by opening a critical section, as in the example below:

volatile byte state = LOW;
// Para configurar seções críticas (interrupções de ativação e interrupções de desativação não disponíveis) // usado para desabilitar e interromper interrupções (cuida da sincronização entre codigo principal e interrupção) portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // instancia o mux portENTER_CRITICAL_ISR(&mux); // abre a seção critica state = !state; // altera o valor da variavel portEXIT_CRITICAL_ISR(&mux); // fecha a seção critica

Step 7: Demonstration

Step 8: WiFi NodeMCU-32S ESP-WROOM-32

Step 9: Assembly

Step 10: Program

We will now run a program in which, by pressing / releasing a button, will trigger the interruption causing a Green or Red LED to light up. We will configure the interrupt to occur with each state change of the pin, that is, we will use the CHANGE mode.

Button pressed> GREEN LED lit

Loose button> RED LED lit

Step 11: Variables

First, we’ll set the interrupt pin and the maximum debounce time for the button. Next, we’ll set the red and green LEDs.

At this stage, we will also declare volatiles to be shared by the ISR and the primary code. We point out the variables for control within the loop.

#define pinBUTTON 23 //pino de interrupção (botão)
#define DEBOUNCETIME 10 //tempo máximo de debounce para o botão (ms) #define pinREDled 2 //pino do led VERMELHO #define pinGREENled 4 //pino do led VERDE //É DECLARADA VOLÁTIL PORQUE SERÁ COMPARTILHADA PELO ISR E PELO CÓDIGO PRINCIPAL volatile int numberOfButtonInterrupts = 0; //número de vezes que a interrupção foi executada volatile bool lastState; //guarda o último estado do botão quando ocorreu a interrupção volatile uint32_t debounceTimeout = 0; //guarda o tempo de debounce //variáveis para controle dentro do loop uint32_t saveDebounceTimeout; bool saveLastState; int save; // For setting up critical sections (enableinterrupts and disableinterrupts not available) // used to disable and interrupt interrupts // Para configurar seções críticas (interrupções de ativação e interrupções de desativação não disponíveis) // usado para desabilitar e interromper interrupções portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

Step 12: Setup

We initialize the pins, including the interrupt. We set the button interrupt on the CHANGE event for the handleButtonInterrupt function. Afterwards, we initialize the red LED as lit.

void setup()
{ Serial.begin(115200); String taskMessage = "Debounced ButtonRead Task running on core "; taskMessage = taskMessage + xPortGetCoreID(); Serial.println(taskMessage); //mostra o core que o botão está executando // set up button Pin pinMode (pinREDled, OUTPUT); pinMode (pinGREENled, OUTPUT); pinMode(pinBUTTON, INPUT_PULLUP); // Pull up to 3.3V on input - some buttons already have this done attachInterrupt(digitalPinToInterrupt(pinBUTTON), handleButtonInterrupt, CHANGE); //configura a interrupção do botão no evento CHANGE para a função handleButtonInterrupt digitalWrite(pinREDled, HIGH); //inicializa o LED VERMELHO como aceso }

Step 13: Loop

Here, we begin and end the critical section. We retrieve the current status of the button, and set it so that if the button is pressed, then it turns on the green LED and extinguishes the red. Otherwise, it’ll turn on the red LED and turn off the green.

Also, we determined the impression of how many times the interrupt was triggered. To do this, we start the critical section, observe that the button was pressed, and reset the interrupt counter. This ends the critical section.

void loop()
{ portENTER_CRITICAL_ISR(&mux); // início da seção crítica save = numberOfButtonInterrupts; saveDebounceTimeout = debounceTimeout; saveLastState = lastState; portEXIT_CRITICAL_ISR(&mux); // fim da seção crítica bool currentState = digitalRead(pinBUTTON); //recupera o estado atual do botão //se o estado do botão mudou, atualiza o tempo de debounce if(currentState != saveLastState) { saveDebounceTimeout = millis(); } //se o tempo passado foi maior que o configurado para o debounce e o número de interrupções ocorridas é maior que ZERO (ou seja, ocorreu alguma), realiza os procedimentos if( (millis() - saveDebounceTimeout) > DEBOUNCETIME && (save != 0) ) { //se o botão está pressionado //liga o led verde e apaga o vermelho //caso contrário //liga o led vermelho e apaga o verde if(currentState) { digitalWrite(pinGREENled, HIGH); digitalWrite(pinREDled, LOW); } else{ digitalWrite(pinGREENled, LOW); digitalWrite(pinREDled, HIGH); } Serial.printf("Button Interrupt Triggered %d times, current State=%u, time since last trigger %dms\n", save, currentState, millis() - saveDebounceTimeout); portENTER_CRITICAL_ISR(&mux); //início da seção crítica numberOfButtonInterrupts = 0; // reconhece que o botão foi pressionado e reseta o contador de interrupção //acknowledge keypress and reset interrupt counter portEXIT_CRITICAL_ISR(&mux); //fim da seção crítica } }

Step 14: HandleButtonInterrupt

Finally, we work with the interruption of the service routine.

We have the IRAM_ATTR function, which is used to indicate that a code snippet will be in the instruction bus section of the RAM (higher speed), not Flash. The portENTER_CRITICAL_ISR / portEXIT_CRITICAL_ISR function is required, as the variable we’ll use is also changed by the main loop, as seen previously. And we need to avoid simultaneous access issues.

Also in this step, we increment the interrupt counter, read the current state of the button, and analyze the version of millis that works from the interrupt.

// Interrupt Service Routine - Keep it short!
//Interrupção //Interrompe a rotina de serviço //IRAM_ATTR --> é utilizado para indicar que esse trecho de código ficará na seção do barramento de instruções da RAM (maior velocidade) e não na Flash //portENTER_CRITICAL_ISR /´portEXIT_CRITICAL_ISR --> Isso é necessário porque a variável que vamos usar também é alterada pelo loop principal, //como visto anteriormente, e precisamos evitar problemas de acesso simultâneo. void IRAM_ATTR handleButtonInterrupt() { portENTER_CRITICAL_ISR(&mux); numberOfButtonInterrupts++; lastState = digitalRead(pinBUTTON); debounceTimeout = xTaskGetTickCount(); //versão do millis () que funciona a partir da interrupção //version of millis() that works from interrupt portEXIT_CRITICAL_ISR(&mux); }

Step 15: Download the Files: