ESP32

FreeRTOS com ESP32 – Filas e dicas para evitar problemas

ESP32 | Do bit Ao Byte | MicroPython no ESP32 | Processamento paralelo com ESP32 | Selecionar uma CPU | mutex no freertos | tasks no esp32 | FreeRTOS com ESP32 | timer com ESP32 | partições no ESP32

Estou gradativamente escrevendo sobre a utilização de recursos do FreeRTOS com ESP32 e antes de começar a fazer programas elaborados, é bom ter em mente algumas regras.

Você precisa de um ESP32?

“Sim” é a resposta. Todo mundo precisa de um ESP32, senão a alegria de um maker não é plena. Cada dia é uma novidade, tem um mundo ainda pra desbravar com o ESP32, por isso recomendo muito a aquisição. Pegue o seu na CurtoCircuito e acompanhe os tutoriais aqui!

Sobre o FreeRTOS

Ele é um sistema operacional de tempo real portável e de código aberto; é preemptivo (isto é, ele pode interromper uma tarefa e depois retomá-la). Tarefas que tenham a mesma prioridade são executadas em round-robin (“um pra mim, um pra você”, continuamente).

A CPU pode ser retomada através da primitiva taskYELD() (veremos em algum artigo).

IPC – Inter Proccess Communication



Os processos podem se relacionar através de filas ou de semáforos. Já escrevi um artigo dando como exemplo a utilização de um mutex, que é um tipo de semáforo também.

Kernel

O kernel é minimalista e o código é simples o suficiente para ser portado. Ainda, ele é um código compreendido por diversas arquiteturas de processadores. Executa em MCUs e CPUs de 8, 16 e 32 bits, ainda que tenham recursos limitados, bastando que a flash tenha no mínimo 32K e no mínimo 16KB de RAM.

As regras de programação

Normalmente as variáveis são precedidas pelo seu tipo:

c char
s short
l long
v void
p pointer
x portBASE_TYPE

Os nomes das funções são precedidas pelo seu tipo, seguido pelo arquivo de sua definição. Por isso para criar uma task temos a xTaskCreate() – um tipo BASE_TYPE (dependente da arquitetura) no arquivo task.c. Significa que a função não tem retorno.

As macros são precedidas pelo nome do arquivo em que estão definidas. A portMAX_DELAY será encontrada como MAX_DELAY no arquivo port.h.

Algumas macros já utilizei em outros artigos, como a pdTRUE, pdFALSE, pdPASS e pdFAIL, que retornam 1, 0, 1, 0, respectivamente.

Em algum momento escreverei um artigo sobre como programar para o ESP32 sem utilizar a IDE do Arduino e aí a estrutura do programa muda. Um código mínimo seria algo como:

Tasks

Já mostrei as tasks em artigos anteriores. Aqui só tenho a dizer que as funções das tasks sempre devem retornar void, assim como receber o parâmetro void *, cujo conteúdo precisará que seja feito um casting para o tipo. Eu exemplifiquei isso nesse artigo.

Normalmente as tarefas tem um ciclo infinito e cada execução de uma tarefa é chamada de job ou instância.

As tarefas nunca, nunca, nunca devem sair da função associada à sua implementação. Não use return dentro das tasks.

O encerramento de uma tarefa deve ser feita explicitamente. Isso significa que quando você quer sair de um loop, deve utilizar o break e então chamar a vTaskDelete(NULL). Isso eu exemplifiquei nesse outro artigo.

Quando uma tarefa é excluída, o stack e o TCB são liberados, mas sua reutilização dependerá do gerenciamento de memória dinâmica e podem não estar livres imediatamente.

Uma task pode estar em um dos quatro possíveis estados:

  • Running  – quando em execução
  • Ready      – Pronta para ser executada
  • Suspend – Execução suspensa
  • Blocked  – Aguardando um recurso para se executar

A função vTaskDelay(ticks) coloca a tarefa em modo de suspensão, portanto ela deixa de consumir ciclos e recursos de CPU. Expliquei em detalhes esse controle neste artigo.

Controle de prioridade da tarefa

Já mostrei como definir a prioridade de tarefas e como selecionar uma CPU do ESP32 para uma determinada task, mas a prioridade pode ser mudada posteriormente, se for necessário. Para isso, utilizamos a função vTaskPrioritySet, que recebe os parâmetros pxTaskUxNewPriority. Isto é, você deve indicar a task e a nova prioridade dela.

Para obter a prioridade de uma tarefa, utilize a função vTaskPriorityGet().

Suspensão de tarefas

Em algum momento você poderá precisar que uma tarefa “durma”, por exemplo, por falta de dados a serem processados. Nesse caso, você pode chamar a função vTaskSuspend, que recebe como parâmetro a tarefa em questão. Ainda, se você cria uma tarefa para uma execução simples, depois pode precisar dela em algum outro momento, invés de fazer o delete da tarefa, você pode incluir essa chamada do vTaskSuspend passando NULL como parâmetro, dentro da própria tarefa. Assim ela se executa e se suspende:

Quando desejar que a task seja retomada, agora a partir de outro ponto qualquer de seu código (e que esteja em execução) chame a vTaskResume passando o manipulador da tarefa (o “nome” passado via parâmetro ao criá-la) para a função. Suponhamos que você tenha criado uma tarefa assim:

Previamente você deve ter criado o manipulador dobitaobyte:

Então, quando quiser que a tarefa volte a ser executada, você faz a seguinte chamada:

Também é possível retomar a tarefa a partir de uma ISR, mas ainda preciso escrever sobre as interrupções para ficar claro.

Filas

Além da utilização dos semáforos para acessar dados em uma variável, existe outro meio de relacionamento entre as tarefas. Esse caso é mais específico para uma tarefa de entrada e uma tarefa de saída, utilizando o chamado FIFO (First In, First Out – ou, o primeiro que entra é o primeiro que sai). Para quem é do mundo Linux, já deve ter criado um pipe pelo próprio shell, através do mkfifo. Nesse caso, é mais que compreendido seu funcionamento. Mas se você não entendeu o conceito, explico. Uma fila é exatamente como uma fila de pessoas. Por exemplo, para comprar ingresso no cinema. Conforme a ordem de chegada é a ordem de saída da fila. Sempre saindo pelo caixa. No sistema é a mesma coisa; o primeiro dado que entrar na fila será o primeiro a sair, portanto não é necessário que entrada e saída sejam consumidas sincronamente pelas tarefas, pois a hora que um valor for retirado da fila, o seguinte passa a ser o primeiro. Porém existe limite para isso. Um exemplo básico seria um tamanho fixo definido.

Agora um ponto importante; a fila pode ser consumida por várias tarefas, assim como alimentada por várias tarefas, mas não ao mesmo tempo.

As tarefas que vão ler dessa fila podem ter um timeout, assim, se não conseguirem ler no tempo especificado, elas voltam para o estado Ready e podem ser chamadas novamente.

Uma fila vazia ou uma fila cheia passa seu estado para blocked para leitura ou escrita, respectivamente. Isso significa que se não houver dados, não poderá ser lida e, se estiver cheia, não poderá ser escrita até que seja consumido ao menos 1 valor dessa fila e então ela voltará ao estado permissivo. Para a escrita também é possível especificar um timeout.

Criação de filas

Se desejar utilizar uma fila, primeiramente você deverá criá-la. Para tal, utlizamos a função xQueueHanfle xQueueCreate(unsigned portBASE_TYPE uxQueuelength, unsigned portBASE_TYPE uxItemSize). O primeiro parâmetro é o número máximo de ítens comportados pela fila. O segundo parâmetro é o tamanho de cada ítem, em Bytes. Se o retorno for NULL, a fila não foi criada:

Escrevendo no PIPE

Tendo as filas sido criadas previamente, já podemos iniciar as tasks que farão uso dela. Essas tasks podem ser iniciadas, por exemplo, dentro da condicional que verifica se a fila foi criada. Nesse caso, garantimos que a task só rodará se a fila foi criada. Já na implementação da task, podemos iniciar a escrita. No ESP32 temos as funções xQueueSendToFront (escreve no início da fila) e xQueueSendToBack (equivalente a xQueueSend). Acho que não precisa explicar, hum? Todas elas pegam os mesmos parâmetros:

Repare que no ESP-IDF portBASE_TYPE é chamado BaseType_t. É, algumas mexidas a Espressif deu no FreeRTOS. Um exemplo de escrita:

O retorno da escrita é pdPASS.

Ler do PIPE

A leitura é feita com xQueueReceive.

Estado da fila

Também é possível saber o estado da fila utilizando:

Essa função retorna o número de ítens contidos em uma fila, permitindo saber se a fila está cheia ou não.

Se for possível para o próximo artigo, pretendo discorrer sobre interrupções. Espero que esse artigo tenha lhe agradado.

Inscreva-se no nosso newsletter, alí em cima à direita e receba novos posts por email.

Siga-nos no Do bit Ao Byte no Facebook.

Prefere twitter? @DobitAoByte.

Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.

Nossos grupos:

Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/

Próximo post a caminho!