Estratégia Profissional de Dimensionamento: Método Passo a Passo
Até aqui entendemos o que é stack, o que é heap, como o FreeRTOS os utiliza internamente e onde surgem os problemas reais. Nesta seção, organizamos isso em um método prático, repetível e defensável tecnicamente, do tipo que você usa em projeto profissional e consegue justificar em revisão de arquitetura.
5.1 Passo 1 — Mapear todas as entidades que consomem memória
O primeiro erro comum é “dimensionar no escuro”. O correto é listar explicitamente tudo que consome stack e heap.
Checklist mínimo:
- Tasks da aplicação
- Idle Task (sempre existe)
- Timer Service Task (se habilitada)
- Filas
- Semáforos
- Mutexes
- Event Groups
- Stream/Message Buffers
- Bibliotecas (lwIP, FATFS, USB, TLS, etc.)
Cada item deve ser classificado como:
- Consome heap?
- Consome stack?
- É criado estaticamente ou dinamicamente?
5.2 Passo 2 — Dimensionar stack individualmente (não por média)
Nunca use um valor genérico para todas as tasks. Cada task tem perfil de execução diferente.
Exemplo típico de classificação:
- Task de controle (PID, sensores):
- Poucas chamadas
- Poucas variáveis locais
- Stack: 256–384 words
- Task de comunicação (UART, TCP/IP, HTTP):
- Muitas chamadas
- Strings, buffers
- Stack: 512–1024 words
- Task de diagnóstico / debug:
- printf, logs, formatação
- Stack: 1024–2048 words
Essa diferenciação reduz drasticamente o consumo total de RAM.
5.3 Passo 3 — Converter tudo para palavras (não bytes)
Regra prática no Cortex-M:
\[
\text{Stack (words)} = \frac{\text{Stack (bytes)}}{4}
\]
Nunca misture unidades. Um erro clássico é calcular em bytes e passar diretamente para xTaskCreate.
5.4 Passo 4 — Calcular o heap total
Use a soma explícita:
\[
Heap = \sum (TCB_i + Stack_i) + Kernel + IPC + Margem
\]
Onde:
- \(TCB_i\) ≈ 80–120 bytes por task
- \(Kernel\) = Idle + Timer Task
- \(IPC\) = filas, mutexes, buffers
- Margem recomendada: 30% a 40%
Esse cálculo deve ser documentado no projeto, não apenas “ajustado até funcionar”.
5.5 Passo 5 — Validar em tempo de execução (watermark)
Cálculo teórico não substitui medição real.
Ferramentas essenciais:
uxTaskGetStackHighWaterMark()
xPortGetFreeHeapSize()
xPortGetMinimumEverFreeHeapSize()
Exemplo típico de monitoramento:
void vMonitorTask(void *arg)
{
for (;;)
{
printf("Heap livre atual: %u bytes\n", xPortGetFreeHeapSize());
printf("Menor heap já observado: %u bytes\n",
xPortGetMinimumEverFreeHeapSize());
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
Se o menor heap já observado estiver muito próximo de zero, o sistema está operando no limite.
5.6 Passo 6 — Habilitar proteções obrigatórias
Nunca entregue firmware profissional sem essas opções:
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
E implementar os hooks:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
/* Log crítico, LED, reset controlado */
taskDISABLE_INTERRUPTS();
for(;;);
}
void vApplicationMallocFailedHook(void)
{
taskDISABLE_INTERRUPTS();
for(;;);
}
Esses hooks transformam erros silenciosos em falhas detectáveis.
5.7 Passo 7 — Preferir criação estática quando possível
Regra de ouro em sistemas robustos:
- Tasks críticas →
xTaskCreateStatic - Buffers grandes → estáticos
- Heap → usado apenas para estruturas inevitáveis
Isso reduz fragmentação, melhora previsibilidade e facilita certificações.