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