DUTs
E_ATM_States
{attribute 'qualified_only'}
TYPE E_ATM_States :
(
Unknown := 0,
OUT := 1,
TARGET1 := 2,
TARGET2 := 3,
TARGET3 := 4,
TARGET4 := 5,
TARGET5 := 6
) UINT;
END_TYPE
- Related:
E_LIC_States
{attribute 'qualified_only'}
TYPE E_LIC_States :
(
Unknown := 0,
OUT := 1,
MIRROR1 := 2,
MIRROR2 := 3,
TARGET := 4
) UINT;
END_TYPE
E_PPM_States
{attribute 'qualified_only'}
TYPE E_PPM_States :
(
Unknown := 0,
OUT := 1,
POWERMETER := 2,
YAG1 := 3,
YAG2 := 4
) UINT;
END_TYPE
E_SXR_SATT_Position
{attribute 'qualified_only'}
TYPE E_SXR_SATT_Position :
(
UNKNOWN := 0, // UNKNOWN must be in slot 0 or the FB breaks
OUT := 1, // OUT at slot 1 is a convention
FILTER1 := 2,
FILTER2 := 3,
FILTER3 := 4,
FILTER4 := 5,
FILTER5 := 6,
FILTER6 := 7,
FILTER7 := 8,
FILTER8 := 9
) UINT;
END_TYPE
E_WFS_States
{attribute 'qualified_only'}
TYPE E_WFS_States :
(
Unknown := 0,
OUT := 1,
TARGET1 := 2,
TARGET2 := 3,
TARGET3 := 4,
TARGET4 := 5,
TARGET5 := 6
) UINT;
END_TYPE
E_XPIM_Filters
{attribute 'qualified_only'}
TYPE E_XPIM_Filters :
(
Unknown := 0,
T50 := 1,
T25 := 2,
T10 := 3,
T5 := 4,
T1 := 5,
T100 := 6
) UINT;
END_TYPE
E_XPIM_States
{attribute 'qualified_only'}
TYPE E_XPIM_States :
(
Unknown := 0,
OUT := 1,
YAG := 2,
DIAMOND := 3,
RETICLE := 4
) UINT;
END_TYPE
ST_SATT_Filter
TYPE ST_SATT_Filter :
STRUCT
{attribute 'pytmc' := '
pv: MATERIAL
io: input
field: DESC Filter material name
'}
sFilterMaterial : STRING;
{attribute 'pytmc' := '
pv: THICKNESS
io: input
field: DESC Filter material thickness
field: EGU um
'}
fFilterThickness_um : LREAL;
END_STRUCT
END_TYPE
GVLs
Global_Version
{attribute 'TcGenerated'}
// This function has been automatically generated from the project information.
VAR_GLOBAL CONSTANT
{attribute 'const_non_replaced'}
{attribute 'linkalways'}
stLibVersion_lcls_twincat_common_components : ST_LibVersion := (iMajor := 3, iMinor := 4, iBuild := 0, iRevision := 0, sVersion := '3.4.0');
END_VAR
POUs
FB_ATM
FUNCTION_BLOCK FB_ATM
(*
Function block for Arrival Time Monitor (ATM) controls:
- X, Y motion
- Y target states
- thermocouple
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// X motor (align target to beam).
stXStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the TARGET1 state.
stTarget1: ST_PositionState;
// Settings for the TARGET2 state.
stTarget2: ST_PositionState;
// Settings for the TARGET3 state.
stTarget3: ST_PositionState;
// Settings for the TARGET4 state.
stTarget4: ST_PositionState;
// Settings for the TARGET5 state.
stTarget5: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_ATM_States;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_ATM_States;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbXStage: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..6
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
{attribute 'pytmc' := 'pv: STC:01'}
fbThermoCouple1: FB_TempSensor;
{attribute 'pytmc' :='pv: FWM'}
fbFlowMeter: FB_AnalogInput := (iTermBits:=15, fTermMax:=60, fTermMin:=0);
END_VAR
VAR CONSTANT
// State defaults if not provided
fDelta: LREAL := 2;
fAccel: LREAL := 200;
fOutDecel: LREAL := 25;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
stXStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fOutDecel);
fbStateDefaults(stPositionState:=stTarget1, sNameDefault:='TARGET1', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget2, sNameDefault:='TARGET2', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget3, sNameDefault:='TARGET3', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget4, sNameDefault:='TARGET4', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget5, sNameDefault:='TARGET5', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
stXStage.bLimitForwardEnable := TRUE;
stXStage.bLimitBackwardEnable := TRUE;
stXStage.bHardwareEnable := TRUE;
stXStage.bPowerSelf := TRUE;
fbYStage(stMotionStage:=stYStage);
fbXStage(stMotionStage:=stXStage);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_ATM_States.OUT] := stOut;
astPositionState[E_ATM_States.TARGET1] := stTarget1;
astPositionState[E_ATM_States.TARGET2] := stTarget2;
astPositionState[E_ATM_States.TARGET3] := stTarget3;
astPositionState[E_ATM_States.TARGET4] := stTarget4;
astPositionState[E_ATM_States.TARGET5] := stTarget5;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_ATM_States.OUT];
stTarget1 := astPositionState[E_ATM_States.TARGET1];
stTarget2 := astPositionState[E_ATM_States.TARGET2];
stTarget3 := astPositionState[E_ATM_States.TARGET3];
stTarget4 := astPositionState[E_ATM_States.TARGET4];
stTarget5 := astPositionState[E_ATM_States.TARGET5];
fbThermoCouple1();
fbFlowMeter();
END_FUNCTION_BLOCK
FB_ATMTest
FUNCTION_BLOCK FB_ATMTest EXTENDS FB_TestSuite
VAR
fbATM: FB_ATM;
stYStage: ST_MotionStage;
stXStage: ST_MotionStage;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbATM.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbATM.stTarget1, sName:='T1', fPosition:=20, sPmpsState:='T1');
fbSetup(stPositionState:=fbATM.stTarget2, sName:='T2', fPosition:=30, sPmpsState:='T2');
fbSetup(stPositionState:=fbATM.stTarget3, sName:='T3', fPosition:=40, sPmpsState:='T3');
fbSetup(stPositionState:=fbATM.stTarget4, sName:='T4', fPosition:=50, sPmpsState:='T4');
fbSetup(stPositionState:=fbATM.stTarget5, sName:='T5', fPosition:=60, sPmpsState:='T5');
// Standard FB call
fbATM(
stYStage:=stYStage,
stXStage:=stXStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_ATM_States.TARGET5;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestATMStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbATM.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbATM.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in ATM move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_AttenuatorElementDensity
FUNCTION_BLOCK FB_AttenuatorElementDensity
VAR_INPUT
sName : STRING;
END_VAR
VAR_OUTPUT
fDensity : LREAL;
END_VAR
VAR
fbElementDensity : FB_ElementDensity;
END_VAR
IF sName = 'C' THEN
(* Special-case diamond here *)
fDensity := 3.51E6; (* C (Diamond) g/m^3 *)
ELSE
fbElementDensity(sName:=sName);
IF fbElementDensity.bFound THEN
fDensity := fbElementDensity.fValue * 1.0E6; (* g/cm^3 -> g/m^3 *)
ELSE
fDensity := 0.0;
END_IF
END_IF
END_FUNCTION_BLOCK
FB_CheckPositionStateWrite
FUNCTION_BLOCK FB_CheckPositionStateWrite
(*
Save a position state during one cycle, then check it the next cycle
*)
VAR_IN_OUT
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
END_VAR
VAR_INPUT
bCheck: BOOL;
bSave: BOOL;
END_VAR
VAR_OUTPUT
bHadWrite: BOOL;
END_VAR
VAR
astCache: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
nIter: UINT;
END_VAR
bHadWrite := FALSE;
IF bCheck THEN
FOR nIter := 1 TO GeneralConstants.MAX_STATES DO
// Only the runtime-editable EPICS fields
bHadWrite S= astPositionState[nIter].sName <> astCache[nIter].sName;
bHadWrite S= astPositionState[nIter].fPosition <> astCache[nIter].fPosition;
bHadWrite S= astPositionState[nIter].fDelta <> astCache[nIter].fDelta;
bHadWrite S= astPositionState[nIter].fVelocity <> astCache[nIter].fVelocity;
bHadWrite S= astPositionState[nIter].fAccel <> astCache[nIter].fAccel;
bHadWrite S= astPositionState[nIter].fDecel <> astCache[nIter].fDecel;
END_FOR
END_IF
IF bSave THEN
astCache := astPositionState;
END_IF
END_FUNCTION_BLOCK
FB_CheckPositionStateWriteTest
FUNCTION_BLOCK FB_CheckPositionStateWriteTest EXTENDS FB_TestSuite
VAR
END_VAR
TestSaveCheck();
END_FUNCTION_BLOCK
METHOD TestSaveCheck
VAR_INST
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbCheckPositionStateWrite: FB_CheckPositionStateWrite;
END_VAR
TEST('TestSaveCheck');
fbCheckPositionStateWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
fbCheckPositionStateWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
AssertFalse(fbCheckPositionStateWrite.bHadWrite, 'Saw a write when there was no write');
astPositionState[1].fPosition := astPositionState[1].fPosition + 1;
fbCheckPositionStateWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
AssertTrue(fbCheckPositionStateWrite.bHadWrite, 'Did not detect position value changed');
TEST_FINISHED();
END_METHOD
- Related:
FB_LIC
FUNCTION_BLOCK FB_LIC
(*
Function block for Laser In-Coupling (LIC) controls:
- Y motion
- Y mirror and target states
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the MIRROR1 state.
stMirror1: ST_PositionState;
// Settings for the MIRROR2 state.
stMirror2: ST_PositionState;
// Settings for the TARGET1 state.
stTarget1: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_LIC_States;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_LIC_States;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..4
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
END_VAR
VAR CONSTANT
// State defaults if not provided
fDelta: LREAL := 2;
fAccel: LREAL := 200;
fOutDecel: LREAL := 25;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fOutDecel);
fbStateDefaults(stPositionState:=stMirror1, sNameDefault:='MIRROR1', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stMirror2, sNameDefault:='MIRROR2', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget1, sNameDefault:='TARGET1', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
fbYStage(stMotionStage:=stYStage);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_LIC_States.OUT] := stOut;
astPositionState[E_LIC_States.MIRROR1] := stMirror1;
astPositionState[E_LIC_States.MIRROR2] := stMirror2;
astPositionState[E_LIC_States.TARGET] := stTarget1;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_LIC_States.OUT];
stMirror1 := astPositionState[E_LIC_States.MIRROR1];
stMirror2 := astPositionState[E_LIC_States.MIRROR2];
stTarget1 := astPositionState[E_LIC_States.TARGET];
END_FUNCTION_BLOCK
FB_LICTest
FUNCTION_BLOCK FB_LICTest EXTENDS FB_TestSuite
VAR
fbLIC: FB_LIC;
stYStage: ST_MotionStage;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbLIC.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbLIC.stMirror1, sName:='T1', fPosition:=20, sPmpsState:='T1');
fbSetup(stPositionState:=fbLIC.stMirror2, sName:='T2', fPosition:=30, sPmpsState:='T2');
// Standard FB call
fbLIC(
stYStage:=stYStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_LIC_States.MIRROR2;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestLICStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbLIC.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbLIC.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in LIC move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_PositionState_Defaults
FUNCTION_BLOCK FB_PositionState_Defaults
VAR_IN_OUT
stPositionState: ST_PositionState;
END_VAR
VAR_INPUT
sNameDefault: STRING;
fVeloDefault: LREAL;
fDeltaDefault: LREAL;
fAccelDefault: LREAL;
fDecelDefault: LREAL;
END_VAR
IF stPositionState.sName = '' OR stPositionState.sName = 'Invalid' THEN
stPositionState.sName := sNameDefault;
END_IF
IF stPositionState.fVelocity = 0 THEN
stPositionState.fVelocity := fVeloDefault;
END_IF
IF stPositionState.fDelta = 0 THEN
stPositionState.fDelta := fDeltaDefault;
END_IF
IF stPositionState.fAccel = 0 THEN
stPositionState.fAccel := fAccelDefault;
END_IF
IF stPositionState.fDecel = 0 THEN
stPositionState.fDecel := fDecelDefault;
END_IF
END_FUNCTION_BLOCK
FB_PPM
FUNCTION_BLOCK FB_PPM
(*
Function block for Power and Profile Monitor (PPM) controls:
- Y motion
- Y power and profile states
- power meter
- camera power
- camera illuminator
- flow meter
- thermocouple
The following IO link points are included and should be used:
- FB_PPM.fbPowerMeter.iVoltageINT should be linked to the power meter analog input voltage
- FB_PPM.fbPowerMeter.fbThermoCouple.bError, bUnderrange, bOverrange, and iRaw should be linked to the corresponding thermocouple terminal inputs.
- FB_PPM.fbGige.iIlluminatorINT should be linked to illuminator dimmer analog output voltage
- FB_PPM.fbGige.bGigePower should be linked to the camera power digial output channel
- FB_PPM.fbFlowMeter.iRaw should be linked to the flow meter current analog input channel
- FB_PPM.fbYagThermoCouple.bError, bUnderrange, bOverrange, and iRaw should be linked to the corresponding thermocouple terminal inputs.
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the POWERMETER state.
stPower: ST_PositionState;
// Settings for the YAG1 state.
stYag1: ST_PositionState;
// Settings for the YAG2 state.
stYag2: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_PPM_States;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
// Offset for the flow meter in engineering units
fFlowOffset: LREAL := 0;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_PPM_States;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..4
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
{attribute 'pytmc' := 'pv: SPM'}
fbPowerMeter: FB_PPM_PowerMeter;
{attribute 'pytmc' := 'pv: CAM'}
fbGige: FB_PPM_Gige;
{attribute 'pytmc' :='pv: FWM'}
fbFlowMeter: FB_AnalogInput := (iTermBits:=15, fTermMax:=60, fTermMin:=0);
{attribute 'pytmc' := 'pv: YAG'}
fbYagThermoCouple: FB_ThermoCouple;
{attribute 'pytmc' := 'pv: FSW'}
fbFlowSwitch: FB_XTES_Flowswitch;
END_VAR
VAR CONSTANT
// State defaults if not provided
fDelta: LREAL := 2;
fAccel: LREAL := 200;
fOutDecel: LREAL := 25;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fOutDecel);
fbStateDefaults(stPositionState:=stPower, sNameDefault:='POWERMETER', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stYag1, sNameDefault:='YAG1', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stYag2, sNameDefault:='YAG2', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
fbYStage(stMotionStage:=stYStage);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_PPM_States.OUT] := stOut;
astPositionState[E_PPM_States.POWERMETER] := stPower;
astPositionState[E_PPM_States.YAG1] := stYag1;
astPositionState[E_PPM_States.YAG2] := stYag2;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_PPM_States.OUT];
stPower := astPositionState[E_PPM_States.POWERMETER];
stYag1 := astPositionState[E_PPM_States.YAG1];
stYag2 := astPositionState[E_PPM_States.YAG2];
fbPowerMeter();
fbGige();
fbFlowMeter(fOffset:=fFlowOffset);
fbYagThermoCouple();
fbFlowSwitch();
END_FUNCTION_BLOCK
FB_PPM_Gige
FUNCTION_BLOCK FB_PPM_Gige
VAR
iIlluminatorINT AT %Q*: INT;
{attribute 'pytmc' := '
pv: PWR
field: ZNAM OFF
field: ONAM ON
'}
bGigePower AT %Q*: BOOL;
{attribute 'pytmc' := '
pv: CIL:PCT
EGU: %
'}
fIlluminatorPercent: LREAL;
fbGetIllPercent: FB_AnalogInput;
fbSetIllPercent: FB_AnalogOutput;
bGigeInit: BOOL := FALSE;
END_VAR
// Turn the GigE on by default
IF NOT bGigeInit THEN
bGigePower := TRUE;
bGigeInit := TRUE;
END_IF
// Illuminator conversion to percentage
fbSetIllPercent(
fReal:=fIlluminatorPercent,
fSafeMax:=100,
fSafeMin:=0,
iTermBits:=15,
fTermMax:=100,
fTermMin:=0,
iRaw=>iIlluminatorINT);
fbGetIllPercent(
iRaw:=iIlluminatorINT,
iTermBits:=15,
fTermMax:=100,
fTermMin:=0,
fReal=>fIlluminatorPercent);
END_FUNCTION_BLOCK
FB_PPM_PowerMeter
FUNCTION_BLOCK FB_PPM_PowerMeter
VAR
iVoltageINT AT %I*: INT;
{attribute 'pytmc' := '
pv: VOLT
io: input
field: EGU mV
'}
fVoltage: LREAL;
{attribute 'pytmc' := '
pv: VOLT_BUFFER
io: input
field: EGU mV
'}
fVoltageBuffer: ARRAY[1..1000] OF LREAL;
{attribute 'pytmc' := '
pv: CALIB
io: input
'}
fCalibBase: LREAL;
{attribute 'pytmc' := '
pv: CALIB_BUFFER
io: input
'}
fCalibBaseBuffer: ARRAY[1..1000] OF LREAL;
{attribute 'pytmc' := '
pv: MJ
io: input
field: EGU mJ
'}
fCalibMJ: LREAL;
{attribute 'pytmc' := '
pv: MJ_BUFFER
io: input
field: EGU mJ
'}
fCalibMJBuffer: ARRAY[1..1000] OF LREAL;
{attribute 'pytmc' := '
pv:
io: input
'}
fbThermoCouple: FB_ThermoCouple;
{attribute 'pytmc' := '
pv: CALIB:OFFSET
io: io
'}
fCalibRelOffset: LREAL;
{attribute 'pytmc' := '
pv: CALIB:RATIO
io: io
'}
fCalibRelRatio: LREAL;
{attribute 'pytmc' := '
pv: CALIB:MJ_RATIO
io: io
'}
fCalibMJRatio: LREAL;
fbGetPMVoltage: FB_AnalogInput;
fbVoltageBuffer: FB_LREALBuffer;
fbCalibBaseBuffer: FB_LREALBuffer;
fbCalibMJBuffer: FB_LREALBuffer;
END_VAR
fbThermoCouple();
// Convert the terminal's integer into a value in millivolts
fbGetPMVoltage(
iRaw := iVoltageINT,
iTermBits := 15,
fTermMax := 10000,
fTermMin := 0,
fReal => fVoltage);
// Power meter calibration
fCalibBase := (fVoltage + fCalibRelOffset) * fCalibRelRatio;
fCalibMJ := fCalibBase * fCalibMJRatio;
// Buffer the full-rate Voltage and calibrated MJ values
fbVoltageBuffer(
bExecute := TRUE,
fInput := fVoltage,
arrOutput => fVoltageBuffer);
fbCalibBaseBuffer(
bExecute := TRUE,
fInput := fCalibBase,
arrOutput => fCalibBaseBuffer);
fbCalibMJBuffer(
bExecute := TRUE,
fInput := fCalibMJ,
arrOutput => fCalibMJBuffer);
END_FUNCTION_BLOCK
FB_PPMTest
FUNCTION_BLOCK FB_PPMTest EXTENDS FB_TestSuite
VAR
fbPPM: FB_PPM;
stYStage: ST_MotionStage;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbPPM.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbPPM.stPower, sName:='T1', fPosition:=20, sPmpsState:='T1');
fbSetup(stPositionState:=fbPPM.stYag1, sName:='T2', fPosition:=30, sPmpsState:='T2');
fbSetup(stPositionState:=fbPPM.stYag2, sName:='T3', fPosition:=40, sPmpsState:='T3');
// Standard FB call
fbPPM(
stYStage:=stYStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_PPM_States.YAG2;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestPPMStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbPPM.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbPPM.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in PPM move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_REF
FUNCTION_BLOCK FB_REF
(*
Function block for reference laser (REF) controls:
- Y motion
- Y in and out states
- laser power and dimmer
The following IO link points are included and should be used:
- FB_REF.fbLaser.iShutdownINT should be linked to the output for the laser dimmer's shutdown voltage
- FB_REF.fbLaser.iLaserINT should be linked to the output for the laser dimmer's dimmer
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the IN state.
stIn: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_EpicsInOut;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_EpicsInOut;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..2
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
{attribute 'pytmc' := 'pv: LAS'}
fbLaser: FB_REF_Laser;
END_VAR
VAR CONSTANT
fDelta: LREAL := 2;
fAccel: LREAL := 10;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stIn, sNameDefault:='IN', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
fbYStage(stMotionStage:=stYStage);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_EpicsInOut.OUT] := stOut;
astPositionState[E_EpicsInOut.IN] := stIn;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_EpicsInOut.OUT];
stIn := astPositionState[E_EpicsInOut.IN];
fbLaser();
END_FUNCTION_BLOCK
FB_REF_Laser
FUNCTION_BLOCK FB_REF_Laser
VAR_INPUT
bShutdown: BOOL;
{attribute 'pytmc' := '
pv: PCT
io: io
'}
fLaserPercent: LREAL;
END_VAR
VAR
iShutdownINT AT %Q*: INT;
iLaserINT AT %Q*: INT;
fbGetLasPercent: FB_AnalogInput;
fbSetLasPercent: FB_AnalogOutput;
END_VAR
// Send 5V to suppress laser
IF bShutdown THEN
iShutdownINT := LREAL_TO_INT(EXPT(2, 14));
ELSE
iShutdownINT := 0;
END_IF
// Limit to 0-5V instead of 10V
fbSetLasPercent(
fReal:=fLaserPercent,
fSafeMax:=100,
fSafeMin:=0,
iTermBits:=15,
fTermMax:=200,
fTermMin:=0,
iRaw=>iLaserInt);
fbGetLasPercent(
iRaw:=iLaserInt,
iTermBits:=15,
fTermMax:=200,
fTermMin:=0,
fReal=>fLaserPercent);
END_FUNCTION_BLOCK
FB_REFTest
FUNCTION_BLOCK FB_REFTest EXTENDS FB_TestSuite
VAR
fbREF: FB_REF;
stYStage: ST_MotionStage;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbREF.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbREF.stIn, sName:='T1', fPosition:=20, sPmpsState:='T1');
// Standard FB call
fbREF(
stYStage:=stYStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_EpicsInOut.IN;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestREFStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbREF.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbREF.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in REF move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_SATTTest
FUNCTION_BLOCK FB_SATTTest EXTENDS FB_TestSuite
VAR
fbSATT: FB_SXR_SATT_Stage;
stYStage: ST_MotionStage;
stDefault: ST_PositionState := (
fDelta:=0.5,
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
bInit: BOOL;
END_VAR
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbSATT.stOut, sName:='OUT', fPosition:=10);
fbSetup(stPositionState:=fbSATT.stFilter1, sName:='T1', fPosition:=20);
fbSetup(stPositionState:=fbSATT.stFilter2, sName:='T2', fPosition:=30);
fbSetup(stPositionState:=fbSATT.stFilter3, sName:='T3', fPosition:=40);
fbSetup(stPositionState:=fbSATT.stFilter4, sName:='T4', fPosition:=50);
fbSetup(stPositionState:=fbSATT.stFilter5, sName:='T5', fPosition:=60);
fbSetup(stPositionState:=fbSATT.stFilter6, sName:='T6', fPosition:=70);
fbSetup(stPositionState:=fbSATT.stFilter7, sName:='T7', fPosition:=80);
fbSetup(stPositionState:=fbSATT.stFilter8, sName:='T8', fPosition:=90);
// Standard FB call
fbSATT(
stAxis:=stYStage,
fbFFHWO:=fbFFHWO,
bEnable:=TRUE,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_SXR_SATT_Position.FILTER8;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestSATTStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#20s);
// Start in Unknown, then go through the state positions one by one
IF fbSATT.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbSATT.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in SATT move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_SLITS
FUNCTION_BLOCK FB_SLITS
END_FUNCTION_BLOCK
ACTION ACT_BLOCK:
END_ACTION
ACTION ACT_CalculatePositions:
END_ACTION
FB_SLITS_POWER
FUNCTION_BLOCK FB_SLITS_POWER EXTENDS FB_SLITS
VAR_IN_OUT
stTopBlade: ST_MotionStage;
stBottomBlade: ST_MotionStage;
stNorthBlade: ST_MotionStage;
stSouthBlade: ST_MotionStage;
END_VAR
VAR_INPUT
{attribute 'pytmc' := '
pv: GO;
io: io;
field: ZNAM False
field: ONAM True
'}
bExecuteMotion:BOOL ;
{attribute 'pytmc' := '
pv: PMPS_OK;
io: i;
field: ZNAM False
field: ONAM True
'}
bMoveOk:BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
fbTopBlade: FB_MotionStage;
fbBottomBlade: FB_MotionStage;
fbNorthBlade: FB_MotionStage;
fbSouthBlade: FB_MotionStage;
fPosTopBlade: LREAL;
fPosBottomBlade: LREAL;
fPosNorthBlade: LREAL;
fPosSouthBlade: LREAL;
(*Motion Parameters*)
fSmallDelta: LREAL := 0.01;
fBigDelta: LREAL := 10;
fMaxVelocity: LREAL := 0.2;
fHighAccel: LREAL := 0.8;
fLowAccel: LREAL := 0.1;
stTop: ST_PositionState;
stBOTTOM: ST_PositionState;
stNorth: ST_PositionState;
stSouth: ST_PositionState;
{attribute 'pytmc' := 'pv: TOP'}
fbTop: FB_StatePTPMove;
{attribute 'pytmc' := 'pv: BOTTOM'}
fbBottom: FB_StatePTPMove;
{attribute 'pytmc' := 'pv: NORTH'}
fbNorth: FB_StatePTPMove;
{attribute 'pytmc' := 'pv: SOUTH'}
fbSouth: FB_StatePTPMove;
(*EPICS pvs*)
{attribute 'pytmc' := '
pv: XWID_REQ;
io: io;
'}
rReqApertureSizeX : REAL;
{attribute 'pytmc' := '
pv: YWID_REQ;
io: io;
'}
rReqApertureSizeY : REAL;
{attribute 'pytmc' := '
pv: XCEN_REQ;
io: io;
'}
rReqCenterX: REAL;
{attribute 'pytmc' := '
pv: YCEN_REQ;
io: io;
'}
rReqCenterY: REAL;
{attribute 'pytmc' := '
pv: ACTUAL_XWIDTH;
io: io;
'}
rActApertureSizeX : REAL;
{attribute 'pytmc' := '
pv: ACTUAL_YWIDTH;
io: io;
'}
rActApertureSizeY : REAL;
{attribute 'pytmc' := '
pv: ACTUAL_XCENTER;
io: io;
'}
rActCenterX: REAL;
{attribute 'pytmc' := '
pv: ACTUAL_YCENTER;
io: io;
'}
rActCenterY: REAL;
{attribute 'pytmc' := '
pv: XCEN_SETZERO;
io: io;
'}
rSetCenterX: REAL;
{attribute 'pytmc' := '
pv: YCEN_SETZERO;
io: io;
'}
rSetCenterY: REAL;
{attribute 'pytmc' := '
pv: OPEN;
io: io;
field: ZNAM False
field: ONAM True
'}
bOpen: BOOL;
{attribute 'pytmc' := '
pv: CLOSE;
io: io;
field: ZNAM False
field: ONAM True
'}
bClose: BOOL;
{attribute 'pytmc' := '
pv: BLOCK;
io: io;
field: ZNAM False
field: ONAM True
'}
bBlock: BOOL;
{attribute 'pytmc' := '
pv: FSW
'}
fbFlowSwitch: FB_XTES_Flowswitch;
//RTDs
{attribute 'pytmc' := '
pv: TOP:RTD:01
'}
RTD_TOP_1: FB_TempSensor;
{attribute 'pytmc' := '
pv: TOP:RTD:02
'}
RTD_TOP_2: FB_TempSensor;
{attribute 'pytmc' := '
pv: BOTTOM:RTD:01
'}
RTD_Bottom_1: FB_TempSensor;
{attribute 'pytmc' := '
pv: BOTTOM:RTD:02
'}
RTD_Bottom_2: FB_TempSensor;
{attribute 'pytmc' := '
pv: NORTH:RTD:01
'}
RTD_North_1: FB_TempSensor;
{attribute 'pytmc' := '
pv: NORTH:RTD:02
'}
RTD_North_2: FB_TempSensor;
{attribute 'pytmc' := '
pv: SOUTH:RTD:01
'}
RTD_South_1: FB_TempSensor;
{attribute 'pytmc' := '
pv: SOUTH:RTD:02
'}
RTD_South_2: FB_TempSensor;
//Local variables
bInit: BOOL :=true;
rTrig_Block: R_TRIG;
rTrig_Open: R_TRIG;
rTrig_Close: R_TRIG;
fPosBlock: LREAL;
fPosClose: LREAL;
fPosOpen: LREAL;
END_VAR
// init the motion stages parameters
IF ( bInit) THEN
stTopBlade.bHardwareEnable := TRUE;
stBottomBlade.bHardwareEnable := TRUE;
stNorthBlade.bHardwareEnable := TRUE;
stSouthBlade.bHardwareEnable := TRUE;
stTopBlade.bPowerSelf :=TRUE;
stBottomBlade.bPowerSelf :=TRUE;
stNorthBlade.bPowerSelf :=TRUE;
stSouthBlade.bPowerSelf :=TRUE;
stTopBlade.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
stBottomBlade.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
stNorthBlade.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
stSouthBlade.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
END_IF
// Instantiate Function block for all the blades
fbTopBlade(stMotionStage:=stTopBlade);
fbBottomBlade(stMotionStage:=stBottomBlade);
fbNorthBlade(stMotionStage:=stNorthBlade);
fbSouthBlade(stMotionStage:=stSouthBlade);
//SET and GET the requested and Actual values
ACT_CalculatePositions();
//ACT_BLOCK();
// PTP Motion for each blade
stTop.sName := 'Top';
stTop.fPosition := fPosTopBlade;
stTop.fDelta := fSmallDelta;
stTop.fVelocity := fMaxVelocity;
stTop.fAccel := fHighAccel;
stTop.fDecel := fHighAccel;
stBOTTOM.sName := 'Bottom';
stBOTTOM.fPosition := fPosBottomBlade;
stBOTTOM.fDelta := fSmallDelta;
stBOTTOM.fVelocity := fMaxVelocity;
stBOTTOM.fAccel := fHighAccel;
stBOTTOM.fDecel := fHighAccel;
stNorth.sName := 'North';
stNorth.fPosition := fPosNorthBlade;
stNorth.fDelta := fSmallDelta;
stNorth.fVelocity := fMaxVelocity;
stNorth.fAccel := fHighAccel;
stNorth.fDecel := fHighAccel;
stSouth.sName := 'South';
stSouth.fPosition := fPosSouthBlade;
stSouth.fDelta := fSmallDelta;
stSouth.fVelocity := fMaxVelocity;
stSouth.fAccel := fHighAccel;
stSouth.fDecel := fHighAccel;
fbTop.bExecute := fbBottom.bExecute :=fbNorth.bExecute := fbSouth.bExecute := bExecuteMotion;
fbTop(
stPositionState:=stTop,
bMoveOk:=bMoveOk,
stMotionStage:=stTopBlade);
fbBottom(
stPositionState:=stBOTTOM,
bMoveOk:=bMoveOk,
stMotionStage:=stBottomBlade);
fbNorth(
stPositionState:=stNorth,
bMoveOk:=bMoveOk,
stMotionStage:=stNorthBlade);
fbSouth(
stPositionState:=stSouth,
bMoveOk:=bMoveOk,
stMotionStage:=stSouthBlade);
////RTDs
RTD_TOP_1();
RTD_TOP_2();
RTD_Bottom_1();
RTD_Bottom_2();
RTD_North_1();
RTD_North_2();
RTD_South_1();
RTD_South_2();
//Flow Switch
fbFlowSwitch();
END_FUNCTION_BLOCK
ACTION ACT_BLOCK:
rTrig_Block (CLK:= bBlock);
rTrig_Open (CLK:= bOpen);
rTrig_Close (CLK:= bClose);
if (rTrig_Block.Q) THEN
//BLOCK
bBlock := false;
END_IF
if (rTrig_Open.Q) THEN
bOpen := false;
END_IF
if (rTrig_Close.Q) THEN
bClose := false;
END_IF
END_ACTION
ACTION ACT_CalculatePositions:
//Calculate requested Positions
fPosTopBlade := (rReqApertureSizeY/2) + rReqCenterY;
fPosBottomBlade := (-1*rReqApertureSizeY/2) + rReqCenterY;
fPosNorthBlade := (rReqApertureSizeX/2) + rReqCenterX;
fPosSouthBlade := (-1*rReqApertureSizeX/2) + rReqCenterX;
//Calculate Actual Positions
rActApertureSizeX := ABS(stNorthBlade.stAxisStatus.fActPosition - stSouthBlade.stAxisStatus.fActPosition);
rActApertureSizeY := ABS(stTopBlade.stAxisStatus.fActPosition - stBottomBlade.stAxisStatus.fActPosition);
rActCenterX := ((stNorthBlade.stAxisStatus.fActPosition + stSouthBlade.stAxisStatus.fActPosition)/2);
rActCenterY := ((stTopBlade.stAxisStatus.fActPosition + stBottomBlade.stAxisStatus.fActPosition)/2);
//ZERO BIAS
// Set Y center to zero
// Set X center to zero
END_ACTION
- Related:
FB_SXR_SATT_Stage
FUNCTION_BLOCK FB_SXR_SATT_Stage
(*
Function block for a single Solid ATTenuator (SATT) filter stack.
This is for the L2SI compact design with 4 filter stacks that each have
many filters.
This function block controls:
- Y motion
- Y filter states
- transmission calculation for one stack
There will be a fast fault during motion, but no other PMPS restrictions.
*)
VAR_IN_OUT
// Y motor (filter select)
stAxis : ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut : ST_PositionState;
// Settings for the FILTER1 state.
stFilter1 : ST_PositionState;
// Settings for the FILTER2 state.
stFilter2 : ST_PositionState;
// Settings for the FILTER3 state.
stFilter3 : ST_PositionState;
// Settings for the FILTER4 state.
stFilter4 : ST_PositionState;
// Settings for the FILTER5 state.
stFilter5 : ST_PositionState;
// Settings for the FILTER6 state.
stFilter6 : ST_PositionState;
// Settings for the FILTER7 state.
stFilter7 : ST_PositionState;
// Settings for the FILTER8 state.
stFilter8 : ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: STATE:SET
io: io
'}
eEnumSet: E_SXR_SATT_Position;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnable: BOOL;
// Filter configuration information
{attribute 'pytmc' := 'pv: FILTERS'}
arrFilters: ARRAY[1..8] OF ST_SATT_Filter;
// Debug helper for setting the stage's nEnableMode
nEnableMode : E_StageEnableMode;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: STATE:GET
io: i
'}
eEnumGet: E_SXR_SATT_Position;
fTemp1 : LREAL;
fTemp2 : LREAL;
bIsStationary : BOOL;
bError : BOOL;
{attribute 'pytmc' := '
pv: MATERIAL
io: i
'}
sActiveFilterMaterial : STRING;
{attribute 'pytmc' := '
pv: THICKNESS
io: i
field: EGU um
'}
fActiveFilterThickness_um : LREAL;
{attribute 'pytmc' := '
pv: TRANSMISSION
io: i
field: DESC Filter transmission
'}
fTransmission : LREAL;
fActiveFilterDensity : LREAL;
fActiveFilterAtomicMass : LREAL;
fAbsorptionConstant : LREAL;
iFilterIndex: INT := 0;
END_VAR
VAR
fbMotion: FB_MotionStage;
{attribute 'pytmc' := '
pv:
astPositionState.array: 1..9
'}
fbStates: FB_PositionState1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
bInitialized: BOOL := FALSE;
fbAtomicMass : FB_AtomicMass;
fbAttenuatorElementDensity : FB_AttenuatorElementDensity;
(* EL3202-0020: 0.01 °C per digit *)
{attribute 'pytmc' := 'pv: RTD:1'}
fbRTD_1: FB_TempSensor := ( fResolution:=0.01 );
{attribute 'pytmc' := 'pv: RTD:2'}
fbRTD_2: FB_TempSensor := ( fResolution:=0.01 );
fbFF: FB_FastFault := (i_Desc := 'Device is moving',
i_TypeCode := E_MotionFFType.DEVICE_MOVE,
i_xAutoReset := TRUE);
END_VAR
IF NOT bInitialized THEN
bInitialized := TRUE;
(* Defaults for ST_MotionStage *)
stAxis.bHardwareEnable := TRUE;
stAxis.bLimitBackwardEnable := TRUE;
stAxis.bLimitForwardEnable := TRUE;
stAxis.bPowerSelf := TRUE;
stAxis.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
stAxis.nHomingMode := ENUM_EpicsHomeCmd.NONE;
(* Defaults for visualization *)
// stExtra.fVisuStep := 0.1;
END_IF
fbRTD_1();
fTemp1 := fbRTD_1.fTemp;
fbRTD_2();
fTemp2 := fbRTD_2.fTemp;
stAxis.nEnableMode := nEnableMode;
fbMotion(stMotionStage:=stAxis);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_SXR_SATT_Position.OUT] := stOut;
astPositionState[E_SXR_SATT_Position.FILTER1] := stFilter1;
astPositionState[E_SXR_SATT_Position.FILTER2] := stFilter2;
astPositionState[E_SXR_SATT_Position.FILTER3] := stFilter3;
astPositionState[E_SXR_SATT_Position.FILTER4] := stFilter4;
astPositionState[E_SXR_SATT_Position.FILTER5] := stFilter5;
astPositionState[E_SXR_SATT_Position.FILTER6] := stFilter6;
astPositionState[E_SXR_SATT_Position.FILTER7] := stFilter7;
astPositionState[E_SXR_SATT_Position.FILTER8] := stFilter8;
END_IF
fbStates(
stMotionStage:=stAxis,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
bEnable:=bEnable,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_SXR_SATT_Position.OUT];
stFilter1 := astPositionState[E_SXR_SATT_Position.FILTER1];
stFilter2 := astPositionState[E_SXR_SATT_Position.FILTER2];
stFilter3 := astPositionState[E_SXR_SATT_Position.FILTER3];
stFilter4 := astPositionState[E_SXR_SATT_Position.FILTER4];
stFilter5 := astPositionState[E_SXR_SATT_Position.FILTER5];
stFilter6 := astPositionState[E_SXR_SATT_Position.FILTER6];
stFilter7 := astPositionState[E_SXR_SATT_Position.FILTER7];
stFilter8 := astPositionState[E_SXR_SATT_Position.FILTER8];
(* Filter indices are off by one due to "Out" being in position 1. *)
iFilterIndex := UINT_TO_INT(eEnumGet - 1);
IF iFilterIndex >= 1 AND iFilterIndex <= 8 THEN
sActiveFilterMaterial := arrFilters[iFilterIndex].sFilterMaterial;
fActiveFilterThickness_um := arrFilters[iFilterIndex].fFilterThickness_um;
fbAtomicMass(sName:=sActiveFilterMaterial, fValue=>fActiveFilterAtomicMass);
fbAttenuatorElementDensity(sName:=sActiveFilterMaterial, fDensity=>fActiveFilterDensity);
fAbsorptionConstant := F_CalculateAbsorptionConstant(
sElement:=sActiveFilterMaterial,
fEnergyEV:=PMPS_GVL.stCurrentBeamParameters.neV,
fDensity_gm3:=fActiveFilterDensity,
fAtomicWeight:=fActiveFilterAtomicMass,
bError=>bError,
);
fTransmission := F_CalculateTransmission(
fAbsorptionConstant:=fAbsorptionConstant,
fThickness_in_m:=fActiveFilterThickness_um * 1.0E-6
);
ELSE
sActiveFilterMaterial := '';
fActiveFilterThickness_um := 0.0;
fAbsorptionConstant := 0.0;
fActiveFilterDensity := 0.0;
fActiveFilterAtomicMass := 0.0;
fTransmission := 1.0;
END_IF
bIsStationary := NOT stAxis.Axis.Status.Moving;
fbFF(
i_DevName:=stAxis.sName,
i_xOK:=bIsStationary AND eEnumGet <> E_SXR_SATT_Position.UNKNOWN,
io_fbFFHWO := fbFFHWO
);
END_FUNCTION_BLOCK
FB_WFS
FUNCTION_BLOCK FB_WFS
(*
Function block for WaveFront Sensor target (WFS) controls:
- X, Z motion
- Y target states
- 2 thermocouples
The following IO link points are included and should be used:
- FB_WFS.fbThermoCouple1.fbThermoCouple.bError, bUnderrange, bOverrange, and iRaw should be linked to the corresponding thermocouple terminal inputs.
- FB_WFS.fbThermoCouple1.fbThermoCouple.bError, bUnderrange, bOverrange, and iRaw should be linked to the corresponding thermocouple terminal inputs.
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// Z motor (focus adjust).
stZStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the TARGET1 state.
stTarget1: ST_PositionState;
// Settings for the TARGET2 state.
stTarget2: ST_PositionState;
// Settings for the TARGET3 state.
stTarget3: ST_PositionState;
// Settings for the TARGET4 state.
stTarget4: ST_PositionState;
// Settings for the TARGET5 state.
stTarget5: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_WFS_States;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_WFS_States;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbZStage: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..6
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
{attribute 'pytmc' := 'pv: STC:01'}
fbThermoCouple1: FB_TempSensor;
{attribute 'pytmc' := 'pv: STC:02'}
fbThermoCouple2: FB_TempSensor;
{attribute 'pytmc' := 'pv: FSW'}
fbFlowSwitch: FB_XTES_Flowswitch;
{attribute 'pytmc' :='pv: FWM'}
fbFlowMeter: FB_AnalogInput := (iTermBits:=15, fTermMax:=60, fTermMin:=0);
END_VAR
VAR CONSTANT
// State defaults if not provided
fDelta: LREAL := 2;
fAccel: LREAL := 200;
fOutDecel: LREAL := 25;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
stZStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fOutDecel);
fbStateDefaults(stPositionState:=stTarget1, sNameDefault:='TARGET1', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget2, sNameDefault:='TARGET2', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget3, sNameDefault:='TARGET3', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget4, sNameDefault:='TARGET4', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stTarget5, sNameDefault:='TARGET5', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
stZStage.bLimitForwardEnable := TRUE;
stZStage.bLimitBackwardEnable := TRUE;
stZStage.bHardwareEnable := TRUE;
stZStage.bPowerSelf := TRUE;
fbYStage(stMotionStage:=stYStage);
fbZStage(stMotionStage:=stZStage);
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_WFS_States.OUT] := stOut;
astPositionState[E_WFS_States.TARGET1] := stTarget1;
astPositionState[E_WFS_States.TARGET2] := stTarget2;
astPositionState[E_WFS_States.TARGET3] := stTarget3;
astPositionState[E_WFS_States.TARGET4] := stTarget4;
astPositionState[E_WFS_States.TARGET5] := stTarget5;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_WFS_States.OUT];
stTarget1 := astPositionState[E_WFS_States.TARGET1];
stTarget2 := astPositionState[E_WFS_States.TARGET2];
stTarget3 := astPositionState[E_WFS_States.TARGET3];
stTarget4 := astPositionState[E_WFS_States.TARGET4];
stTarget5 := astPositionState[E_WFS_States.TARGET5];
fbThermoCouple1();
fbThermoCouple2();
fbFlowSwitch(); // WFS in FEE
fbFlowMeter(); // WFS in hutch
END_FUNCTION_BLOCK
FB_WFSTest
FUNCTION_BLOCK FB_WFSTest EXTENDS FB_TestSuite
VAR
fbWFS: FB_WFS;
stYStage: ST_MotionStage;
stZStage: ST_MotionStage;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbWFS.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbWFS.stTarget1, sName:='T1', fPosition:=20, sPmpsState:='T1');
fbSetup(stPositionState:=fbWFS.stTarget2, sName:='T2', fPosition:=30, sPmpsState:='T2');
fbSetup(stPositionState:=fbWFS.stTarget3, sName:='T3', fPosition:=40, sPmpsState:='T3');
fbSetup(stPositionState:=fbWFS.stTarget4, sName:='T4', fPosition:=50, sPmpsState:='T4');
fbSetup(stPositionState:=fbWFS.stTarget5, sName:='T5', fPosition:=60, sPmpsState:='T5');
// Standard FB call
fbWFS(
stYStage:=stYStage,
stZStage:=stZStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_WFS_States.TARGET5;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestWFSStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbWFS.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbWFS.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in WFS move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_XPIM
FUNCTION_BLOCK FB_XPIM
(*
Function block for XTES Imager (XPIM = XTES PIM = Profile Impager) controls:
- Y, Zoom, Focus motion
- Y scintillator states
- filterwheel serial commands
- camera power
- LED on/off
- flowswitch (not fully implemented)
The following IO link points are included and should be used:
- FB_XPIM.bZoomEndFwd should be linked to the zoom motor's forward limit switch.
- FB_XPIM.bZoomEndBwd should be linked to the zoom motor's backward limit switch.
- FB_XPIM.bFocusEndFwd should be linked to the focus motor's forward limit switch.
- FB_XPIM.bFocusEndBwd should be linked to the focus motor's backward limit switch.
- FB_XPIM.fbOpal.bOpalPower should be linked to the camera power digital output.
- FB_XPIM.fbLED.bLEDPower should be linked to the LED power digital output.
- FB_XPIM.fbFlowSwitch.bFlowOk should be linked to the flow switch digital input, if present.
*)
VAR_IN_OUT
// Y motor (state select).
stYStage: ST_MotionStage;
// Zoom motor (camera zoom).
stZoomStage: ST_MotionStage;
// Focus motor (camera focus).
stFocusStage: ST_MotionStage;
// The fast fault output to fault to.
fbFFHWO: FB_HardwareFFOutput;
// The arbiter to request beam conditions from.
fbArbiter: FB_Arbiter;
// Serial input
stEl6In: EL6inData22b;
// Serial output
stEl6Out: EL6OutData22b;
END_VAR
VAR_INPUT
// Settings for the OUT state.
stOut: ST_PositionState;
// Settings for the YAG state.
stYag: ST_PositionState;
// Settings for the DIAMOND state.
stDiamond: ST_PositionState;
// Settings for the RETICLE state.
stReticle: ST_PositionState;
// Set this to a non-unknown value to request a new move.
{attribute 'pytmc' := '
pv: MMS:STATE:SET
io: io
'}
eEnumSet: E_XPIM_States;
// Set this to TRUE to enable input state moves, or FALSE to disable them.
bEnableMotion: BOOL;
// Set this to TRUE to enable beam parameter checks, or FALSE to disable them.
bEnableBeamParams: BOOL;
// Set this to TRUE to enable position limit checks, or FALSE to disable them.
bEnablePositionLimits: BOOL;
// The name of the device for use in the PMPS DB lookup and diagnostic screens.
sDeviceName: STRING;
// The name of the transition state in the PMPS database.
sTransitionKey: STRING;
// Set this to TRUE to re-read the loaded database immediately (useful for debug).
bReadDBNow: BOOL;
// While TRUE, the zoom motor cannot be moved.
{attribute 'pytmc' := '
pv: CLZ:LOCK
io: io
field: ZNAM Unlocked
field: ONAM Locked
'}
bZoomLock: BOOL;
// While TRUE, the focus motor cannot be moved.
{attribute 'pytmc' := '
pv: CLF:LOCK
io: io
field: ZNAM Unlocked
field: ONAM Locked
'}
bFocusLock: BOOL;
// Forward limit disable for zoom
bZoomEndFwd AT %I*: BOOL;
// Backward limit disable for zoom
bZoomEndBwd AT %I*: BOOL;
// Forward limit disable for focus
bFocusEndFwd AT %I*: BOOL;
// Backward limit disable for focus
bFocusEndBwd AT %I*: BOOL;
END_VAR
VAR_OUTPUT
// The current position state as an enum.
{attribute 'pytmc' := '
pv: MMS:STATE:GET
io: i
'}
eEnumGet: E_XPIM_States;
// The PMPS database lookup associated with the current position state.
stDbStateParams: ST_DbStateParams;
END_VAR
VAR
bInit: BOOL;
fbYStage: FB_MotionStage;
fbZoom: FB_MotionStage;
fbFocus: FB_MotionStage;
fbStateDefaults: FB_PositionState_Defaults;
{attribute 'pytmc' := '
pv: MMS
astPositionState.array: 1..4
'}
fbStates: FB_PositionStatePMPS1D;
astPositionState: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
fbArrCheckWrite: FB_CheckPositionStateWrite;
{attribute 'pytmc' := 'pv: MFW'}
fbFilterWheel: FB_XPIM_FilterWheel;
{attribute 'pytmc' := 'pv: CAM'}
fbOpal: FB_XPIM_Opal;
{attribute 'pytmc' := 'pv: CIL'}
fbLED: FB_XPIM_LED;
{attribute 'pytmc' := 'pv: FSW'}
fbFlowSwitch: FB_XTES_Flowswitch;
END_VAR
VAR CONSTANT
// State defaults if not provided
fDelta: LREAL := 2;
fAccel: LREAL := 200;
fOutDecel: LREAL := 25;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
stYStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
stZoomStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
stFocusStage.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
// Partial backcompat, this used to set fVelocity too but this should be set per install
fbStateDefaults(stPositionState:=stOut, sNameDefault:='OUT', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fOutDecel);
fbStateDefaults(stPositionState:=stYag, sNameDefault:='YAG', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stDiamond, sNameDefault:='DIAMOND', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
fbStateDefaults(stPositionState:=stReticle, sNameDefault:='RETICLE', fDeltaDefault:=fDelta, fAccelDefault:=fAccel, fDecelDefault:=fAccel);
END_IF
stYStage.bHardwareEnable := TRUE;
stYStage.bPowerSelf := FALSE;
// No limit switch at the bottom
stYStage.bLimitBackwardEnable := TRUE;
// Extra lock on lens + lens limits are NO instead of NC
stZoomStage.bHardwareEnable := NOT bZoomLock;
stZoomStage.bPowerSelf := TRUE;
stZoomStage.bLimitForwardEnable := NOT bZoomEndFwd;
stZoomStage.bLimitBackwardEnable := NOT bZoomEndBwd;
stZoomStage.nHomingMode := ENUM_EpicsHomeCmd.LOW_LIMIT;
stZoomStage.fHomePosition := 0;
stFocusStage.bHardwareEnable := NOT bFocusLock;
stFocusStage.bPowerSelf := TRUE;
stFocusStage.bLimitForwardEnable := NOT bFocusEndFwd;
stFocusStage.bLimitBackwardEnable := NOT bFocusEndBwd;
stFocusStage.nHomingMode := ENUM_EpicsHomeCmd.LOW_LIMIT;
stFocusStage.fHomePosition := 0;
fbYStage(stMotionStage:=stYStage);
fbZoom(stMotionStage:=stZoomStage);
fbFocus(stMotionStage:=stFocusStage);
// Set special error message for lens lock
IF stZoomStage.bExecute AND bZoomLock THEN
stZoomStage.bError := TRUE;
stZoomStage.sCustomErrorMessage := 'Zoom lens is locked!';
END_IF
IF stFocusStage.bExecute AND bFocusLock THEN
stFocusStage.bError := TRUE;
stFocusStage.sCustomErrorMessage := 'Focus lens is locked!';
END_IF
// We need to update from PLC or from EPICS, but not both
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=TRUE,
bSave:=FALSE,
);
IF NOT fbArrCheckWrite.bHadWrite THEN
astPositionState[E_XPIM_States.OUT] := stOut;
astPositionState[E_XPIM_States.YAG] := stYag;
astPositionState[E_XPIM_States.DIAMOND] := stDiamond;
astPositionState[E_XPIM_States.RETICLE] := stReticle;
END_IF
fbStates(
stMotionStage:=stYStage,
astPositionState:=astPositionState,
eEnumSet:=eEnumSet,
eEnumGet:=eEnumGet,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
bEnableMotion:=bEnableMotion,
bEnableBeamParams:=bEnableBeamParams,
bEnablePositionLimits:=bEnablePositionLimits,
sDeviceName:=sDeviceName,
sTransitionKey:=sTransitionKey,
bReadDBNow:=bReadDBNow,
stDbStateParams=>stDbStateParams,
);
fbArrCheckWrite(
astPositionState:=astPositionState,
bCheck:=FALSE,
bSave:=TRUE,
);
stOut := astPositionState[E_XPIM_States.OUT];
stYag := astPositionState[E_XPIM_States.YAG];
stDiamond := astPositionState[E_XPIM_States.DIAMOND];
stReticle := astPositionState[E_XPIM_States.RETICLE];
fbFilterWheel(
bExecute:=TRUE,
stIn_El6:=stEl6In,
stOut_El6:=stEl6Out,
);
fbOpal();
fbLED(enumXPIM:=eEnumGet);
fbFlowSwitch();
END_FUNCTION_BLOCK
FB_XPIM_FilterWheel
FUNCTION_BLOCK FB_XPIM_FilterWheel
VAR_INPUT
bExecute: BOOL;
{attribute 'pytmc' := '
pv: ERR:RESET
io: output
'}
bResetError: BOOL;
{attribute 'pytmc' := '
pv: SET
io: io
'}
nSetPos: E_XPIM_Filters;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: GET
io: i
'}
nGetPos: E_XPIM_Filters;
bBusy: BOOL;
bError: BOOL;
sError: STRING;
{attribute 'pytmc' := '
pv: ERR:MSG
io: input
'}
sLastError: STRING;
sErrorTS: STRING;
END_VAR
VAR_IN_OUT
stIn_EL6: EL6inData22B;
stOut_EL6: EL6outData22B;
END_VAR
VAR
{attribute 'pytmc' := '
pv: RAW
'}
fbCom: FB_EL6_COM;
nStep: USINT;
bIsTest: BOOL;
fbGetTime: NT_GetTime;
bStopOnErr: BOOL;
END_VAR
fbCom.sSendSuffix := '$R';
fbCom.sRecvSuffix := '$R';
IF bExecute AND nStep = 0 THEN
IF bResetError OR NOT bError THEN
nStep := 10;
END_IF
ELSIF NOT bExecute THEN
nStep := 0;
END_IF
CASE nStep OF
0:
; // idle
10:
// Get position
bIsTest := FALSE;
fbCom(sCmd:='pos?',
bSend:=TRUE,
stIn_EL6:=stIn_EL6,
stOut_EL6:=stOut_EL6);
nStep := nStep + 10;
20:
// Wait for response and set variables
fbCom(stIn_EL6:=stIn_EL6,
stOut_EL6:=stOut_EL6);
IF fbCom.bDone THEN
bError := FALSE;
sError := '';
nGetPos := STRING_TO_USINT(fbCom.sResponse);
nSetPos := nGetPos;
nStep := nStep + 10;
IF nGetPos = 0 THEN
sError := 'Filter wheel in invalid state';
bStopOnErr := TRUE;
nStep := 50;
END_IF
END_IF
30:
// Wait for a move request
IF nSetPos <> nGetPos THEN
fbCom(sCmd:=CONCAT('pos=', INT_TO_STRING(nSetPos)),
bSend:=TRUE,
stIn_EL6:=stIn_EL6,
stOut_EL6:=stOut_EL6);
nStep := nStep + 10;
bBusy := TRUE;
END_IF
40:
fbCom(stIn_EL6:=stIn_EL6,
stOut_EL6:=stOut_EL6);
// Wait for move to be done
IF fbCom.bDone THEN
bBusy := FALSE;
nStep := 10;
// Handle setpoint error
IF fbCom.sResponse = 'Command error CMD_ARG_INVALID$N$R' THEN
sError := 'Invalid set position';
nStep := 50;
END_IF
END_IF
50:
// Set sError and then jump here for standard handling
sLastError := sError;
bError := TRUE;
fbGetTime(NETID:='',
START:=TRUE);
nStep := nStep + 10;
60:
// Error handling continued
fbGetTime();
IF NOT fbGetTime.BUSY THEN
sErrorTS := SYSTEMTIME_TO_STRING(fbGetTime.TIMESTR);
fbGetTime.START := FALSE;
// set bStopOnErr to TRUE if it was a major error
IF bStopOnErr THEN
nStep := 0;
ELSE
nStep := 10;
END_IF
bStopOnErr := FALSE;
END_IF
END_CASE
// Check for inner comms errors, report to EPICS same way
IF NOT bError AND
(fbCom.eRecvErrorID <> COMERROR_NOERROR
OR fbCom.eSendErrorID <> COMERROR_NOERROR
OR fbCom.eRecvErrorID <> COMERROR_NOERROR) THEN
sError := 'Serial Communication Error';
bStopOnErr := TRUE;
nStep := 50;
END_IF
END_FUNCTION_BLOCK
- Related:
FB_XPIM_LED
FUNCTION_BLOCK FB_XPIM_LED
VAR_INPUT
{attribute 'pytmc' := '
pv: PWR
io: io
field: ZNAM OFF
field: ONAM ON
'}
bLEDPower AT %Q*: BOOL;
{attribute 'pytmc' := '
pv: AUTO
io: io
'}
bLEDAuto: BOOL := TRUE;
{attribute 'pytmc' := '
pv: CLK:TIMEOUT
io: io
field: EGU min
'}
fLEDTimeOut: LREAL := 10;
enumXPIM: E_XPIM_States;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: CLK:REMAINING
io: io
field: EGU min
'}
fLEDRemaining: LREAL;
END_VAR
VAR
tonLED: TON;
enumLastCycle: E_XPIM_States := E_XPIM_States.Unknown;
END_VAR
// If configured, change the LED level automatically
// LED is always (and only) useful at Reticle state
IF bLEDAuto AND enumXPIM <> enumLastCycle THEN
// Turn on the LED when we get to the Reticle
IF enumXPIM = E_XPIM_States.Reticle THEN
bLEDPower := TRUE;
// Turn off the LED when we stop at any other state
ELSIF enumXPIM <> E_XPIM_States.Unknown THEN
bLEDPower := FALSE;
END_IF
END_IF
enumLastCycle := enumXPIM;
// If configured, start a shutdown timer when LED goes high
IF fLEDTimeOut <> 0 THEN;
tonLED(IN:=bLEDPower,
PT:=LREAL_TO_TIME(fLEDTimeOut * 60 * 1000));
fLEDRemaining := fLEDTimeOut - TIME_TO_LREAL(tonLED.ET) / 60 / 1000;
IF tonLED.Q THEN
bLEDPower := FALSE;
END_IF
END_IF
END_FUNCTION_BLOCK
- Related:
FB_XPIM_Opal
FUNCTION_BLOCK FB_XPIM_Opal
VAR_INPUT
{attribute 'pytmc' := '
pv: PWR
io: io
field: ZNAM OFF
field: ONAM ON
'}
bOpalPower AT %Q*: BOOL;
END_VAR
VAR
bOpalInit: BOOL := FALSE;
END_VAR
// Turn the Opal on by default
IF NOT bOpalInit THEN
bOpalPower := TRUE;
bOpalInit := TRUE;
END_IF
END_FUNCTION_BLOCK
FB_XPIMTest
FUNCTION_BLOCK FB_XPIMTest EXTENDS FB_TestSuite
VAR
fbXPIM: FB_XPIM;
stYStage: ST_MotionStage;
stZoomStage: ST_MotionStage;
stFocusStage: ST_MotionStage;
stEl6In: EL6inData22b;
stEl6Out: EL6OutData22b;
stDefault: ST_PositionState := (
fVelocity:=10,
bMoveOk:=TRUE,
bValid:=TRUE
);
fbSetup: FB_StateSetupHelper;
fbFFHWO: FB_HardwareFFOutput;
fbArbiter: FB_Arbiter(1);
fbSubSysIO: FB_DummyArbIO;
bInit: BOOL;
END_VAR
// Fake PMPS handling
fbSubSysIO(
LA:=fbArbiter,
FFO:=fbFFHWO,
);
// Fake limit handling
stYStage.bLimitBackwardEnable := TRUE;
stYStage.bLimitForwardEnable := TRUE;
// Standard state setup
fbSetup(stPositionState:=stDefault, bSetDefault:=TRUE);
fbSetup(stPositionState:=fbXPIM.stOut, sName:='OUT', fPosition:=10, sPmpsState:='T0');
fbSetup(stPositionState:=fbXPIM.stReticle, sName:='T1', fPosition:=20, sPmpsState:='T1');
fbSetup(stPositionState:=fbXPIM.stYag, sName:='T2', fPosition:=30, sPmpsState:='T2');
fbSetup(stPositionState:=fbXPIM.stDiamond, sName:='T3', fPosition:=40, sPmpsState:='T3');
// Standard FB call
fbXPIM(
stYStage:=stYStage,
stZoomStage:=stZoomStage,
stFocusStage:=stFocusStage,
fbFFHWO:=fbFFHWO,
fbArbiter:=fbArbiter,
stEl6In:=stEl6In,
stEl6Out:=stEl6Out,
bEnableMotion:=TRUE,
bEnableBeamParams:=TRUE,
bEnablePositionLimits:=TRUE,
sDeviceName:='DEVICE',
sTransitionKey:='T9',
bReadDBNow:=NOT bInit,
);
TestStateMove();
bInit := TRUE;
END_FUNCTION_BLOCK
METHOD TestStateMove
VAR_INST
tonTimer: TON;
nIterState: UINT := 0;
END_VAR
VAR CONSTANT
nLastState: UINT := E_XPIM_States.RETICLE;
END_VAR
// Sanity check: can we at least move to every named state?
TEST('TestXPIMStateMove');
// Prepare a timeout
tonTimer(IN:=TRUE, PT:=T#10s);
// Start in Unknown, then go through the state positions one by one
IF fbXPIM.eEnumGet = nIterState THEN
nIterState := nIterState + 1;
fbXPIM.eEnumSet := nIterState;
END_IF
IF tonTimer.Q OR nIterState > nLastState THEN
AssertFalse(tonTimer.Q, 'Timeout in XPIM move test');
TEST_FINISHED();
END_IF
END_METHOD
- Related:
FB_XTES_Flowswitch
{attribute 'analysis' := '-33'}
FUNCTION_BLOCK FB_XTES_Flowswitch
VAR_OUTPUT
{attribute 'pytmc' := '
pv: FLOW_OK
field: ZNAM LOW
field: ONAM OK
'}
bFlowOk AT %I*: BOOL;
END_VAR
END_FUNCTION_BLOCK
PRG_TEST
{attribute 'analysis' := '-33'}
PROGRAM PRG_TEST
VAR
fbATMTest: FB_ATMTest;
fbLICTest: FB_LICTest;
fbPPMTest: FB_PPMTest;
fbREFTest: FB_REFTest;
fbSATTTest: FB_SATTTest;
fbWFSTest: FB_WFSTest;
fbXPIMTest: FB_XPIMTest;
fbCheckPositionStateWriteTest: FB_CheckPositionStateWriteTest;
fbSetupJson: FB_PMPSJsonTestHelper;
astBeamParams: ARRAY[0..9] OF ST_DbStateParams;
nIter: UINT;
END_VAR
// Setup a fake db export for the test suite
FOR nIter := 0 TO 9 DO
astBeamParams[nIter].stBeamParams := PMPS_GVL.cstFullBeam;
astBeamParams[nIter].sPmpsState := CONCAT('T', UINT_TO_STRING(nIter));
END_FOR
fbSetupJson(
astBeamParams := astBeamParams,
bExecute := TRUE,
sDevNAme := 'DEVICE',
);
// Run the tests
TcUnit.RUN();
END_PROGRAM