Threads (Tasks) no FreeRTOS: Modelo de Execução, Prioridades e Boas Práticas
No FreeRTOS, uma thread é chamada de task e representa um fluxo de execução independente dentro do sistema. Cada task possui sua própria pilha (stack), prioridade, estado e contexto de CPU. O kernel do FreeRTOS é responsável por salvar e restaurar esse contexto durante as trocas de tarefas (context switch), permitindo que múltiplas funções “pareçam” rodar em paralelo em um microcontrolador de núcleo único.
A criação de uma task é feita tipicamente pela função xTaskCreate(). Ao criar uma task, o desenvolvedor define explicitamente o tamanho da pilha, a prioridade e a função que será executada. Essa decisão é arquitetural: pilhas subdimensionadas geram stack overflow silencioso; pilhas superdimensionadas desperdiçam RAM — um recurso crítico em sistemas embarcados.
Exemplo básico de criação de uma Task
#include "FreeRTOS.h"
#include "task.h"
/**
* @brief Task responsável por piscar um LED.
*/
void vLedTask(void *pvParameters)
{
(void) pvParameters;
for (;;)
{
toggle_led();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main(void)
{
hardware_init();
xTaskCreate(
vLedTask, // Função da task
"LED Task", // Nome (debug)
256, // Tamanho da pilha (words)
NULL, // Parâmetros
1, // Prioridade
NULL // Handle da task
);
vTaskStartScheduler();
for (;;);
}
Estados de uma Task
Internamente, uma task pode estar em cinco estados principais:
- Running: está atualmente usando a CPU.
- Ready: pronta para executar, aguardando escalonamento.
- Blocked: aguardando um evento (delay, fila, semáforo).
- Suspended: explicitamente suspensa.
- Deleted: removida do sistema.
O uso correto do estado Blocked é fundamental para sistemas eficientes. Tasks bem projetadas não ficam em loop verificando condições, mas sim bloqueiam até que algo relevante aconteça.
Prioridades e Preempção
O FreeRTOS utiliza um escalonador baseado em prioridade. Tasks com prioridade maior sempre preemptam tasks de prioridade menor. Tasks de mesma prioridade compartilham a CPU por time slicing (se habilitado).
Boas práticas importantes:
- Evite muitas prioridades distintas; normalmente 3 a 5 níveis são suficientes.
- Tasks de controle em tempo real devem ter prioridade maior.
- Tasks de interface, log ou comunicação geralmente ficam em prioridades mais baixas.
- Nunca use prioridade alta apenas “para garantir que funcione”.
Quando criar (ou não) uma Task
Criar uma task faz sentido quando:
- Existe um fluxo lógico independente.
- A atividade precisa bloquear aguardando eventos.
- A funcionalidade possui temporalidade própria.
Não crie uma task quando:
- A lógica é curta e pode ser executada via callback ou ISR.
- O código roda raramente e não justifica uma pilha dedicada.
- A task ficará majoritariamente ociosa consumindo RAM.
Vantagens do modelo de Tasks
- Estrutura clara e modular.
- Facilita manutenção e testes.
- Reduz acoplamento entre módulos.
- Permite escalabilidade do sistema.
Armadilhas comuns
- Criar tasks demais.
- Subestimar o tamanho da pilha.
- Usar
vTaskDelay()como mecanismo de sincronização. - Misturar lógica de ISR com lógica de task sem proteção adequada.
Na próxima seção, avançaremos para um mecanismo mais leve e muitas vezes subutilizado: Task Notifications, explicando por que elas são frequentemente a melhor alternativa a semáforos e filas em comunicação ponto-a-ponto.