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


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_START e ST_RECOVER fazem ações de entrada previsíveis, sem depender de eventos “certos”.
  • Resposta atrasada: req_id impede 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.
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