MCU & FPGA RTOS Padrões de Projeto Aplicados a RTOS (FreeRTOS)

Padrões de Projeto Aplicados a RTOS (FreeRTOS)


Introdução geral

Em sistemas embarcados com RTOS, especialmente em aplicações industriais, automotivas e IoT crítico, o maior desafio não é apenas “fazer funcionar”, mas manter previsibilidade temporal, escalabilidade e manutenibilidade. É nesse ponto que os padrões de projeto aplicados a RTOS se tornam fundamentais.

Diferente de aplicações desktop, padrões em RTOS precisam respeitar restrições de tempo real, uso determinístico de memória, prioridades, latência de interrupções e sincronização segura entre contexto de ISR e tarefas. Muitos padrões clássicos de software são adaptados ou reinterpretados nesse contexto.

Neste artigo, começaremos pelos padrões estruturais básicos de tarefas, que formam a fundação de praticamente qualquer sistema com FreeRTOS.


1 – Padrões Fundamentais de Estruturação de Tarefas

1.1 Superloop + Tasks (Incremental RTOS Adoption)

Problema resolvido:
Projetos legados em superloop (while(1)) tornam-se difíceis de manter à medida que crescem. Migrar tudo de uma vez para RTOS é arriscado.

Ideia do padrão:
Manter o superloop como uma tarefa principal, introduzindo gradualmente novas tarefas RTOS.

Quando usar:

  • Migração de firmware bare-metal para FreeRTOS
  • Sistemas simples que estão crescendo
  • Prototipação controlada

Estrutura típica:

void LegacySuperloopTask(void *pvParameters)
{
    for (;;)
    {
        ReadSensors();
        ProcessData();
        UpdateOutputs();

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}
xTaskCreate(
    LegacySuperloopTask,
    "Legacy",
    1024,
    NULL,
    tskIDLE_PRIORITY + 1,
    NULL
);

Vantagens:

  • Baixo risco na migração
  • Preserva código validado
  • Facilita testes incrementais

Risco comum:
Transformar essa tarefa em um “monstro” que ignora o espírito do RTOS. Este padrão deve ser transitório, não permanente.


1.2 One Task Per Responsibility (Uma tarefa por responsabilidade)

Problema resolvido:
Tarefas que fazem “de tudo” dificultam análise temporal, debugging e escalonamento.

Ideia do padrão:
Cada tarefa possui uma única responsabilidade funcional bem definida.

Exemplo típico em FreeRTOS:

  • Task de aquisição
  • Task de processamento
  • Task de comunicação
void SensorTask(void *pvParameters)
{
    for (;;)
    {
        ReadADC();
        xTaskNotify(ProcessTaskHandle, 0, eNoAction);
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}
void ProcessTask(void *pvParameters)
{
    for (;;)
    {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        FilterData();
        ComputeResults();
    }
}

Vantagens:

  • Facilita análise de prioridades
  • Reduz acoplamento
  • Favorece paralelismo real

Boas práticas:

  • Nomear tarefas claramente
  • Documentar WCET (Worst Case Execution Time)
  • Evitar bloqueios longos

1.3 Cyclic Executive com RTOS

Problema resolvido:
Algumas aplicações precisam de periodicidade rígida, mesmo usando RTOS.

Ideia do padrão:
Uma tarefa de alta prioridade atua como orquestrador temporal, liberando outras tarefas de forma cíclica.

Estrutura conceitual:

  • Task cíclica principal
  • Subtarefas acionadas por notificações
void CyclicTask(void *pvParameters)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();

    for (;;)
    {
        xTaskNotify(TaskAHandle, 0, eNoAction);
        xTaskNotify(TaskBHandle, 0, eNoAction);

        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
    }
}

Vantagens:

  • Controle temporal explícito
  • Previsibilidade elevada
  • Útil em controle e automação

Limitação:
Menos flexível que arquiteturas puramente orientadas a eventos.


1.4 Idle Task como Padrão Arquitetural

Problema resolvido:
Desperdício de CPU e energia quando o sistema está ocioso.

Ideia do padrão:
Usar a Idle Task como ponto central para:

  • Economia de energia
  • Limpeza de recursos
  • Instrumentação
void vApplicationIdleHook(void)
{
    EnterLowPowerMode();
}

Uso típico:

  • __WFI() em Cortex-M
  • Estatísticas de CPU
  • Monitoramento de heap

Importante:
Nunca bloquear, nunca usar delays e nunca acessar recursos não protegidos.


2 – Padrões de Comunicação e Sincronização em RTOS

Em FreeRTOS, concorrência mal projetada é a principal fonte de bugs: deadlocks, starvation, jitter temporal e corrupção de dados. Os padrões desta seção existem para substituir variáveis globais, flags soltas e volatile mal utilizados por mecanismos determinísticos e auditáveis.

Aqui falamos de comunicação, não apenas de sincronização.


2.1 Event Queue (Fila de Eventos)

Problema resolvido:
Múltiplos produtores gerando eventos assíncronos para um único consumidor.

Ideia do padrão:
Eventos são encapsulados em estruturas e enviados por uma fila RTOS. O consumidor processa eventos em ordem temporal.

Quando usar:

  • Interface homem-máquina
  • Eventos de sensores
  • Drivers desacoplados da lógica de aplicação

Estrutura típica do evento:

typedef enum {
    EVT_BUTTON,
    EVT_SENSOR,
    EVT_TIMEOUT
} EventType;

typedef struct {
    EventType type;
    uint32_t  data;
} AppEvent;

Fila global (criada na inicialização):

QueueHandle_t EventQueue;

EventQueue = xQueueCreate(10, sizeof(AppEvent));

Produtor:

void ButtonISR(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    AppEvent evt = {
        .type = EVT_BUTTON,
        .data = 1
    };

    xQueueSendFromISR(EventQueue, &evt, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

Consumidor:

void EventTask(void *pvParameters)
{
    AppEvent evt;

    for (;;)
    {
        if (xQueueReceive(EventQueue, &evt, portMAX_DELAY))
        {
            switch (evt.type)
            {
                case EVT_BUTTON:
                    HandleButton(evt.data);
                    break;

                case EVT_SENSOR:
                    HandleSensor(evt.data);
                    break;

                default:
                    break;
            }
        }
    }
}

Vantagens:

  • Desacoplamento total
  • Ordem garantida
  • Fácil instrumentação

Erro comum:
Usar fila para stream contínuo de dados (para isso existem buffers).


2.2 Mailbox (Fila de Mensagem Única)

Problema resolvido:
Apenas o último valor importa, não o histórico.

Ideia do padrão:
Uma fila de tamanho 1 funciona como mailbox sobrescrevível.

QueueHandle_t Mailbox;

Mailbox = xQueueCreate(1, sizeof(uint32_t));

Produtor:

uint32_t temperature = ReadTemperature();
xQueueOverwrite(Mailbox, &temperature);

Consumidor:

uint32_t temp;

if (xQueuePeek(Mailbox, &temp, 0))
{
    UseTemperature(temp);
}

Quando usar:

  • Telemetria
  • Estados globais observáveis
  • Última leitura válida

Vantagem-chave:
Evita backlog artificial.


2.3 Publish–Subscribe (Pub-Sub) com FreeRTOS

Problema resolvido:
Múltiplos consumidores interessados no mesmo evento.

Ideia do padrão:
Uma tarefa “dispatcher” recebe eventos e os redistribui para múltiplas filas.

Arquitetura:

ISR / Producers

  Event Queue

  Dispatcher Task
     ↓          ↓           ↓
 Task A Task B Task C

Dispatcher:

void DispatcherTask(void *pvParameters)
{
    AppEvent evt;

    for (;;)
    {
        if (xQueueReceive(EventQueue, &evt, portMAX_DELAY))
        {
            xQueueSend(QueueA, &evt, 0);
            xQueueSend(QueueB, &evt, 0);
        }
    }
}

Vantagens:

  • Extensível
  • Isola produtores de consumidores
  • Facilita testes

Custo:
Mais RAM e latência — aceitável em troca de clareza.


2.4 Task Notification como Padrão de Sinalização Rápida

Problema resolvido:
Overhead de filas quando apenas sinalização simples é necessária.

Ideia do padrão:
Usar Task Notifications como semáforos ultraleves.

Produtor:

xTaskNotify(TaskHandle, 0x01, eSetBits);

Consumidor:

uint32_t flags;

xTaskNotifyWait(0, 0xFFFFFFFF, &flags, portMAX_DELAY);

if (flags & 0x01)
{
    ProcessEvent();
}

Vantagens:

  • Zero alocação dinâmica
  • Extremamente rápido
  • Ideal para ISR → Task

Limitação:
Apenas 32 bits por tarefa → não serve para dados complexos.


2.5 Stream Buffer e Message Buffer

Problema resolvido:
Troca eficiente de fluxos contínuos de dados.

Stream Buffer

  • Dados binários contínuos
  • Sem fronteira de mensagem
StreamBufferHandle_t sb;
sb = xStreamBufferCreate(128, 1);

Message Buffer

  • Mensagens delimitadas
  • Ideal para pacotes
MessageBufferHandle_t mb;
mb = xMessageBufferCreate(128);

Uso típico:

  • UART
  • SPI
  • Áudio
  • Protocolos binários

Boa prática:
Preferir buffers a filas para dados “em volume”.


3 – Padrões de Exclusão Mútua, Proteção de Recursos e Prevenção de Deadlock

Em FreeRTOS, concorrência não controlada não falha sempre — falha quando você menos espera. Esta seção trata de padrões que evitam corrupção de dados, inversão de prioridade e deadlocks, sem sacrificar o determinismo.


3.1 Mutex com Priority Inheritance

Problema resolvido:
Duas ou mais tarefas acessam o mesmo recurso (UART, I²C, SPI, memória compartilhada).

Risco clássico:
🔴 Priority Inversion — tarefa de baixa prioridade bloqueia uma de alta.

Ideia do padrão:
Usar Mutex (não semáforo) para herança de prioridade automática.

SemaphoreHandle_t uartMutex;

uartMutex = xSemaphoreCreateMutex();

Uso correto:

void UartTask(void *pvParameters)
{
    for (;;)
    {
        if (xSemaphoreTake(uartMutex, portMAX_DELAY))
        {
            UART_Write("Hello RTOS\n");
            xSemaphoreGive(uartMutex);
        }
    }
}

Por que não semáforo binário?

  • Semáforos não fazem herança de prioridade
  • Mutexes são projetados para exclusão mútua real

Regra de ouro:
🔒 Mutex protege recurso, não evento.


3.2 Gatekeeper Task (Padrão Fundamental em FreeRTOS)

Problema resolvido:
Múltiplas tarefas acessando um único periférico (UART, Flash, Display).

Ideia do padrão:
Apenas uma tarefa é dona do recurso. As demais pedem serviço via fila.

Este é um dos padrões mais importantes para firmware de produção.


Estrutura:

Tasks ──► Request Queue ──► Gatekeeper Task ──► Hardware

Mensagem de requisição:

typedef struct {
    char message[64];
} UartRequest;

Gatekeeper:

void UartGatekeeperTask(void *pvParameters)
{
    UartRequest req;

    for (;;)
    {
        if (xQueueReceive(UartQueue, &req, portMAX_DELAY))
        {
            UART_Write(req.message);
        }
    }
}

Cliente:

UartRequest req = { .message = "Log message\n" };
xQueueSend(UartQueue, &req, portMAX_DELAY);

Vantagens:

  • Elimina mutexes
  • Elimina deadlocks
  • Simplifica drivers

Trade-off:
Leve aumento de latência → ganho enorme em robustez.


3.3 Critical Section (Uso cirúrgico)

Problema resolvido:
Trechos extremamente curtos que não podem ser interrompidos.

taskENTER_CRITICAL();
sharedCounter++;
taskEXIT_CRITICAL();

O que acontece:

  • Desabilita interrupções (localmente)
  • Bloqueia escalonamento

Quando usar:

  • Incrementos atômicos
  • Flags simples
  • Estruturas lock-free auxiliares

Quando NÃO usar:
🚫 Código longo
🚫 Acesso a drivers
🚫 Comunicação entre tarefas

Se você precisa de printf() dentro de critical section, algo está errado.


3.4 Read–Modify–Write protegido (Anti-pattern clássico)

Problema comum (ERRADO):

if (flag == 0)
{
    flag = 1;
}

Correção com mutex:

xSemaphoreTake(flagMutex, portMAX_DELAY);
if (flag == 0)
{
    flag = 1;
}
xSemaphoreGive(flagMutex);

Ou melhor ainda:
Substituir flag por task notification ou event group.


3.5 Event Groups como Padrão de Coordenação

Problema resolvido:
Sincronizar múltiplas tarefas com condições compostas.

EventGroupHandle_t systemEvents;

#define EVT_NET_READY   (1 << 0)
#define EVT_SENSOR_OK   (1 << 1)

Sinalização:

xEventGroupSetBits(systemEvents, EVT_NET_READY);

Aguardar múltiplas condições:

xEventGroupWaitBits(
    systemEvents,
    EVT_NET_READY | EVT_SENSOR_OK,
    pdFALSE,
    pdTRUE,
    portMAX_DELAY
);

Uso típico:

  • Startup sequencial
  • Estados globais do sistema
  • Dependências entre subsistemas

Vantagem:
Muito mais expressivo que flags globais.


3.6 Deadlock Avoidance (Padrões de Prevenção)

Causa clássica de deadlock:

  • Ordem inconsistente de aquisição de mutexes

Padrão de prevenção:
📐 Sempre adquirir recursos na mesma ordem

xSemaphoreTake(mutexA, portMAX_DELAY);
xSemaphoreTake(mutexB, portMAX_DELAY);

/* uso */

xSemaphoreGive(mutexB);
xSemaphoreGive(mutexA);

Alternativa mais segura:
Eliminar múltiplos mutexes → usar Gatekeeper.


4 – Padrões de Controle de Fluxo, Estados e Recuperação em RTOS

Em sistemas com FreeRTOS, fluxo implícito é inimigo do determinismo. if encadeado, flags globais e dependências ocultas tornam o comportamento do sistema impossível de prever sob carga, falhas ou eventos raros.

Os padrões desta seção tornam o comportamento do sistema explícito, rastreável e recuperável.


4.1 State Machine (Máquina de Estados Finita – FSM)

Problema resolvido:
Fluxo de controle espalhado por múltiplas tarefas e condicionais.

Ideia do padrão:
Representar o comportamento como estados explícitos e transições bem definidas.


Estrutura básica

typedef enum {
    STATE_INIT,
    STATE_IDLE,
    STATE_ACTIVE,
    STATE_ERROR
} SystemState;
static SystemState currentState = STATE_INIT;

Execução da máquina de estados (task dedicada)

void StateMachineTask(void *pvParameters)
{
    for (;;)
    {
        switch (currentState)
        {
            case STATE_INIT:
                InitHardware();
                currentState = STATE_IDLE;
                break;

            case STATE_IDLE:
                if (StartCondition())
                    currentState = STATE_ACTIVE;
                break;

            case STATE_ACTIVE:
                RunControlLoop();
                if (FaultDetected())
                    currentState = STATE_ERROR;
                break;

            case STATE_ERROR:
                HandleFault();
                currentState = STATE_IDLE;
                break;
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

Vantagens:

  • Fluxo claro
  • Fácil depuração
  • Excelente para controle e sequenciamento

Limitação:
Cresce mal quando há muitos estados e eventos → entra a HSM.


4.2 Hierarchical State Machine (HSM)

Problema resolvido:
Explosão combinatória de estados.

Ideia do padrão:
Estados possuem comportamento comum herdado de estados “pais”.


Exemplo conceitual

SYSTEM
 ├── OPERATIONAL
 │    ├── IDLE
 │    └── ACTIVE
 └── FAULT
      ├── WARNING
      └── CRITICAL

Implementação simplificada em C

typedef enum {
    SUPER_OPERATIONAL,
    SUPER_FAULT
} SuperState;

typedef enum {
    SUB_IDLE,
    SUB_ACTIVE,
    SUB_WARNING,
    SUB_CRITICAL
} SubState;

static SuperState superState;
static SubState   subState;
void StateMachineStep(void)
{
    switch (superState)
    {
        case SUPER_OPERATIONAL:
            if (FaultDetected())
            {
                superState = SUPER_FAULT;
                subState = SUB_WARNING;
                break;
            }

            if (subState == SUB_IDLE && StartCondition())
                subState = SUB_ACTIVE;

            break;

        case SUPER_FAULT:
            HandleFaultCommon();

            if (subState == SUB_WARNING && EscalateFault())
                subState = SUB_CRITICAL;

            if (FaultCleared())
            {
                superState = SUPER_OPERATIONAL;
                subState = SUB_IDLE;
            }
            break;
    }
}

Vantagens:

  • Redução de duplicação
  • Escala bem
  • Muito usada em automotivo e aeroespacial

4.3 Mode Manager (Gerenciador de Modos)

Problema resolvido:
Múltiplas tarefas precisam reagir a mudanças globais de modo.

Ideia do padrão:
Uma tarefa central gerencia o modo do sistema, e os demais módulos se adaptam.


Modos típicos

typedef enum {
    MODE_STARTUP,
    MODE_NORMAL,
    MODE_LOW_POWER,
    MODE_MAINTENANCE
} SystemMode;

Mode Manager

static SystemMode currentMode;

void ModeManagerTask(void *pvParameters)
{
    for (;;)
    {
        if (LowPowerRequested())
            currentMode = MODE_LOW_POWER;

        if (MaintenanceRequested())
            currentMode = MODE_MAINTENANCE;

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

Uso pelas tarefas

void SensorTask(void *pvParameters)
{
    for (;;)
    {
        if (currentMode == MODE_NORMAL)
            ReadSensors();

        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

Boa prática:
Substituir variável global por:

  • Event Groups
  • Message Queue
  • Broadcast via Pub-Sub

4.4 Recovery Pattern (Padrão de Recuperação)

Problema resolvido:
Falhas transitórias travam o sistema.

Ideia do padrão:
Falhas são tratadas como estados, não exceções implícitas.


Exemplo de recuperação controlada

case STATE_ERROR:
    LogFault();
    ResetSubsystem();
    if (RetryAllowed())
        currentState = STATE_INIT;
    else
        EnterSafeState();
    break;

Vantagens:

  • Evita reboot desnecessário
  • Permite retry controlado
  • Essencial para sistemas remotos

4.5 Watchdog como Padrão Arquitetural

Problema resolvido:
Deadlocks silenciosos e travamentos não detectados.

Ideia do padrão:
Cada tarefa sinaliza vida periodicamente para um supervisor.


void SupervisorTask(void *pvParameters)
{
    for (;;)
    {
        if (!AllTasksAlive())
            SystemReset();

        FeedWatchdog();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Importante:
O watchdog não substitui arquitetura correta — ele a complementa.


Excelente. Agora avançamos para a arquitetura interna do firmware, onde decisões erradas costumam gerar acoplamento irreversível, e decisões corretas permitem portabilidade, testes e evolução por anos.


5 – Padrões de Arquitetura em Camadas, Drivers e Serviços em RTOS

Em sistemas com FreeRTOS, misturar driver, lógica de negócio e política de tempo real na mesma tarefa é um erro estrutural. Os padrões desta seção tratam de separação de responsabilidades, isolamento de hardware e execução previsível.


5.1 Layered Architecture (Arquitetura em Camadas)

Problema resolvido:
Firmware “espaguete”, onde qualquer módulo acessa diretamente registradores, RTOS e aplicação.

Ideia do padrão:
Organizar o sistema em camadas bem definidas, com dependências unidirecionais.


Camadas típicas em FreeRTOS

Application / State Machines
Services (Comms, Storage)
Drivers / HAL
RTOS (FreeRTOS Kernel)
Hardware

Exemplo de separação correta

Driver (HAL):

void UartHw_SendByte(uint8_t b);

Serviço:

void UartService_Send(const uint8_t *data, size_t len);

Aplicação:

UartService_Send((uint8_t*)"OK\n", 3);

Vantagens:

  • Portabilidade de MCU
  • Testes unitários viáveis
  • Evolução controlada

Regra prática:
⬆ Camadas superiores não conhecem detalhes das inferiores.


5.2 Hardware Abstraction Layer (HAL) como Padrão

Problema resolvido:
Dependência direta de registradores ou SDK do fabricante.

Ideia do padrão:
Isolar acesso ao hardware atrás de uma API mínima e estável.


typedef struct {
    void (*init)(void);
    void (*write)(const uint8_t *buf, size_t len);
} UartDriver;
static UartDriver uart0 = {
    .init  = STM32_UartInit,
    .write = STM32_UartWrite
};

Benefício crítico:
Permite trocar STM32 → NXP → RP2040 sem reescrever aplicação.


5.3 Service Layer (Camada de Serviços)

Problema resolvido:
Drivers não devem conter lógica de negócio nem política de concorrência.

Ideia do padrão:
Serviços encapsulam:

  • Sincronização
  • Filas
  • Retries
  • Timeout
  • Políticas de acesso

Exemplo: Serviço de UART com Gatekeeper

void UartService_Send(const uint8_t *data, size_t len)
{
    UartRequest req = {
        .data = data,
        .len  = len
    };

    xQueueSend(UartQueue, &req, portMAX_DELAY);
}

Vantagens:

  • Aplicação fica limpa
  • Política centralizada
  • Drivers permanecem simples

5.4 Active Object Pattern

Problema resolvido:
Misturar lógica de controle com concorrência explícita.

Ideia do padrão:
Cada objeto ativo possui:

  • Estado interno
  • Fila própria
  • Tarefa dedicada

Estrutura conceitual

Active Object
 ├── Queue
 ├── Task
 └── State Machine

Exemplo simplificado

typedef struct {
    QueueHandle_t queue;
} MotorController;
void MotorTask(void *pvParameters)
{
    MotorCommand cmd;

    for (;;)
    {
        xQueueReceive(motor.queue, &cmd, portMAX_DELAY);
        ExecuteMotorCommand(&cmd);
    }
}

Vantagens:

  • Forte encapsulamento
  • Ideal para controle, comunicação e I/O
  • Elimina mutexes internos

Custo:
Uso maior de RAM (1 task + 1 fila por objeto)


5.5 Deferred Interrupt Processing

Problema resolvido:
ISR longa causa jitter e perda de deadlines.

Ideia do padrão:
ISR apenas sinaliza → processamento pesado ocorre em task.


void ADC_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTaskNotifyFromISR(AdcTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void AdcTask(void *pvParameters)
{
    for (;;)
    {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        ProcessAdcData();
    }
}

Vantagens:

  • ISR mínima
  • Melhor escalonamento
  • Sistema mais previsível

6 – Padrões de Inicialização, Startup Sequencial e Monitoramento de Saúde

Em sistemas com FreeRTOS, o boot é o momento mais frágil do sistema. Muitos firmwares falham não durante a operação normal, mas durante inicialização parcial, falhas de periféricos, ordem incorreta de dependências ou estados indefinidos.

Os padrões desta seção tornam o startup determinístico, auditável e recuperável.


6.1 Startup Task (Task de Inicialização)

Problema resolvido:
Inicialização espalhada entre main(), ISRs e tarefas diversas.

Ideia do padrão:
Criar uma tarefa exclusiva de startup, responsável por inicializar tudo em ordem controlada, antes do sistema entrar em operação normal.


Estrutura típica

int main(void)
{
    HardwareInit();

    xTaskCreate(StartupTask, "Startup", 1024, NULL, tskIDLE_PRIORITY + 3, NULL);

    vTaskStartScheduler();
}
void StartupTask(void *pvParameters)
{
    InitDrivers();
    InitServices();
    InitApplication();

    xTaskCreate(AppTask, "App", 1024, NULL, tskIDLE_PRIORITY + 1, NULL);

    vTaskDelete(NULL);
}

Vantagens:

  • Ordem explícita
  • Inicialização rastreável
  • Elimina “efeitos colaterais” no main()

Boa prática:
Startup task deve se autodestruir após cumprir sua função.


6.2 Init Sequencing (Sequenciamento de Inicialização)

Problema resolvido:
Tarefas começam a rodar antes de suas dependências estarem prontas.

Ideia do padrão:
Inicialização ocorre em fases bem definidas, com sincronização explícita.


Exemplo com Event Groups

#define EVT_DRIVERS_READY   (1 << 0)
#define EVT_SERVICES_READY  (1 << 1)
void StartupTask(void *pvParameters)
{
    InitDrivers();
    xEventGroupSetBits(systemEvents, EVT_DRIVERS_READY);

    InitServices();
    xEventGroupSetBits(systemEvents, EVT_SERVICES_READY);

    vTaskDelete(NULL);
}
void AppTask(void *pvParameters)
{
    xEventGroupWaitBits(
        systemEvents,
        EVT_DRIVERS_READY | EVT_SERVICES_READY,
        pdFALSE,
        pdTRUE,
        portMAX_DELAY
    );

    RunApplication();
}

Vantagem:
Dependências explícitas → menos bugs “fantasma”.


6.3 Dependency Ordering Pattern

Problema resolvido:
Serviços que dependem implicitamente uns dos outros.

Ideia do padrão:
Cada serviço declara o que precisa antes de operar.


Exemplo conceitual

bool Network_IsReady(void);
bool Storage_IsReady(void);
void TelemetryTask(void *pvParameters)
{
    while (!Network_IsReady() || !Storage_IsReady())
    {
        vTaskDelay(pdMS_TO_TICKS(100));
    }

    StartTelemetry();
}

Melhoria recomendada:
Substituir polling por eventos ou notificações.


6.4 Health Monitoring Pattern (Monitoramento de Saúde)

Problema resolvido:
Tarefas travam silenciosamente.

Ideia do padrão:
Cada tarefa reporta periodicamente seu estado para um supervisor central.


Estrutura básica

typedef struct {
    uint32_t taskId;
    TickType_t lastAlive;
} TaskHealth;
void HealthReport(uint32_t id)
{
    healthTable[id].lastAlive = xTaskGetTickCount();
}
void SupervisorTask(void *pvParameters)
{
    for (;;)
    {
        if (TaskTimeoutDetected())
            EnterRecoveryMode();

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

Vantagens:

  • Detecção precoce de falhas
  • Integração natural com watchdog
  • Essencial em sistemas remotos

6.5 Fail-Safe State Pattern

Problema resolvido:
Sistema entra em estado indefinido após falha grave.

Ideia do padrão:
Definir explicitamente um estado seguro, conhecido e controlado.


void EnterSafeState(void)
{
    DisableOutputs();
    StopMotors();
    SignalErrorLED();
}

Importante:
Fail-safe não é reset. É sobrevivência controlada.


6.6 Warm Restart vs Cold Restart

Problema resolvido:
Reset total desnecessário causa perda de estado e latência.

Ideia do padrão:
Diferenciar:

  • Cold restart → reboot completo
  • Warm restart → reinicialização parcial de serviços

if (RecoverableFault())
    RestartServices();
else
    SystemReset();

7 – Síntese Geral e Checklist Arquitetural de Padrões para FreeRTOS

Após percorrer os principais grupos de padrões aplicáveis a sistemas com RTOS, fica claro que FreeRTOS não é apenas um escalonador, mas uma plataforma arquitetural que exige decisões conscientes desde o primeiro xTaskCreate().

Esta seção final transforma o conteúdo técnico em instrumento de uso real, algo que pode ser consultado durante o desenho ou revisão de um firmware.


7.1 Mapa Conceitual dos Padrões em RTOS

Podemos organizar os padrões apresentados em camadas de decisão, do mais fundamental ao mais sistêmico:

🔹 Estrutura Básica

  • Superloop + Tasks
  • One Task per Responsibility
  • Cyclic Executive
  • Idle Task Pattern

➡️ Define como o sistema respira


🔹 Comunicação e Sincronização

  • Event Queue
  • Mailbox
  • Publish–Subscribe
  • Task Notifications
  • Stream Buffer / Message Buffer

➡️ Define como a informação flui


🔹 Proteção de Recursos

  • Mutex com Priority Inheritance
  • Gatekeeper Task
  • Critical Sections
  • Event Groups
  • Deadlock Avoidance

➡️ Define como o sistema se protege


🔹 Controle de Comportamento

  • Finite State Machine (FSM)
  • Hierarchical State Machine (HSM)
  • Mode Manager
  • Recovery Pattern
  • Watchdog Pattern

➡️ Define como o sistema se comporta e reage


🔹 Arquitetura Interna

  • Layered Architecture
  • HAL
  • Service Layer
  • Active Object
  • Deferred Interrupt Processing

➡️ Define como o sistema é construído


🔹 Inicialização e Robustez

  • Startup Task
  • Init Sequencing
  • Dependency Ordering
  • Health Monitoring
  • Fail-Safe State
  • Warm vs Cold Restart

➡️ Define como o sistema nasce e sobrevive


7.2 Checklist Arquitetural para Projetos com FreeRTOS

Use este checklist como ferramenta de revisão técnica:

✔️ Estrutura

  • Cada tarefa tem uma responsabilidade clara?
  • Existe uma política clara de prioridades?
  • Tarefas críticas têm WCET conhecido?

✔️ Comunicação

  • Variáveis globais foram substituídas por filas/eventos?
  • ISR apenas sinalizam, nunca processam?
  • Fluxos contínuos usam buffers, não filas?

✔️ Proteção

  • Mutexes são usados apenas para recursos?
  • Existe Gatekeeper para periféricos compartilhados?
  • Ordem de aquisição de recursos é consistente?

✔️ Estados e Modos

  • O comportamento está modelado em FSM ou HSM?
  • Modos globais são explícitos?
  • Falhas são estados, não exceções implícitas?

✔️ Arquitetura

  • Aplicação não conhece registradores?
  • Drivers não conhecem RTOS?
  • Serviços encapsulam política e sincronização?

✔️ Inicialização e Saúde

  • Startup ocorre em task dedicada?
  • Dependências são explícitas?
  • Existe supervisão de tarefas?
  • Fail-safe é definido?

7.3 Erros Clássicos que Esses Padrões Evitam

  • ❌ “Só mais um volatile resolve”
  • ❌ “Vamos proteger tudo com mutex”
  • ❌ “ISR rápida não precisa de cuidado”
  • ❌ “Reset resolve qualquer coisa”
  • ❌ “Depois a gente organiza a arquitetura”

Todos esses erros são sintomas da ausência de padrões, não de falta de RTOS.


Conclusão Geral do Artigo

A maturidade em sistemas embarcados com FreeRTOS não está em quantas tasks você cria, mas em como você estrutura comportamento, comunicação e recuperação.

RTOS sem padrões é apenas concorrência.
RTOS com padrões é engenharia.

Os padrões apresentados aqui não são teóricos: eles surgiram de sistemas industriais, médicos, automotivos e aeroespaciais, e quando aplicados corretamente, reduzem drasticamente:

  • Bugs intermitentes
  • Tempo de depuração
  • Retrabalho arquitetural
  • Risco em produção
0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x