Manual
do
Maker
.
com
Considerando o feedback, acredito que os artigos relacionados à utilização dos recursos do FreeRTOS no ESP32 estão despertando bem o interesse da galera por novidades. Mas também, que não se interessaria em tirar proveito de tão bons recursos desse sistema operacional de tempo real rodando nessa maravilha com WiFi e bluetooth?
No ESP32 as coisas funcionam de uma maneira incrivelmente diferente, como você deve ter notado nos artigos anteriores. Nesse artigo quero citar 4 tipos de delays que você pode usar no ESP32, sendo um deles a implementação do Arduino e a outra, o que é comum utilizar no Arduino, mas não deveria ser utilizado no ESP32.
Quando queremos fazer um delay no FreeRTOS, utilizamos essa função. Ela recebe como parâmetro o número de ticks do processador a esperar, mas quando estamos programando, é mais óbvio definir o tempo de delay. Para isso, utilizamos a função pdMS_TO_TICKS, passam em milissegundos o tempo desejado de delay.
Para passar o tempo invés dos ticks para as funções de delay, utilizamos essa função. Podemos chamá-la diretamente como parâmetro das funções vTaskDelay e vTaskDelayUntil.
Eu já ví algumas necessidades de ter uma execução em "exatamente" X tempo. No Arduino é impossível conseguir essa exatidão por qualquer meio próprio, apenas por interrupção externa, porque não existe paralelismo no Arduino. Dependendo da criticidade, você pode inclusive dar mais prioridade a essa task sobre as demais. No código mais adiante você vê os exemplos.
Se você estiver em um loop no Arduino e não quiser travar uma porção de código em um loop, uma opção é utilizar a função millis() a cada ciclo do loop. Porém, não haverá precisão entre os intervalos, uma vez que as instruções seguidas da função millis() podem levar tempos diferentes, ainda mais se dependerem de fatores externos, como uma comunicação remota por qualquer barramento ou protocolo. É ótima para ser utilizada no Arduino de qualquer modo, pois não trava a MCU em um loop infinito. Porém para ESP32 pode-se chamar de gambiarra se não implementar direito. Prendi a tarefa em um loop com a função millis() e o resultado não é nada bonito:
No Arduino o delay disperdiça montes de ciclos, mas não processa nada. Já no ESP32, a função delay implementada na API para utilizar com a IDE do Arduino utiliza-se da vTaskDelay do FreeRTOS, suspendendo a tarefa invés de colocá-la a consumir ciclos.
A única opção não recomendada é um loop baseado na função millis(). Deixe-a para quando estiver programando um Arduino. As demais são totalmente viáveis; isto é, se desejar utilizar delay(), você estará utilizando a vTaskDelay. Se estiver utilizando a vTaskDelay, estará evidenciando em seu código a utilização dos recursos do FreeRTOS e se desejar precisão absoluta, utilize a vTaskDelayUntil.
Baseado no mesmo código de exemplo dos artigos anteriores, demonstro abaixo a utilização de cada uma das funções citadas, inclusive com a prioridade maior para vTaskDelayUntil().
byte my_shared_var = 1;
SemaphoreHandle_t xMutex;
void teste(void *pvParameters){
while (true){
xSemaphoreTake(xMutex,portMAX_DELAY);
Serial.println(my_shared_var);
my_shared_var = my_shared_var > 1 ? my_shared_var-1 : my_shared_var+1;
Serial.println((char*)pvParameters);
//delay(500);
vTaskDelay(pdMS_TO_TICKS(500)); //ticks para ms
xSemaphoreGive(xMutex);
}
}
//executar em intervalos precisos, independente de outras tarefas
void executeInExactly500ms(void *pvParameters){
char *nomeDaTarefa;
TickType_t momentoDoInicio;
nomeDaTarefa = (char *) pvParameters;
momentoDoInicio = xTaskGetTickCount();
for (;;){
Serial.print("exatamente em 500ms: ");
Serial.println(nomeDaTarefa);
vTaskDelayUntil(&momentoDoInicio,pdMS_TO_TICKS(500));
}
}
void executeEach500ms(void *pvParameters){
char *nomeDaTarefa = {(char *) pvParameters};
for (;;){
Serial.print("Executa em 500ms: ");
Serial.println(nomeDaTarefa);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void arduinoDelay(void *pvParameters){
char *nomeDaTarefa = {(char *) pvParameters};
for (;;){
Serial.print("Executa em 500ms: ");
Serial.println(nomeDaTarefa);
delay(500);
}
}
void gambiarra(void *pvParameters){
char *nomeDaTarefa = {(char *) pvParameters};
while (true){
unsigned long startTime = millis();
while ((millis()-startTime) < 500);
Serial.print("Ciclos gastos a toa: ");
Serial.println(nomeDaTarefa);
}
}
void setup(){
Serial.begin(115200);
xMutex = xSemaphoreCreateMutex();
if(xMutex != NULL){
xTaskCreatePinnedToCore(teste ,"Print1", 10000,(void *) "pvParameters da task 1", 3, NULL,0);
xTaskCreatePinnedToCore(teste ,"Print2", 10000,(void *) "pvParameters da task 2", 3, NULL,0);
}
xTaskCreatePinnedToCore(executeInExactly500ms ,"Print3", 10000,(void *) "vTaskDelayUntil" , 4, NULL,0);
xTaskCreatePinnedToCore(executeEach500ms ,"Print3", 10000,(void *) "vTaskDelay" , 3, NULL,0);
xTaskCreatePinnedToCore(arduinoDelay ,"Print3", 10000,(void *) "arduinoDelay" , 3, NULL,0);
//xTaskCreatePinnedToCore(gambiarra ,"Print3", 10000,(void *) "gambiarra" , 3, NULL,0);
}
void loop(){
delay(2000);
Serial.println("batata");
Serial.println(configTICK_RATE_HZ);
}
Se não leu os artigos anteriores e não sabe sobe as prioridades, recomendo que leia esse artigo. Nele você também verá como designar a tarefa a um núcleo qualquer do ESP32 (que é dual core, mas isso você já deve saber). Se não sabe ainda como especificar em qual núcleo executar uma tarefa, recomendo esse outro artigo. Se precisar manipular uma variável global por diferentes tasks, recomendo esse artigo sobre mutex. Precisando passar parâmetros na task? Leia esse artigo. Se não tem ainda o suporte ao ESP32 na IDE do Arduino, sugiro esse outro artigo.
Não tem o ESP32 ainda? Oh, que pena, não vai poder brincar agora. Mas não é tarde demais, compre um ESP32 na CurtoCircuito e acompanhe os tutoriais aqui, tem muita diversão ainda pela frente!
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.