Estrutura do projeto FreeRTOS: tipos, filas e as duas tasks
A partir daqui a gente começa a “travar o concreto”: definir o contrato de mensagens entre as threads e o esqueleto das tasks. O desenho é simples e robusto:
- Task 1 (SM Task): dona da máquina de estados. Ela tem uma fila de entrada (
smQueue) e processa eventos em run-to-completion. - Task 2 (IO Task): simula/representa o mundo externo (sensor, UART, SPI…). Ela recebe requisições por outra fila (
ioQueue) e responde para a SM com eventos (EVT_IO_DONE/EVT_IO_FAIL).
Isso implementa, na prática, Active Object + Message Queue, e evita que o fluxo de estado dependa de chamadas bloqueantes.
Abaixo está um código completo (exemplo didático) apenas com a infraestrutura. Na próxima seção a gente preenche os handlers e a política de recuperação.
/**
* Exemplo didático: Máquina de Estados (FSM) com FreeRTOS usando duas tasks.
*
* - StateMachineTask (SM): recebe eventos, executa transições e governa o ciclo.
* - IOTask (IO): processa requisições de I/O e responde via eventos.
*
* Padrões aplicados:
* - Active Object: cada "objeto" (FSM e IO) roda em sua própria task e consome filas.
* - Message Queue: comunicação por mensagens (eventos e requisições) evita acoplamento direto.
* - Recovery Block: estados explícitos de erro e recuperação.
*
* Observação:
* Este exemplo usa "simulação" de IO. Em firmware real, a IOTask chamaria drivers,
* faria DMA, aguardaria interrupção, etc., e sinalizaria resultado por mensagem.
*/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdint.h>
#include <stdbool.h>
/* =========================
* Configurações do exemplo
* ========================= */
#define SM_QUEUE_LEN 16
#define IO_QUEUE_LEN 16
#define SM_EVENT_WAIT_MS 10 /* Quanto a SM bloqueia esperando eventos */
#define IO_REQ_WAIT_MS 50 /* IO task espera requisição */
#define IO_SIM_LATENCY_MS 15 /* Latência simulada do "hardware" */
#define IO_SIM_FAIL_EVERY_N 5 /* Falha simulada: a cada N operações, falha 1 */
/* Timeouts do ciclo */
#define IO_DEADLINE_MS 50 /* Deadline para receber resposta do IO */
#define MAX_RETRIES 3 /* Máximo de tentativas antes de entrar em SAFE */
/* =========================
* Tipos (Eventos e Estados)
* ========================= */
/**
* @brief Tipos de eventos consumidos pela FSM.
*/
typedef enum {
EVT_NONE = 0,
EVT_START,
EVT_TICK,
EVT_IO_DONE,
EVT_IO_FAIL,
EVT_TIMEOUT,
EVT_RESET_LOGIC,
EVT_FAULT_CLEAR
} SmEventType;
/**
* @brief Estados da FSM (máquina cíclica e recuperável).
*/
typedef enum {
ST_BOOT = 0,
ST_INIT,
ST_IDLE,
ST_CYCLE_START,
ST_WAIT_IO,
ST_PROCESS,
ST_ERROR,
ST_RECOVER,
ST_SAFE
} SmState;
/**
* @brief "Causas" de erro para diagnóstico e decisões de recuperação.
*/
typedef enum {
ERR_NONE = 0,
ERR_TIMEOUT,
ERR_IO_FAIL,
ERR_BAD_DATA
} SmError;
/**
* @brief Payload do evento da FSM.
* Em sistemas reais, aqui você coloca dados vindos do driver (buffer, status, etc.).
*/
typedef struct {
SmEventType type;
uint32_t timestamp_ms; /* opcional: "agora" no momento da emissão */
union {
struct {
uint8_t data[8];
uint8_t len;
} io;
struct {
SmError cause;
} fault;
} u;
} SmEvent;
/* =========================
* Requisições para a IOTask
* ========================= */
/**
* @brief Tipos de requisições que a FSM pode pedir para a IOTask.
*/
typedef enum {
IOREQ_NONE = 0,
IOREQ_READ_SAMPLE,
IOREQ_RESET_BUS
} IoReqType;
/**
* @brief Mensagem de requisição para a IO task.
*/
typedef struct {
IoReqType type;
uint32_t req_id; /* ID para correlacionar request/response */
} IoRequest;
/* =========================
* Contexto da FSM
* ========================= */
/**
* @brief Contexto interno da FSM.
* Mantém estado atual, contadores e dados para decisões.
*/
typedef struct {
SmState st;
SmError last_error;
uint32_t cycle_count;
uint32_t retry_count;
uint32_t pending_req_id;
TickType_t io_deadline_tick; /* tick em que o WAIT_IO expira */
} SmContext;
/* =========================
* Filas globais (exemplo)
* ========================= */
static QueueHandle_t smQueue = NULL; /* eventos para a StateMachineTask */
static QueueHandle_t ioQueue = NULL; /* requisições para a IOTask */
/* =========================
* Utilitário de tempo
* ========================= */
static inline uint32_t now_ms(void) {
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
/**
* @brief Envia um evento para a FSM de forma segura.
*/
static bool sm_send_event(const SmEvent *ev, TickType_t to) {
if (!smQueue) return false;
return (xQueueSend(smQueue, ev, to) == pdPASS);
}
/**
* @brief Envia uma requisição para a IO task.
*/
static bool io_send_req(const IoRequest *req, TickType_t to) {
if (!ioQueue) return false;
return (xQueueSend(ioQueue, req, to) == pdPASS);
}
/* =========================
* Protótipos das tasks
* ========================= */
static void StateMachineTask(void *arg);
static void IOTask(void *arg);
/* =========================
* Setup: criação das tasks
* ========================= */
void app_start(void) {
smQueue = xQueueCreate(SM_QUEUE_LEN, sizeof(SmEvent));
ioQueue = xQueueCreate(IO_QUEUE_LEN, sizeof(IoRequest));
/* Em firmware real, valide retorno e trate falta de heap. */
xTaskCreate(StateMachineTask, "SM", 1024, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(IOTask, "IO", 1024, NULL, tskIDLE_PRIORITY + 1, NULL);
}
/* =========================================================
* Task 2: IO Task (simulação de mundo externo / periférico)
* ========================================================= */
static void IOTask(void *arg) {
(void)arg;
IoRequest req;
uint32_t op_count = 0;
for (;;) {
if (xQueueReceive(ioQueue, &req, pdMS_TO_TICKS(IO_REQ_WAIT_MS)) == pdPASS) {
op_count++;
/* Simula latência do hardware */
vTaskDelay(pdMS_TO_TICKS(IO_SIM_LATENCY_MS));
SmEvent ev = {0};
ev.timestamp_ms = now_ms();
/* Falha simulada periódica */
bool fail = (IO_SIM_FAIL_EVERY_N > 0) && ((op_count % IO_SIM_FAIL_EVERY_N) == 0);
if (req.type == IOREQ_READ_SAMPLE && !fail) {
ev.type = EVT_IO_DONE;
ev.u.io.len = 3;
ev.u.io.data[0] = 0xAA;
ev.u.io.data[1] = (uint8_t)(req.req_id & 0xFF);
ev.u.io.data[2] = 0x55;
} else {
ev.type = EVT_IO_FAIL;
ev.u.fault.cause = ERR_IO_FAIL;
}
(void)sm_send_event(&ev, 0);
}
/* A IO task poderia também emitir EVT_TICK/telemetria, mas aqui deixamos simples. */
}
}
/* =========================================================
* Task 1: State Machine Task (a dona da FSM)
* ========================================================= */
static void StateMachineTask(void *arg) {
(void)arg;
SmContext ctx = {
.st = ST_BOOT,
.last_error = ERR_NONE,
.cycle_count = 0,
.retry_count = 0,
.pending_req_id = 0,
.io_deadline_tick = 0
};
/* Evento inicial: start */
SmEvent start = {.type = EVT_START, .timestamp_ms = now_ms()};
(void)sm_send_event(&start, 0);
for (;;) {
SmEvent ev = {0};
/* A FSM bloqueia pouco tempo esperando eventos (event-driven). */
if (xQueueReceive(smQueue, &ev, pdMS_TO_TICKS(SM_EVENT_WAIT_MS)) != pdPASS) {
/* Sem evento -> podemos gerar EVT_TICK ou checar timeout interno. */
ev.type = EVT_TICK;
ev.timestamp_ms = now_ms();
}
/* Aqui entra o "motor" da FSM (switch de estado + tratamento). */
/* Nesta seção deixamos como esqueleto; a próxima seção implementa. */
(void)ctx;
(void)ev;
/* placeholder: o motor de transições vem na próxima seção */
}
}
Até aqui você já tem a arquitetura correta: duas tasks, duas filas, mensagens tipadas, e um contexto interno da FSM (onde vamos guardar deadline, contadores e estado). O ponto crítico é que a FSM é “dona” do comportamento: ela decide quando pedir I/O, quanto esperar, quando declarar timeout e como recuperar.