Skip to content

FSM Driver

Overview

The FSM driver provides generic state machine management. Implementation requires three components:

  1. Decision function: evaluates current state and event snapshot data to determine state transitions.
  2. Event snapshot builder: aggregates hardware, timer, and communication flags into a state snapshot before evaluation.
  3. State configuration table: defines entry, exit, and action callback pointers per state.

During each cycle (FSM_step):

  1. Snapshot builder updates event data.
  2. Decision function evaluates next state.
  3. If state change occurs: executes current state exit callback, updates active state tracking, and executes new state entry callback.
  4. Executes active state action callback every step.

Cross-FSM requests are supported via FSM_request_mode_change(). An FSM can request state transitions on a target FSM, evaluated on the target FSM's next step.


State and Flow Diagram

Click to expand FSM cycle diagram

API Reference

void FSM_init(driver, decide_fn, build_snapshot_fn, state_configs, num_states, initial_state, event_snapshot)

Initializes the FSM instance. event_snapshot expects a pointer to application snapshot memory (cast to FSM_Event_Snapshot_t).

uint8_t FSM_step(driver)

Executes single FSM step: updates events, evaluates transitions, runs entry/exit callbacks when state changes occur, and calls active state action callback. Returns 1 on transition, 0 otherwise.

FSM_State_t FSM_get_current_state(driver)

Returns active FSM state.

FSM_Reason_t FSM_get_last_reason(driver)

Returns reason code associated with the most recent transition.

uint32_t FSM_get_transition_count(driver)

Returns total transition count since initialization.

void FSM_set_fault_latch(driver) / uint8_t FSM_is_fault_latched(driver) / void FSM_reset_fault_latch(driver)

Manages fault latch flag. Once latched, flag remains active until explicitly cleared via FSM_reset_fault_latch().

void FSM_request_bootloader(driver)

Sets bootloader request flag on tracking structure.

void FSM_request_mode_change(driver, requested_mode, reason)

Queues an external transition request to requested_mode with reason code.


Integration Pattern

FSM implementations utilize two source files per state machine:

FileContents
board_fsm.cState enum, snapshot struct, decision function, snapshot builder, init routine
board_fsm_actions.cEntry, exit, and action callback implementations

FSM Configuration Example

c
typedef enum {
    STATE_IDLE,
    STATE_ACTIVE,
    STATE_FAULT,
    STATE_COUNT
} My_State_t;

typedef struct {
    uint8_t sensor_ready;
    uint8_t fault_detected;
} My_Event_Snapshot_t;

static FSM_State_Config_t state_configs[STATE_COUNT] = {
    [STATE_IDLE]   = { idle_entry,   NULL, idle_action   },
    [STATE_ACTIVE] = { active_entry, NULL, active_action },
    [STATE_FAULT]  = { fault_entry,  NULL, fault_action  },
};

static FSM_State_t decide(FSM_State_t current, const FSM_Event_Snapshot_t *events, FSM_Reason_t *reason) {
    My_Event_Snapshot_t *snap = (My_Event_Snapshot_t *)(*events);

    if (snap->fault_detected) {
        *reason = REASON_FAULT;
        return STATE_FAULT;
    }
    if (current == STATE_IDLE && snap->sensor_ready) {
        *reason = REASON_SENSOR_READY;
        return STATE_ACTIVE;
    }
    return current;
}

static My_Event_Snapshot_t snapshot;
FSM_Driver_t my_fsm;

void my_fsm_init(void) {
    FSM_init(&my_fsm, decide, build_events, state_configs,
             STATE_COUNT, STATE_IDLE, (FSM_Event_Snapshot_t)&snapshot);
}

// Main loop step:
FSM_step(&my_fsm);

Example: CAN-Gateway FSM

Single FSM implementation cycling through operational modes:

INIT -> IDLE -> PROCESS_SENSORS -> OUTPUT_SENSORS -> IDLE
CAN-Gateway decision function
c
FSM_State_t board_fsm_decide_mode(FSM_State_t current_mode,
    const FSM_Event_Snapshot_t *events, FSM_Reason_t *reason)
{
    if (FSM_is_fault_latched(&board_fsm_driver))
        return BOARD_FSM_MODE_FAULT;

    const Board_FSM_Event_Snapshot_t *snap = (const Board_FSM_Event_Snapshot_t *)(*events);

    if (snap->bootloader_requested) {
        *reason = BOARD_FSM_REASON_BOOTLOADER_REQUEST;
        return BOARD_FSM_MODE_BOOTLOADER;
    }

    switch ((Board_FSM_Mode_t)current_mode) {
        case BOARD_FSM_MODE_INIT:
            *reason = BOARD_FSM_REASON_INIT_COMPLETE;
            return BOARD_FSM_MODE_IDLE;

        case BOARD_FSM_MODE_IDLE:
            if (snap->adc_ready) {
                *reason = BOARD_FSM_REASON_ADC_READY;
                return BOARD_FSM_MODE_PROCESS_SENSORS;
            }
            return BOARD_FSM_MODE_IDLE;

        case BOARD_FSM_MODE_PROCESS_SENSORS:
            *reason = BOARD_FSM_REASON_PROCESS_SENSORS_DONE;
            return BOARD_FSM_MODE_OUTPUT_SENSORS;

        case BOARD_FSM_MODE_OUTPUT_SENSORS:
            *reason = BOARD_FSM_REASON_OUTPUT_SENSORS_COMPLETE;
            return BOARD_FSM_MODE_IDLE;

        default:
            return BOARD_FSM_MODE_FAULT;
    }
}

Example: ECU Multi-FSM Architecture

The ECU uses sub-FSM controllers managed by a master ECU state machine:

ECU FSM:  IDLE -> R2D -> CONTROL -> SHUTDOWN
                  |        |
                  v        v
            R2D FSM    Control FSM    Shutdown FSM

Master FSM entry callbacks request state changes on child FSM instances:

c
// Master ECU FSM entry action for R2D state
void ecu_fsm_state_r2d_entry(FSM_State_t state) {
    (void)state;
    ecu_send_software_frame();
    FSM_request_mode_change(&r2d_fsm_driver, SYSTEM_R2D_FIRST_BTN, R2D_FSM_REASON_NONE);
}

// Master ECU FSM decision function
static FSM_State_t ecu_fsm_decide(FSM_State_t current_state,
    const FSM_Event_Snapshot_t *events, FSM_Reason_t *reason)
{
    ECU_FSM_Event_Snapshot_t *snap = (ECU_FSM_Event_Snapshot_t *)(*events);
    FSM_State_Tracking_t *tracking = &ecu_fsm_driver.tracking;

    if (snap->can_sdc_fault_requested) {
        *reason = ECU_FSM_REASON_FAULT_CAN_SDC;
        FSM_request_mode_change(&shutdown_fsm_driver,
            SYSTEM_SHUTDOWN_START, SHUTDOWN_FSM_REASON_SDC_FAULT);
        return SYSTEM_SHUTDOWN;
    }

    if (tracking->mode_change_requested) {
        *reason = tracking->mode_change_reason;
        tracking->mode_change_requested = 0U;
        return tracking->mode_change_state;
    }

    return current_state;
}

Main Loop Execution

c
r2d_fsm_init();
control_fsm_init();
shutdown_fsm_init();
ecu_fsm_init();

while (1) {
    process_can_frames(&can_driver);
    set_can_frames(&can_driver);
    service_can_tx();

    FSM_step(&ecu_fsm_driver);
}

Released under the MIT License.