DUTs
DUT_HOMS
TYPE DUT_HOMS :
STRUCT
// System initializiation
fbRunHOMS : FB_RunHOMS;
// Couple/Decouple motors
{attribute 'pytmc' := '
pv: COUPLE_Y
io: o
'}
bExecuteCoupleY : BOOL;
{attribute 'pytmc' := '
pv: DECOUPLE_Y
io: o
'}
bExecuteDecoupleY : BOOL;
{attribute 'pytmc' := '
pv: COUPLE_X
io: o
'}
bExecuteCoupleX : BOOL;
{attribute 'pytmc' := '
pv: DECOUPLE_X
io: o
'}
bExecuteDecoupleX : BOOL;
// Coupling status
{attribute 'pytmc' := '
pv: ALREADY_COUPLED_Y
io: i
field: ZSV MAJOR
'}
bGantryAlreadyCoupledY : BOOL;
{attribute 'pytmc' := '
pv: ALREADY_COUPLED_X
io: i
field: ZSV MAJOR
'}
bGantryAlreadyCoupledX : BOOL;
// Current gantry differences
nCurrGantryY : LINT; // encoder counts = nm
nCurrGantryX : LINT; // encoder counts = nm
// Convert gantry differences to um (smaller number) to readout in epics
{attribute 'pytmc' := '
pv: GANTRY_Y
field: EGU um
io: i
'}
fCurrGantryY_um : REAL; // Y Gantry difference in um
{attribute 'pytmc' := '
pv: GANTRY_X
field: EGU um
io: i
'}
fCurrGantryX_um : REAL; // X Gantry difference in um
END_STRUCT
END_TYPE
- Related:
E_PiezoControl
TYPE E_PiezoControl :
(
//Piezo Control Machine
EPC_Idle := 0,
EPC_Init := 10,
EPC_MoveRequested := 50,
EPC_MovingPositive := 100,
EPC_MovingNegative := 200,
EPC_MoveCompleted := 350,
EPC_Error := 500
);
END_TYPE
E_PitchControl
TYPE E_PitchControl :
(
//Pitch Control Machine
PCM_Init := 0,
PCM_Standby := 1,
PCM_MoveRequested := 10,
PCM_Coarse50Piezo := 20,
PCM_CoarseMove := 21,
PCM_CoarseMoveCleanup := 22,
PCM_FineMove := 30,
PCM_Halt := 50,
PCM_Done := 8000, //why is 8000 done? Where did this come from??
PCM_Error := 9000, //Anything above 9000 is considered an error
PCM_StepperError := 9100,
PCM_PiezoError := 9200,
PCM_OtherError := 9300,
PCM_STOHit := 9400
);
END_TYPE
HOMS_PitchMechanism
TYPE HOMS_PitchMechanism :
STRUCT
Piezo : ST_PiezoAxis; //Piezo
(* Soft limits, egu urad *)
ReqPosLimHi : REAL;
ReqPosLimLo : REAL;
(* Hard limits, egu INC *)
(* These are discovered during installation, and represent encoder ticks, unbiased *)
(* We changed to these when our pitch mechanism limit switches were insufficient for
good control. They had too much hysteresis/ lack of precision. At this point the limit
switches are ignored, and instead their function is carried out by these. *)
diEncPosLimHi : LINT;
diEncPosLimLo : LINT;
//Raw encoder count
diEncCnt AT %I* : LINT;
END_STRUCT
END_TYPE
- Related:
ST_PiezoAxis
TYPE ST_PiezoAxis :
STRUCT
(* IO *)
//Readback
sIdn : STRING; //Identity
iCurError : INT; //Current error code, should be 0 most of the time
iLastError : INT; //Last error code, for history
rActVoltage : REAL; //Actual voltage
rLastReqVoltage : REAL; //Last requested piezo voltage
//Control
rSetVoltage : REAL; //this parameter is set by the control loop/ voltage mode
sAxis : STRING :='A'; //Axis, e.g. A, B, C...A if single unit
//Summaries
xTimeout : BOOL;
xDriverError : BOOL; //Summary of any driver errors
(* Operation *)
xEnable : BOOL; //Enable control.
(* Note: Voltage mode and Idle mode overrides "DirectPiezoMode" of FB_PitchControl *)
xVoltageMode : BOOL; //Voltage mode gives direct access to piezo voltage, false means closed loop position acquisition (see FB_PitchControl for piezo and stepper separation)
xIdleMode : BOOL; //Use to put the piezo at half-stroke
rReqVoltage : REAL; //Requested piezo voltage in voltage mode
rReqAbsPos : LREAL; //Requested Position, latched at rising StartAbsMov
xStop : BOOL; //Stops piezo and holds position
(* Control Parameters *)
rActPos : LREAL; //Encoder Readback
//Pitch piezo dmove range (urad)
rPiezoDmovRange : REAL := 1.0;
stPIParams : ST_CTRL_PI_PARAMS := (
tCtrlCycleTime := T#0MS,
tTaskCycleTime := T#0MS,
tTn := T#200MS,
fKp := 0.0005,
fOutMaxLimit := 1,
fOutMinLimit := -1,
bARWOnIPartOnly := FALSE);
(* Voltage ranges, come from specifications of the driver *)
UpperVoltage : REAL := GVL_Constants.cPiezoMaxVoltage; // E-816 has no software limits
LowerVoltage : REAL := GVL_Constants.cPiezoMinVoltage; // E-816 has no software limits
END_STRUCT
END_TYPE
- Related:
GVLs
Global_Version
{attribute 'TcGenerated'}
{attribute 'no-analysis'}
{attribute 'linkalways'}
// This function has been automatically generated from the project information.
VAR_GLOBAL CONSTANT
{attribute 'const_non_replaced'}
stLibVersion_lcls_twincat_optics : ST_LibVersion := (iMajor := 0, iMinor := 8, iBuild := 0, iRevision := 0, nFlags := 1, sVersion := '0.8.0');
END_VAR
GVL_Constants
{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
nGANTRY_TOLERANCE_NM_DEFAULT : LINT := 50000; // default gantry tolerance in encoder counts = nm
cPiezoMaxVoltage : LREAL := 120; // in Volts
cPiezoMinVoltage : LREAL := -10; // in Volts
cPiezoRange : REAL := 60.0; // From Old HOMS_FEE Project, 90 um of piezo stroke, unsure what these units are
END_VAR
GVL_TestStructs
{attribute 'qualified_only'}
VAR_GLOBAL
TestPitch_LimitSwitches : HOMS_PitchMechanism := (ReqPosLimHi:=2000,
ReqPosLimLo:=-2000,
diEncPosLimHi:=10768330,
diEncPosLimLo:=8141680);
END_VAR
- Related:
POUs
FB_Axilon_Cooling_1f1p
FUNCTION_BLOCK FB_Axilon_Cooling_1f1p
VAR_INPUT
END_VAR
VAR_OUTPUT
// Mirrors with 1 Cooling Flow Meter and 1 Pressure Meter
{attribute 'pytmc' := '
pv: FWM:1
field: EGU lpm
field: HIGH 2.3
field: HIHI 3.0
field: LOW 1.7
field: LOLO 1.5
field: LSV MINOR
field: LLSV MAJOR
field: HSV MINOR
field: HHSV MAJOR
io: i
'}
fFlow_1_val : LREAL;
{attribute 'pytmc' := '
pv: PRSM:1
field: EGU bar
field: LOW 0.1
field: LSV MAJOR
io: i
'}
fPress_1_val : LREAL;
END_VAR
VAR
fbFlow_1 : FB_AnalogInput;
fbPress_1 : FB_AnalogInput;
END_VAR
fbFlow_1(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fFlow_1_val := fbFlow_1.fReal;
fbPress_1(iTermBits:=15, fTermMax:=4.0, fTermMin:=0);
fPress_1_val := fbPress_1.fReal;
END_FUNCTION_BLOCK
FB_Axilon_Cooling_2f1p
FUNCTION_BLOCK FB_Axilon_Cooling_2f1p EXTENDS FB_Axilon_Cooling_1f1p
// Mirrors with 2 Cooling Flow Meters and 1 Pressure Meter
VAR_INPUT
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: FWM:2
field: EGU lpm
field: HIGH 2.3
field: HIHI 3.0
field: LOW 1.7
field: LOLO 1.5
field: LSV MINOR
field: LLSV MAJOR
field: HSV MINOR
field: HHSV MAJOR
io: i
'}
fFlow_2_val : LREAL;
END_VAR
VAR
fbFlow_2 : FB_AnalogInput;
END_VAR
fbFlow_2(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fFlow_2_val := fbFlow_2.fReal;
SUPER^();
END_FUNCTION_BLOCK
- Related:
FB_Bender
FUNCTION_BLOCK FB_Bender
VAR_IN_OUT
stBender : ST_MotionStage;
bSTOEnable1 : BOOL;
bSTOEnable2 : BOOL;
END_VAR
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
// Simple FB to tie stBender.bHardwareEnable to STO
// Originally part of FB_RunHOMS, but want to use this for all systems, not all of which have a bender motor
stBender.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
END_FUNCTION_BLOCK
- Related:
FB_HomsStats
FUNCTION_BLOCK FB_HomsStats
VAR_INPUT
homs : DUT_HOMS;
fbUpStreamY : ST_MotionStage;
fbUpStreamX : ST_MotionStage;
END_VAR
VAR_OUTPUT
END_VAR
VAR
fEncYScale : LREAL;
fEncXScale : LREAL;
fbDataYGantryDiff : FB_LREALBuffer; // YGantry Data Acquisition FB
fbDataXGantryDiff : FB_LREALBuffer; // XGantry Data Acquisition FB
fbYGantryStats : FB_BasicStats; // Calculate mean/standard deviation of YGantryDiff
fbXGantryStats : FB_BasicStats; // Calculate mean/standard deviation of XGantryDiff
{attribute 'pytmc' := '
pv: YGANDIFFMEAN
io: i
'}
fYGantryDiffMean : LREAL;
{attribute 'pytmc' := '
pv: YGANDIFFSTDEV
io: i
'}
fYGantryDiffStDev : LREAL;
{attribute 'pytmc' := '
pv: XGANDIFFMEAN
io: i
'}
fXGantryDiffMean : LREAL;
{attribute 'pytmc' := '
pv: XGANDIFFSTDEV
io: i
'}
fXGantryDiffStDev : LREAL;
bNewEncArray : BOOL;
{attribute 'pytmc' := '
pv: YGANDIFFARRAY
io: i
'}
aYGantryDiff : ARRAY [1..1000] OF LREAL;
{attribute 'pytmc' := '
pv: XGANDIFFARRAY
io: i
'}
aXGantryDiff : ARRAY [1..1000] OF LREAL;
END_VAR
// Ecn scale for end result stats
fEncYScale := fbUpStreamY.stAxisParameters.fEncScaleFactorNumerator / fbUpstreamY.stAxisParameters.fEncScaleFactorDenominator;
fEncXScale := fbUpStreamX.stAxisParameters.fEncScaleFactorNumerator / fbUpstreamX.stAxisParameters.fEncScaleFactorDenominator;
// Gantry Diff Readback/Storage
fbDataYGantryDiff(bExecute:=True,
fInput:= LINT_TO_LREAL(homs.nCurrGantryY),
arrOutput=>aYGantryDiff,
bNewArray=>bNewEncArray);
fbDataXGantryDiff(bExecute:=True,
fInput:= LINT_TO_LREAL(homs.nCurrGantryX),
arrOutput=>aXGantryDiff,
bNewArray=>bNewEncArray);
fbYGantryStats(aSignal:=aYGantryDiff,
bAlwaysCalc:=TRUE,
fMean=>fYGantryDiffMean,
fStDev=>fYGantryDiffStDev);
fbXGantryStats(aSignal:=aXGantryDiff,
bAlwaysCalc:=TRUE,
fMean=>fXGantryDiffMean,
fStDev=>fXGantryDiffStDev);
//scale outputs to actual values
fYGantryDiffMean := fbYGantryStats.fMean * fEncYScale;
fYGantryDiffStDev := fbYGantryStats.fStDev * fEncYScale;
fXGantryDiffMean := fbXGantryStats.fMean * fEncXScale;
fXGantryDiffStDev := fbXGantryStats.fStDev * fEncXScale;
END_FUNCTION_BLOCK
- Related:
FB_MirrorTwoCoatingProtection
FUNCTION_BLOCK FB_MirrorTwoCoatingProtection
VAR_INPUT
nCurrentEncoderCount : UDINT; // Current encoder count
neVRange : DWORD; // Current ev range from stCurrentBeamParams
sDevName : STRING := ''; // Device name
nUpperCoatingBoundary : UDINT; // Encoder count for upper boundary
sUpperCoatingType : STRING := ''; // Type of coating
nLowerCoatingBoundary : UDINT; // Encoder count for lower boundary
sLowerCoatingType : STRING := ''; // Type of coating
bAutoClear : BOOL := TRUE; // Auto-clear these fast faults
bReadPmpsDb : BOOL; // Trigger a re-read of the JSON Beam Parameters
bUsePmpsDb : BOOL := FALSE; // Set TRUE to lookup Beam Parameters via DB.
nUpperCoatingBitmask : DWORD := 0;
nLowerCoatingBitMask : DWORD := 0;
bMirrorTempFaults : BOOL := FALSE;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
FFO : FB_HardwareFFOutput;
END_VAR
VAR
// Coating Enums for local coding, not a readback.
E_CoatingPos : (Upper:=0, Lower:=1);
ffUpperCoating: FB_FastFault := (
i_xAutoReset := FALSE,
i_TypeCode := 16#401,
i_Desc := ' mirror coating incompatible with beam photon energy');
ffLowerCoating : FB_FastFault := (
i_xAutoReset := FALSE,
i_TypeCode := 16#401,
i_Desc := ' mirror coating incompatible with beam photon energy');
ffBeamParamsNotLoaded : FB_FastFault := (
i_xAutoReset := TRUE,
i_TypeCode := 16#488,
i_Desc := ' mirror coating beam parameters not loaded');
// Mirrors have two RTDs on the chin guard, Left and Right
ffUpperCoatingLTemp : FB_TempSensor_FFO;
ffUpperCoatingRTemp : FB_TempSensor_FFO;
ffLowerCoatingLTemp : FB_TempSensor_FFO;
ffLowerCoatingRTemp : FB_TempSensor_FFO;
aDbStateParams : ARRAY[0..1] OF ST_DbStateParams;
fbGetCoatingBPs : FB_JsonDocToSafeBP;
ftReadJsonDocDone : F_TRIG;
bBPsLoaded : BOOL := FALSE;
i : INT;
sDevState : STRING := '';
bInit : BOOL;
END_VAR
IF NOT bInit THEN
ffUpperCoating.i_DevName := sDevName;
ffLowerCoating.i_DevName := sDevName;
IF bUsePmpsDb THEN
FOR i:=0 to 1 BY 1 DO
sDevState := CONCAT(sDevState, sDevName);
sDevState := CONCAT(sDevState, '-');
CASE i OF
0:
sDevState := CONCAT(sDevState, sUpperCoatingType);
1:
sDevState := CONCAT(sDevState, sLowerCoatingType);
END_CASE
aDbStateParams[i].sPmpsState := sDevState;
sDevState := '';
END_FOR
ELSE
bBPsLoaded := TRUE;
END_IF
bInit := TRUE;
END_IF
IF bUsePmpsDb THEN
IF bReadPmpsDB THEN
bBPsLoaded := FALSE;
END_IF
fbGetCoatingBPs(bExecute := bReadPmpsDB,
jsonDoc := PMPS_GVL.BP_jsonDoc,
sDeviceName:=sDevName,
io_fbFFHWO:=FFO,
arrStates := aDbStateParams);
ftReadJsonDocDone(CLK:=fbGetCoatingBPs.bBusy);
IF ftReadJsonDocDone.Q AND NOT fbGetCoatingBps.bError THEN
bBPsLoaded := TRUE;
END_IF
IF bBPsLoaded THEN
E_CoatingPos := Upper;
nUpperCoatingBitmask := aDbStateParams[E_CoatingPos].stBeamParams.neVRange;
E_CoatingPos := Lower;
nLowerCoatingBitMask := aDbStateParams[E_CoatingPos].stBeamParams.neVRange;
END_IF
END_IF
IF nCurrentEncoderCount <= nLowerCoatingBoundary THEN
ffLowerCoating.i_xOK := (neVRange AND nLowerCoatingBitMask) = neVRange;
ffUpperCoating.i_xOK := TRUE;
IF bMirrorTempFaults THEN
E_CoatingPos := Lower;
ffLowerCoatingLTemp(fFaultThreshold:=aDbStateParams[E_CoatingPos].stReactiveParams.nTempSP,
fHysteresis:= 10.0,
sDevName:= sDevName,
bAutoReset:=FALSE,
io_fbFFHWO:=FFO);
ffLowerCoatingRTemp(fFaultThreshold:=aDbStateParams[E_CoatingPos].stReactiveParams.nTempSP,
fHysteresis:= 10.0,
sDevName:= sDevName,
bAutoReset:=FALSE,
io_fbFFHWO:=FFO);
END_IF
ELSIF nCurrentEncoderCount >= nUpperCoatingBoundary THEN
ffUpperCoating.i_xOK := (neVRange AND nUpperCoatingBitmask) = neVRange;
ffLowerCoating.i_xOK := TRUE;
IF bMirrorTempFaults THEN
E_CoatingPos := Upper;
ffUpperCoatingLTemp(fFaultThreshold:=aDbStateParams[E_CoatingPos].stReactiveParams.nTempSP,
fHysteresis:= 10.0,
sDevName:= sDevName,
bAutoReset:=FALSE,
io_fbFFHWO:=FFO);
ffUpperCoatingRTemp(fFaultThreshold:=aDbStateParams[E_CoatingPos].stReactiveParams.nTempSP,
fHysteresis:= 10.0,
sDevName:= sDevName,
bAutoReset:=FALSE,
io_fbFFHWO:=FFO);
END_IF
ELSE
ffLowerCoating.i_xOK := FALSE;
ffUpperCoating.i_xOK := FALSE;
END_IF
ffBeamParamsNotLoaded.i_xOK := bBPsLoaded;
ffUpperCoating(io_fbFFHWO:=FFO, i_xAutoReset := bAutoClear);
ffLowerCoating(io_fbFFHWO:=FFO, i_xAutoReset := bAutoClear);
ffBeamParamsNotLoaded(io_fbFFHWO:=FFO);
END_FUNCTION_BLOCK
FB_PI_E621_SerialDriver
FUNCTION_BLOCK FB_PI_E621_SerialDriver
VAR_INPUT
/// rising edge execute
i_xExecute: BOOL;
/// Maximum wait time for reply
i_tTimeOut: TIME := TIME#1S0MS;
// i_xReset : BOOL := FALSE; //reset function, for timeout etc
END_VAR
VAR_OUTPUT
q_xDone: BOOL;
q_xError: BOOL;
q_sResult: T_MaxString;
/// Last Strings Sent to Serial Device - for debugging
q_asLastSentStrings: ARRAY[1..50] OF STRING;
/// Last Strings Received from Serial Device - for debugging
q_asLastReceivedStrings: ARRAY[1..50] OF STRING;
END_VAR
VAR_IN_OUT
iq_stPiezoAxis : ST_PiezoAxis;
iq_stSerialRXBuffer: ComBuffer;
iq_stSerialTXBuffer: ComBuffer;
END_VAR
VAR
rtExecute : R_TRIG;
rtTransDone : R_TRIG;
iStep: INT;
sSendData: STRING;
fbPITransaction: FB_PI_E621_SerialTransaction;
fbFormatString: FB_FormatString;
sErrMesg : STRING := 'In step %d fbPITransaction failed with message: %s';
i : INT := 1;
END_VAR
(* S. Stubbs, 2-23-2017 *)
(* This function block drives serial communication with a PI E-816 or compatible comm module.
Note this needs to be re-jiggered if the E-517 is used, uses number rather than letter for axis *)
(* RS232 default settings: 115200 8N1, RTS/CTS
All commands follow format:
CMD X sV.V(Line feed)
Where CMD is the command, X is axis, and sV.V is sign and number (float or int).
Not all commands use axis and parameter, for example ERR?
*)
(* rising edge trigger *)
rtExecute(CLK:= i_xExecute);
IF rtExecute.Q THEN
q_xDone := FALSE;
q_xError := FALSE;
q_sResult:= '';
iq_stPiezoAxis.xDriverError := FALSE;
// i_xReset := FALSE;
a_ClearTrans(); (* to provide rising edge for execute *)
IF iq_stPiezoAxis.sIdn= '' THEN (* Should only need to check identity once *)
iStep := 5;
ELSE
iStep := 10;
END_IF
END_IF
CASE iStep OF
0: (* idle *)
;
(* Commands *)
5: (* Get Identity *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= '*IDN?';
IF fbPITransaction.q_xDone THEN
iq_stPiezoAxis.sIdn := fbPITransaction.q_sResponseData; //Hello I am a piezo
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := 10;
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
10: (* Check Servo Mode
To use manual voltage servo mode must be off *)
(* Response: 0$L or 1$L *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'SVO?';
fbPITransaction.i_sAxis:= iq_stPiezoAxis.sAxis;
IF fbPITransaction.q_xDone THEN
IF FIND('1',fbPITransaction.q_sResponseData) <> 0 THEN //Iff in servo mode, turn it off
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := iStep + 10;
ELSE
a_ClearTrans();
iStep := iStep + 20; //Skip setting servo mode
END_IF
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
20: (* Set Servo Mode *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'SVO';
fbPITransaction.i_sAxis:= iq_stPiezoAxis.sAxis;
fbPITransaction.i_sParam:= '0';
fbPITransaction.i_xExpectReply:= FALSE;
IF fbPITransaction.q_xDone THEN
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := iStep + 10;
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
30: (* Set Voltage, only if needed *)
IF iq_stPiezoAxis.rSetVoltage <> iq_stPiezoAxis.rLastReqVoltage THEN
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'SVA';
fbPITransaction.i_sAxis:= iq_stPiezoAxis.sAxis;
fbPITransaction.i_sParam:=REAL_TO_STRING(iq_stPiezoAxis.rSetVoltage);
fbPITransaction.i_xExpectReply:= FALSE;
IF fbPITransaction.q_xDone THEN
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := iStep + 10;
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
ELSE
iStep := iStep + 30; //Should only need to check error and setpoint if setting voltage
END_IF
40: (* Get Error Code, also resets current error *)
(* Response: integer error code *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'ERR?';
IF fbPITransaction.q_xDone THEN
iq_stPiezoAxis.iCurError := STRING_TO_INT(fbPITransaction.q_sResponseData);
IF iq_stPiezoAxis.iCurError <> 0 THEN
iq_stPiezoAxis.iLastError:= iq_stPiezoAxis.iCurError;
END_IF
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := iStep + 10;
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
50: (* Get Last Requested Piezo Voltage *)
(* Response: (float)$L *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'SVA?';
fbPITransaction.i_sAxis:= iq_stPiezoAxis.sAxis;
IF fbPITransaction.q_xDone THEN
iq_stPiezoAxis.rLastReqVoltage := STRING_TO_REAL(fbPITransaction.q_sResponseData);
//Check and reset attempts if it went through
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := iStep + 10;
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
60: (* Get Actual Piezo Voltage *)
(* Response: (float)$L *)
fbPITransaction.i_xExecute:= TRUE;
fbPITransaction.i_sCmd:= 'VOL?';
// E-517 works differently, uses number rather than letter for axis
fbPITransaction.i_sAxis:= iq_stPiezoAxis.sAxis;
IF fbPITransaction.q_xDone THEN
iq_stPiezoAxis.rActVoltage := STRING_TO_REAL(fbPITransaction.q_sResponseData);
a_ClearTrans(); (* to provide rising edge for execute *)
iStep := 8000; (* Done *)
ELSIF fbPITransaction.q_xError THEN
a_ErrorMesg();
iStep := 9000;
END_IF
8000: (* done *)
q_xDone := TRUE;
IF i_xExecute = FALSE THEN
q_xDone:= FALSE;
iStep := 0;
END_IF
9000: (* Error *)
a_ClearTrans(); (* to provide rising edge for execute *)
IF fbPITransaction.q_xTimeout THEN
iStep:=10;//start over
ELSE
q_xError := TRUE;
iq_stPiezoAxis.xDriverError := TRUE;
END_IF
END_CASE
//call transaction
fbPITransaction(
iq_stSerialRXBuffer:= iq_stSerialRXBuffer,
iq_stSerialTXBuffer:= iq_stSerialTXBuffer);
iq_stPiezoAxis.xTimeout:=fbPITransaction.q_xTimeout;
(* Rising edge trigger to take care of debugging history *)
rtTransDone(CLK:= fbPITransaction.q_xDone);
IF rtTransDone.Q THEN
q_asLastSentStrings[i] := fbPITransaction.q_sLastSentString;
q_asLastReceivedStrings[i] := fbPITransaction.q_sLastReceivedString;
i := i + 1;
END_IF
IF i = 51 THEN i := 1; END_IF
END_FUNCTION_BLOCK
ACTION a_ClearTrans:
(* Refactor this action to match your transaction *)
fbPITransaction.i_xExecute := TRUE;
fbPITransaction.i_sCmd:= ''; //Input args are Cmd, Axis and Param
fbPITransaction.i_sAxis:= '';
fbPITransaction.i_sParam:= '';
fbPITransaction(
i_tTimeOut:= i_tTimeOut,
iq_stSerialRXBuffer:= iq_stSerialRXBuffer,
iq_stSerialTXBuffer:= iq_stSerialTXBuffer );
fbPITransaction.i_xExecute := FALSE;
fbPITransaction(
i_tTimeOut:= i_tTimeOut,
iq_stSerialRXBuffer:= iq_stSerialRXBuffer,
iq_stSerialTXBuffer:= iq_stSerialTXBuffer );
fbPITransaction.i_xExpectReply:=TRUE;
END_ACTION
ACTION a_ErrorMesg:
fbFormatString( sformat:=sErrMesg,
arg1:=F_INT(iStep),
arg2:=F_STRING(fbPITransaction.q_sResult),
sOut => q_sResult);
END_ACTION
ACTION a_UnknownError:
q_sResult:= 'Unknown error';
fbFormatString( sformat:=sErrMesg,
arg1:=F_INT(iStep),
arg2:=F_STRING(q_sResult), //Little silly, but have to do this because F_STRING requires read/write access
sOut => q_sResult);
END_ACTION
- Related:
FB_PI_E621_SerialTransaction
FUNCTION_BLOCK FB_PI_E621_SerialTransaction
VAR_INPUT
/// rising edge execute
i_xExecute: BOOL;
/// Maximum wait time for reply
i_tTimeOut: TIME := TIME#1s0ms;
// Command field
i_sCmd: T_MaxString;
// Axis field
i_sAxis: T_MaxString;
// Parameter field
i_sParam: T_MaxString;
// Does command have a reply? Default behavior is the same as the other drivers.
i_xExpectReply: BOOL := TRUE;
END_VAR
VAR_OUTPUT
q_xDone: BOOL;
q_sResponseData: STRING;
q_xError: BOOL;
q_xTimeout: BOOL;
q_sResult: T_MaxString;
/// Last String Sent to Serial Device - for debugging
q_sLastSentString: STRING;
/// Last String Received from Serial Device - for debugging
q_sLastReceivedString: STRING;
END_VAR
VAR_IN_OUT
iq_stSerialRXBuffer: ComBuffer;
iq_stSerialTXBuffer: ComBuffer;
END_VAR
VAR
rtExecute: R_TRIG;
iStep: INT;
fbClearComBuffer: ClearComBuffer;
sSendString: STRING;
fbFormatString: FB_FormatString;
iChecksum: INT;
fbSendString: SendString;
fbReceiveString: ReceiveString;
sReceivedString: STRING;
tonTimeout: TON;
sRXStringForChecksum: STRING;
sReceiveStringWOChecksum: STRING;
sRXCheckSum: STRING;
sRXAddress: STRING;
sRXParmNum: STRING;
END_VAR
(* This function block performs serial transactions with a PI E-816 or compatible comm module *)
(* rising edge trigger *)
rtExecute(CLK:= i_xExecute);
IF rtExecute.Q THEN
q_xDone := FALSE;
q_sResponseData := '';
q_xError := FALSE;
q_sResult:= '';
q_sLastSentString := '';
q_sLastReceivedString:= '';
iStep := 10;
END_IF
CASE iStep OF
0:
; (* idle *)
10: (* clear com buffers *)
fbClearComBuffer(Buffer:= iq_stSerialRXBuffer);
fbClearComBuffer(Buffer:= iq_stSerialTXBuffer);
(* build the send string *)
IF i_sParam = '' AND i_sAxis <> '' THEN //Axis but no parameter
fbFormatString( sFormat:= '%s %s$L',
arg1:= F_STRING(i_sCmd),
arg2:= F_STRING(i_sAxis),
sOut=> sSendString);
ELSIF i_sParam <> '' AND i_sAxis = '' THEN //Parameter but no axis, global command
fbFormatString( sFormat:= '%s %s$L',
arg1:= F_STRING(i_sCmd),
arg2:= F_STRING(i_sParam), //May not work for all commands, good enough for now
sOut=> sSendString);
ELSIF i_sParam = '' AND i_sAxis = '' THEN //Global Query/Command
fbFormatString( sFormat:= '%s$L',
arg1:= F_STRING(i_sCmd),
sOut=> sSendString);
ELSE
fbFormatString( sFormat:= '%s %s %s$L',
arg1:= F_STRING(i_sCmd),
arg2:= F_STRING(i_sAxis),
arg3:= F_STRING(i_sParam), //May not work for all commands, good enough for now
sOut=> sSendString);
END_IF
(* send it *)
fbSendString( SendString:= sSendString, TXbuffer:= iq_stSerialTXBuffer );
q_sLastSentString := sSendString;
iStep := iStep + 10;
20: (* Finish sending the String *)
IF fbSendString.Busy THEN
fbSendString( SendString:= sSendString, TXbuffer:= iq_stSerialTXBuffer );
ELSIF fbSendString.Error <> 0 THEN
q_sResult := CONCAT('In step 20 fbSendString resulted in error: ', INT_TO_STRING(fbSendString.Error));
iStep := 9000;
ELSIF NOT fbSendString.Busy THEN
IF i_xExpectReply THEN
iStep := iStep + 10;
ELSE //No reply expected, transaction complete
q_xDone:= TRUE;
q_sResult := 'Success.';
q_xTimeout := FALSE; //no timeout
iStep := 100;
END_IF
END_IF
(* Reset receive *)
fbReceiveString(
Reset:= TRUE,
ReceivedString:= sReceivedString,
RXbuffer:= iq_stSerialRXBuffer );
tonTimeout(IN:= FALSE);
30: (* Get reply, if there is one *)
fbReceiveString(
Prefix:= ,
Suffix:= '$L',
Timeout:= i_tTimeOut,
Reset:= FALSE,
ReceivedString:= sReceivedString,
RXbuffer:= iq_stSerialRXBuffer );
tonTimeout(IN:= TRUE, PT:= i_tTimeOut);
IF fbReceiveString.Error <> 0 AND fbReceiveString.Error <> 16#1008 THEN //16#1008 is timeout error
q_sResult := CONCAT('In step 30 fbReceiveString resulted in error: ', INT_TO_STRING(fbReceiveString.Error));
iStep := 9000;
ELSIF fbReceiveString.RxTimeout OR tonTimeout.Q THEN
q_sResult := 'Device failed to reply within timeout period';
q_xTimeout := TRUE;
iStep := 9000;
ELSIF fbReceiveString.StringReceived THEN
q_xTimeout := FALSE; //no timeout
q_sLastReceivedString := sReceivedString;
q_sResponseData := sReceivedString;
q_sResult := 'Success.';
q_xDone:= TRUE;
iStep := 100;
END_IF
100: (* done *)
IF i_xExecute = FALSE THEN
q_xDone:= FALSE;
iStep := 0;
END_IF
9000:
q_xError := TRUE;
END_CASE
END_FUNCTION_BLOCK
FB_PiezoControl
FUNCTION_BLOCK FB_PiezoControl
VAR_IN_OUT
iq_Piezo : ST_PiezoAxis;
END_VAR
VAR_INPUT
xExecute : BOOL; //Rising edge being piezo motion
xReset : BOOL;
Enable_Positive : BOOL; //Reverse of Positive Limit Switch
Enable_Negative : BOOL; //Reverse of Negative Limit Switch
END_VAR
VAR_OUTPUT
xBusy : BOOL; //Busy remains true while piezo position is being adjusted
xDone : BOOL; //Reached target position
xError : BOOL; //General error
xLimited: BOOL; //Piezo move was limited
END_VAR
VAR
E_State : E_PiezoControl; //ENUM for Piezo Control State
rtStartMove : R_TRIG; //Rising Trigger for Execution
rtReset : R_TRIG; //Rising Trigger for Error reset
rSetpoint : REAL; //Internal Storage of Setpoint
rReqVoltage : REAL; //requested voltage
rLLSV: REAL := 0;
rHLSV: REAL := 120;
fbPI: FB_CTRL_PI;
fbRamp: FB_CTRL_RAMP_GENERATOR_EXT;
// FB initialized flag
bInitialized: BOOL;
//Get cycle time for control FBs
fbGetCycleTime : FB_CTRL_GET_TASK_CYCLETIME;
tTaskCycleTime: TIME;
bCycleTimeValid: BOOL;
rtVoltMode: R_TRIG;
fOut: LREAL;
fPiezoBias: LREAL := 60;
fScale: REAL := -60;
tonPiezoDone: TON := (PT:=T#2S);
tonPiezoLimited: TON := (PT:=T#500MS);
xVoltageLimited: BOOL;
ftEnPos : F_TRIG;
ftEnNeg : F_TRIG;
rtEnPos : R_TRIG;
rtEnNeg : R_TRIG;
fOutLimitHolder : LREAL; //holds the limit value until restored
fOutHiLimHolder : LREAL; //holds the limit value until restored
fOutLoLimHolder : LREAL; //holds the limit value until restored
xFirstPass : BOOL := TRUE;
END_VAR
// FB Piezo Control
//Triggers
///////////////////////////////
rtStartMove(CLK:=xExecute);
rtReset(CLK:=iq_Piezo.xEnable);
rtVoltMode(CLK:=iq_Piezo.xVoltageMode);
//Status bits
///////////////////////////
xBusy S= rtStartMove.Q;
xDone R= rtStartMove.Q;
//Keep requested voltage to within limits
iq_Piezo.rReqVoltage := LIMIT(iq_Piezo.LowerVoltage, iq_Piezo.rReqVoltage, iq_Piezo.UpperVoltage);
//Limits
(* These appear flipped, but in-fact are not *)
ftEnPos(CLK:=Enable_Positive);
ftEnNeg(CLK:=Enable_Negative);
rtEnPos(CLK:=Enable_Positive);
rtEnNeg(CLK:=Enable_Negative);
IF xFirstPass THEN
//Want to hold the limits on first pass if a switch is hit.
(* When we move off the limit, we'll restore the init value (usually 1). This will be reset
to something less than 1 when the limit gets tripped again, because presumably the actual limit
would have been set at a value < 1 if the system had been runing.
We just need to hold the init value to make it past this edge case that is present at startup. *)
IF NOT Enable_Positive THEN fOutHiLimHolder := iq_Piezo.stPIParams.fOutMaxLimit; END_IF
IF NOT Enable_Negative THEN fOutLoLimHolder := iq_Piezo.stPIParams.fOutMinLimit; END_IF
ELSE
IF ftEnPos.Q THEN
rLLSV := iq_Piezo.rSetVoltage;
fOutHiLimHolder := iq_Piezo.stPIParams.fOutMaxLimit;
iq_Piezo.stPIParams.fOutMaxLimit := fbPI.fOut;
ELSIF rtEnPos.Q THEN
rLLSV := iq_Piezo.LowerVoltage;
iq_Piezo.stPIParams.fOutMaxLimit := fOutHiLimHolder;
END_IF
IF ftEnNeg.Q THEN
rHLSV := iq_Piezo.rSetVoltage;
fOutLoLimHolder := iq_Piezo.stPIParams.fOutMinLimit;
iq_Piezo.stPIParams.fOutMinLimit := fbPI.fOut;
ELSIF rtEnNeg.Q THEN
rHLSV := iq_Piezo.UpperVoltage;
iq_Piezo.stPIParams.fOutMinLimit := fOutLoLimHolder;
END_IF
END_IF
// Don't do anything until we're ready
IF bInitialized THEN
// While the block is working, a new position may be requested, this is OK
IF xBusy THEN
fbPI.fSetpointValue := iq_Piezo.rReqAbsPos;
END_IF
(* The next chunk of code prevents the PI block from winding up.
First, when the PI block begins to request a voltage that is
beyond the permitted range (this range is affected by the state
of limit switches/ or enable fwd/bwd), we latch the requested position.
Presumeably this position request *)
//Select the PI block control mode
////////////////////////////////////////
IF iq_Piezo.xVoltageMode THEN
//Set PI block to idle
fbPI.eMode := eCTRL_MODE_PASSIVE;
rReqVoltage := iq_Piezo.rReqVoltage; //TODO add a ramp
ELSE
IF iq_Piezo.xIdleMode THEN
rReqVoltage := fScale * 0 + fPiezoBias;
fbPI.eMode := eCTRL_MODE_MANUAL;
ACT_Controller();
fbPI.bHold := TRUE;
ELSE
//Fout is connected to the piezo voltage control
rReqVoltage := fScale * fbPI.fOut + fPiezoBias;
fbPI.bHold := FALSE;
//Control mode is always active, so compensation takes over more smoothly
fbPI.eMode := eCTRL_MODE_ACTIVE;
END_IF
END_IF
ACT_Controller();
xVoltageLimited := rLLSV > rReqVoltage OR rHLSV < rReqVoltage;
//This is where the voltage request gets sent to the piezo driver
iq_Piezo.rSetVoltage := LIMIT(rLLSV, rReqVoltage, rHLSV);
//Initialization
ELSE
fbGetCycleTime( eMode := eCTRL_MODE_ACTIVE,
tTaskCycleTime => tTaskCycleTime,
bCycleTimeValid => bCycleTimeValid);
IF bCycleTimeValid THEN
iq_Piezo.stPIParams.tTaskCycleTime := tTaskCycleTime;
iq_Piezo.stPIParams.tCtrlCycleTime := tTaskCycleTime;
bInitialized := TRUE;
END_IF
END_IF
tonPiezoDone.IN := WithinRange(ValA:=iq_Piezo.rActPos, Center:=iq_Piezo.rReqAbsPos, Range:=iq_Piezo.rPiezoDmovRange, Offset:=0)
AND NOT rtStartMove.Q; //rtStartMove interrupts the timer, resetting it
tonPiezoDone();
tonPiezoLimited.IN := (fbPI.bARWactive OR xVoltageLimited) AND NOT rtStartMove.Q;
tonPiezoLimited();
xDone S= xBusy AND (tonPiezoDone.Q OR tonPiezoLimited.Q);
xLimited := tonPiezoLimited.Q;
xBusy R= xDone;
xFirstPass := FALSE;
END_FUNCTION_BLOCK
ACTION ACT_CheckLimits:
END_ACTION
ACTION ACT_Controller:
END_ACTION
- Related:
FB_PitchControl
FUNCTION_BLOCK FB_PitchControl
VAR_IN_OUT
Pitch : HOMS_PitchMechanism;
Stepper : ST_MotionStage;
END_VAR
VAR_INPUT
lrCurrentSetpoint : LREAL; // Setpoint: Epics writes to ST_MotionStage which gets fed into this
END_VAR
VAR_OUTPUT
q_bError : BOOL;
q_bDone : BOOL;
q_bBusy : BOOL;
END_VAR
VAR
// Logging
stDiag : ST_fbDiagnostics;
fbFormatString : FB_FormatString;
{attribute 'instance-path'}
{attribute 'no_init'}
POUName : T_MaxString; // Name of the POU for logging/error reporting
// Stepper Motion
lrActPos : LREAL; // Actual Position of piezo mechanism
lrPrevStepperPos : LREAL; // Previous successfully achieved stepper position
ftLimitSwitch : F_TRIG;
lrOriginalPosRequest : LREAL; // Used for logging
lrLastSetpoint : LREAL; // Previous successfully achieved setpoint
fbMotionRequest : FB_MotionRequest;
fbMotionStage : FB_MotionStage;
bLimitHit : BOOL;
tonStepperHold : TON := (PT:=T#100MS); // Timer to hold stepper position while the system relaxes
rSettledRange : REAL := 5.0; // Units = urad
bResetStepper : BOOL;
bExecuteStepper : BOOL;
enumMotionRequest : ENUM_MotionRequest := ENUM_MotionRequest.WAIT; // Wait for move to complete before taking another request
// Piezo
tonPiezoSettled : TON := (PT:=T#2S);
fbPiezoControl : FB_PiezoControl;
rtPiezoMoveDone : R_TRIG;
// State Machine
PC_State : E_PitchControl := PCM_Init;
bCoarse50PiezoMove : BOOL;
END_VAR
(* HOMS Pitch Control
A. Wallace
J. Sheppard - Updating to new lcls-twincat-motion API
The HOMS Pitch mechanism consists of a stepper and piezo that work together to adjust
the pitch of the mirror assembly.
Pitch control state machine
If the target position is beyond the range of the piezo mechanism,
execute a coarse pitch move with the stepper.
The target of the coarse move shall be set to the requested position.
Once coarse motion has completed the coarse motion drive position
correction output shall be set to zero.
Fine pitch motion with the piezo will be initiated to finish closing the loop.
The piezo mechanism can actuate ~ 180urad or 90um.
*)
lrActPos := Stepper.stAxisStatus.fActPosition;
// If we hit a limit during a move, we need to change the setpoint
ftLimitSwitch(CLK:=Stepper.bAllForwardEnable AND Stepper.bAllBackwardEnable);
IF ftLimitSwitch.Q THEN
bExecuteStepper := FALSE;
bLimitHit := TRUE;
lrCurrentSetpoint := lrActPos;
END_IF
// Left out Manual Mode Switch and Tweak FBs
// State Machine
CASE PC_State OF
PCM_Init:
lrCurrentSetpoint := lrActPos;
lrLastSetpoint := lrCurrentSetpoint;
lrPrevStepperPos := lrCurrentSetpoint;
PC_State := PCM_Standby;
PCM_Standby:
// Waits for move requests and determines if they are valid
IF (lrLastSetpoint <> lrCurrentSetpoint) THEN // lrLastSetpoint initially set in PCM_Done
// Check for bad setpoints -> revert to previous setpoint
IF (lrCurrentSetpoint > Pitch.ReqPosLimHi) OR (lrCurrentSetpoint < Pitch.ReqPosLimLo) OR NOT Stepper.bHardwareEnable THEN
// Outside range of limit switches or bHardwareEnable is FALSE
ACT_ResetSetpoint();
ELSIF lrCurrentSetpoint > lrLastSetpoint AND NOT Stepper.bAllForwardEnable THEN
// Forward move when on HL
ACT_ResetSetpoint();
ELSIF lrCurrentSetpoint < lrLastSetpoint AND NOT Stepper.bAllBackwardEnable THEN
// Backward move when on LL
ACT_ResetSetpoint();
END_IF
// If the current setpoint still differs from the prvious, we know the move is safe and OK to proceed
IF lrLastSetpoint <> lrCurrentSetpoint THEN
q_bDone := FALSE;
PC_State := PCM_MoveRequested;
END_IF
END_IF
PCM_MoveRequested:
// A move has been requested, is it within range of the piezo?
IF WithinRange(ValA:=lrCurrentSetpoint, Center:=lrPrevStepperPos, Range:=GVL_Constants.cPiezoRange, Offset:=0) THEN
// Move is within the nominal range of the piezo
fbFormatString.sFormat := 'Within range, fine move %f';
fbFormatString.arg1 := F_LREAL(lrCurrentSetpoint);
fbFormatString(sOut=>stDiag.asResults[stDiag.resultIdx.IncVal()]);
PC_State := PCM_FineMove;
ELSE
// Out of range, head to coarse move
fbFormatString.arg1 := F_LREAL(lrCurrentSetpoint);
fbFormatString.sFormat := 'OoR, using stepper %f';
fbFormatString(sOut=>stDiag.asResults[stDiag.resultIdx.IncVal()]);
PC_State := PCM_Coarse50Piezo;
END_IF
PCM_Coarse50Piezo:
// A coarse move uses the stepper to do a best-effort position
// First set the piezo to nominal 50% extension using idle mode
//////////////////////////////////////////////////////////////////////////////
Pitch.Piezo.xIdleMode := TRUE;
// Indicate we are doing the coarse 50% piezo move
bCoarse50PiezoMove := TRUE;
// Wait for piezo to settle
tonPiezoSettled.IN := TRUE;
bCoarse50PiezoMove R= tonPiezoSettled.Q;
IF tonPiezoSettled.Q THEN
//Piezo has moved to 50% position, finish with the stepper
PC_State := PCM_CoarseMove;
tonPiezoSettled.IN := FALSE;
END_IF
PCM_CoarseMove:
// With the piezo at a nominal 50% extension, move the stepper to requested position
bExecuteStepper := TRUE;
// Timer that waits to start until stepper is within range of the setpoint
tonStepperHold.IN := WithinRange(ValA:=LREAL_TO_REAL(lrActPos), Center:=lrCurrentSetpoint, Range:=rSettledRange, Offset:=0);
tonStepperHold(); // call this here to reset Q just below on first cycle
// If the coarse move is complete, finish position correction with the piezo
IF tonStepperHold.Q OR ftLimitSwitch.Q THEN
PC_State := PCM_CoarseMoveCleanup;
lrPrevStepperPos := lrActPos;
ELSIF Stepper.bError THEN
bExecuteStepper := FALSE;
PC_State := PCM_StepperError;
// Left out logging
END_IF
PCM_CoarseMoveCleanup:
bExecuteStepper := FALSE;
PC_State := PCM_FineMove;
PCM_FineMove:
Pitch.Piezo.xIdleMode := FALSE;
fbPiezoControl.xExecute := TRUE;
IF bLimitHit THEN
Pitch.Piezo.rReqAbsPos := lrActPos;
ELSE
Pitch.Piezo.rReqAbsPos := lrCurrentSetpoint;
END_IF
rtPiezoMoveDone(CLK:=fbPiezoControl.xDone);
IF rtPiezoMoveDone.Q THEN
fbPiezoControl.xExecute := FALSE;
PC_State := PCM_Done;
END_IF
PCM_Done:
// Set the previously requested position here
lrLastSetpoint := lrCurrentSetpoint;
bLimitHit := FALSE;
// Indicate we're done
q_bDone := TRUE;
// Move back to standby
PC_State := PCM_Standby;
PCM_StepperError:
PC_State := PCM_Init;
PCM_PiezoError:
PC_State := PCM_Init;
PCM_OtherError:
PC_State := PCM_Init;
END_CASE
fbMotionStage(stMotionStage:=Stepper);
// Transfer to the Piezo
Pitch.Piezo.rActPos := lrActPos;
tonPiezoSettled();
tonStepperHold();
fbPiezoControl(iq_Piezo:=Pitch.Piezo,
Enable_Positive:=Stepper.bLimitForwardEnable,
Enable_Negative:=Stepper.bLimitBackwardEnable);
END_FUNCTION_BLOCK
ACTION ACT_ResetSetpoint:
// Action to reset the Setpoint to the previous value when:
// - New setpoint outside range of soft limits
// - bHardwareEnable is FALSE
// - Limit switches are hit and new setpoint the direction of the hit switch
lrOriginalPosRequest := lrCurrentSetpoint;
lrCurrentSetpoint := lrLastSetpoint;
// Only want to log one warning about a bad position request
IF lrOriginalPosRequest <> lrCurrentSetpoint THEN
// Log a warning
fbFormatString.sFormat := 'Pitch req OoR fb (%s), reset within limits, %f';
fbFormatString.arg1 := F_STRING(POUName);
fbFormatString.arg2 := F_LREAL(lrOriginalPosRequest);
fbFormatString(sOut=>stDiag.asResults[stDiag.resultIdx.IncVal()]);
PC_State := PCM_Standby;
END_IF
END_ACTION
FB_RMSWatch
FUNCTION_BLOCK FB_RMSWatch
VAR_INPUT
END_VAR
VAR_OUTPUT
// RMS Error
fMaxRMSError : LREAL := 0;
fMinRMSError : LREAL := 1000; // start at something huge, FB will update with any smaller measured value
END_VAR
VAR_IN_OUT
stMotionStage : ST_MotionStage;
END_VAR
VAR
fEncScalingNum : LREAL := 1.0;
fEncScalingDenom : LREAL := 1.0;
fEncOffset : LREAL := 0;
fEncScale : LREAL := 1.0;
fbDataEncPos : FB_LREALBuffer; // ActPos Data Acquisition FB
fbDataSetPos : FB_LREALBuffer; // SetPos Data Acquisition FB
bExecuteDataStorage : BOOL := TRUE; // Take data of both ActPos and SetPos
bNewEncArray : BOOL;
fbStats : FB_BasicStats; // Calculate mean/standard deviation of ActPos
{attribute 'pytmc' := '
pv: MEAN
io: i
'}
fEncMean : LREAL;
{attribute 'pytmc' := '
pv: STDEV
io: i
'}
fEncStDev : LREAL;
{attribute 'pytmc' := '
pv: RMS
io: i
'}
fCurrRMSError : LREAL := 0;
nIndex : DINT;
fSum : LREAL := 0; // Just for calculating rms
fDiff : LREAL := 0;
{attribute 'pytmc' := '
pv: ACTPOSARRAY
io: i
'}
aEncActPos : ARRAY [1..1000] OF LREAL;
{attribute 'pytmc' := '
pv: SETPOSARRAY
io: i
'}
aEncSetPos : ARRAY [1..1000] OF LREAL;
END_VAR
// Encoder Scaling
fEncScalingNum := stMotionStage.stAxisParameters.fEncScaleFactorNumerator;
fEncScalingDenom := stMotionStage.stAxisParameters.fEncScaleFactorDenominator;
fEncOffset := stMotionStage.stAxisParameters.fEncOffset;
fEncScale := fEncScalingNum / fEncScalingDenom;
// FB to store encoder positions in 1000 element arrays, compute RMS errors, and watch for min/max
// Encoder Readback/Storage
fbDataEncPos(bExecute:=bExecuteDataStorage,
fInput:= ULINT_TO_LREAL(stMotionStage.nRawEncoderULINT),
arrOutput=>aEncActPos,
bNewArray=>bNewEncArray);
fbDataSetPos(bExecute:=bExecuteDataStorage,
fInput:=(stMotionStage.Axis.NcToPlc.SetPos - fEncOffset) / fEncScale,
arrOutput=>aEncSetPos);
fbStats(aSignal:=aEncActPos,
bAlwaysCalc:=TRUE,
fMean=>fEncMean,
fStDev=>fEncStDev);
// Calculate RMS Error:
If bNewEncArray THEN
fCurrRMSError := 0;
FOR nIndex := 2 TO 1000 DO
// First point in array stuck as 0 for some reason...
fDiff := aEncActPos[nIndex] - aEncSetPos[nIndex];
fSum := EXPT(fDiff, 2);
fCurrRMSError := fCurrRMSError + fSum;
END_FOR;
fCurrRMSError := fCurrRMSError / 999.0; // 1000 element array but ditched the first point
fCurrRMSError := SQRT(fCurrRMSError);
// Watch for max:
IF fCurrRMSError > fMaxRMSError THEN
fMaxRMSError := fCurrRMSError;
END_IF
// Watch for min:
IF fCurrRMSError < fMinRMSError THEN
fMinRMSError := fCurrRMSError;
END_IF
fCurrRMSError := fCurrRMSError * fEncScale;
fMaxRMSError := FMaxRMSError * fEncScale;
fMinRMSError := FMinRMSError * fEncScale;
END_IF
//Scale to Actual values
fEncStDev := fEncStDev * fEncScale;
END_FUNCTION_BLOCK
FB_RunHOMS
FUNCTION_BLOCK FB_RunHOMS
VAR_INPUT
// Encoder Reference Values
nYupEncRef : ULINT;
nYdwnEncRef : ULINT;
nXupEncRef : ULINT;
nXdwnEncRef : ULINT;
// Gantry Tolerances
nGantryTolY : LINT := GVL_Constants.nGANTRY_TOLERANCE_NM_DEFAULT; // Encoder counts = nm
nGantryTolX : LINT := GVL_Constants.nGANTRY_TOLERANCE_NM_DEFAULT; // Encoder counts = nm
END_VAR
VAR_OUTPUT
// Gantry coupling status
bGantryAlreadyCoupledY : BOOL;
bGantryAlreadyCoupledX : BOOL;
// Current gantry difference
nCurrGantryY : LINT;
nCurrGantryX : LINT;
END_VAR
VAR_IN_OUT
// Motor Structs
stYup : ST_MotionStage;
stYdwn : ST_MotionStage;
stXup : ST_MotionStage;
stXdwn : ST_MotionStage;
stPitch : ST_MotionStage;
// Manual coupling Gantried Axes
bExecuteCoupleY : BOOL;
bExecuteCoupleX : BOOL;
bExecuteDecoupleY : BOOL;
bExecuteDecoupleX : BOOL;
END_VAR
VAR
// STO Button
bSTOEnable1 AT %I* : BOOL;
bSTOEnable2 AT %I* : BOOL;
// Encoders
stYupEnc AT %I* : ST_RenishawAbsEnc;
stYdwnEnc AT %I* : ST_RenishawAbsEnc;
stXupEnc AT %I* : ST_RenishawAbsEnc;
stXdwnEnc AT %I* : ST_RenishawAbsEnc;
// Autocoupling Gantried Axes
fbAutoCoupleY : FB_GantryAutoCoupling;
fbAutoCoupleX : FB_GantryAutoCoupling;
END_VAR
// Encoder Reference Values
stYupEnc.Ref := nYupEncRef;
stYdwnEnc.Ref := nYdwnEncRef;
stXupEnc.Ref := nXupEncRef;
stXdwnEnc.Ref := nXdwnEncRef;
// Gantry Differences to monitor
nCurrGantryY := ((ULINT_TO_LINT(stYupEnc.Count) - ULINT_TO_LINT(stYupEnc.Ref)) - (ULINT_TO_LINT(stYdwnEnc.Count) - ULINT_TO_LINT(stYdwnEnc.Ref)));
nCurrGantryX := ((ULINT_TO_LINT(stXupEnc.Count) - ULINT_TO_LINT(stXupEnc.Ref)) - (ULINT_TO_LINT(stXdwnEnc.Count) - ULINT_TO_LINT(stXdwnEnc.Ref)));
// Release the hounds!
stYup.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
stYdwn.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
stXup.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
stXdwn.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
stPitch.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
// Start Autocoupling
fbAutoCoupleY(nGantryTol:=nGantryTolY,
Master:=stYup,
MasterEnc:= stYupEnc,
Slave:=stYdwn,
SlaveEnc:=stYdwnEnc,
bExecuteCouple:=bExecuteCoupleY,
bExecuteDecouple:=bExecuteDecoupleY,
bGantryAlreadyCoupled=>bGantryAlreadyCoupledY);
fbAutoCoupleX(nGantryTol:=nGantryTolX,
Master:=stXup,
MasterEnc:= stXupEnc,
Slave:=stXdwn,
SlaveEnc:=stXdwnEnc,
bExecuteCouple:=bExecuteCoupleX,
bExecuteDecouple:=bExecuteDecoupleX,
bGantryAlreadyCoupled=>bGantryAlreadyCoupledX);
END_FUNCTION_BLOCK
- Related:
Main
PROGRAM Main
VAR
(*
// Test Pitch Control
fbPitchControl : FB_PitchControl;
TestPitch : HOMS_PitchMechanism := (ReqPosLimHi:=2000,
ReqPosLimLo:=-2000,
diEncPosLimHi:=10768330,
diEncPosLimLo:=8141680);
M1 : ST_MotionStage;
bPitchDone : BOOL;
*)
// Test Bender vs No Bender
TESTWithBender : DUT_HOMS;
M1 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
M2 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
M3 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
M4 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
M5 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
M6 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS);
fbBender : FB_Bender;
fbMotionStage_m1 : FB_MotionStage;
fbMotionStage_m2 : FB_MotionStage;
fbMotionStage_m3 : FB_MotionStage;
fbMotionStage_m4 : FB_MotionStage;
fbMotionStage_m6 : FB_MotionStage;
END_VAR
(*
// Test Pitch Control
M1.bLimitBackwardEnable;
M1.bLimitForwardEnable;
M1.bHardwareEnable;
M1.fVelocity := 150.0;
fbPitchControl(Pitch:=TestPitch,
Stepper:=M1,
lrCurrentSetpoint:=M1.fPosition,
q_bDone=>bPitchDone,
q_bBusy=>);
IF NOT M1.bHardwareEnable THEN
M1.fPosition := M1.stAxisStatus.fActPosition;
END_IF
*)
// Test Bender vs. No Bender:
// M1L0
M1.bLimitForwardEnable := TRUE;
M1.bLimitBackwardEnable := TRUE;
M1.bPowerSelf := TRUE;
M2.bLimitForwardEnable := TRUE;
M2.bLimitBackwardEnable := TRUE;
M2.bPowerSelf := TRUE;
M3.bLimitForwardEnable := TRUE;
M3.bLimitBackwardEnable := TRUE;
M3.bPowerSelf := TRUE;
M4.bLimitForwardEnable := TRUE;
M4.bLimitBackwardEnable := TRUE;
M4.bPowerSelf := TRUE;
M5.bLimitForwardEnable := TRUE;
M5.bLimitBackwardEnable := TRUE;
M5.bPowerSelf := TRUE;
M6.bLimitForwardEnable := TRUE;
M6.bLimitBackwardEnable := TRUE;
M6.bPowerSelf := TRUE;
TESTWithBender.fbRunHOMS(stYup:=M1,
stYdwn:=M2,
stXup:=M3,
stXdwn:=M4,
stPitch:=M5,
nYupEncRef:=0,
nYdwnEncRef:=0,
nXupEncRef:=0,
nXdwnEncRef:=0,
bExecuteCoupleY:=TESTWithBender.bExecuteCoupleY,
bExecuteCoupleX:=TESTWithBender.bExecuteCoupleX,
bExecuteDecoupleY:=TESTWithBender.bExecuteDecoupleY,
bExecuteDecoupleX:=TESTWithBender.bExecuteDecoupleX,
bGantryAlreadyCoupledY=>TESTWithBender.bGantryAlreadyCoupledY,
bGantryAlreadyCoupledX=>TESTWithBender.bGantryAlreadyCoupledX,
nCurrGantryY=>TESTWithBender.nCurrGantryY,
nCurrGantryX=>TESTWithBender.nCurrGantryX);
fbBender(stBender:=M6,
bSTOEnable1:=TESTWithBender.fbRunHOMS.bSTOEnable1,
bSTOEnable2:=TESTWithBender.fbRunHOMS.bSTOEnable2);
fbMotionStage_m1(stMotionStage:=M1);
fbMotionStage_m2(stMotionStage:=M2);
fbMotionStage_m3(stMotionStage:=M3);
fbMotionStage_m4(stMotionStage:=M4);
fbMotionStage_m6(stMotionStage:=M6);
END_PROGRAM
MC_SmoothMover
FUNCTION_BLOCK MC_SmoothMover
VAR_IN_OUT
Axis : AXIS_REF;
END_VAR
VAR_INPUT
Velocity : LREAL;
ReqAbsPos : LREAL; //New requested position
Enable : BOOL; //While true the block will accept new positions and attempt to move to them if they are different
Execute : BOOL; //Will retry a move if the target position is the same
END_VAR
VAR_OUTPUT
Done : BOOL;
Busy : BOOL;
Error : BOOL;
END_VAR
VAR
mcMoveAbsolute : ARRAY[1..2] OF MC_MoveAbsolute;
iI: INT;
imcBlockIndex: INT;
ReqAbsPosPrevious : LREAL;
rtExecute: R_TRIG;
END_VAR
(* Smooth Mover
2017-8-30
A. Wallace
Enable means the block will always aquire new positions as they are updated. Execute
can be used to retry a move. Axis must be enabled by a power block.
*)
rtExecute(CLK:=Execute);
IF ( (ReqAbsPos <> ReqAbsPosPrevious AND Enable) OR rtExecute.Q) THEN
mcMoveAbsolute[imcBlockIndex].Execute := FALSE;
imcBlockIndex := imcBlockIndex + 1;
IF imcBlockIndex >2 THEN imcBlockIndex := 1; END_IF
mcMoveAbsolute[imcBlockIndex].Position := ReqAbsPos;
mcMoveAbsolute[imcBlockIndex].Execute := TRUE;
ReqAbsPosPrevious := ReqAbsPos;
ELSIF mcMoveAbsolute[imcBlockIndex].Done OR
mcMoveAbsolute[imcBlockIndex].CommandAborted OR
mcMoveAbsolute[imcBlockIndex].Busy OR
mcMoveAbsolute[imcBlockIndex].Error THEN
mcMoveAbsolute[imcBlockIndex].Execute := FALSE;
END_IF
FOR iI := 1 TO 2 DO
mcMoveAbsolute[iI](Axis := Axis, Velocity:=Velocity, BufferMode:=MC_Aborting);
END_FOR
Error := mcMoveAbsolute[1].Error OR mcMoveAbsolute[2].Error;
Done S= mcMoveAbsolute[1].Done OR mcMoveAbsolute[2].Done;
Busy := mcMoveAbsolute[1].Busy OR mcMoveAbsolute[2].Busy;
Done R= Busy OR Error;
END_FUNCTION_BLOCK
TEST_PitchControl
{attribute 'call_after_init'}
FUNCTION_BLOCK TEST_PitchControl EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
LimitSwitches();
END_FUNCTION_BLOCK
METHOD LimitSwitches
VAR_INPUT
END_VAR
VAR
fActPosition : LREAL;
bPitchDone : BOOL;
END_VAR
VAR_INST
fbPitchControl : FB_PitchControl;
ExpertMode : BOOL := FALSE;
PitchManualMode : BOOL := FALSE;
iStep : UINT := 0;
rtDone : R_TRIG;
tonHack : TON := (PT:=T#2s);
END_VAR
VAR CONSTANT
BwdLimPos : LREAL := -500;
FwdLimPos : LREAL := 500;
ForwardTestSP : LREAL := 1000;
END_VAR
fActPosition := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bLimitBackwardEnable := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition > BwdLimPos;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bLimitForwardEnable := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition < FwdLimPos;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bHardwareEnable := TRUE;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fVelocity := 150.0;
fbPitchControl(Pitch:=GVL_TestStructs.TestPitch_LimitSwitches,
q_bDone=>bPitchDone,
q_bBusy=>);
rtDone(CLK:=bPitchDone);
tonHack(IN:=GVL_TestStructs.TestPitch_LimitSwitches.Stepper.Axis.Status.MotionState = MC_AXISSTATE_STANDSTILL);
TEST('PitchControlLimitSwitchTests');
CASE iStep OF
0: // Set SP to 1000, hit a limit, and stop.
// Test failing, not as important with limit switches b/c if you hit the limit you will stop and won't be allowed to move forward until you manually reset the SP to within limits
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition := ForwardTestSP;
TEST('Forward Limit Switch Stop');
IF fActPosition > FwdLimPos AND rtDone.Q THEN
// AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = fActPosition, 'Setpoint did not reset to stopped position');
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.Axis.Status.MotionState = MC_AXISSTATE_STANDSTILL AND NOT GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bAllForwardEnable, 'Did not stop at forward limit');
TEST_FINISHED_NAMED('Forward Limit Switch Stop');
iStep := 20;
END_IF
20: // Attempt to back off limit and succeed
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition := FwdLimPos -1;
TEST('Forward Limit BO');
IF rtDone.Q THEN
// AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition, 'Position not at back off position');
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = fActPosition, 'Position not at back off position');
TEST_FINISHED_NAMED('Forward Limit BO');
iStep := 30;
END_IF
30: // Set SP to beyond bwd limit and stop
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition := -1*ForwardTestSP;
TEST('Bwd Limit Switch Stop');
IF fActPosition > FwdLimPos AND rtDone.Q THEN
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = fActPosition, 'Setpoint did not reset to stopped position');
TEST_FINISHED_NAMED('Bwd Limit Switch Stop');
iStep := 50;
END_IF
40: // Attempt to move again past bwd limit and be denied
50: // Attempt to back off limit and succeed
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition := BwdLimPos +1;
TEST('Bwd Limit BO');
IF rtDone.Q THEN
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = fActPosition, 'Position not at back off position');
TEST_FINISHED_NAMED('Bwd Limit BO');
iStep := 8000;
END_IF
60:
8000:
TEST_FINISHED_NAMED('PitchControlLimitSwitchTests');
END_CASE
END_METHOD
METHOD StepperPiezoExchange
VAR_INPUT
END_VAR
VAR
fActPosition : LREAL;
END_VAR
VAR_INST
fbPitchControl : FB_PitchControl;
ExpertMode : BOOL := FALSE;
PitchManualMode : BOOL := FALSE;
iStep : UINT := 0;
END_VAR
VAR CONSTANT
BwdLimPos : LREAL := -500;
FwdLimPos : LREAL := 500;
ForwardTestSP : LREAL := 1000;
END_VAR
fActPosition := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bLimitBackwardEnable := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition > BwdLimPos;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bLimitForwardEnable := GVL_TestStructs.TestPitch_LimitSwitches.Stepper.stAxisStatus.fActPosition < FwdLimPos;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.bHardwareEnable := TRUE;
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fVelocity := 150.0;
fbPitchControl(Pitch:=GVL_TestStructs.TestPitch_LimitSwitches,
q_bDone=>,
q_bBusy=>);
CASE iStep OF
0: // Set SP to 1000, hit a limit, and stop.
GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition := ForwardTestSP;
TEST('Forward Limit Switch Stop');
IF fActPosition > FwdLimPos AND GVL_TestStructs.TestPitch_LimitSwitches.Stepper.Axis.Status.MotionState = MC_AXISSTATE_STANDSTILL THEN
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.Axis.Status.MotionState = MC_AXISSTATE_STANDSTILL, 'Axis not at standstill');
AssertTrue(GVL_TestStructs.TestPitch_LimitSwitches.Stepper.fPosition = fActPosition, 'Setpoint did not reset to stopped position');
TEST_FINISHED_NAMED('Forward Limit Switch Stop');
END_IF
10: // Attempt to move again past fwd limit and be denied
20: // Attempt to back off limit and succeed
30: // Set SP to beyond bwd limit and stop
40: // Attempt to move again past bwd limit and be denied
50: // Attempt to back off limit and succeed
END_CASE
END_METHOD
- Related:
WithinRange
FUNCTION WithinRange : BOOL
VAR_INPUT
ValA : REAL; //New position to evaluate
Center : REAL; //Current position
Range : REAL; //Span of the range
Offset : REAL := 0; //Offset from center if the range is non-symetric
END_VAR
VAR
END_VAR
IF ValA < (Center + Offset - (Range/2) ) THEN
WithinRange := FALSE;
ELSIF ValA > (Center + Offset + (Range/2) ) THEN
WithinRange := FALSE;
ELSE
WithinRange := TRUE;
END_IF
END_FUNCTION