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:

  1. Call Piper Module Interface
  2. Convert IO to useful information (scale, interpolate, store internally),
  3. Check for commands and permissive/inhibit logic
  4. Do logic
    1. Handle aborting commands
    2. Implement process logic (This is where your state machine or sequence would go if one is required
  5. Call FUBs
  6. 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.

See Subsystem Communications

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