Manual

do

Maker

.

com

Passando parâmetros através de tasks no ESP32

Passando parâmetros através de tasks no ESP32

Task é uma característica do RTOS. No caso, utilizamos o FreeRTOS no ESP32, que se baseia no Vanilla FreeRTOS, mas com a habilidade de trabalhar com mais de um núcleo.

Não tenho a pretensão de "ensinar" FreeRTOS, não é minha especialidade, mas confesso que tenho dado um passo por vez para, quem sabe em alguns meses, me tornar um bom programador nesse sistema operacional de tempo real. Mas para isso é necessário absorver diversos conceitos, principalmente o relacionado a tasks, que é a "menina dos olhos" desse sistema. Vamos então ver um pouco mais sobre tasks. Deixo minha recomendação para que leia o artigo anterior, caso ainda não o tenha feito, uma vez que fiz uma implementação divertida e simples.

Tasks são incríveis

Antes de discorrer sobre o conceito, o que me levou a iniciar os estudos no FreeRTOS (que outrora fui resistente a fazê-lo) foi justamente ver esse recurso incrível, que é a habilidade de criar paralelismo, tal como se fosse uma thread. Se você só programa para MCUs, provavelmente não deve conhecer o conceito de threads também, mas se você tiver um ESP32, vai dominar um recurso que é muito poderoso.

Não tem o ESP32 ainda?

Sem querer induzi-lo a isso, mas com o ESP32 você descobrirá um novo mundo. Além de poder utilizar a IDE do Arduino (que lhe dá acesso a recursos do C++) e assim já programar seu ESP32 como faz hoje com Arduino, você poderá agregar os recursos que só encontra no FreeRTOS e  assim ter um programa poderoso e muitas vezes mais eficiente.

O ESP32 não é uma MCU, mas uma CPU de 32 bits da arquitetura Tensilica. Sua CPU tem um clock de 240MHz, possui 2 núcleos, WiFi e bluetooth. Em muitos casos é possível substituir um Raspberry Pi por um ESP32. E estou dizendo isso porque já peguei projetos em que o Raspberry era necessário por falta de recursos de uma MCU, mas facilmente substituível por um ESP32, que economiza muito em termos de espaço, energia, custo e desenvolvimento, uma vez que você não precisa ter um bom conhecimento de Linux para fazer um bom projeto.

Então, se não adquiriu ainda seu ESP32, recomendo que pegue a sua na CurtoCircuito através desse link.

Compreendendo as tasks

As tasks são implementadas como funções da linguagem C. A única coisa especial sobre seu protótipo é que devem retornar void e receber um void como parâmetro. Exemplo:

void suaFuncao(void pv*Parameters);

Aí você pode pensar que não tem como passar uma variável como parâmetro, já que obrigatoriamente a declaração da função explicita que o parâmetro é void. Calma, lembre-se que estamos falando de C.

Outra característica da task é que ela é um programa por si só. Pense da seguinte maneira; você tem aí seu sistema operacional no computador ou no seu smartphone. Através de um menu ou de uma linha de comando você abre um determinado programa. No FreeRTOS você faz algo muito parecido, mas seu programa específico é determinado através de sua task. Você pode chamá-lo apenas uma vez, como faz em um sistema tradicional. Você pode então usá-lo, fechar o programa e liberar a memória para usar outra aplicação, mas é muito comum rodar esse programa de forma contínua, dentro de um loop infinito. E enquanto esse programa está rodando, você pode colocar a CPU para fazer outras coisas, sem interromper sua task.

Quando uma task é finalizada, ela deve ser excluída, lembrando que em C o gerenciamento de memória é responsabilidade do programador.

No artigo anterior eu fiz um exemplo utilizando 2 vezes uma mesma função, em tasks diferentes. Quando você faz isso, não está rodando duas vezes a mesma função, mas está rodando duas instâncias diferentes de um pequeno algoritmo que poderá atuar de forma diferente, conforme as condições do fluxo do programa. Com isso, menos código precisa ser escrito, tanto no que se refere a funções como ao fluxo do programa, que em MCUs como o Arduino é síncrono.

Excluir uma task

Sem aborrecê-lo com muitos conceitos, uma task pode ser excluída chamando ao final de sua execução vTaskDelete(NULL).

void umaFuncao(void *pvParameters){
    ...
    vTaskDelete(NULL);
}

Se a task for criada para execução em um loop infinito mas em algum momento você sair do loop (chamando um break), não deve deixar de chamar a vTaskDelete(NULL).

Passando parâmetro para a task

No artigo anterior eu utilizei 2 funções diferentes que faziam a mesma coisa, exceto imprimir um identificador diferente, para que eu soubesse de qual task estava vindo o resultado. Obviamente é uma solução horrível duplicar uma função para trocar 1 linha. E ainda mais um linha de debug! Então, como fazer para passar um parâmetro? Bem, é simples. Vou me basear no mesmo exemplo do artigo anterior para fazer esse novo código:

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);
    xSemaphoreGive(xMutex);
    
    }
}

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);
    }
}

void loop(){
    delay(2000);
    Serial.println("batata");
}

Repare que dentro da função bastou fazer um casting do tipo recebido no parâmetro (que é o ponteiro de um void) e, na criação da task, fazer o casting do array de char para um ponteiro de void. Ou seja, força o tipo esperado, depois força o tipo pretendido.

Talvez esse não seja seu primeiro contato com o FreeRTOS, e você pode estranhar não ter utilizado a função vTaskStartScheduler(), que inicia o tick do kernel para as tarefas. Bem, essa função passa o controle das tarefas em execução para o kernel. Ela normalmente é chamada dentro da função main() quando programando fora da IDE do Arduino, e ela só é chamada 1 vez. Logo, dentro do Arduino só seria possível chamá-la dentro da função setup() porque a função loop() é, bem, um loop. Mas, se chamar essa função na IDE do Arduino, vai gerar contínuos resets. Pode ser que ela esteja implementada por padrão na API para Arduino, uma vez que ela pode ser chamada antes de iniciar uma task, mas eu não achei essa implementação para poder afirmar.

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.