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


Motor da FSM: transições, deadlines, retentativas e estado seguro

Agora a gente completa a parte principal: o “motor” que faz a FSM funcionar. Vou manter em C puro, com código legível e bem comentado, e já embutindo a lógica de ciclo (rodar continuamente) e recuperação (timeout → erro → retentativa → safe).

A estratégia:

  1. Cada iteração a SM recebe um evento (ou cria um EVT_TICK se não houver).
  2. Se estivermos em ST_WAIT_IO, a SM checa se o deadline expirou. Se sim, cria um EVT_TIMEOUT.
  3. O switch(ctx.st) processa o evento e decide próximo estado.
  4. Transições que precisam “fazer algo” (ex.: enviar IOREQ, armar deadline) fazem isso explicitamente.

Abaixo vai o bloco que substitui o “placeholder” dentro de StateMachineTask().

/* =========================
 *  Helpers de transição
 * ========================= */

/**
 * @brief Entra em um novo estado (ponto único para ações de entrada).
 * Em firmware real você pode registrar log, métricas, etc.
 */
static void sm_enter_state(SmContext *ctx, SmState next) {
    ctx->st = next;
}

/**
 * @brief Arma um deadline para o estado ST_WAIT_IO (timeout relativo).
 */
static void sm_arm_io_deadline(SmContext *ctx, uint32_t timeout_ms) {
    TickType_t now = xTaskGetTickCount();
    ctx->io_deadline_tick = now + pdMS_TO_TICKS(timeout_ms);
}

/**
 * @brief Verifica se o deadline de I/O expirou.
 */
static bool sm_io_deadline_expired(const SmContext *ctx) {
    if (ctx->io_deadline_tick == 0) return false;
    /* Comparação segura para overflow de ticks: diferença em signed */
    TickType_t now = xTaskGetTickCount();
    return ((int32_t)(now - ctx->io_deadline_tick) >= 0);
}

/**
 * @brief Envia uma requisição de leitura para a IO task e atualiza o contexto.
 */
static void sm_request_io_read(SmContext *ctx) {
    ctx->pending_req_id++;

    IoRequest req = {
        .type = IOREQ_READ_SAMPLE,
        .req_id = ctx->pending_req_id
    };

    /* Em produção, trate falha de fila cheia como erro recuperável também. */
    (void)io_send_req(&req, pdMS_TO_TICKS(5));

    /* Arma deadline para aguardar resposta */
    sm_arm_io_deadline(ctx, IO_DEADLINE_MS);
}

/**
 * @brief Faz checagens de sanidade nos dados recebidos.
 * Retorna false se os dados forem incoerentes.
 */
static bool sm_validate_io_payload(const SmEvent *ev) {
    if (ev->type != EVT_IO_DONE) return false;
    if (ev->u.io.len < 3) return false;

    /* Exemplo didático de framing: AA ... 55 */
    if (ev->u.io.data[0] != 0xAA) return false;
    if (ev->u.io.data[ev->u.io.len - 1] != 0x55) return false;

    return true;
}

/**
 * @brief Emite erro no contexto e vai para ST_ERROR.
 */
static void sm_raise_error(SmContext *ctx, SmError err) {
    ctx->last_error = err;
    sm_enter_state(ctx, ST_ERROR);
}

/* =========================
 *  (Opcional) Backoff simples
 * ========================= */
static void sm_backoff_delay(uint32_t retry_count) {
    /* 20ms, 40ms, 80ms... limitado para não “sumir” do sistema */
    uint32_t ms = 20u << (retry_count > 4 ? 4 : retry_count);
    vTaskDelay(pdMS_TO_TICKS(ms));
}

/* =========================================================
 *  Motor da FSM (coloque dentro de StateMachineTask loop)
 * ========================================================= */
static void sm_step(SmContext *ctx, SmEvent *ev) {
    /* Timeout interno: se estamos esperando IO e expirou, substitui evento por TIMEOUT */
    if (ctx->st == ST_WAIT_IO && sm_io_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) {
            ctx->retry_count = 0;
            ctx->cycle_count = 0;
            ctx->last_error = ERR_NONE;
            ctx->io_deadline_tick = 0;
            sm_enter_state(ctx, ST_INIT);
        }
        break;

    case ST_INIT:
        /* Aqui seria inicialização real de periféricos, buffers, etc. */
        if (ev->type == EVT_TICK) {
            /* Sucesso na init (simulado) */
            sm_enter_state(ctx, ST_IDLE);
        }
        break;

    case ST_IDLE:
        if (ev->type == EVT_START || ev->type == EVT_TICK) {
            /* Neste exemplo, a operação é cíclica: a cada tick, inicia um ciclo. */
            sm_enter_state(ctx, ST_CYCLE_START);
        }
        break;

    case ST_CYCLE_START:
        /* Ação de entrada do ciclo: incrementa contagem e pede IO */
        ctx->cycle_count++;
        sm_request_io_read(ctx);
        sm_enter_state(ctx, ST_WAIT_IO);
        break;

    case ST_WAIT_IO:
        if (ev->type == EVT_IO_DONE) {
            /* Recebemos resposta: desarma deadline */
            ctx->io_deadline_tick = 0;

            /* Vai para PROCESS para validar e usar dados */
            sm_enter_state(ctx, ST_PROCESS);
        } else if (ev->type == EVT_IO_FAIL) {
            ctx->io_deadline_tick = 0;
            sm_raise_error(ctx, ERR_IO_FAIL);
        } else if (ev->type == EVT_TIMEOUT) {
            /* Timeout interno declarando falha */
            ctx->io_deadline_tick = 0;
            sm_raise_error(ctx, ERR_TIMEOUT);
        } else {
            /* Ignora outros eventos enquanto espera */
        }
        break;

    case ST_PROCESS:
        if (ev->type == EVT_IO_DONE) {
            /* PROCESS espera o IO_DONE “que nos trouxe aqui”.
             * Mas perceba: quando mudamos para ST_PROCESS,
             * o evento atual ainda é EVT_IO_DONE, então podemos processar já.
             */
            if (!sm_validate_io_payload(ev)) {
                sm_raise_error(ctx, ERR_BAD_DATA);
                break;
            }

            /* Sucesso: zera retentativas e volta ao ciclo (ou IDLE) */
            ctx->retry_count = 0;

            /* Ciclo contínuo: volta para IDLE (que por tick vai iniciar novo ciclo) */
            sm_enter_state(ctx, ST_IDLE);
        } else {
            /* Se por algum motivo chegar evento inesperado, mantenha previsível */
            sm_enter_state(ctx, ST_IDLE);
        }
        break;

    case ST_ERROR:
        /* Centraliza a decisão: retentar? reinit? safe? */
        if (ctx->retry_count < MAX_RETRIES) {
            sm_enter_state(ctx, ST_RECOVER);
        } else {
            sm_enter_state(ctx, ST_SAFE);
        }
        break;

    case ST_RECOVER:
        /* Recuperação: pode ser reset de barramento, reinit parcial, etc. */
        ctx->retry_count++;

        /* Backoff para não “martelar” o periférico */
        sm_backoff_delay(ctx->retry_count);

        /* Exemplo: pedir para IO resetar o “bus” (simulado) */
        {
            IoRequest r = {.type = IOREQ_RESET_BUS, .req_id = ctx->pending_req_id + 1};
            (void)io_send_req(&r, pdMS_TO_TICKS(5));
        }

        /* Depois da recuperação, tenta novamente um ciclo */
        sm_enter_state(ctx, ST_CYCLE_START);
        break;

    case ST_SAFE:
        /* Estado seguro: não opera normalmente. Aguarda intervenção/condição. */
        if (ev->type == EVT_FAULT_CLEAR || ev->type == EVT_RESET_LOGIC) {
            sm_enter_state(ctx, ST_BOOT);
        }
        break;

    default:
        /* Estado inválido -> falha controlada */
        sm_enter_state(ctx, ST_SAFE);
        break;
    }
}

E agora você só precisa chamar sm_step(&ctx, &ev); dentro do loop da StateMachineTask(), substituindo o placeholder. Fica assim:

/* Dentro do loop da StateMachineTask() */
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();
    }

    sm_step(&ctx, &ev);
}

O que foi garantido aqui (na prática)

  • Cíclico: ST_IDLE -> ST_CYCLE_START -> ST_WAIT_IO -> ST_PROCESS -> ST_IDLE ...
  • Recuperável: WAIT_IO expira ou falha → ST_ERRORST_RECOVER (com backoff) → tenta de novo.
  • Falha controlada: após MAX_RETRIES, entra em ST_SAFE.
  • Sem “bloqueio sujo”: a SM não chama IO e fica travada; ela governa por eventos.
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

Guia Completo de Comandos Git: Configuração, Branches, Merge, Rebase, Stash, Reversões e Trabalho com Repositórios RemotosGuia Completo de Comandos Git: Configuração, Branches, Merge, Rebase, Stash, Reversões e Trabalho com Repositórios Remotos

Este guia abrangente apresenta uma explicação didática e detalhada de todos os principais comandos Git, incluindo configuração inicial, criação e gerenciamento de branches, processos de merge e rebase, resolução de

0
Adoraria saber sua opinião, comente.x