Sequencing Design
Sequences should have a clearly defined start and end. They should always move in an increasing number order, not jump around. Skipping steps is acceptable, but they should not go backwards.
Keep in mind the following State machine. This state machine controls the operation that the sequence runs. It should be able to force the sequence to start any operation and know when the operation is complete.
Here we have defined
LIFT_READY:
LIFT_DONE:
LIFT_ERROR:
as special steps that are the handshakes between the State and the Sequence
The name of the entry point of a sequence should indicate that it is a starting step. Other steps within a specific sequence should not be entered without caution.
STATE_READY:
liftSequence.step = LIFT_READY;
if( moveTarget ){
state = STATE_MOVE_ABSOLUTE;
moveAbs.position = TargetPos;
}
else if( moveZero ){
state = STATE_MOVE_ABSOLUTE;
moveAbs.position = 0;
}
else if( executeLift ){
state = STATE_LIFT;
}
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
STATE_LIFT:
//Fully contained state, with partially contained sequence.
// The state is responsible for initiating a sequence.
// The sequence is implemented elsewhere, and ends in the DONE or ERROR step
// Any step other than READY, DONE or ERROR is part of the process,
// and should be ignored by this state machine
CASE liftSequence.step OF
LIFT_READY:
status = "Lift Sequence Started"
state = STATE_READY;
liftSequence.step = LIFT_START_SEQUENCE;
LIFT_DONE:
status = "Lift Sequence Finished"
state = STATE_READY;
liftSequence.step = LIFT_READY;
LIFT_ERROR:
Alarm( "Lift Sequence Errored!" );
status = "Lift Sequence Errored"
state = STATE_READY;
liftSequence.step = LIFT_READY;
DEFAULT:
status = "Executing Lift Sequence"
END_IF
...
moveTarget = 0;
moveZero = 0;
executeLift = 0;
moveAbs();
moveAbs.execute := FALSE;
Implementing the sequence
Sequences work well when defined in ACTIONS. There is some debate at to whether these actions should be called continuously or only when the action is meant to be active. I will not weigh in on that right now.
ACTION MySequence1
CASE liftSequence.step OF
LIFT_SEQUENCE_MySequence1_Start:
status = "Starting Lift Sequence 1"
liftSequence.step = LIFT_SEQUENCE_MySequence1_Step1
LIFT_SEQUENCE_MySequence1_Step1:
status = "Executing Lift Sequence 1 Step 1"
liftSequence.step = LIFT_SEQUENCE_MySequence1_Step2
LIFT_SEQUENCE_MySequence1_Step2:
status = "Executing Lift Sequence 1 Step 2"
liftSequence.step = LIFT_SEQUENCE_MySequence1_Step3
LIFT_SEQUENCE_MySequence1_Step3:
status = "Executing Lift Sequence 1 Step 3"
liftSequence.step = LIFT_DONE
//These could be covered by default
LIFT_READY,
LIFT_DONE,
LIFT_ERROR:
//Do nothing
DEFAULT:
//Do nothing
END_IF
END_ACTION
ACTION MySequence2
CASE liftSequence.step OF
LIFT_SEQUENCE_MySequence2_Start:
status = "Starting Lift Sequence 2"
liftSequence.step = LIFT_SEQUENCE_MySequence2_Step1
LIFT_SEQUENCE_MySequence2_Step1:
status = "Executing Lift Sequence 2 Step 1"
liftSequence.step = LIFT_SEQUENCE_MySequence2_Step2
LIFT_SEQUENCE_MySequence2_Step2:
status = "Executing Lift Sequence 2 Step 2"
liftSequence.step = LIFT_SEQUENCE_MySequence2_Step3
LIFT_SEQUENCE_MySequence2_Step3:
status = "Executing Lift Sequence 2 Step 3"
liftSequence.step = LIFT_DONE
//These could be covered by default
LIFT_READY,
LIFT_DONE,
LIFT_ERROR:
//Do nothing
DEFAULT:
//Do nothing
END_IF
END_ACTION