State Machines
Don’t assume you need one.
Reason you might need a State Machine:
- There are operations that require multi-step handshaking
- There are states or operations that are exclusive (only one can run at a time and should not interrupt)
Reasons why a state machine might be cumbersome:
-
Wrapping another state machine.
This happens a lot with PLCOpen motion function blocks. PLCOpen Axes already have a well defined state machine built-in. They handle throwing good errors and won’t try to do operations that they can’t.
In the case you want to handle errors differently, simple if statements based on Axis status bits is generally less bug prone than case statements. Since there is less of an opportunity to have the two state machine come out of sync.
-
Parallel state machines can easily get out of sync
Reason you might need a Sequence Machine:
- You have a Processes that has multiple ordered steps
- You have an operations that has multiple steps with status checking
Am I building a Sequence or a State?
It is useful to distinguish between State machines and Sequence machines.
State Machines are a structure, usually implemented as a switch (or case) statement, in which you have a single “State” or “Operation” that happens at a time. The states can be freely transitioned to and should be fully self contained. Anything that should take place in the state should not require coming from any other specific state. It is possible that initialization should be done before entering a state, but it should not be required for second or later calls of the state, unless something about the state should change.
Sequences are similar in structure (using a switch or case), but typically have a specific order in which the cases must be run. For example, one step might check if something can be done, the next step would do an operation, the final step does some cleanup and goes back to an IDLE state.
It is often the case that developers mix state machines and sequence machines. There are good reasons to do this, and there are some bad ones. Mixing state and sequence can simplify simple systems. Mixing state and sequence becomes a problem as the size of the state/sequence grows. As the mixture grows, it becomes harder to follow the operations and often tends to lead to jumping around states.
If you do find yourself mixing state and sequence, it is helpful to denote by the state/sequence name if this is a state, or part of a sequences. It should also be clear in the name if the current state is a state can be entered, for example by:
STATE_READY:
if( moveTarget ){
state = STATE_MOVE_ABSOLUTE;
moveAbs.position = TargetPos;
}
else if( moveZero ){
state = STATE_MOVE_ABSOLUTE;
moveAbs.position = 0;
}
else if( executeLift ){
state = SEQUENCE_LIFT_START;
}
STATE_MOVE_ABSOLUTE:
// State with a fully contained function block
// The moveAbs block is configured before entering the state
// by populating the position based on a command
IF moveAbs.error THEN
Alarm( "Motion Error!" );
state = STATE_READY;
ELSIF moveAbs.done THEN
state = STATE_READY;
ELSIF NOT moveAbs.busy THEN
// State execution
moveAbs.execute := TRUE;
END_IF
// This sequence MUST start with the SEQUENCE_XX_START state
SEQUENCE_LIFT_START:
...
state = SEQUENCE_LIFT_MOVE_1;
SEQUENCE_LIFT_MOVE_1:
...
state = SEQUENCE_LIFT_MOVE_2;
SEQUENCE_LIFT_MOVE_2:
...
state = SEQUENCE_LIFT_CLEANUP;
SEQUENCE_LIFT_CLEANUP:
...
state = STATE_READY;
...
moveTarget = 0;
moveZero = 0;
executeLift = 0;