DUTs
E_DeviceState
{attribute 'qualified_only'}
TYPE E_DeviceState :
(
INVALID := 0,
INIT := EC_DEVICE_STATE_INIT,
PREOP := EC_DEVICE_STATE_PREOP,
BOOTSTRAP := EC_DEVICE_STATE_BOOTSTRAP,
SAFEOP := EC_DEVICE_STATE_SAFEOP,
OP := EC_DEVICE_STATE_OP
) WORD := INVALID;
END_TYPE
- Related:
ST_DeviceDiagnostic
// PLC Terminal device diagnostics
TYPE ST_DeviceDiagnostic :
STRUCT
{attribute 'pytmc' := '
pv: TYPE
io: i
'}
sType : STRING[15];
{attribute 'pytmc' := '
pv: ADDRESS
io: i
'}
nAddr : WORD;
{attribute 'pytmc' := '
pv: NAME
io: i
'}
sName : STRING[31];
{attribute 'pytmc' := '
pv: DEVICE_STATE
io: i
field: ZRVL 0
field: ONVL 1
field: TWVL 2
field: THVL 3
field: FRVL 4
field: SXVL 8
field: ZRST INVALID
field: ONST INIT
field: TWST PREOP
field: THST BOOTSTRAP
field: FRST SAFEOP
field: SXST OP
field: ZRSV MAJOR
field: ONSV MAJOR
field: TWSV MAJOR
field: THSV MAJOR
field: FRSV MAJOR
field: FVSV MINOR
field: SXSV NO_ALARM
field: UNSV MAJOR
'}
nDeviceState : ENUM;
{attribute 'pytmc' := '
pv: DEVICE_STATE_FLAGS
io: i
'}
nDeviceStateFlags : BYTE;
{attribute 'pytmc' := '
pv: LINK_STATE_FLAGS
io: i
'}
nLinkStateFlags : BYTE;
END_STRUCT
END_TYPE
ST_ErrorStates
// Error states that can occur in the program
TYPE ST_ErrorStates :
STRUCT
// Leak Alarms
bLeakDetected : BOOL; // Leak detected on one of the sensors
// Hardware Failure
bHardwareFailure : BOOL; // A hardware component has failed
END_STRUCT
END_TYPE
GVLs
GVL_IO
// PLC IO from terminal modules
{attribute 'qualified_only'}
VAR_GLOBAL
(* Slot1 EL2212 -- Cooling Valve Outputs *)
{attribute 'pytmc' := '
pv: @(PREFIX):VALVE:01
io: i
field: ZNAM "CLOSED"
field: ONAM "OPEN"
'}
{attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 1^Control^Output'}
bChillerValve01 AT %Q* : BOOL; // Ch1
{attribute 'pytmc' := '
pv: @(PREFIX):VALVE:02
io: i
field: ZNAM "CLOSED"
field: ONAM "OPEN"
'}
{attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 2^Control^Output'}
bChillerValve02 AT %Q* : BOOL; // Ch2
(* Slot2 EL1004 -- Leak Detector Inputs *)
{attribute 'pytmc' := '
pv: @(PREFIX):DETECTOR:01
io: i
field: ZNAM "OK"
field: ONAM "LEAK"
'}
{attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E03_EL1004]^Channel 1^Input'}
bLeakRelay01 AT %I* : BOOL; // Ch1
{attribute 'pytmc' := '
pv: @(PREFIX):DETECTOR:02
io: i
field: ZNAM "OK"
field: ONAM "LEAK"
'}
{attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E03_EL1004]^Channel 2^Input'}
bLeakRelay02 AT %I* : BOOL; // Ch2
END_VAR
GVL_PLC
// PLC global variables
{attribute 'qualified_only'}
VAR_GLOBAL
AMSNetId AT %I* : AMSNETID;
END_VAR
POUs
F_ChillerValveEnable
// Interlock logic for the Dump Chiller
FUNCTION F_ChillerValveEnable : BOOL
VAR_INPUT
END_VAR
VAR_IN_OUT CONSTANT
stErrors : ST_ErrorStates; // Currently detected errors
END_VAR
VAR
END_VAR
IF stErrors.bHardwareFailure OR stErrors.bLeakDetected THEN
F_ChillerValveEnable := FALSE;
ELSE
F_ChillerValveEnable := TRUE;
END_IF
END_FUNCTION
- Related:
FB_EcatDiag
(*
Ecat bus diagnostic tool
2015-11-4 Alex Wallace
This function block checks the states of all slaves on the ecat bus network,
it could be modified to export the states of the slaves on an individual basis,
but for now it sets the output boolean true if all slaves are OP and false otherwise.
To start the block provide a falling edge on the first pass boolean input.
2018-05-05 Margaret Ghaly
Function block has been modified to retrieve the Device State of the Ethercat Master.
It also exports the states and information of each individual configured Slave.
And saves them in the array q_aEcConfSlaveInfo.
2019-08-28 Nathan Telles
Customized for EPS project using PyTMC. Export all states to a structure which is
linked to EPICS records. Remove unecessary diagnostics.
*)
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_EcatDiag
VAR_INPUT
{attribute 'naming' := 'omit'}
AMSNetId : AMSNETID; //Link to the AMSNETID name in the ethercat master info.
//i_xFirstPass: BOOL; //Hook to system first pass boolean for proper intialization (must be true for the first cycle of the PLC)
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: ERROR
io: i
field: OSV MAJOR
field: ZSV NO_ALARM
field: ZNAM OK
field: ONAM ERROR
'}
bError : BOOL;
{attribute 'pytmc' := '
pv: SLAVES_OK
io: i
field: OSV NO_ALARM
field: ZSV MAJOR
field: ZNAM ERROR
field: ONAM OK
'}
bAllSlaveStatesGood: BOOL; // Set to True if all Slaves are in OP State
{attribute 'pytmc' := '
pv: MASTER_OK
io: i
field: OSV NO_ALARM
field: ZSV MAJOR
field: ZNAM ERROR
field: ONAM OK
'}
bMasterStateGood : BOOL;
END_VAR
VAR
{attribute 'pytmc' := '
pv: MASTER_DEV_STATE
io: i
'}
nMasterDevState: WORD; // The Device State of the Master
{attribute 'pytmc' := '
pv: MASTER_STATE
io: i
'}
nMasterState: WORD; // The State of the Master
{attribute 'pytmc' := '
pv: AMS_ID
io: i
'}
sNetId: STRING(23); //NetId string
astTermStates: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveState; //ECAT Slave States Buffer
astEcConfSlaveInfo: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveConfigData; //ECAT Slave Configs Buffer
fbGetAllSlaveStates: FB_EcGetAllSlaveStates; //Acquires the ECAT Slave States puts them into astTermStates
fbGetMasterDevState: FB_EcGetMasterDevState; //Acquires ECAT Master State
fbGetMasterState: FB_EcGetMasterState; //Acquires ECAT Master State
fbGetConfSlaves: FB_EcGetConfSlaves; //Acquires the ECAT slave configuration of the bus (how many, what kind, etc)
{attribute 'naming' := 'omit'}
ftReset: F_TRIG; //Reset trigger sensor
{attribute 'naming' := 'omit'}
ftMasterDevReset: F_TRIG; //Retrigger sensor for GetMasterDevState
{attribute 'naming' := 'omit'}
ftMasterReset: F_TRIG; //Retrigger sensor for GetMasterState
{attribute 'pytmc' := 'pv: SLAVE01'}
stSlave01 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE02'}
stSlave02 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE03'}
stSlave03 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE04'}
stSlave04 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE05'}
stSlave05 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE06'}
stSlave06 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE07'}
stSlave07 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE08'}
stSlave08 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE09'}
stSlave09 : ST_DeviceDiagnostic;
{attribute 'pytmc' := 'pv: SLAVE10'}
stSlave10 : ST_DeviceDiagnostic;
aSlaves : ARRAY [1..MAX_SLAVES] OF POINTER TO ST_DeviceDiagnostic := [ADR(stSlave01),ADR(stSlave02),
ADR(stSlave03),ADR(stSlave04),ADR(stSlave05),ADR(stSlave06),ADR(stSlave07),ADR(stSlave08),
ADR(stSlave09), ADR(stSlave10)];
nIterator: UINT;
bInit : BOOL := TRUE;
{attribute 'pytmc' := '
pv: SLAVES_COUNT
io: i
'}
nSlaves : UINT;
END_VAR
VAR CONSTANT
MAX_SLAVES : UINT := 10;
END_VAR
sNetId := F_CreateAmsNetId(AMSNetId);
//Query the state of all terminals, collect in astTermStates
ftReset(CLK:=fbGetAllSlaveStates.bBusy OR bInit);
fbGetAllSlaveStates.bExecute := ftReset.Q;
fbGetAllSlaveStates(sNetId:=sNetId, pStateBuf := ADR(astTermStates), cbBufLen:=SIZEOF(astTermStates));
//Keep checking...
//Cycle through each entry in the array and check if we have anyone not in OP and that the link state is good.
// If so, then set our global IO bad boolean.
IF fbGetAllSlaveStates.nSlaves > 0 THEN
bAllSlaveStatesGood := TRUE;
FOR nIterator := 1 TO MAX_SLAVES DO
IF nIterator > fbGetAllSlaveStates.nSlaves // Slave is missing
OR NOT( (astTermStates[nIterator].deviceState = EC_DEVICE_STATE_OP) AND (astTermStates[nIterator].linkState = EC_LINK_STATE_OK))
THEN
bAllSlaveStatesGood := FALSE;
END_IF
IF nIterator <= fbGetAllSlaveStates.nSlaves THEN
aSlaves[nIterator]^.nDeviceState := astTermStates[nIterator].deviceState AND EC_DEVICE_STATE_MASK;
aSlaves[nIterator]^.nDeviceStateFlags := SHR(astTermStates[nIterator].deviceState, 4);
aSlaves[nIterator]^.nLinkStateFlags :=astTermStates[nIterator].linkState;
ELSE
// Missing slaves should be set to invalid
aSlaves[nIterator]^.nDeviceState := E_DeviceState.INVALID;
END_IF
END_FOR
END_IF
// Read the EtherCAT state of the master. If the call is successful,
//the State output variable of type WORD contains the requested status information.
ftMasterDevReset(CLK:=fbGetMasterDevState.bBusy OR bInit);
fbGetMasterDevState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, nDevState => nMasterDevState);
ftMasterReset(CLK:=fbGetMasterState.bBusy OR bInit);
fbGetMasterState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, state => nMasterState);
bMasterStateGood:= (fbGetMasterState.state = BYTE_TO_UINT(EC_DEVICE_STATE_OP) AND fbGetMasterDevState.nDevState = 0);
//This function is used to read a list of all configured slaves from the EtherCat master object Directory
//needs to run only once
fbGetConfSlaves(bExecute := bInit, sNetId :=sNetId, pArrEcConfSlaveInfo := ADR(astEcConfSlaveInfo),cbBufLen := SIZEOF(astEcConfSlaveInfo));
nSlaves:=fbGetConfSlaves.nSlaves;
IF NOT (fbGetConfSlaves.bBusy) THEN
FOR nIterator := 1 TO MIN(MAX_SLAVES, fbGetConfSlaves.nSlaves) DO
IF nIterator > fbGetConfSlaves.nSlaves THEN // Missing terminal
aSlaves[nIterator]^.nAddr := 0;
aSlaves[nIterator]^.sName := '';
aSlaves[nIterator]^.sType := '';
ELSE
aSlaves[nIterator]^.nAddr :=astEcConfSlaveInfo[nIterator].nAddr;
aSlaves[nIterator]^.sName :=astEcConfSlaveInfo[nIterator].sName;
aSlaves[nIterator]^.sType :=astEcConfSlaveInfo[nIterator].sType;
END_IF
END_FOR
fbGetConfSlaves.bExecute := FALSE;
END_IF
bInit := FALSE;
bError := fbGetMasterDevState.bError OR fbGetMasterState.bError OR fbGetAllSlaveStates.bError OR
fbGetConfSlaves.bError OR NOT bAllSlaveStatesGood OR NOT bMasterStateGood;
END_FUNCTION_BLOCK
- Related:
FB_ErrorTriggers
// Detects rising-edge errors
FUNCTION_BLOCK FB_ErrorTriggers
VAR_IN_OUT CONSTANT
stErrors : ST_ErrorStates; // Errors that have been detected
END_VAR
VAR_OUTPUT
stNewErrors : ST_ErrorStates; // Rising-edge errors
END_VAR
VAR
// R_TRIG function blocks for detecting new errors
fbLeakTrig : R_TRIG;
END_VAR
fbLeakTrig(
CLK := stErrors.bLeakDetected,
Q => stNewErrors.bLeakDetected
);
END_FUNCTION_BLOCK
// Returns whether a rising-edge leak error has occured
METHOD NewLeakError : BOOL
VAR_INPUT
END_VAR
NewLeakError := fbLeakTrig.Q;
END_METHOD
// Reset latched leak errors
METHOD ResetLeakTriggers
VAR_INPUT
END_VAR
fbLeakTrig(
CLK := FALSE,
Q => stNewErrors.bLeakDetected
);
END_METHOD
- Related:
FB_LeakMonitor
// Detect leaks and leak locations
FUNCTION_BLOCK FB_LeakMonitor
VAR_INPUT
bLeakRelay01 : BOOL; // Whether a leak was detected on sensor 1
bLeakRelay02 : BOOL; // Whether a leak was detected on sensor 2
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: LEAK_FOUND
io: i
field: OSV MAJOR
field: ZSV NO_ALARM
field: ZNAM OK
field: ONAM LEAK
'}
bError: BOOL := FALSE; //Error flag
END_VAR
(* Set error flag if leak is detected on either sensor*)
IF (bLeakRelay01 = FALSE OR bLeakRelay02 = FALSE) THEN
bError := TRUE;
END_IF
END_FUNCTION_BLOCK
// Reset latched errors
METHOD Reset
VAR_INPUT
END_VAR
bError := FALSE;
END_METHOD
MAIN
PROGRAM MAIN
VAR
stErrors : ST_ErrorStates; // Triggered error states
fbErrorTriggers : FB_ErrorTriggers; // Brand new errors this cycl
{attribute 'pytmc' := 'pv: @(PREFIX):LEAK_MONITOR'}
fbLeakMonitor : FB_LeakMonitor; //Leak detection
{attribute 'pytmc' := '
pv: @(PREFIX):VALVE:RESET
io: o
'}
bValveLatchReset : BOOL; //Manual latch for valves
(*
{attribute 'pytmc' := 'pv: @(PREFIX):FTL:PLC'}
fbDiag : FB_EcatDiag; // Ethercat diagnostic
*)
END_VAR
(*
(* Check PLC hardware for failures *)
fbDiag(AMSNetId := GVL_PLC.AMSNetId);
stErrors.bHardwareFailure := fbDiag.bError;
*)
(* Monitor for leaks on cooling loop*)
fbLeakMonitor(
bLeakRelay01 := GVL_IO.bLeakRelay01,
bLeakRelay02 := GVL_IO.bLeakRelay02,
bError => stErrors.bLeakDetected
);
(* Open NC chiller loop valves if interlock logic passes *)
GVL_IO.bChillerValve01 := F_ChillerValveEnable(stErrors := stErrors);
GVL_IO.bChillerValve02 := F_ChillerValveEnable(stErrors := stErrors);
(* Check for rising-edge errors *)
fbErrorTriggers(stErrors := stErrors);
(* Check for new leak errors *)
IF fbErrorTriggers.NewLeakError() THEN
bValveLatchReset := FALSE; //If there is a new error turn off the valve reset
ELSIF bValveLatchReset THEN
// If there are no new errors and reset is enabled, reset the errors
fbLeakMonitor.Reset();
fbErrorTriggers.ResetLeakTriggers();
END_IF
END_PROGRAM