Manual

do

Maker

.

com

Múltiplas interrupções externas com ESP32

Múltiplas interrupções externas com ESP32

Interrupções externas com ESP32

Dessa vez eu gostaria de mostrar uma implementação um pouco diferente. Funciona tudo certinho, mas tenho absoluta certeza de que alguém experiente em RTOS não faria assim, porque eu consegui comprovar formas de gerar inconsistência. Mas do jeito que escrevi no exemplo, vai dar pra ver alguns recursos interessantes; um deles, o controle do LED onboard do ESP32, utilizando os recursos de controle nativos do ESP-IDF. Outro recurso bem legal (pelo menos eu achei) é gerar tarefas que finalizam, a partir de uma interrupção. Com isso, uma mesma tarefa pode ser executada múltiplas vezes. Mas, do jeito que implementei só causaria conflitos em uma situação de uso real. Aqui a "sirene" com ESP32 pode ser executada múltiplas vezes e, rodando duas vezes o resultado é fascinante! O processador faz round-robin nas tarefas e parece que tem 2 dispositivos em paralelo!

Vou colocar o código e em seguida comento um pouco mais.

#define ESP_INTR_FLAG_DEFAULT 0
//O pino de GPIO tem um tipo específico, por isso deve-se utilizar o
//formato pré-definido ou então fazer o define passando a macro.
#define PIN_TO_INT            GPIO_NUM_17
#define PIN_LED               GPIO_NUM_2

#define PIN_PLAY              GPIO_NUM_15
#define INT_PLAY              GPIO_NUM_13

bool led_status = 0;
bool turn_on    = false;
bool play_sound = false;
bool playing    = false;

int channel     = 0;
int frequence   = 2000;
int resolution  = 10;

//eu peguei do artigo que escrevi sobre buzzer, mas não estou realmente
//utilizando ele aqui...
TaskHandle_t dobitaobyte;

//semáforo para gerenciar o acesso às tarefas.
SemaphoreHandle_t xSemaphore    = xSemaphoreCreateBinary();
SemaphoreHandle_t playSemaphore = xSemaphoreCreateBinary();
   
//declaração da ISR
void IRAM_ATTR my_isr_handler(void* arg){
    xSemaphoreGiveFromISR(xSemaphore, pdFALSE);
}

void IRAM_ATTR play_isr_handler(void* arg){
    play_sound =! play_sound;
    xSemaphoreGiveFromISR(playSemaphore, pdFALSE);
}

//função usada na task chamada pelo ISR
void sirene(void *pvParameters){
    float sinVal;
    int   toneVal;

    while (play_sound){
      for (byte t = 0; t<3;t++){
        for (byte x=0;x<180;x++){
            //converte graus em radianos
            sinVal = (sin(x*(3.1412/180)));
            //agora gera uma frequencia
            toneVal = 2000+(int(sinVal*100));
            //toca o valor no buzzer
            ledcWriteTone(channel,toneVal);
            //Serial.print("*");
            //atraso de 4ms e gera novo tom
            delay(4);
        }
      }
      play_sound = false;
    }
    
    ledcWriteTone(channel, 0);
    vTaskDelete(NULL);
}

void play_task(void *pvParameters){
  while (true){
    if (xSemaphoreTake(playSemaphore,portMAX_DELAY) == pdTRUE){
      gpio_intr_disable(INT_PLAY);
      //play_sound = !play_sound;
      Serial.print("play_sound: ");
      Serial.println(play_sound);
      if (play_sound){
        xTaskCreatePinnedToCore(sirene,"sirene", 10000, NULL, 1, &dobitaobyte,0);
      }
    }
  }
}

//tarefa que manipula o LED e entra em sleep antes de desligá-lo novamente
void led_task(void* pvParameters) {
  while (true){
    if(xSemaphoreTake(xSemaphore,portMAX_DELAY) == pdTRUE) {
      gpio_intr_disable(PIN_TO_INT);
      turn_on = false;
      //Serial.println("Mudando status do LED");
      //led_status = !led_status;
      gpio_set_level(PIN_LED, (uint32_t) HIGH);
      vTaskDelay(pdMS_TO_TICKS(2000));
      gpio_set_level(PIN_LED, (uint32_t) LOW);
      turn_on = true;
    }
  }
}

//habilitando as interrupções
void enableInterrupt(){
//equivalente a pinMode() do Arduino
  gpio_set_pull_mode(PIN_TO_INT,GPIO_PULLUP_ONLY);
  gpio_set_pull_mode(INT_PLAY,GPIO_PULLUP_ONLY);
  
  //sem isso, as interrupções não funcionam
  gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
  
  gpio_set_intr_type(PIN_TO_INT, GPIO_INTR_NEGEDGE);
  gpio_intr_enable(PIN_TO_INT);
  gpio_isr_handler_add(PIN_TO_INT, my_isr_handler, NULL);

  gpio_set_intr_type(INT_PLAY, GPIO_INTR_NEGEDGE);
  gpio_intr_enable(INT_PLAY);
  gpio_isr_handler_add(INT_PLAY, play_isr_handler, NULL);
}
void setup(){
    gpio_set_direction(PIN_TO_INT, GPIO_MODE_INPUT);
    gpio_set_direction(PIN_LED,GPIO_MODE_OUTPUT);
    ledcSetup(channel,frequence,resolution);
    ledcAttachPin(PIN_PLAY,channel);
    enableInterrupt();
    xTaskCreatePinnedToCore(led_task,"led_task",8192,NULL,10,NULL,0);
    xTaskCreatePinnedToCore(play_task,"play_task",8192,NULL,10,NULL,0);
    Serial.begin(115200);
}

void loop(){
    Serial.print("led_status: ");
    Serial.println(digitalRead(led_status));
    
    if (turn_on){
      gpio_intr_enable(PIN_TO_INT);
      turn_on = false;
    }
    
    if (play_sound){
      gpio_intr_enable(INT_PLAY);
      play_sound = false;
    }
    delay(500);
}

Primeiramente, se já não tem mais nada da API do Arduino, com excessão da função Serial. Isso é bem divertido, porque até a manipulação do GPIO já está sendo feita com os recursos do ESP-IDF. Acredito que já dê pra usar uma IDE melhor e escrever diretamente no formato main() invés de setup()loop(). Ainda estou indeciso de qual IDE utilizar para programar o ESP32, mas no próximo artigo já será totalmente nele e aí sim, vamos ver se as coisas continuam funcionando no "ambiente nativo". :-)

Quem manja bem de FreeRTOS vai encontrar facilmente maneiras melhores de implementar isso que dispus acima. Eu poderia ter colocado uma flag pra não deixar acontecer sobre-execução ou outro recurso baseado em tempo, daí uma task extra para tal finalidade etc. Daí você lê eu escrevendo isso e pensa: "Se não é a melhor maneira, porque fez assim"? - Minha humilde resposta - Não tenha receio de fazer nada, desde que isso não ponha a vida de ninguém em risco. É importante testar, sentir as dificuldades e estudar como contorná-las. Tendo um modelo funcional, agora consigo ver como funciona o que quero, então posso modificar livremente para assimilar os conceitos. Se você também está iniciando, atreva-se comigo, diversão não falta pra quem gosta de aprender. E conforme eu sentir confiança como tenho em outras plataformas, os resultados serão melhores.

Se chegou nesse post através do facebook, twitter ou linkedin e tem boas sugestões a respeito, comente, por favor. Será um prazer melhorar!

Veja o video da brincadeira. O LED fica acesso por 2 segundos e apaga. Dentro da função que acende e apaga o LED, tem um delay entre essas duas operações. Mas como é uma task, ela não influencia no restante do processamento.

Repare que a interrupção para o pino do LED foi desabilitada e é reabilitada na função loop(). Não dá (pelo menos eu não sei como) pra reabilitar a interrupção dentro da ISR que atende à interrupção, por isso coloquei no loop, mas poderia ser uma tarefa específica que analisa flags com um mutex, ou um timer disparado pela ISR ou alguma outra coisa.

Com qualquer uma das tasks que reagem à interrupção em execução, é possível executar a outra, sem interferência. Já na sirene, sugiro que você execute duas vezes pra ver que interessante. Agora, o video:

https://youtu.be/ezjIJDVX3hM

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.