Uso de interrupções externas com ESP32

Uso de interrupções externas com ESP32 6

O ESP32 já é um SoC muito conceituado no mercado, seja no maker ou no profissional. Muitas soluções em Internet das Coisas (IoT, em inglês) podem se tornar realidade hoje graças aos grandes recursos do ESP32, pequeno tamanho físico e seu preço muito atrativo.

Quando desenvolvemos sistemas embarcados (seja para projetos IoT ou não), sempre interagimos com entradas e saídas digitais, as chamadas GPIOs (General Purpose Input/Output). No quesito leitura, pode-se fazer isso de forma contínua / manual ou via interrupção, sendo que, em muitos casos, a leitura via interrupção é muito mais eficiente, elegante e útil.

Este post explica como ler GPIOs com interrupções externas no ESP32.

Por que utilizar leitura de GPIOs via interrupção?

Para responder a essa pergunta, imagine primeiro a seguinte situação: você, professor de uma sala de aula, deseja saber se algum aluno tem alguma dúvida sobre o conteúdo ministrado na aula até então. Você tem duas alternativas para isso:

  1. Forma 1: perguntar a todos os alunos, um a um, quem tem dúvida. Isso significa que se a sala de aula tem 30 pessoas, você fará 30 perguntas.
  2. Forma 2: pergunta a todos de uma vez se há alguma dúvida e, caso alguém levantar a mão, você pergunta a essa pessoa qual a dúvida e a esclarece.

Por motivos óbvios, a forma 2 é a mais eficiente, pois leva muito menos tempo e é algo mais focado (interação somente com quem tem dúvida).

Nos sistemas embarcados, esse dilema também existe se trocarmos o professor pela CPU (processador) e os alunos por GPIOs. Nesse caso, a forma 1 – menos eficiente – se chama leitura polling, enquanto a forma 2 – mais eficiente – se chama leitura via interrupção. Ou seja, na forma 1, o processador gasta tempo checando todos os GPIOs relevantes; enquanto na forma 2, o processador somente faz a leitura do GPIO quando for sinalizado a ele que algo mudou neste GPIO em específico. Isso significa dizer que, na leitura de GPIO via interrupção, o processador só gastará tempo lendo um GPIO quando este realmente tiver alguma mudança, otimizando muito o processamento do projeto como um todo.

Isso pode não parecer muito útil com 2 ou 3 GPIOs, porém imagine-se lidando com 10, 20 ou mais GPIOs. É muito, mas muito melhor e otimizado, nesses casos, utilizar leitura via interrupção.

Logo, uma interrupção é um aviso que é dado ao processador que um GPIO deve ser lido / sofreu uma alteração de estado.

Quais modos de interrupção de GPIOs são suportados no ESP32?

No ESP32, há os seguintes modos de interrupção suportados para os GPIOs:

  • FALLING: um modo que faz ser gerada uma interrupção quando um GPIO vai do nível alto (3V3) para nível baixo (0V). Ou seja, interrupção gerada na transição de nível alto para baixo;
  • RISING: um modo que faz ser gerada uma interrupção quando um GPIO vai do nível baixo (0V) para nível alto (3V3). Ou seja, interrupção gerada na transição de nível baixo para alto;
  • LOW: um modo que faz ser gerada uma interrupção gerada quando o GPIO está em nível baixo;
  • HIGH: um modo que faz ser gerada uma interrupção gerada quando o GPIO está em nível alto;
  • CHANGE: um modo que faz ser gerada uma interrupção quando há qualquer transição de nível no GPIO. Ou seja, tanto de nível baixo para alto quanto de nível alto para baixo.

Para melhor compreender o conceito de RISING e FALLING, observe a figura 1. O RISING é uma transição de nível baixo para o alto, sendo a interrupção gerada na borda de subida do pulso. Já o FALLING é uma transição de nível alto para o baixo, sendo a interrupção gerada na borda de descida do pulso.

Uso de interrupções externas com ESP32
Figura 1 – borda de subida e borda de descida de um pulso (fonte da imagem: https://www.electronicwings.com/pic/pic18f4550-timer-capture)

Como exemplo, imagine o circuito de um botão no GPIO5 do ESP32 mostrado na figura 2.

Uso de interrupções externas com ESP32
Figura 2 – circuito de botão (com pull-down) no GPIO5 do ESP32

Neste circuito, quando o botão é apertado, o nível de tensão no GPIO5 vai de GND para 3V3, ou seja, haverá transição de nível baixo para nível alto. Logo, se fosse desejado capturar o acionamento do botão (executar uma determinada rotina quando o botão fosse acionado), seria adequado utilizar o modo de interrupção RISING.

O que acontece quando uma interrupção é gerada?

Quando uma interrupção é gerada, automaticamente é chamada uma função de tratamento de interrupção. Este tipo de função tem uma categoria especial: ISR (Interrupt Service Routine).

Quando a interrupção acontece, o processador para tudo que estiver fazendo para atendê-la (= executar a rotina da função ISR). Logo, é muito importante que a rotina programada dentro da função ISR seja a mais breve e otimizada possível, de forma a não fazer o processador perder muito tempo em sua execução e continuar executando o código “comum” do projeto. Como boas práticas de uma função ISR, são:

  • Código-fonte o mais otimizado e curto possível;
  • Não usar delays / atrasos;
  • Não esperar por qualquer outra coisa (deve ser uma rotina independente das outras do projeto).

Como utilizar uma interrupção de GPIO com ESP32 e Arduino IDE?

Para isso, utilize a função attachInterrupt, cujo protótipo é attachInterrupt(GPIOPin, ISR, Mode). Onde:

  • GPIOPin: número do GPIO que quer monitorar utilizando interrupção externa;
  • ISR: nome da função a ser chamada na hora de tratar a interrupção;
  • Mode: modo da interrupção (LOW, HIGH, RISING, FALLING ou CHANGE).

Por exemplo, para o circuito da figura 2, conforme dito, é adequado que seja utilizado o modo de interrupção RISING. Assumindo que a função ISR neste caso chama-se funcao_ISR, a interrupção externa deste GPIO pode ser configurada por:

attachInterrupt(5, funcao_ISR, RISING);

É importante que, antes da chamada de attachInterrupt(), o GPIO seja configurado como entrada.

A função ISR, conforme dito, é uma categoria de função especial. Logo, seu protótipo e formato também o são. Uma função ISR deve ter a seguinte forma:

void IRAM_ATTR nome_da_funcao_isr()

{
/* Código de tratamento da interrupção */
}

Exemplo: captura de acionamento de botão

Como exemplo prático, segue o código-fonte completo da aplicação de capturar o acionamento de um botão via interrupção externa de GPIO. O circuito esquemático é o mesmo da figura 1. O exemplo consiste em contar quantas vezes o botão foi apertado, escrevendo esta informação na serial (que pode ser vista via Serial Monitor da Arduino IDE).

Este exemplo tem uma peculiaridade: deve ser feito o debounce do botão em software dentro da interrupção, a fim de as trepidações no botão não serem contadas erroneamente como acionamentos. Isso não pode ser feito com delays, por isso deve-se fazer uso da função millis() para ajudar na comparação do tempo do acionamento com o tempo do último acionamento válido. Dessa forma, o impacto em termos de tempo de execução dentro da função ISR é mínimo.

O código-fonte deste exemplo pode ser visto abaixo:

#define GPIO_BOTAO 5
#define TEMPO_DEBOUNCE 10 //ms

int contador_acionamentos = 0;
unsigned long timestamp_ultimo_acionamento = 0;

/* Função ISR (chamada quando há geração da
interrupção) */
void IRAM_ATTR funcao_ISR()
{
/* Conta acionamentos do botão considerando debounce */
if ( (millis() - timestamp_ultimo_acionamento) >= TEMPO_DEBOUNCE )
{
contador_acionamentos++;
timestamp_ultimo_acionamento = millis();
}
}


void setup()
{
Serial.begin(115200);

/* Configura o GPIO do botão como entrada
e configura interrupção externa no modo
RISING para ele.
*/
pinMode(GPIO_BOTAO, INPUT);
attachInterrupt(GPIO_BOTAO, funcao_ISR, RISING);
}

void loop()
{
Serial.print("Acionamentos do botao: ");
Serial.println(contador_acionamentos);
delay(1000);
}

Neste post, você aprendeu o que são interrupções externas no ESP32, quais os modos disponíveis e, ainda, como utilizar isso na prática para ler acionamentos de um botão.

Dessa forma, você pode monitorar entradas digitais de forma eficiente e eficaz, o que pode fazer a diferença em muitos projetos em sistemas embarcados.

Faça seu comentário

Acesse sua conta e participe

6 Comentários

  1. Estou com esse mesmo erro SPI_FAST_FLASH_BOOT ao usar ESP 32 com LoRa e interrupção tanto para Heltec quanto para TTGO. Vcs têm algum exemplo pra eu fazer um teste básico de interrupção com envio LoRa pra eu verificar ? Desde já os agradeço

    Antonio Carlos AGuiar Gagno Junior
    1. Olá Antônio!

      Você pode tentar utilizar o decodificador de exceções do ESP para tentar identificar de onde está vindo o problema:
      https://github.com/me-no-dev/EspExceptionDecoder

      Abraços!
      Vinícius – Equipe FilipeFlop

  2. Muito didático. Difícil encontrar explicação simples, eficaz e compreensível. Parabéns!!

  3. Parabéns pelo post professor, uma duvida e quando trabalhos com multi processamento na esp32, como ela trata essas interrupções.

    1. Olá Edinilson,

      Depende se são interrupções de periféricos externos ao processador, ou internos.
      Nesse link aqui você pode ler mais a respeito: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/intr_alloc.html#multicore-issues

      Abraços!
      Vinícius – Equipe FilipeFlop

  4. Ótimo post, professor! Bem esclarecedor e funcional (testei). Obrigado e parabéns!