Event-driven finite state machine framework with timer support and optional hierarchical states.
Each state machine is a function (SSFSMHandler_t) that receives events via a switch on
SSFSMEventId_t. The framework delivers SSF_SM_EVENT_ENTRY and SSF_SM_EVENT_EXIT
automatically on state transitions triggered by SSFSMTran(). Timers post events after a
configurable delay. One level of state hierarchy is supported: a child handler names its parent
with SSF_SM_SUPER() in its default case, and unhandled events are forwarded automatically.
Dependencies | Notes | Configuration | API Summary | Function Reference | Detailed Example
↑ Dependencies
↑ Notes
SSFSMInit(),SSFSMDeInit(),SSFSMInitHandler(),SSFSMDeInitHandler(), andSSFSMTask()must all be called from the same single thread of execution unlessSSF_CONFIG_ENABLE_THREAD_SUPPORT == 1.SSFSMPutEventData()andSSFSMPutEvent()may be called from any execution context (including ISRs) whenSSF_CONFIG_ENABLE_THREAD_SUPPORT == 1and the OS primitives support it; otherwise they must be called from the same single-threaded context as the other functions.SSFSMTran(),SSFSMStartTimerData(),SSFSMStartTimer(),SSFSMStopTimer(),SSF_SM_SUPER(), andSSF_SM_EVENT_DATA_ALIGN()are only valid when called from within a state handler during event processing.SSF_SM_SUPER()must appear only in thedefaultcase of a child state handler; parent (super) state handlers must not name a further parent.- A state transition automatically stops all running timers for that state machine before
delivering
SSF_SM_EVENT_EXITto the current state andSSF_SM_EVENT_ENTRYto the new state. SSFSMStartTimer()withinterval == 0fires the event at the nextSSFSMTask()call, making it useful as a deferred self-post.SSFSMTask()returnstruewhen there are still pending events to process; call it again immediately in that case rather than waiting on the wake primitive.- In multi-threaded builds,
SSFSMTask()is typically run in a dedicated high-priority thread that blocks usingSSF_SM_THREAD_WAKE_WAIT()between calls. SSFSMList_tandSSFSMEventList_tenumerations are mandatory and must be defined inssfoptions.h.
↑ Configuration
All options are set in ssfoptions.h.
| Option | Default | Description |
|---|---|---|
SSF_SM_MAX_ACTIVE_EVENTS |
3 |
Maximum number of simultaneously queued events across all state machines; increase if events are dropped under peak load |
SSF_SM_MAX_ACTIVE_TIMERS |
3 |
Maximum number of simultaneously running timers across all state machines |
The following enumerations are required in ssfoptions.h:
/* State machine identifiers — one entry per state machine instance */
typedef enum
{
SSF_SM_MIN = -1, /* Required sentinel; must be first */
SSF_SM_MY_APP_1, /* User-defined state machines */
SSF_SM_MY_APP_2,
SSF_SM_MAX /* Required sentinel; must be last */
} SSFSMList_t;
/* Event identifiers — shared across all state machines */
typedef enum
{
SSF_SM_EVENT_MIN = -1, /* Required sentinel; must be first */
SSF_SM_EVENT_ENTRY, /* Required: delivered on state entry */
SSF_SM_EVENT_EXIT, /* Required: delivered on state exit */
SSF_SM_EVENT_SUPER, /* Required: used internally for hierarchy probing */
/* User-defined events follow */
SSF_SM_EVENT_MY_DATA_RX,
SSF_SM_EVENT_MY_TIMER,
SSF_SM_EVENT_MAX /* Required sentinel; must be last */
} SSFSMEventList_t;Thread sync macros (required when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1):
| Macro | Description |
|---|---|
SSF_SM_THREAD_SYNC_DECLARATION |
Declare the mutex object |
SSF_SM_THREAD_SYNC_INIT() |
Initialize the mutex |
SSF_SM_THREAD_SYNC_DEINIT() |
De-initialize the mutex |
SSF_SM_THREAD_SYNC_ACQUIRE() |
Acquire the mutex |
SSF_SM_THREAD_SYNC_RELEASE() |
Release the mutex |
Thread wake macros (required when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1); implement with
a counting semaphore capped at 1:
| Macro | Description |
|---|---|
SSF_SM_THREAD_WAKE_DECLARATION |
Declare the wake semaphore |
SSF_SM_THREAD_WAKE_INIT() |
Initialize the wake semaphore |
SSF_SM_THREAD_WAKE_DEINIT() |
De-initialize the wake semaphore |
SSF_SM_THREAD_WAKE_POST() |
Signal the FSM thread that an event is ready |
SSF_SM_THREAD_WAKE_WAIT(timeout) |
Block the FSM thread until an event arrives or timeout elapses |
↑ API Summary
| Function | Description | |
|---|---|---|
| e.g. | void SSFSMInit(maxEvents, maxTimers) |
Initialize the framework; must be called before any other ssfsm function |
| e.g. | void SSFSMDeInit() |
De-initialize the framework and release all resources |
| e.g. | void SSFSMInitHandler(smid, initial) |
Register a state machine and deliver SSF_SM_EVENT_ENTRY to the initial state |
| e.g. | void SSFSMDeInitHandler(smid) |
Unregister a state machine |
| e.g. | bool SSFSMTask(nextTimeout) |
Process all pending events and expired timers; returns time until next timer |
| e.g. | void SSFSMPutEventData(smid, eid, data, dataLen) |
Post an event with a data payload to a state machine |
| e.g. | void SSFSMPutEvent(smid, eid) |
Post an event without data (expands to SSFSMPutEventData with NULL/0) |
| e.g. | void SSFSMTran(next) |
Trigger a state transition; valid only inside a state handler |
| e.g. | void SSFSMStartTimerData(eid, interval, data, dataLen) |
Start a timer that posts an event with data; valid only inside a state handler |
| e.g. | void SSFSMStartTimer(eid, to) |
Start a timer without data (expands to SSFSMStartTimerData with NULL/0) |
| e.g. | void SSFSMStopTimer(eid) |
Cancel a running timer; valid only inside a state handler |
| e.g. | void SSF_SM_SUPER(super) |
Name the parent state handler; place in the default case of a child handler |
| e.g. | void SSF_SM_EVENT_DATA_ALIGN(v) |
Copy event data into a typed variable; valid only inside a state handler |
↑ Function Reference
void SSFSMInit(uint32_t maxEvents, uint32_t maxTimers);Initializes the framework, allocating the internal event queue and timer table. Must be called
once before SSFSMInitHandler(), SSFSMTask(), or any event-posting function. Pass values
that match SSF_SM_MAX_ACTIVE_EVENTS and SSF_SM_MAX_ACTIVE_TIMERS from ssfoptions.h.
| Parameter | Direction | Type | Description |
|---|---|---|---|
maxEvents |
in | uint32_t |
Maximum simultaneously queued events. Must equal SSF_SM_MAX_ACTIVE_EVENTS. |
maxTimers |
in | uint32_t |
Maximum simultaneously running timers. Must equal SSF_SM_MAX_ACTIVE_TIMERS. |
Returns: Nothing.
Example:
/* Initialize framework — values must match ssfoptions.h constants */
SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* Framework ready; call SSFSMInitHandler() for each state machine */void SSFSMDeInit(void);De-initializes the framework and frees internal resources. After this call, no other ssfsm
function may be used until SSFSMInit() is called again.
Returns: Nothing.
Example:
SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* ... initialize handlers and run ... */
SSFSMDeInit();
/* Internal resources freed; SSFSMInit() required before further use */void SSFSMInitHandler(SSFSMId_t smid, SSFSMHandler_t initial);Registers a state machine identified by smid and sets initial as the current state.
Immediately delivers SSF_SM_EVENT_ENTRY to initial. If initial has a parent state
(established through SSF_SM_SUPER()), SSF_SM_EVENT_ENTRY is delivered to the parent first,
then to initial.
| Parameter | Direction | Type | Description |
|---|---|---|---|
smid |
in | SSFSMId_t |
State machine identifier from SSFSMList_t. Must be between SSF_SM_MIN+1 and SSF_SM_MAX-1. |
initial |
in | SSFSMHandler_t |
Pointer to the initial state handler function. Must not be NULL. |
Returns: Nothing.
Example:
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* Register state machine; SSF_SM_EVENT_ENTRY is delivered to IdleHandler() immediately */
SSFSMInitHandler(SSF_SM_STATUS_LED, IdleHandler);
/* Superloop */
while (true)
{
SSFSMTask(NULL);
}void SSFSMDeInitHandler(SSFSMId_t smid);Unregisters the state machine identified by smid and cancels all its pending timers and
events. After this call the slot may be re-registered with SSFSMInitHandler().
| Parameter | Direction | Type | Description |
|---|---|---|---|
smid |
in | SSFSMId_t |
State machine identifier to unregister. |
Returns: Nothing.
Example:
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
SSFSMInitHandler(SSF_SM_STATUS_LED, IdleHandler);
/* ... */
SSFSMDeInitHandler(SSF_SM_STATUS_LED);
/* State machine unregistered; pending timers and events cancelled */bool SSFSMTask(SSFSMTimeout_t *nextTimeout);Processes all pending events and fires any expired timers by invoking the appropriate state handlers. Must be called repeatedly from the framework's execution context.
| Parameter | Direction | Type | Description |
|---|---|---|---|
nextTimeout |
out | SSFSMTimeout_t * |
Receives the number of system ticks until the next timer expires, or SSF_SM_MAX_TIMEOUT if no timers are running. Pass NULL in single-threaded superloop builds. |
Returns: true if there are additional pending events that should be processed immediately
(call SSFSMTask() again without waiting); false when no more events are queued and the
caller may safely block for up to *nextTimeout ticks.
Example:
/* Single-threaded superloop */
while (true)
{
SSFSMTask(NULL);
}
/* Multi-threaded: FSM thread blocks between calls using the wake primitive */
SSFSMTimeout_t to;
while (true)
{
if (SSFSMTask(&to) == false)
{
/* No more pending events — block until next timer or SSFSMPutEvent() wakes us */
SSF_SM_THREAD_WAKE_WAIT(to);
}
}void SSFSMPutEventData(SSFSMId_t smid, SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen);Enqueues an event for state machine smid. If data is non-NULL, up to dataLen bytes are
copied into the event queue entry and delivered to the handler as the data/dataLen
parameters. May be called from any context when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1.
| Parameter | Direction | Type | Description |
|---|---|---|---|
smid |
in | SSFSMId_t |
Target state machine identifier. |
eid |
in | SSFSMEventId_t |
Event identifier from SSFSMEventList_t. Must not be SSF_SM_EVENT_ENTRY, SSF_SM_EVENT_EXIT, or SSF_SM_EVENT_SUPER. |
data |
in | const SSFSMData_t * |
Pointer to the event data payload. Pass NULL when there is no data. |
dataLen |
in | SSFSMDataLen_t |
Number of bytes of event data. Must be 0 when data is NULL. |
Returns: Nothing.
Example:
/* Post an event carrying a 16-bit received-length payload */
uint16_t rxLen = 42u;
SSFSMPutEventData(SSF_SM_STATUS_LED, SSF_SM_EVENT_RX_DATA,
(SSFSMData_t *)&rxLen, (SSFSMDataLen_t)sizeof(rxLen));#define SSFSMPutEvent(smid, eid) SSFSMPutEventData(smid, eid, NULL, 0)Convenience macro for posting an event without a data payload. Expands to
SSFSMPutEventData() with NULL and 0 for the data parameters. All
threading constraints of SSFSMPutEventData() apply.
Example:
/* Post an event without a data payload */
SSFSMPutEvent(SSF_SM_STATUS_LED, SSF_SM_EVENT_RX_DATA);void SSFSMTran(SSFSMHandler_t next);Requests a transition to the state handler next. Valid only when called from within a state
handler during event processing. The framework delivers SSF_SM_EVENT_EXIT to the current
state (and its parent if applicable), stops all running timers for the machine, then delivers
SSF_SM_EVENT_ENTRY to next (and its parent if applicable). Must not be called from within
the SSF_SM_EVENT_ENTRY or SSF_SM_EVENT_EXIT event cases.
| Parameter | Direction | Type | Description |
|---|---|---|---|
next |
in | SSFSMHandler_t |
Pointer to the target state handler function. Must not be NULL. |
Returns: Nothing.
Example:
static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
/* Turn LED off on entry */
break;
case SSF_SM_EVENT_EXIT:
break;
case SSF_SM_EVENT_RX_DATA:
/* Transition: EXIT fires for IdleHandler, ENTRY fires for BlinkHandler */
SSFSMTran(BlinkHandler);
break;
default:
break;
}
}void SSFSMStartTimerData(SSFSMEventId_t eid, SSFSMTimeout_t interval,
const SSFSMData_t *data, SSFSMDataLen_t dataLen);Starts or restarts a timer that posts event eid to the owning state machine after interval
system ticks. If a timer for eid is already running, it is restarted with the new interval
and data. An interval of 0 posts the event at the next SSFSMTask() call. Valid only
when called from within a state handler.
| Parameter | Direction | Type | Description |
|---|---|---|---|
eid |
in | SSFSMEventId_t |
Event to post when the timer expires. Must be a user-defined event from SSFSMEventList_t. |
interval |
in | SSFSMTimeout_t |
Delay in system ticks. 0 fires at the next SSFSMTask() call; SSF_SM_MAX_TIMEOUT is the maximum. |
data |
in | const SSFSMData_t * |
Data payload to deliver with the timer event. Pass NULL when there is no data. |
dataLen |
in | SSFSMDataLen_t |
Number of bytes of data. Must be 0 when data is NULL. |
Returns: Nothing.
Example:
static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
{
/* Start a 1-second timer carrying a blink-count payload */
uint8_t count = 5u;
SSFSMStartTimerData(SSF_SM_EVENT_BLINK_TIMER, SSF_TICKS_PER_SEC,
(SSFSMData_t *)&count, (SSFSMDataLen_t)sizeof(count));
break;
}
default:
break;
}
}#define SSFSMStartTimer(eid, to) SSFSMStartTimerData(eid, to, NULL, 0)Convenience macro for starting a timer without a data payload. Expands to
SSFSMStartTimerData() with NULL and 0 for the data parameters.
Valid only when called from within a state handler.
Example:
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
/* Fire immediately at next SSFSMTask(); idle timeout after 10 seconds */
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, 0);
SSFSMStartTimer(SSF_SM_EVENT_IDLE_TIMER, SSF_TICKS_PER_SEC * 10u);
break;
case SSF_SM_EVENT_BLINK_TIMER:
/* Toggle LED and reschedule for 1-second repeat */
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, SSF_TICKS_PER_SEC);
break;
case SSF_SM_EVENT_IDLE_TIMER:
SSFSMTran(IdleHandler);
break;
default:
break;
}
}void SSFSMStopTimer(SSFSMEventId_t eid);Cancels the timer for event eid on the owning state machine if it is running. Has no effect
if no timer for eid is active. Valid only when called from within a state handler. Note that
SSFSMTran() automatically stops all running timers, so explicit cancellation is only needed
when stopping a timer without transitioning.
| Parameter | Direction | Type | Description |
|---|---|---|---|
eid |
in | SSFSMEventId_t |
Event identifier of the timer to cancel. |
Returns: Nothing.
Example:
static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_RX_DATA:
/* Cancel the idle timeout; keep blinking */
SSFSMStopTimer(SSF_SM_EVENT_IDLE_TIMER);
/* Note: SSFSMTran() would cancel all timers automatically */
break;
default:
break;
}
}#define SSF_SM_SUPER(super) *superHandler = (SSFVoidFn_t)super;Names the parent (super) state handler for the current child state. Must appear in the
default case of a child state handler; writes super into the superHandler output
parameter of the handler function. When an event is not handled by the child, the framework
re-delivers it to the parent. Parent handlers must not call SSF_SM_SUPER(). Valid only when
called from within a state handler.
| Parameter | Description |
|---|---|
super |
The parent state handler function (SSFSMHandler_t). The framework will route unhandled events to this handler. |
Example:
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void ChildHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
break;
case SSF_SM_EVENT_EXIT:
break;
case SSF_SM_EVENT_RX_DATA:
/* Handle event specific to this child state */
break;
default:
/* Route all other events to ParentHandler for processing */
SSF_SM_SUPER(ParentHandler);
break;
}
}
static void ParentHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
break;
case SSF_SM_EVENT_EXIT:
break;
case SSF_SM_EVENT_BLINK_TIMER:
/* Shared behavior: handled for any child that names ParentHandler as super */
SSFSMTran(IdleHandler);
break;
default:
/* Parent handlers must NOT call SSF_SM_SUPER() */
break;
}
}#define SSF_SM_EVENT_DATA_ALIGN(v) { \
SSF_ASSERT((sizeof(v) >= dataLen) && (data != NULL)); \
memcpy(&(v), data, dataLen); }Safely copies the event data payload into a typed variable v. Asserts that sizeof(v) >= dataLen and that data is non-NULL. Accesses data and dataLen from the enclosing state
handler's parameter scope; valid only when called from within a state handler.
| Parameter | Description |
|---|---|
v |
A variable whose address receives the event data via memcpy. Must be large enough to hold dataLen bytes. |
Example:
static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_RX_DATA:
{
uint16_t rxLen;
/* Safely copy the event data payload into a typed variable */
SSF_SM_EVENT_DATA_ALIGN(rxLen);
/* rxLen now holds the value posted via SSFSMPutEventData() */
SSFSMTran(BlinkHandler);
break;
}
default:
break;
}
}
<a id="detailed-example"></a>
## [↑](#ssfsm--finite-state-machine-framework) Detailed Example
This example builds a two-state LED blinker from scratch. The RED state blinks a RED LED at
1 Hz; the GREEN state blinks a GREEN LED at 2 Hz. A `GOOD` event moves the machine from RED
to GREEN, and a `BAD` event moves it back. The system starts in the RED state.
### Configuration (`ssfoptions.h`)
Two enumerations are required. `SSFSMList_t` names every state machine instance in the system
— here just one, `SSF_SM_LED`. `SSFSMEventList_t` declares every event. The first three
entries (`ENTRY`, `EXIT`, `SUPER`) are mandatory framework events; the three that follow are
application-defined:
- `SSF_SM_EVENT_BLINK_TIMER` — the periodic half-period tick used by both states to drive the
LED toggle
- `SSF_SM_EVENT_GOOD` — posted from outside the state machine to trigger the RED → GREEN
transition
- `SSF_SM_EVENT_BAD` — posted from outside to trigger the GREEN → RED transition
```c
typedef enum
{
SSF_SM_MIN = -1,
SSF_SM_LED, /* single LED blinker instance */
SSF_SM_MAX
} SSFSMList_t;
typedef enum
{
SSF_SM_EVENT_MIN = -1,
SSF_SM_EVENT_ENTRY, /* required: delivered on state entry */
SSF_SM_EVENT_EXIT, /* required: delivered on state exit */
SSF_SM_EVENT_SUPER, /* required: used internally for hierarchy */
SSF_SM_EVENT_BLINK_TIMER, /* half-period tick; drives LED toggle */
SSF_SM_EVENT_GOOD, /* external trigger: RED -> GREEN */
SSF_SM_EVENT_BAD, /* external trigger: GREEN -> RED */
SSF_SM_EVENT_MAX
} SSFSMEventList_t;Each state is a plain C function with the SSFSMHandler_t signature. The framework calls it
with the current event ID; the switch dispatches to the appropriate case.
A 1 Hz blink toggles every 500 ms (one half-period). A 2 Hz blink toggles every 250 ms. Both
states reuse SSF_SM_EVENT_BLINK_TIMER — this is safe because SSFSMTran() automatically
cancels all running timers before the EXIT/ENTRY sequence, so a timer from the departing state
can never fire in the arriving state.
/* ledblink.c */
#include "ssf.h"
/* Half-periods for each blink rate (assumes SSF_TICKS_PER_SEC == 1000):
* 1 Hz -> 500 ms half-period (RED)
* 2 Hz -> 250 ms half-period (GREEN) */
#define RED_HALF_PERIOD_TICKS (SSF_TICKS_PER_SEC / 2u)
#define GREEN_HALF_PERIOD_TICKS (SSF_TICKS_PER_SEC / 4u)
/* Forward declarations so each handler can name the other as a transition target */
static void BlinkRedHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void BlinkGreenHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
/* ── RED state: blinks RED LED at 1 Hz ─────────────────────────────── */
static void BlinkRedHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
/* State just became active. Turn the RED LED on and arm the half-period
timer. The first BLINK_TIMER event will arrive in 500 ms, toggling the
LED off, giving a symmetric 1 Hz square wave. */
HalRedLedOn();
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, RED_HALF_PERIOD_TICKS);
break;
case SSF_SM_EVENT_EXIT:
/* State is leaving. Extinguish the LED so the hardware is dark during
the transition; the arriving state's ENTRY will control it from there. */
HalRedLedOff();
break;
case SSF_SM_EVENT_BLINK_TIMER:
/* Half-period elapsed: toggle the LED and immediately re-arm the timer
for the next half-period. Repeats indefinitely at 1 Hz. */
HalRedLedToggle();
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, RED_HALF_PERIOD_TICKS);
break;
case SSF_SM_EVENT_GOOD:
/* External caller signals a GOOD condition. SSFSMTran() fires
SSF_SM_EVENT_EXIT here then SSF_SM_EVENT_ENTRY in BlinkGreenHandler. */
SSFSMTran(BlinkGreenHandler);
break;
default:
break;
}
}
/* ── GREEN state: blinks GREEN LED at 2 Hz ──────────────────────────── */
static void BlinkGreenHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
switch (eid)
{
case SSF_SM_EVENT_ENTRY:
/* Turn the GREEN LED on and arm the 250 ms half-period timer for 2 Hz. */
HalGreenLedOn();
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, GREEN_HALF_PERIOD_TICKS);
break;
case SSF_SM_EVENT_EXIT:
/* Extinguish the GREEN LED on departure. */
HalGreenLedOff();
break;
case SSF_SM_EVENT_BLINK_TIMER:
/* Toggle and re-arm for a continuous 2 Hz square wave. */
HalGreenLedToggle();
SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, GREEN_HALF_PERIOD_TICKS);
break;
case SSF_SM_EVENT_BAD:
/* External caller signals a BAD condition: return to the RED state. */
SSFSMTran(BlinkRedHandler);
break;
default:
break;
}
}void LedBlinkInit(void)
{
/* Initialize the framework with the pool sizes declared in ssfoptions.h. */
SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* Register the LED state machine. SSFSMInitHandler() delivers
SSF_SM_EVENT_ENTRY to BlinkRedHandler immediately before returning,
so the RED LED is already on and the first timer is already armed
by the time this function returns. */
SSFSMInitHandler(SSF_SM_LED, BlinkRedHandler);
}
/* Single-threaded superloop — call from main() after LedBlinkInit() */
void LedBlinkRun(void)
{
while (true)
{
SSFSMTask(NULL);
}
}SSFSMTask() checks the event queue and fires any expired timers on every call. In a
single-threaded build it can be called as fast as the loop allows; the framework only invokes
a handler when there is actually something to deliver.
From anywhere in the application — a button ISR, a network callback, or another state
machine — posting one of the external events causes the next SSFSMTask() call to deliver it:
/* Signal a GOOD condition; the machine will leave the RED state on the next task call */
SSFSMPutEvent(SSF_SM_LED, SSF_SM_EVENT_GOOD);
/* Signal a BAD condition; the machine will leave the GREEN state on the next task call */
SSFSMPutEvent(SSF_SM_LED, SSF_SM_EVENT_BAD);SSFSMPutEvent() only enqueues the event — it does not invoke the handler directly. The
actual transition, including the EXIT and ENTRY calls, happens inside the next SSFSMTask()
invocation. When SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1, SSFSMPutEvent() is safe to call
from any execution context, including interrupt service routines.
The table below traces every framework action from power-on through one complete RED → GREEN → RED cycle. Steps 2–3 and 7–8 repeat at their respective rates until an external event arrives.
| Step | Event delivered | Handler | Action |
|---|---|---|---|
| 1 | SSF_SM_EVENT_ENTRY |
BlinkRedHandler |
RED LED on; 500 ms timer started |
| 2 | SSF_SM_EVENT_BLINK_TIMER |
BlinkRedHandler |
RED LED off; 500 ms timer restarted |
| 3 | SSF_SM_EVENT_BLINK_TIMER |
BlinkRedHandler |
RED LED on; 500 ms timer restarted |
| 4 | SSF_SM_EVENT_GOOD (external) |
BlinkRedHandler |
SSFSMTran(BlinkGreenHandler) called |
| 5 | SSF_SM_EVENT_EXIT |
BlinkRedHandler |
RED LED off; all timers stopped |
| 6 | SSF_SM_EVENT_ENTRY |
BlinkGreenHandler |
GREEN LED on; 250 ms timer started |
| 7 | SSF_SM_EVENT_BLINK_TIMER |
BlinkGreenHandler |
GREEN LED off; 250 ms timer restarted |
| 8 | SSF_SM_EVENT_BLINK_TIMER |
BlinkGreenHandler |
GREEN LED on; 250 ms timer restarted |
| 9 | SSF_SM_EVENT_BAD (external) |
BlinkGreenHandler |
SSFSMTran(BlinkRedHandler) called |
| 10 | SSF_SM_EVENT_EXIT |
BlinkGreenHandler |
GREEN LED off; all timers stopped |
| 11 | SSF_SM_EVENT_ENTRY |
BlinkRedHandler |
RED LED on; 500 ms timer started |
The EXIT step always fires before ENTRY, ensuring neither LED is ever on simultaneously during a transition and that the departing state's blink timer is always cancelled before the arriving state starts its own.