Exemplo consolidado (mais “real”): correlação req_id, entry-actions, e ISR → fila
A seguir vai uma versão consolidada e coerente do exemplo. Ainda é didática, mas já está com cara de firmware de produção: FSM como Active Object, I/O isolado, correlação de request/response, ações de entrada e um ponto mostrando como um ISR faria o envio de evento (sem reentrância).
Observação: eu mantenho a IO task “simulada” para o exemplo ser autocontido. Em um produto, a IO task seria a dona de UART/SPI/I2C/DMA e/ou receberia eventos de interrupção.
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdint.h>
#include <stdbool.h>
/* ========= Config ========= */
#define SM_QUEUE_LEN 16
#define IO_QUEUE_LEN 16
#define SM_EVENT_WAIT_MS 10
#define IO_REQ_WAIT_MS 50
#define IO_SIM_LATENCY_MS 15
#define IO_SIM_FAIL_EVERY_N 5
#define IO_DEADLINE_MS 50
#define MAX_RETRIES 3
/* ========= Tipos ========= */
typedef enum {
EVT_NONE = 0,
EVT_START,
EVT_TICK,
EVT_IO_DONE,
EVT_IO_FAIL,
EVT_TIMEOUT,
EVT_RESET_LOGIC,
EVT_FAULT_CLEAR
} SmEventType;
typedef enum {
ST_BOOT = 0,
ST_INIT,
ST_IDLE,
ST_CYCLE_START,
ST_WAIT_IO,
ST_PROCESS,
ST_ERROR,
ST_RECOVER,
ST_SAFE
} SmState;
typedef enum {
ERR_NONE = 0,
ERR_TIMEOUT,
ERR_IO_FAIL,
ERR_BAD_DATA,
ERR_QUEUE_FULL
} SmError;
typedef struct {
SmEventType type;
uint32_t timestamp_ms;
union {
struct {
uint32_t req_id;
uint8_t data[8];
uint8_t len;
} io;
struct {
SmError cause;
} fault;
} u;
} SmEvent;
typedef enum {
IOREQ_NONE = 0,
IOREQ_READ_SAMPLE,
IOREQ_RESET_BUS
} IoReqType;
typedef struct {
IoReqType type;
uint32_t req_id;
} IoRequest;
/* ========= Contexto FSM ========= */
typedef struct {
SmState st;
SmState prev;
SmError last_error;
uint32_t cycle_count;
uint32_t retry_count;
uint32_t pending_req_id;
TickType_t io_deadline_tick;
} SmContext;
/* ========= Filas ========= */
static QueueHandle_t smQueue = NULL;
static QueueHandle_t ioQueue = NULL;
/* ========= Util ========= */
static inline uint32_t now_ms(void) {
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
static bool sm_send_event(const SmEvent *ev, TickType_t to) {
return (smQueue && xQueueSend(smQueue, ev, to) == pdPASS);
}
static bool io_send_req(const IoRequest *req, TickType_t to) {
return (ioQueue && xQueueSend(ioQueue, req, to) == pdPASS);
}
/* ========= ISR-friendly (exemplo) =========
* Em um driver real, um ISR poderia postar o evento assim:
*/
static bool sm_send_event_from_isr(const SmEvent *ev, BaseType_t *hpw) {
return (smQueue && xQueueSendFromISR(smQueue, ev, hpw) == pdPASS);
}
/* ========= Helpers FSM ========= */
static void sm_enter(SmContext *ctx, SmState next) { ctx->st = next; }
static void sm_arm_deadline(SmContext *ctx, uint32_t ms) {
ctx->io_deadline_tick = xTaskGetTickCount() + pdMS_TO_TICKS(ms);
}
static bool sm_deadline_expired(const SmContext *ctx) {
if (ctx->io_deadline_tick == 0) return false;
TickType_t now = xTaskGetTickCount();
return ((int32_t)(now - ctx->io_deadline_tick) >= 0);
}
static void sm_backoff(uint32_t retry) {
uint32_t ms = 20u << (retry > 4 ? 4 : retry);
vTaskDelay(pdMS_TO_TICKS(ms));
}
static bool sm_validate_payload(const SmEvent *ev) {
if (ev->type != EVT_IO_DONE) return false;
if (ev->u.io.len < 3) return false;
if (ev->u.io.data[0] != 0xAA) return false;
if (ev->u.io.data[ev->u.io.len - 1] != 0x55) return false;
return true;
}
static void sm_raise(SmContext *ctx, SmError err) {
ctx->last_error = err;
sm_enter(ctx, ST_ERROR);
}
/* ========= Ações de entrada ========= */
static void sm_on_entry(SmContext *ctx) {
switch (ctx->st) {
case ST_BOOT:
ctx->retry_count = 0;
ctx->cycle_count = 0;
ctx->last_error = ERR_NONE;
ctx->io_deadline_tick = 0;
break;
case ST_INIT:
/* init real aconteceria aqui (drivers, buffers, etc.) */
break;
case ST_CYCLE_START: {
ctx->cycle_count++;
/* novo request */
ctx->pending_req_id++;
IoRequest req = {.type = IOREQ_READ_SAMPLE, .req_id = ctx->pending_req_id};
if (!io_send_req(&req, pdMS_TO_TICKS(5))) {
sm_raise(ctx, ERR_QUEUE_FULL);
break;
}
sm_arm_deadline(ctx, IO_DEADLINE_MS);
sm_enter(ctx, ST_WAIT_IO);
break;
}
case ST_RECOVER: {
ctx->retry_count++;
sm_backoff(ctx->retry_count);
/* Em produção: reset seletivo de recurso (flush UART, reset I2C, etc.) */
IoRequest r = {.type = IOREQ_RESET_BUS, .req_id = ctx->pending_req_id + 1};
(void)io_send_req(&r, pdMS_TO_TICKS(5));
sm_enter(ctx, ST_CYCLE_START);
break;
}
default:
break;
}
}
/* ========= Reação a eventos ========= */
static void sm_on_event(SmContext *ctx, SmEvent *ev) {
/* Timeout interno enquanto aguarda I/O */
if (ctx->st == ST_WAIT_IO && sm_deadline_expired(ctx)) {
ev->type = EVT_TIMEOUT;
ev->timestamp_ms = now_ms();
ev->u.fault.cause = ERR_TIMEOUT;
}
switch (ctx->st) {
case ST_BOOT:
if (ev->type == EVT_START || ev->type == EVT_RESET_LOGIC) {
sm_enter(ctx, ST_INIT);
}
break;
case ST_INIT:
if (ev->type == EVT_TICK) {
sm_enter(ctx, ST_IDLE);
}
break;
case ST_IDLE:
/* ciclo contínuo: um tick inicia ciclo */
if (ev->type == EVT_TICK || ev->type == EVT_START) {
sm_enter(ctx, ST_CYCLE_START);
}
break;
case ST_WAIT_IO:
if (ev->type == EVT_IO_DONE) {
/* correlação */
if (ev->u.io.req_id != ctx->pending_req_id) {
/* resposta atrasada -> descarta */
break;
}
ctx->io_deadline_tick = 0;
sm_enter(ctx, ST_PROCESS);
} else if (ev->type == EVT_IO_FAIL) {
if (ev->u.io.req_id != ctx->pending_req_id) break;
ctx->io_deadline_tick = 0;
sm_raise(ctx, ERR_IO_FAIL);
} else if (ev->type == EVT_TIMEOUT) {
ctx->io_deadline_tick = 0;
sm_raise(ctx, ERR_TIMEOUT);
}
break;
case ST_PROCESS:
/* Processamos o mesmo evento que nos trouxe aqui (EVT_IO_DONE) */
if (ev->type == EVT_IO_DONE && ev->u.io.req_id == ctx->pending_req_id) {
if (!sm_validate_payload(ev)) {
sm_raise(ctx, ERR_BAD_DATA);
break;
}
/* sucesso */
ctx->retry_count = 0;
sm_enter(ctx, ST_IDLE);
} else {
sm_enter(ctx, ST_IDLE);
}
break;
case ST_ERROR:
if (ctx->retry_count < MAX_RETRIES) sm_enter(ctx, ST_RECOVER);
else sm_enter(ctx, ST_SAFE);
break;
case ST_SAFE:
if (ev->type == EVT_FAULT_CLEAR || ev->type == EVT_RESET_LOGIC) {
sm_enter(ctx, ST_BOOT);
}
break;
default:
sm_enter(ctx, ST_SAFE);
break;
}
}
/* ========= Tasks ========= */
static void StateMachineTask(void *arg) {
(void)arg;
SmContext ctx = {
.st = ST_BOOT,
.prev = (SmState)0xFF,
.last_error = ERR_NONE,
.cycle_count = 0,
.retry_count = 0,
.pending_req_id = 0,
.io_deadline_tick = 0
};
/* start event */
SmEvent start = {.type = EVT_START, .timestamp_ms = now_ms()};
(void)sm_send_event(&start, 0);
for (;;) {
SmEvent ev = {0};
if (xQueueReceive(smQueue, &ev, pdMS_TO_TICKS(SM_EVENT_WAIT_MS)) != pdPASS) {
ev.type = EVT_TICK;
ev.timestamp_ms = now_ms();
}
if (ctx.prev != ctx.st) {
sm_on_entry(&ctx);
ctx.prev = ctx.st;
}
sm_on_event(&ctx, &ev);
}
}
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++;
vTaskDelay(pdMS_TO_TICKS(IO_SIM_LATENCY_MS));
SmEvent ev = {0};
ev.timestamp_ms = now_ms();
ev.u.io.req_id = req.req_id;
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;
/* reaproveita u.fault em vez de u.io se quiser; aqui mantemos req_id */
}
(void)sm_send_event(&ev, 0);
}
}
}
/* ========= Start ========= */
void app_start(void) {
smQueue = xQueueCreate(SM_QUEUE_LEN, sizeof(SmEvent));
ioQueue = xQueueCreate(IO_QUEUE_LEN, sizeof(IoRequest));
xTaskCreate(StateMachineTask, "SM", 1024, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(IOTask, "IO", 1024, NULL, tskIDLE_PRIORITY + 1, NULL);
}
O que esse “consolidado” resolve que o exemplo anterior não garantia tão bem
- Entry-actions:
ST_CYCLE_STARTeST_RECOVERfazem ações de entrada previsíveis, sem depender de eventos “certos”. - Resposta atrasada:
req_idimpede consumir evento velho como se fosse novo. - Sem reentrância: ISR (ou callback) manda evento para fila; a FSM processa em task.
- Recuperação controlada: backoff + retentativa limitada + safe state.