Anatomy of a Submodule
Before integrating a subsystem into a machine using Piper, build the subsystem in isolation.
Now let’s take a look at a Piper Submodules.
You can think of Piper as a plugin system. You define your subsystem and Piper coordinates it with the rest system. As an API with a well defined interface, we have the ability to give you some good templates to start with.
Here is your cyclic task. Just add the PiperModule interface to the top of whatever task you are integrating.
// MyTaskName.ST
PROGRAM _CYCLIC
// Call PiperModuleInterface to interface with the rest of the machine
PiperModuleInterface;
//Put your code here
END_PROGRAM
Here is a local var file to define the variables required by Piper
// PiperModule.var
VAR
ModuleInterface : Module_Interface_typ; //Structure for the user to interface to
Module : Piper_Module_Fub; //Function block for processing commands
END_VAR
This is the main interface for integrating into Piper.
//PiperInterface.st
ACTION PiperModuleInterface:
//Give this module a name so it is easier to debug
ModuleInterface.ModuleName:= 'Name';
//Add a module to the Piper
Module.ModuleInterface:= ADR(ModuleInterface);
Module.Piper:= ADR(gMachine);
Module();
// Handle any machine states that this module needs to respond to
CASE ModuleInterface.PiperState OF
// MACH_ST_BOOTING:
// MACH_ST_CLEARING:
// MACH_ST_STOPPED:
// MACH_ST_STARTING:
// MACH_ST_IDLE:
// MACH_ST_SUSPENDED:
// MACH_ST_EXECUTE:
// MACH_ST_STOPPING:
// MACH_ST_ABORTING:
// MACH_ST_ABORTED:
// MACH_ST_HOLDING:
// MACH_ST_HELD:
// MACH_ST_UNHOLDING:
// MACH_ST_SUSPENDING:
// MACH_ST_UNSUSPENDING:
// MACH_ST_RESETTING:
// MACH_ST_COMPLETING:
// MACH_ST_COMPLETE:
MACH_ST_BYPASSED:
ELSE
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_CASE
END_ACTION
Since we built our task in isolation, we can easily integrate into the machine. We start by uncommenting any states that we need to handle and responding. From here I will remove some reduntant code.. so beware.
It’s a good idea to uncomment the states you need to respond to, and respond. Now you can test that your system is integrated by setting gMachine.IN.CMD.xxx and you should see your system going through states.
//PiperInterface.st
ACTION PiperModuleInterface:
...
// Handle any machine states that this module needs to respond to
CASE ModuleInterface.PiperState OF
MACH_ST_BOOTING:
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
MACH_ST_ABORTING:
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
MACH_ST_RESETTING:
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
// MACH_ST_CLEARING:
// MACH_ST_STOPPED:
// MACH_ST_STARTING:
...
MACH_ST_BYPASSED:
ELSE
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_CASE
END_ACTION
Now let’s make it useful by integrating our subsystem. We will force the system to wait for us to be initialized before booting. We should also power on during Resetting and power off during Aborting.
//PiperInterface.st
ACTION PiperModuleInterface:
...
// Handle any machine states that this module needs to respond to
CASE ModuleInterface.PiperState OF
MACH_ST_BOOTING:
//If I'm done initializing, let the system know
IF gThisSubsystem.status.isBootedUp THEN
//Responding with the current state indicates we are finished with the state
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_IF
MACH_ST_ABORTING:
gThisSubsystem.cmd.PowerOn = FALSE;
//Respond immediatley. We are sure power off will work
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
MACH_ST_RESETTING:
//It's ok to hold the power on true since our system was designed to detect edges
gThisSubsystem.cmd.PowerOn = TRUE;
//Once we have successully powered on we respond with done
IF gThisSubsystem.status.isPoweredOn THEN
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
//If we got an error, respond so and it will force the system into aborting.
// You may also interface to gMachine.IN.CMD.Abort, but this is not traceable.
ELSIF gThisSubsystem.status.Error THEN
ModuleInterface.ModuleResponse:= MACH_ST_ERROR;
END_IF
// MACH_ST_CLEARING:
// MACH_ST_STOPPED:
// MACH_ST_STARTING:
...
MACH_ST_BYPASSED:
ELSE
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_CASE
END_ACTION
Group statuses
A common practice is to check if an array of items is ready before moving on. This usually works by settings a groupStatus to true, then setting to false if anything is not complete. If you make it to the end and your group status is still true then you are GOOD TO GO!
//PiperInterface.st
ACTION PiperModuleInterface:
...
// Handle any machine states that this module needs to respond to
CASE ModuleInterface.PiperState OF
MACH_ST_RESETTING:
//Set the status to true and assume we are done
groupStatus := TRUE;
//Check all the axes, if any are NOT done, we need to wait
FOR axis := 0 TO AXES_MAI DO
gAxis[axis].Power := TRUE;
IF NOT gAxis[axis].isPowered THEN
groupStatus := FALSE;
END_IF
END_FOR
//If groups status is still true then it means every axis is ready
// To move on
IF groupStatus = TRUE THEN
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_IF
...
MACH_ST_BYPASSED:
ELSE
ModuleInterface.ModuleResponse:= ModuleInterface.PiperState;
END_CASE
END_ACTION
That’s IT
You’ve integrated into Piper.
There is still more to learn. As long as you followed the rules about State Machines your module should function well. In case you don’t remember:
- Expect to enter any State at any time.
- Do not call Function blocks within a state
- Do not implement Sequences within State Unless you are using Piper Substates.