Tasks
Developing
Task should be developed in isolation of the external systems first. During development, the watch window is your best friend. This allows for easier simulation and testing, and helps to distill what the subsystem will do. Each task/subsystem should have a well defined scope of operation (Driver, Application, Process)
Order of operations
Tasks should be able to run independently of the system. Task cyclic code should go as follows:
- Call Piper Module Interface
- Convert IO to useful information (scale, interpolate, store internally),
- Check for commands and permissive/inhibit logic
- Do logic
- Handle aborting commands
- Implement process logic (This is where your state machine or sequence would go if one is required
- Call FUBs
- Reset inputs (unconditionally)
Interfacing to external tasks
The interface to external systems should be as minimal as feasible. It is tempting to add every status, command, and IO point to the global interface of a subsystem, but doing so tends to lead to spaghetti code. If everything is accessible you never know where information is used or coming from, leading to side affects.
What is a task if it can’t interface to the outside world?
It can.
Template Task:
PROGRAM _CYCLIC
///////////////////////////////////////////////////////////////
//API:
// Requires:
// - TODO: What does this task wait for to be ready?
// - Axis Powered
// - Axis Homed
// Commands:
// - TODO: What commands does it handle
// - MoveUp
// Inhbitits:
// - TODO: What type of things can stop it from accepting command
// - inhibitMoveUp
///////////////////////////////////////////////////////////////
// Call PiperModuleInterface to interface with the rest of the machine
PiperModuleInterface;
//Read in external components
localinterface.internal.pAxis := gAxisBasic[ AXIS_ENUM ].MpLink;
//Read external states
localinterface.internal.ready := stateAllTrue( gThisSystem.status.subSystemsReady, TRUE );
///////////////////////////////////////////////////////////////
//Handle interpreting commands that do not require STATE_READY
///////////////////////////////////////////////////////////////
IF localinterface.internal.ready THEN
//Local lockout
IF localinterface.internal.localLockout THEN
//Any of the following commands can interrupt others
// so they are handled outside the state machine
ELSIF localinterface.command.moveUp THEN
localinterface.internal.newCommand := 1;
IF stateAllFalse( gThisSystem.inhibit.moveUp, TRUE) THEN
localinterface.internal.state := STATE_ABSOLUTE_MOVE;
localinterface.internal.moveAbsolute.Position := 0;
localinterface.internal.moveAbsolute.Velocity := 10;
ELSE
//Respond to PLCOpen
localinterface.internal.PLCOpen.status := ERR_INHIBITIED;
// Generate inhibit message
stateTrueMessage( gThisSystem.inhibit.moveUp,
ADR(gThisSystem.status.message),
SIZEOF(gThisSystem.status.message) );
END_IF
END_IF
ELSE
//TODO: Tell the user that a command failed if commanded here?
localinterface.internal.PLCOpen.status := THIS_SYS_NOT_READY_ERROR;
localinterface.internal.state := STATE_NOT_READY;
localinterface.internal.localLockout := FALSE;
END_IF
///////////////////////////////////////////////////////////////
//Handle any canceled commands that are required
///////////////////////////////////////////////////////////////
IF localinterface.internal.newCommand THEN
//TODO: Log state changes
//Reset any sequences that were active
localinterface.internal.sequence := SEQUENCE_IDLE;
//If a new command came into the system,
// abort any local PLCOpen commands that were active.
// If the command came from PLCOpen FirstPass
// will be true and it will not abort it
atnPLCOpenAbort(localinterface.internal.PLCOpen);
//Check the previous state to handle any sort of aborting that is required
CASE localinterface.internal.previousState OF
STATE_ABSOLUTE_MOVE:
//An absolute move should be halted
localinterface.internal.halt.Execute := TRUE;
localinterface.internal.halt();
//Important if it should be canceled by another moveAbsolute call
localinterface.internal.moveAbsolute.Execute:=FALSE;
localinterface.internal.moveAbsolute();
END_CASE
//NOTE: If using mapp motion, we require 1 extra state to abort a move
// ACP10 does not require this
IF localinterface.internal.state = STATE_ABSOLUTE_MOVE THEN
localinterface.internal.state := STATE_ABSOLUTE_WAIT;
END_IF
END_IF
///////////////////////////////////////////////////////////////
//Handle the current state
///////////////////////////////////////////////////////////////
CASE localinterface.internal.state OF
//Post information to the user for why we aren't ready
STATE_NOT_READY:
localinterface.internal.sequence := SEQUENCE_IDLE;
localinterface.internal.statusMessage := '[THIS] is not in motion ready state';
IF localinterface.internal.ready THEN
localinterface.internal.state := STATE_READY;
localinterface.internal.statusMessage := '[THIS] is in ready state';
END_IF
//Wait here for commands to change state
STATE_READY:
localinterface.internal.sequence := SEQUENCE_IDLE;
//TODO: Handle any commands that require lockout
localinterface.internal.localLockout := FALSE;
//This state is required if a Move absolute is aborted by itself in mapp motion
STATE_ABSOLUTE_WAIT:
//Wait until the FUB is no longer busy
IF NOT localinterface.internal.moveAbsolute.Busy THEN
localinterface.internal.state := STATE_ABSOLUTE_MOVE;
END_IF
STATE_ABSOLUTE_MOVE:
//STANDARD PLC Open Call
IF localinterface.internal.moveAbsolute.Done THEN
localinterface.internal.PLCOpen.status := ERR_OK;
localinterface.internal.state := STATE_READY;
ELSIF localinterface.internal.moveAbsolute.Error
OR localinterface.internal.moveAbsolute.CommandAborted THEN
localinterface.internal.PLCOpen.status := THIS_SYS_MOVE_ERROR;
localinterface.internal.state := STATE_READY;
ELSIF NOT localinterface.internal.moveAbsolute.Busy THEN
localinterface.internal.moveAbsolute.Execute:=TRUE;
END_IF
ELSE
//TODO: Handle if we go here, not sure why we are here..
END_CASE;
///////////////////////////////////////////////////////////////
//Call all PLCOpen function blocks
///////////////////////////////////////////////////////////////
//Halt first. Might get canceled by a new move
localinterface.internal.halt.Axis := localinterface.internal.pAxis;
localinterface.internal.halt.Deceleration := 10;
localinterface.internal.halt();
localinterface.internal.halt.Execute := FALSE;
localinterface.internal.moveAbsolute.Axis := localinterface.internal.pAxis;
//TODO: Where does these parameters come from?
// localinterface.internal.moveAbsolute.BufferMode //Default is ok
// localinterface.internal.moveAbsolute.Direction //Default is OK
localinterface.internal.moveAbsolute.Acceleration:= 10;
localinterface.internal.moveAbsolute.Deceleration:= 10;
// localinterface.internal.moveAbsolute.Jerk //Only zero is supported
// localinterface.internal.moveAbsolute.Position //Set by the commands
// localinterface.internal.moveAbsolute.Velocity //Set by the command
localinterface.internal.moveAbsolute();
localinterface.internal.moveAbsolute.Execute:= FALSE;
// Auxiliary Function blocks
localinterface.internal.readPosition.Enable := localinterface.internal.ready;
localinterface.internal.readPosition.Axis := localinterface.internal.pAxis;
localinterface.internal.readPosition();
localinterface.status.actualPosition := localinterface.internal.readPosition.Position;
///////////////////////////////////////////////////////////////
//Reset all commands to ensure they don't get buffered
///////////////////////////////////////////////////////////////
localinterface.command.moveUp := 0;
//Reset the newCommand Bit
localinterface.internal.newCommand := 0;
//TODO: Handle loggin state changes if desired
//Keep track of the previous state
localinterface.internal.previousState := localinterface.internal.state;
END_PROGRAM