Manual

do

Maker

.

com

Alarme com buzzer no ESP32

Alarme com buzzer no ESP32

Uma das coisas mais apaixonantes (e que não canso de repetir) é executar threads no ESP32. Para quem está mergulhado em Arduino, talvez ainda não tenha percebido o quanto isso pode ser importante e para mostrar  de verdade como isso funciona, pensei em fazer esse som de alarme com buzzer, disparado a partir de um timer. Talvez essa seja uma das poucas aplicações para a função millis() no ESP32 e acredite, pode dar muito errado usar **millis()**no ESP32, como vou mostrar no próximo artigo.

Cenário

Bem, o cenário é o seguinte:

  • Variáveis serão declaradas globalmente; algumas dentro da função da task.
  • Uma função é criada para a task (chamei de "sirene").
  • Variará apenas o tom, a frequência permanece a mesma.
  • Ao final da execução, o tom vai para 0 e a task é excluída.
  • Na função setup() definidos o pino, a frequência e a resolução.
  • Iniciamos o o alarme uma vez.
  • No loop fazemos um timer de 10 segundos e a cada 10 segundos, uma task é iniciada.
  • Imprimimos uma string no terminal a cada 1 segundo para mostrar a thread funcionando

Isso é uma coisa que você não verá no Arduino, porque enquanto a função de alarme fica presa no loop, outras tasks podem ser executadas em paralelo. No caso, o alarme está sendo executado no núcleo 0 e a mensagem "executando livremente...") está sendo executada no núcleo 1. Ou seja, 1 tarefa para cada núcleo do processador.

Não tem um ESP32 ainda?

Sério, não dá pra ficar sem. Provavelmente você tem alguns Arduino e pelo menos 1 ESP8266, então já está habituado a utilizar a IDE do Arduino de algum modo, mas não pode perder essa experiência de programar para um RTOS. Se ainda não te convenci nos outros artigos, talvez nesse já tenha uma motivação extra. Pegue na CurtoCircuito seu ESP32 para acompanhar os artigos, tem muita coisa por vir.

Pinout

esp32-pinout.webp

Não é exatamente essa a board que tenho aqui, mas pelo menos o GPIO 15 bateu. Então, um pino do buzzer vai ao 3v3 e o outro pino do buzzer vai ao GPIO15.

Função tone/ledc*

A API para a IDE do Arduino não tem a função tone(), por isso temos que utilizar um outro recurso:

ledcSetup(channel, frequence, resolution);
ledcAttachPin(buzzer_pin, channel);
ledcWriteTone(channel,toneVal);

Não é "tão" complexo, certo? Definimos as variaveis que são o canal do PWM, frequência inicial e resolução. Depois conectamos o pino do buzzer e indicamos o canal PWM. Por fim, escrevemos o valor no canal, simples assim.

O código completo é esse:

int buzzer_pin      = 15;
int channel         = 0;
int frequence       = 2000;
int resolution      = 10;
unsigned long timer = millis();

TaskHandle_t dobitaobyte;
eTaskState statusOf;

void sirene(void *pvParameters){
    float sinVal;
    int   toneVal;
    for (byte t = 0; t<10;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 2ms e gera novo tom
            delay(4);
        }
    }
    ledcWriteTone(channel, 0);
    vTaskDelete(NULL);
}

void setup() {
  ledcSetup(channel, frequence, resolution);
  ledcAttachPin(buzzer_pin, channel);
  timer = millis();
  Serial.begin(115200);
  xTaskCreatePinnedToCore(sirene,"sirene", 10000, NULL, 1, &dobitaobyte,0);
}

void loop() {
  if (millis()-timer >10000){
    statusOf = eTaskGetState(dobitaobyte);
    if (statusOf == eReady){
      timer = millis();
      xTaskCreatePinnedToCore(sirene,"sirene", 10000, NULL, 1, &dobitaobyte,0);
    }
    
  }
  Serial.println("executando livremente...");
  delay(1000);
}

Já é impressionante, hum? Uma tarefa não interfere na outra. No Arduino não seria possível rodar uma função qualquer ininterruptamente, porque nada mais seria executado. Daí, gerar um alarme sonoro significaria abandonar todo o resto do processo. Se o alarme fosse por exemplo de temperatura e durante o alarme a temperatura voltasse ao padrão? O alarme não seria desligado. Para contornar isso no Arduino, seria necessário executar leituras no meio da execução do alarme e portanto, mais código necessitaria ser escrito, inclusive código repetido. Já com o ESP32 você não precisará se preocupar com isso porque tudo continuará sendo executado como se o alarme não tivesse ocorrido.

Foi um exemplo interessante, mas temos a tarefa do loop() rodando no núcleo 1 e a tarefa do alarme rodando no núcleo 0. E se fossem duas tarefas no mesmo núcleo? - Respondo; sem problemas! Basta que a prioridade de ambas as tarefas sejam a mesma. Vamos ao exemplo e em seguida o video mostrando o alarme e os prints na tela.

int buzzer_pin      = 15;
int channel         = 0;
int frequence       = 2000;
int resolution      = 10;
unsigned long timer = millis();

TaskHandle_t dobitaobyte;
eTaskState statusOf;

void justAmessage(void *pvParameters){
  while(true){
      Serial.println("cuco...");
      delay(1000);
  }
}

void sirene(void *pvParameters){
    float sinVal;
    int   toneVal;
    for (byte t = 0; t<10;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 2ms e gera novo tom
            delay(4);
        }
    }
    ledcWriteTone(channel, 0);
    vTaskDelete(NULL);
}

void setup() {
  ledcSetup(channel, frequence, resolution);
  ledcAttachPin(buzzer_pin, channel);
  timer = millis();
  Serial.begin(115200);
  xTaskCreatePinnedToCore(sirene,     "sirene", 10000, NULL, 1, &dobitaobyte,0);
  xTaskCreatePinnedToCore(justAmessage,"cuco",  10000, NULL, 1, NULL,        0);
}

void loop() {
  if (millis()-timer >10000){
    statusOf = eTaskGetState(dobitaobyte);
    if (statusOf == eReady){
      timer = millis();
      xTaskCreatePinnedToCore(sirene,"sirene", 10000, NULL, 1, &dobitaobyte,0);
    }
    
  }
  Serial.println("executando livremente...");
  delay(1000);
}

Penso que esse seja o melhor exemplo, porque agora temos 3 tarefas; o loop(), que é um laço infinito; a sirene(), que é uma tarefa que finaliza, mas tem um loop que leva um determinado periodo de tempo pra finalizar; e a justAmessage(), que é um loop infinito com delay! No próximo post explico a diferença da função delay() do Arduino e da função delay() implementada para o ESP32, acompanhe.

https://youtu.be/\_V61tP\_uTCY

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.