Se você está lendo este post, muito provavelmente já conferiu nossa postagem sobre a teoria por trás do RTOS. Se não, não perca tempo e acesse já este link para aprender mais sobre essa maravilhosa ferramenta que é o Sistema Operacional de Tempo Real! Feito isso, vamos mostrar para você quais são as funcionalidades do ESP32 dentro da programação em tempo real. Se o leitor não estiver familiarizado com o componente ESP32, é aconselhável que confira nossa explicação sobre seu funcionamento e sua aplicação no ambiente da programação. Basta clicar nos links abaixo.
Para agendar uma tarefa, é necessário fazer duas coisas: criar uma função que contenha o código que deseja executar e, em seguida, criar uma tarefa que chame essa função.
Por exemplo, digamos que eu queira acender e apagar um LED continuamente.
Primeiro, será definido o pino ao qual o LED está conectado e seu modo para OUTPUT:
int led1 = 2; // Pino do LED void setup(){ pinMode(led1, OUTPUT); }
Posteriormente, será criada uma função que se tornará a base da tarefa. O comando digitalWrite() é usado para ligar e desligar o LED, e vTaskDelay (ao invés de delay()) é usado para pausar a tarefa por 500 ms entre mudanças de estados:
void toggleLED(void * parameter){ for(;;){ // loop infinito // Ligar LED digitalWrite(led1, HIGH); // Pausa a tarefa por 500ms vTaskDelay(500 / portTICK_PERIOD_MS); // Desligar LED digitalWrite(led1, LOW); // Pausa a tarefa por 500ms vTaskDelay(500 / portTICK_PERIOD_MS); } }
Essa foi a primeira tarefa. Algumas coisas a considerar:
Sim, foi criado um loop infinito for (;;) e isso pode parecer um pouco estranho. Como podemos realizar multitarefas se escrevermos uma tarefa que dura para sempre? O truque é vTaskDelay, que informa ao planejador que essa tarefa não deve ser executada por um determinado período. O planejador irá pausar o loop for e executar outras tarefas (se houver).
Por último, mas não menos importante, temos que informar ao planejador sobre nossa tarefa. Podemos fazer isso na função setup ():
void setup() { xTaskCreate( toggleLED, // Função a ser chamada "Toggle LED", // Nome da tarefa 1000, // Tamanho (bytes) NULL, // Parametro a ser passado 1, // Prioridade da Tarefa NULL // Task handle ); }
Portanto, é isso aí! Quer piscar outro LED em um intervalo diferente? Basta criar outra tarefa e relaxar enquanto o planejador cuida da execução de ambas.
Você também pode criar tarefas que são executadas apenas uma vez. Esses tipos de tarefas não precisam de um loop for (;;) sem fim; em vez disso, elas têm a seguinte aparência:
void TarefaUnica(void * parameter){ // Implemente sua execução lógica aqui // Quando terminar, chame vTaskDelete. Não se esqueça! vTaskDelete(NULL); }
É possível perceber uma semelhança muito grande com uma função C ++ comum, exceto para o vTaskDelete (). Depois de chamá-lo, o FreeRTOS sabe que a tarefa foi concluída e não deve ser reprogramada. (Nota: não se esqueça de chamar esta função, ou fará com que o watchdog reinicie o ESP32).
xTaskCreate( uploadToAWS, // Função a ser chamada "Upload to AWS", // Nome da tarefa 1000, // Tamanho (bytes) NULL, // Parametro a ser passado 1, // Prioridade da Tarefa NULL // Task handle );
Quando xTaskCreate () é utilizado, o planejador fica livre para escolher em qual núcleo ele executará sua tarefa. A princípio, esta é a solução mais flexível.
No entanto, é possível fixar uma tarefa em um núcleo específico com xTaskCreatePinnedToCore (). É como xTaskCreate () e leva um parâmetro adicional, o núcleo no qual você deseja executar a tarefa:
xTaskCreatePinnedToCore( uploadToAWS, // Função a ser chamada "Upload to AWS", // Nome da tarefa 1000, // Tamanho (bytes) NULL, // Parametro a ser passado 1, // Prioridade da tarefa NULL, // Task handle 0, // Núcleo que deseja rodar a tarefa (0 or 1) );
A maioria das placas ESP32 tem processadores dual-core, então como saber em qual núcleo sua tarefa está sendo executada?
Basta chamar xPortGetCoreID () de dentro da sua tarefa:
void TarefaExemplo(void * parameter){ Serial.print("A tarefa está rodando no seguinte núcleo: "); Serial.println(xPortGetCoreID()); vTaskDelay(100 / portTICK_PERIOD_MS); }
Quando há tarefas suficientes, o planejador irá despachá-las para os dois núcleos.
Agora, e se você adicionou uma tarefa ao agendador, mas deseja interrompê-la? Duas opções: você exclui a tarefa de dentro dela mesma ou usa um identificador de tarefa. Encerrar uma tarefa por dentro já foi discutido antes (use vTaskDelete).
Para interromper uma tarefa de outro lugar (como outra tarefa ou seu loop principal), temos que armazenar um identificador de tarefa:
// Essa TaskHandle irá permitir TaskHandle_t task1Handle = NULL; void task1(void * parameter){ // A lógica da sua tarefa } xTaskCreate( task1, "Task 1", 1000, NULL, 1, task1Handle // Task handle );
Bastou apenas definir o identificador e passá-lo como o último parâmetro de xTaskCreate (). Agora podemos eliminá-lo com vTaskDelete ():
void OutraTarefa(void * parameter){ // Encerra Tarefa1 se está rodando if(task1Handle != NULL) { vTaskDelete(task1Handle); } }
Ao criar tarefas, devemos atribuir-lhes uma prioridade, e isso é feito através do quinto parâmetro da função xTaskCreate (). As prioridades são importantes quando duas ou mais tarefas estão competindo por tempo de CPU. Quando isso acontece, o planejador executa primeiro a tarefa de prioridade mais alta, o que faz sentido.
No FreeRTOS, um número de prioridade mais alta significa que uma tarefa é mais importante. Isso pode parecer estranho, já que uma “prioridade 1” parece mais importante do que uma “prioridade 2”, mas no FreeRTOS, é justamente o contrário.
Quando duas tarefas compartilham a mesma prioridade, o FreeRTOS compartilhará o tempo de processamento disponível entre elas.
Cada tarefa pode ter uma prioridade entre 0 e 24. O limite superior é definido por configMAX_PRIORITIES () no arquivo FreeRTOSConfig.h.
A publicação deste post está sendo feito na primavera, e já estamos nos aproximando do verão, época mais quente do ano, e, por isso, nosso blog decidiu desenvolver um projeto voltado justamente a ajudar nossos leitores a passar por esse calor todo da maneira mais confortável possível, e, lógico, com a ajuda do Arduino. Neste post, iremos desenvolver um projeto que contém duas tarefas, as quais serão realizadas simultaneamente pelos dois núcleos do ESP32. A primeira tarefa será a automatização do controle de um ventilador a partir da temperatura ambiente, e a segunda tarefa será o desenvolvimento de um lembrete diário para beber água. Este projeto é extremamente benéfico principalmente para quem está fazendo home office ou EAD, já que permite o leitor a focar completamente no trabalho/estudo sem se preocupar em esquecer de beber água ou passar calor. A automação do ventilador também é ótima para usar a noite, enquanto dorme, já que, nos períodos em que a temperatura cai, o ventilador desliga sozinho, economizando energia.
Os materiais a seguir compõem o projeto desenvolvido aqui.
O diagrama a seguir representa a montagem do projeto:
Nota: Não consegui achar o modelo correto do sensor de temperatura no fritzing, esse foi o mais próximo que encontrei, porém ele não é muito diferente do que estamos usando aqui. Ele possui um fio a mais, o branco, que deve ser ignorado. Os fios vermelho e preto são, respectivamente, o positivo e o negativo, igual ao DS18B20, porém o fio laranja aqui é, na verdade, amarelo no que estamos usando, portanto não se esqueça de considerar essa pequena diferença na hora de montar.
É importante salientar também que, na parte da conexão do relé com o ventilador, uma extensão foi confeccionada. É possível fazer esse projeto de outras formas, cortando o fio do ventilador, por exemplo, porém essa foi a maneira escolhida neste post. As imagens a seguir mostram a extensão.
Os materiais utilizados foram:
Aqui, foi utilizado um módulo relé com quatro módulos, porém apenas um é necessário.
Atenção: Se você for menor de idade, peça ajuda a um adulto responsável para confeccionar essa extensão.
O código a seguir compõe os comandos do projeto desenvolvido:
#include <LiquidCrystal.h> #include <OneWire.h> #include <DallasTemperature.h> LiquidCrystal lcd(22,23,5,18,19,21); TaskHandle_t Task1; TaskHandle_t Task2; // O fio de dados é conectado no pino digital 33 no ESP32 #define ONE_WIRE_BUS 33 // Prepara uma instância oneWire para comunicar com qualquer outro dispositivo oneWire OneWire oneWire(ONE_WIRE_BUS); // Passa uma referência oneWire para a biblioteca DallasTemperature DallasTemperature sensors(&oneWire); // Pinos dos componentes do projeto const int led = 2; const int buzzer = 4; const int PushButton = 15; const int rele = 13; void setup() { Serial.begin(115200); pinMode(led, OUTPUT); pinMode(buzzer, OUTPUT); pinMode(PushButton, INPUT); pinMode(rele, OUTPUT); lcd.begin(16, 2); lcd.clear(); // Cria uma tarefa que será executada na função Task1code(), com prioridade 1 e execução no núcleo 0 xTaskCreatePinnedToCore( Task1code, /* Função da tarefa */ "Lembrete", /* nome da tarefa */ 10000, /* Tamanho (bytes) */ NULL, /* parâmetro da tarefa */ 1, /* prioridade da tarefa */ &Task1, /* observa a tarefa criada */ 0); /* tarefa alocada ao núcleo 0 */ delay(500); // Cria uma tarefa que será executada na função Task2code(), com prioridade 1 e execução no núcleo 1 xTaskCreatePinnedToCore( Task2code, /* Função da tarefa */ "Ventilador",/* nome da tarefa */ 10000, /* Tamanho (bytes) */ NULL, /* parâmetro da tarefa */ 1, /* prioridade da tarefa */ &Task2, /* observa a tarefa criada */ 1); /* tarefa alocada ao núcleo 1 */ delay(500); } //Task1code: Lembrete para beber água void Task1code( void * pvParameters ){ for(;;){ vTaskDelay(5000/portTICK_PERIOD_MS); // Tempo de espera do lembrete (recomendado: 1800000 (30 min)) lcd.setCursor(0,0); lcd.print("HIDRATE-SE!!!!"); // Escreve no Display lcd.setCursor(0,1); lcd.print ("BEBA AGUA AGORA!"); // Botão para indicar se a pessoa já bebeu a água int botao = digitalRead(PushButton); // Toca o buzzer digitalWrite(buzzer, HIGH); vTaskDelay(500/portTICK_PERIOD_MS); // ...for 1 sec digitalWrite(buzzer, LOW); // Stop sound... vTaskDelay(500/portTICK_PERIOD_MS); digitalWrite(buzzer, HIGH); // Send 1KHz sound signal... vTaskDelay(500/portTICK_PERIOD_MS); // ...for 1 sec digitalWrite(buzzer, LOW); // Stop sound... for(;;){ // Loop infinito botao = digitalRead(PushButton); if(botao == HIGH){ // Se a pessoa já bebeu a água... break; // Sai do loop } else{ // Se não... // Led pisca digitalWrite(led, HIGH); vTaskDelay(700/portTICK_PERIOD_MS); digitalWrite(led, LOW); vTaskDelay(700/portTICK_PERIOD_MS); } } lcd.clear(); // Limpa Display lcd.setCursor(0,0); lcd.print("PEDRA NOS RINS, "); // Escreve no Display lcd.setCursor(0,1); lcd.print ("VAO EMBORA!!!"); vTaskDelay(5000/portTICK_PERIOD_MS); lcd.clear(); } } //Task2code: Controle do ventilador void Task2code( void * pvParameters ){ for(;;){ // Loop infinito // Manda comando para ler temperaturas sensors.requestTemperatures(); // Escreve a temperatura em Celsius Serial.print("Temperatura: "); Serial.print(sensors.getTempCByIndex(0)); Serial.println(" graus"); float temp = sensors.getTempCByIndex(0); if(temp>30){ // Se temperatura for maior que 30 graus... digitalWrite(rele, HIGH); // Liga ventilador } if(temp<29){ // Se temperatura for menor que 29 graus... digitalWrite(rele, LOW); // Desliga ventilador } } } void loop() { }
O código em si é auto explicativo, porém acho pertinente detalhá-lo mais uma vez. Basicamente, são criadas duas tarefas, as quais serão realizadas simultaneamente, porém em núcleos diferentes, uma no núcleo 1 e a outra no núcleo 2. A primeira tarefa (Task1code) se resume em um lembrete para beber água. Em síntese, este lembrete espera um período de tempo determinado, e, passado o tempo, ele toca o buzzer, pisca o led e avisa pelo display que o usuário deve beber água. O led continuará piscando e o display continuará aceso até o usuário apertar o push button para desativar o lembrete. É possível perceber que o período de tempo do código acima é de apenas 5 segundos, porém o leitor pode mudar esse tempo para um valor maior, o indicado é 30 minutos (delay de 1800000). Já a segunda tarefa (Task2code) se baseia em ligar ou desligar um ventilador dependendo da temperatura ambiente. Em suma, o código lê a temperatura ambiente a partir do sensor e, após printar a temperatura no monitor serial, ele toma uma decisão: se a temperatura for maior que 30 graus, ele liga o relé (ventilador), e se for menor que 29 graus, ele desliga o relé (ventilador). Obviamente, estas temperaturas são simplesmente demonstrativas e foram escolhidas com o único objetivo de mostrar o funcionamento mais facilmente. Aconselhamos o leitor a escolher temperaturas mais adequadas ao seu próprio ambiente, como, por exemplo, 27 graus para ligar o ventilador e 24 para desligá-lo.
Vamos ver o funcionamento do projeto na prática!
Contextualizando o que está acontecendo no vídeo: inicialmente, o lembrete de água é acionado, e, após beber a água, basta apenas pressionar o botão até a mensagem no display mudar. No código utilizado aqui, o lembrete acontece de 5 em 5 segundos, portanto é aconselhável que o leitor mude esse tempo. Logo em seguida, são mostrados o relé, o sensor de temperatura e o ventilador, que compõem a segunda parte do projeto. Para que a temperatura capturada pelo sensor suba mais rápido, eu apenas segurei-o em minha mão para esquentá-lo, e, a partir dos valores apresentados pelo monitor serial, é possível perceber que, no momento em que a temperatura lida foi superior a 30 graus, o ventilador ligou, e quando a temperatura ficou abaixo de 29 graus, o ventilador desligou. Essas temperaturas foram escolhidas apenas para facilitar a demonstração, logo também é aconselhável que o leitor mude-as no código.
A partir do post acima, foi possível compreender um pouco mais sobre a aplicação de sistemas RTOS em componentes ESP32, bem como seu funcionamento e as vantagens de sua utilização. Se o leitor se interessou pela implementação prática do projeto, acredito que gostará dos seguintes posts relacionados:
Curtiu o post? Avalie e deixe um comentário!
Siga-nos também no Instagram e nos marque quando fizer algum projeto nosso: @eletrogate.
Até a próxima!
Tenha a Metodologia Eletrogate na sua Escola! Conheça nosso Programa de Robótica Educacional.
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!