MCU & FPGA Algoritimos Máquina de Estados em Sistemas Embarcados com FreeRTOS: Projeto Cíclico, Recuperável e Orientado a Eventos

Máquina de Estados em Sistemas Embarcados com FreeRTOS: Projeto Cíclico, Recuperável e Orientado a Eventos


Padrões de projeto embarcados aplicados: nomes, papéis e como encaixar no seu firmware

Agora vamos “carimbar” o que já fizemos com linguagem de arquitetura — porque isso é o que permite você escalar o design para vários periféricos, vários módulos e times diferentes mantendo consistência. Vou descrever os padrões e como eles aparecem no exemplo, e como evoluir para um firmware real.

Active Object (Objeto Ativo)

No exemplo, a StateMachineTask é um Active Object: ela tem seu próprio thread de execução, seu próprio estado interno (SmContext) e uma fila de entrada (smQueue). Isso garante uma propriedade muito importante: o estado não é compartilhado concorrentemente. A FSM não precisa de mutex para seu contexto, porque só ela toca nele. Em embarcados, isso é ouro: menos lock, menos deadlock, menos “bug que só aparece no cliente”.

Papel típico no seu projeto: qualquer coisa que tenha comportamento interno e reaja a eventos — “protocolo UART”, “estado de rede”, “controle de motor”, “supervisão de sensores” — vira um Active Object com fila própria. A disciplina é: ninguém chama função interna do objeto por fora; só manda mensagem.

Message Queue / Event-Driven Boundary (Fronteira por Mensagens)

A fila é mais do que transporte: é uma fronteira arquitetural. Ela define um contrato: “essas são as mensagens válidas”. Isso substitui acoplamento implícito (globais e callbacks soltos) por acoplamento explícito (tipos de mensagens). No nosso caso:

  • ioQueue é o contrato “FSM → I/O”.
  • smQueue é o contrato “I/O → FSM”.

Em firmware real, isso permite:

  • simular I/O (como fizemos) para testar a FSM sem hardware,
  • inserir um logger ou trace no caminho,
  • e até migrar parte do I/O para DMA/ISR sem mudar a FSM (desde que o evento seja o mesmo).

Gatekeeper (Dono do Periférico)

O Gatekeeper é um padrão muito prático: um único contexto (task) é o dono de um periférico, e todo mundo fala com ele por mensagens. A nossa IOTask é um Gatekeeper “mínimo”. Em produção, isso é onde você coloca:

  • UART RX/TX, framing, CRC, timeouts de driver,
  • SPI transactions serializadas,
  • I2C com arbitragem e recovery (bus reset),
  • e políticas de power management do periférico.

Por que isso importa? Porque periféricos raramente são “thread-safe”. Se duas tasks chamam HAL_UART_Transmit() sem governança, você ganha corrupção e travamento. Gatekeeper impede isso por construção.

State Pattern (variação em C) e disciplina de entry/actions

A separação sm_on_entry() + sm_on_event() é a forma embarcada de “State Pattern”. Em vez de objetos e herança, você usa:

  • enum de estados,
  • handlers por estado,
  • e regras claras de transição.

O ganho real não é “elegância”: é evitar duplicação de ações de entrada e garantir “run-to-completion”. Isso faz o comportamento ficar rastreável.

Uma evolução natural (quando o switch crescer) é uma tabela de transições (State Transition Table). Você define:

  • (estado, evento) -> (ação, próximo estado)
    e deixa o motor genérico. Isso reduz complexidade ciclomática e torna mais fácil revisão em equipe.

Supervisor (FSM acima de FSMs)

Quando seu firmware tem vários subsistemas (rede, sensores, atuadores, storage), um padrão comum é um Supervisor: uma FSM “acima” que:

  • decide modos globais (BOOT, NORMAL, DEGRADED, SAFE),
  • arbitra prioridades (ex.: desligar rádio para economizar energia),
  • e coordena sequências (ex.: “para operar, rede precisa estar up e sensor calibrado”).

O nosso ST_SAFE já sugere isso. Em um sistema maior, ST_SAFE poderia:

  • mandar comandos para cada Gatekeeper colocar saídas em estado seguro,
  • e bloquear transições até receber EVT_FAULT_CLEAR.

Watchdog lógico (mais útil que só “chutar o cão”)

Muita gente alimenta o watchdog no while(1) e acha que está protegido. Isso só prova que o loop roda, não que o sistema está saudável.

Um watchdog lógico é: “o sistema está avançando em estados esperados dentro de um orçamento de tempo?”. Exemplo prático:

  • se cycle_count não aumenta por X segundos, algo travou,
  • se retry_count está alto por muito tempo, você está em falha persistente,
  • se o estado está preso em ST_WAIT_IO, o driver não responde.

Você implementa isso com um contador/heartbeat que só é atualizado quando transições saudáveis acontecem. O Supervisor (ou uma task de monitoramento) alimenta o watchdog de hardware somente se os critérios lógicos forem atendidos.

Error Containment / Recovery Block

Separar ST_ERROR e ST_RECOVER é um padrão de contenção de falhas:

  • ST_ERROR: classifica e registra causa, decide estratégia.
  • ST_RECOVER: executa recovery (reset, flush, backoff, reinit).
  • fallback: ST_SAFE.

Isso evita o “espalhamento de tratamento de erro” pelo código todo (cada função tratando de um jeito).

Como isso vira uma arquitetura real (sem “explodir” em complexidade)

Uma forma comum de escalar:

  • Cada periférico crítico vira um Gatekeeper (UART_GK, I2C_GK, NET_GK).
  • Cada subsistema vira um Active Object com FSM (PROTO_SM, SENSOR_SM, MOTOR_SM).
  • Um Supervisor coordena modos globais.
  • Um Monitor implementa watchdog lógico e métricas.

E o FreeRTOS vira o “tecido conjuntivo”: filas e notificações, com prioridades bem definidas.

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