Nested conditional logic main loops are hard to read and maintain.
State machines are a common way to implement complex logic.
States have transitions that are triggered by events or conditions.
States can have entry / exit routines that are executed when the state is entered / exited.
The “run” status of a state is commonly referred to as the “state machine tick” and recurrently loops.
State diagrams are used to visualize state machines.
State structures are used to capture variables associated with describing the state.
Implementation
Switch-Case
The simplest implementation of a state machine is a switch-case statement.
The switch statement is used to select the current state.
The case statements are used to implement the logic for each state.
Cases can be nested to implement sub-states.
Enumerations can be used to give states verbose names instead of numbers.
The break statement is used to exit the switch statement.
The default statement is used to handle unexpected states.
Use the State to Dictate Function
Avoid testing for a condition in a state to figure out what to do; let being in the state dictate the function (i.e., you shouldn’t have to test for the state you are in to know what to do).
Use entry and exit routines to handle state transitions; do not test for first or last iteration of that state.
“Run” states can iterate in a loop until a condition is met to change the state, or can wait for an event.
If you find you need to timeout an event wait to allow other stuff to happen, consider using k_event_test().
Pseudo-Code
enum device_states { init, run, error_entry, error_run, error_exit };int device_state = init;// initialize state/* structure to bookkeep state variables */struct device_state_vars {int var1;int var2;};while(1){switch(device_state){case init:/* do stuff to initialize device */ device_state = run;// change the statebreak;case run:/* run device */if(condition){ device_state = error;}break;case error_entry: illuminate_error_led();break;case error_run:if(condition_to_leave_error){ device_state = error_exit;}breakcase error_entry: turn_off_error_led(); device_state = run;break;default:/* handle unexpected state */break;}}
The switch-case approach loses some of its elegance when there are many states and many transitions and states have entry / exit routines.