đź§ What Is a State Machine? #
A State Machine models system behavior using three fundamental components:
- State – Where the system currently is
- Event – What just happened
- Action / Transition – What to do next and where to go
At runtime, a state machine continuously answers:
Given the current state and an incoming event, what action should I take and what is my next state?
State machines are everywhere: protocol stacks, device drivers, UI logic, power management, and real-time embedded systems.
🔀 Method 1: Switch–Case State Machine #
The switch–case method is the most direct and commonly used approach. States and events are represented as enums, and behavior is encoded using nested switch statements.
Basic Structure #
typedef enum {
STATE_IDLE,
STATE_BUSY,
STATE_ERROR
} state_t;
typedef enum {
EVT_START,
EVT_STOP,
EVT_FAIL
} event_t;
void fsm_handle_event(event_t evt)
{
switch (g_state) {
case STATE_IDLE:
switch (evt) {
case EVT_START:
start_work();
g_state = STATE_BUSY;
break;
default:
break;
}
break;
case STATE_BUSY:
switch (evt) {
case EVT_STOP:
stop_work();
g_state = STATE_IDLE;
break;
case EVT_FAIL:
handle_error();
g_state = STATE_ERROR;
break;
default:
break;
}
break;
case STATE_ERROR:
reset_system();
g_state = STATE_IDLE;
break;
}
}
Characteristics #
Pros
- Extremely intuitive
- Easy to debug with breakpoints
- No indirection or memory overhead
Cons
- Poor scalability as states Ă— events grow
- High maintenance cost
- Logic and structure tightly coupled
Practical Tip #
Because switch statements are evaluated sequentially, place high-frequency states and events first to minimize branch latency.
đź§© Method 2: Table-Driven State Machine #
The table-driven approach decouples logic from structure by representing the state machine as a lookup table. Each (state, event) pair maps to an action + next state.
Core Data Structure #
typedef struct {
void (*action)(void *event);
uint8_t next_state;
} fsm_node_t;
FSM Table Definition #
fsm_node_t fsm_table[NUM_STATES][NUM_EVENTS] = {
[STATE_IDLE] = {
[EVT_START] = { start_work, STATE_BUSY },
},
[STATE_BUSY] = {
[EVT_STOP] = { stop_work, STATE_IDLE },
[EVT_FAIL] = { handle_error, STATE_ERROR },
},
};
Runtime Execution #
void fsm_dispatch(uint8_t state, uint8_t event, void *evt)
{
fsm_node_t *node = &fsm_table[state][event];
if (node->action) {
node->action(evt);
g_state = node->next_state;
}
}
Why It’s Fast #
- Constant-time lookup
O(1) - CPU branch predictor friendly
- Ideal for high-throughput systems
đź§± Extended State Machines: The Compressed Table #
Classic tables struggle with Extended State Machines (ESM), where transitions depend on runtime conditions.
Solution: Action Returns the Next State #
typedef uint8_t (*fsm_action_t)(void *evt);
typedef struct {
fsm_action_t action;
} fsm_node_t;
uint8_t busy_action(void *evt)
{
if (error_detected(evt))
return STATE_ERROR;
return STATE_IDLE;
}
This hybrid approach keeps table speed while restoring decision flexibility.
đź§ Method 3: Function Pointer State Machine #
This technique removes numeric state IDs entirely. The state is the function itself.
Conceptual Model #
- Global state = function pointer
- Each state function handles events
- Returns pointer to the next state function
Example Implementation #
typedef void (*state_fn_t)(event_t evt);
void state_idle(event_t evt);
void state_busy(event_t evt);
state_fn_t current_state = state_idle;
void state_idle(event_t evt)
{
if (evt == EVT_START) {
start_work();
current_state = state_busy;
}
}
void state_busy(event_t evt)
{
if (evt == EVT_STOP) {
stop_work();
current_state = state_idle;
}
}
Why Experts Use This #
Pros
- Extremely flexible
- No tables, no enums
- Natural mapping to behavior
Cons
- Difficult debugging
- Vulnerable to memory corruption
- Stack traces are less informative
⚠️ A corrupted function pointer usually results in an immediate crash.
📊 Comparison Summary #
| Method | Complexity | Performance | Flexibility | Safety |
|---|---|---|---|---|
| Switch–Case | Low | Medium | Medium | High |
| Table-Driven | Medium | High | Low | Medium |
| Function Pointer | High | High | Very High | Low |
🏗 Beyond FSM: Hierarchical State Machines (HSM) #
For large systems, flat FSMs become unmanageable. Hierarchical State Machines (HSM) introduce nesting:
- A parent state defines shared behavior
- Child states override specifics
Example:
-
POWER_ONIDLEACTIVELOW_BATTERY
HSMs dramatically reduce duplication and are widely used in:
- RTOS kernels
- Communication protocols
- Automotive and avionics software
âś… Final Takeaways #
- Use switch–case for small, simple systems
- Use table-driven FSMs for speed and scalability
- Use function-pointer FSMs when flexibility outweighs safety
- For complex products, consider HSM frameworks
A well-designed state machine is not just code — it is architecture.