(*═══════════════════════════════════════════════════════════════════════════════ FB_CylinderMonitor S7-1500 SCL — Universal Pneumatic/Hydraulic Cylinder Monitor TIA Portal V18+ | Optimized Block Access ───────────────────────────────────────────────────────────────────────────── PURPOSE: Wraps any cylinder (pneumatic or hydraulic) with: • Configurable sensor topology (NONE / ONE_FWD / ONE_BWD / TWO) • Timeout fault generation per motion direction • Sensor conflict detection • Intermediate-position detection • HMI-ready status word, fault code, and text description • Output gate: only passes actuator command when FB is healthy → plug CylCmd_Safe into your sequencer actuator output instead of the raw command bit ───────────────────────────────────────────────────────────────────────────── SENSOR TOPOLOGY (SensorConfig input INT): ┌──────┬────────────────────────────────────────────────────────────────────┐ │ 0 │ NONE No position sensors. Advance purely by external timer. │ │ │ No position fault possible. ConflictFault impossible. │ │ 1 │ ONE_FWD One sensor at FORWARD (extended) end only. │ │ │ FWD cmd → timeout if Sen_Fwd not TRUE within T_Timeout │ │ │ BWD cmd → no feedback; only intermediate state reported │ │ 2 │ ONE_BWD One sensor at BACKWARD (retracted) end only. │ │ │ BWD cmd → timeout if Sen_Bwd not TRUE within T_Timeout │ │ │ FWD cmd → no feedback; only intermediate state reported │ │ 3 │ TWO Sensor at BOTH ends (most common in PLC applications) │ │ │ FWD cmd → timeout if Sen_Fwd not TRUE within T_Timeout │ │ │ BWD cmd → timeout if Sen_Bwd not TRUE within T_Timeout │ │ │ BOTH TRUE simultaneously → ConflictFault │ └──────┴────────────────────────────────────────────────────────────────────┘ ───────────────────────────────────────────────────────────────────────────── CYLINDER STATE (CylState output INT): 0 IDLE No command active 1 MOVING_FWD FWD command active, waiting for sensor 2 AT_FWD FWD sensor confirmed (or FWD cmd active, NONE config) 3 MOVING_BWD BWD command active, waiting for sensor 4 AT_BWD BWD sensor confirmed (or BWD cmd active, NONE config) 5 INTERMEDIATE No sensor active when both sensors expected (TWO config) 6 FAULTED Timeout or conflict — CylCmd_Safe output gated OFF ───────────────────────────────────────────────────────────────────────────── FAULT CODES (FaultCode output INT): 0 No fault 1 FWD timeout — Sen_Fwd did not confirm within T_Timeout after FWD cmd 2 BWD timeout — Sen_Bwd did not confirm within T_Timeout after BWD cmd 3 Conflict — Sen_Fwd AND Sen_Bwd both TRUE at the same time 4 Lost — Cylinder left known position without a command (TWO only) ───────────────────────────────────────────────────────────────────────────── OUTPUT GATING STRATEGY: CylCmd_FwdSafe / CylCmd_BwdSafe output ONLY when: • Input command is TRUE • FB is NOT faulted • No conflict detected Connect these "safe" outputs directly to your DQ outputs or sequencer. ───────────────────────────────────────────────────────────────────────────── INTEGRATION WITH FB_WeldSequencer: Each cylinder gets one FB_CylinderMonitor instance. The sequencer reads Cyl.Fault_Active to trigger a pause or stop. The sequencer reads Cyl.AtFwd / Cyl.AtBwd for step advance condition. See FC_CylIntegration at the bottom of this file for wiring example. ═══════════════════════════════════════════════════════════════════════════════*) FUNCTION_BLOCK "FB_CylinderMonitor" { S7_Optimized_Access := 'TRUE' } VERSION : 0.1 VAR_INPUT //── CYLINDER IDENTITY ──────────────────────────────────────────────────── CylName : String[32]; // Human-readable name for HMI messages // e.g. 'Clamp Cylinder' or 'Weld Head' //── SENSOR CONFIGURATION ───────────────────────────────────────────────── SensorConfig : Int; // 0=NONE 1=ONE_FWD 2=ONE_BWD 3=TWO T_Timeout : Time; // Fault if position not reached in time // Typical: T#2S for fast pneumatics // T#5S for slow hydraulics //── COMMANDS ───────────────────────────────────────────────────────────── Cmd_Fwd : Bool; // Command cylinder to extend (FWD) Cmd_Bwd : Bool; // Command cylinder to retract (BWD) Cmd_Reset : Bool; // Clear fault (rising edge) //── SENSOR INPUTS ──────────────────────────────────────────────────────── Sen_Fwd : Bool; // Forward / extended position sensor Sen_Bwd : Bool; // Backward / retracted position sensor // Leave FALSE if not wired (NONE/ONE) //── ENABLE ─────────────────────────────────────────────────────────────── Enable : Bool; // FALSE = ignore commands, hold state // Use for E-Stop or mode control END_VAR VAR_OUTPUT //── SAFE GATED OUTPUTS ─────────────────────────────────────────────────── // Connect THESE to your DQ / actuator solenoid, not the raw Cmd_Fwd/Bwd CylCmd_FwdSafe : Bool; // Gated FWD command (only if healthy) CylCmd_BwdSafe : Bool; // Gated BWD command (only if healthy) //── POSITION STATUS ────────────────────────────────────────────────────── AtFwd : Bool; // Cylinder is confirmed at FWD position AtBwd : Bool; // Cylinder is confirmed at BWD position Intermediate : Bool; // Cylinder between positions (TWO config) Moving : Bool; // Command active, position not yet confirmed //── FAULT STATUS ───────────────────────────────────────────────────────── Fault_Active : Bool; // TRUE = any fault present Fault_FwdTimeout : Bool; // FWD timeout fault bit Fault_BwdTimeout : Bool; // BWD timeout fault bit Fault_Conflict : Bool; // Sensor conflict fault bit Fault_Lost : Bool; // Lost position fault bit //── CODES AND HMI ──────────────────────────────────────────────────────── FaultCode : Int; // Numeric fault code (0 = OK, 1-4 see header) CylState : Int; // Cylinder state code (0-6, see header) HMI_StatusText : String[80]; // Ready-to-display operator message HMI_FaultText : String[80]; // Ready-to-display fault message for HMI END_VAR VAR //── TIMERS ─────────────────────────────────────────────────────────────── _timerFwd : TON; // Starts on FWD command; fault on .Q _timerBwd : TON; // Starts on BWD command; fault on .Q //── EDGE DETECTION ─────────────────────────────────────────────────────── _prevReset : Bool; _prevCmdFwd : Bool; _prevCmdBwd : Bool; //── INTERNAL STATE ─────────────────────────────────────────────────────── _faulted : Bool; // Internal faulted latch _faultCode : Int; // Internal fault code _cylState : Int; // Internal state code _lastKnownState : Int; // Last confirmed position (for lost detection) END_VAR VAR_TEMP rReset : Bool; rCmdFwd : Bool; rCmdBwd : Bool; t_Cfg : Int; // Local copy of SensorConfig for readability END_VAR VAR CONSTANT //── SENSOR CONFIG CONSTANTS ────────────────────────────────────────────── CFG_NONE : Int := 0; CFG_ONE_FWD : Int := 1; CFG_ONE_BWD : Int := 2; CFG_TWO : Int := 3; //── CYLINDER STATE CONSTANTS ───────────────────────────────────────────── CYL_IDLE : Int := 0; CYL_MOVING_FWD : Int := 1; CYL_AT_FWD : Int := 2; CYL_MOVING_BWD : Int := 3; CYL_AT_BWD : Int := 4; CYL_INTERMEDIATE : Int := 5; CYL_FAULTED : Int := 6; //── FAULT CODE CONSTANTS ───────────────────────────────────────────────── FAULT_NONE : Int := 0; FAULT_FWD_TIMEOUT : Int := 1; FAULT_BWD_TIMEOUT : Int := 2; FAULT_CONFLICT : Int := 3; FAULT_LOST : Int := 4; END_VAR BEGIN (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 1 — EDGE DETECTION ░░ ═══════════════════════════════════════════════════════════════════════════*) rReset := Cmd_Reset AND NOT _prevReset; rCmdFwd := Cmd_Fwd AND NOT _prevCmdFwd; rCmdBwd := Cmd_Bwd AND NOT _prevCmdBwd; _prevReset := Cmd_Reset; _prevCmdFwd := Cmd_Fwd; _prevCmdBwd := Cmd_Bwd; t_Cfg := SensorConfig; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 2 — FAULT RESET ░░ ═══════════════════════════════════════════════════════════════════════════*) IF rReset THEN _faulted := FALSE; _faultCode := FAULT_NONE; Fault_FwdTimeout := FALSE; Fault_BwdTimeout := FALSE; Fault_Conflict := FALSE; Fault_Lost := FALSE; // Force both timers off on reset _timerFwd(IN := FALSE, PT := T_Timeout); _timerBwd(IN := FALSE, PT := T_Timeout); END_IF; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 3 — SENSOR CONFLICT CHECK (highest priority, every scan) ░░ Only possible with TWO sensor config ═══════════════════════════════════════════════════════════════════════════*) IF (t_Cfg = CFG_TWO) AND Sen_Fwd AND Sen_Bwd THEN _faulted := TRUE; _faultCode := FAULT_CONFLICT; Fault_Conflict := TRUE; END_IF; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 4 — TIMEOUT TIMERS ░░ Timer IN = command active AND sensor NOT yet confirmed AND not faulted This ensures timers only run during active motion, not in hold. Timers auto-reset when command drops or sensor arrives. ═══════════════════════════════════════════════════════════════════════════*) // ── FWD timeout timer ───────────────────────────────────────────────────── // Active when: FWD commanded AND sensor not confirmed AND config allows it CASE t_Cfg OF CFG_ONE_FWD, CFG_TWO: // Timer runs while FWD commanded but Sen_Fwd not yet TRUE _timerFwd( IN := Cmd_Fwd AND NOT Sen_Fwd AND NOT _faulted AND Enable, PT := T_Timeout ); ELSE: // NONE or ONE_BWD — no FWD sensor, no FWD timeout possible _timerFwd(IN := FALSE, PT := T_Timeout); END_CASE; // ── BWD timeout timer ───────────────────────────────────────────────────── CASE t_Cfg OF CFG_ONE_BWD, CFG_TWO: _timerBwd( IN := Cmd_Bwd AND NOT Sen_Bwd AND NOT _faulted AND Enable, PT := T_Timeout ); ELSE: _timerBwd(IN := FALSE, PT := T_Timeout); END_CASE; // ── Trigger faults on timer expiry ───────────────────────────────────── IF _timerFwd.Q AND NOT _faulted THEN _faulted := TRUE; _faultCode := FAULT_FWD_TIMEOUT; Fault_FwdTimeout := TRUE; END_IF; IF _timerBwd.Q AND NOT _faulted THEN _faulted := TRUE; _faultCode := FAULT_BWD_TIMEOUT; Fault_BwdTimeout := TRUE; END_IF; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 5 — LOST POSITION DETECTION (TWO sensor config only) ░░ If both sensors go FALSE when we expect one to be TRUE, the cylinder has moved without a command (mechanical failure, etc.) ═══════════════════════════════════════════════════════════════════════════*) IF t_Cfg = CFG_TWO AND NOT _faulted THEN // We had a confirmed position... IF _lastKnownState = CYL_AT_FWD OR _lastKnownState = CYL_AT_BWD THEN // ...but now neither sensor is active and no command is running IF NOT Sen_Fwd AND NOT Sen_Bwd AND NOT Cmd_Fwd AND NOT Cmd_Bwd THEN _faulted := TRUE; _faultCode := FAULT_LOST; Fault_Lost := TRUE; END_IF; END_IF; END_IF; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 6 — CYLINDER STATE MACHINE ░░ ═══════════════════════════════════════════════════════════════════════════*) IF _faulted THEN _cylState := CYL_FAULTED; ELSIF NOT Enable THEN // Disabled — stay in IDLE regardless _cylState := CYL_IDLE; ELSIF Cmd_Fwd AND NOT Cmd_Bwd THEN // FWD command active CASE t_Cfg OF CFG_NONE: // No sensors — trust the command, immediately "at FWD" _cylState := CYL_AT_FWD; CFG_ONE_FWD, CFG_TWO: IF Sen_Fwd THEN _cylState := CYL_AT_FWD; _lastKnownState := CYL_AT_FWD; ELSE _cylState := CYL_MOVING_FWD; END_IF; CFG_ONE_BWD: // FWD cmd but sensor is only on BWD side — report moving until BWD clears IF NOT Sen_Bwd THEN _cylState := CYL_AT_FWD; // Best we can say: not at BWD ELSE _cylState := CYL_MOVING_FWD; END_IF; END_CASE; ELSIF Cmd_Bwd AND NOT Cmd_Fwd THEN // BWD command active CASE t_Cfg OF CFG_NONE: _cylState := CYL_AT_BWD; CFG_ONE_BWD, CFG_TWO: IF Sen_Bwd THEN _cylState := CYL_AT_BWD; _lastKnownState := CYL_AT_BWD; ELSE _cylState := CYL_MOVING_BWD; END_IF; CFG_ONE_FWD: IF NOT Sen_Fwd THEN _cylState := CYL_AT_BWD; ELSE _cylState := CYL_MOVING_BWD; END_IF; END_CASE; ELSIF NOT Cmd_Fwd AND NOT Cmd_Bwd THEN // No command — check resting state CASE t_Cfg OF CFG_NONE: _cylState := CYL_IDLE; CFG_ONE_FWD: IF Sen_Fwd THEN _cylState := CYL_AT_FWD; ELSE _cylState := CYL_IDLE; // Could be at BWD, we don't know END_IF; CFG_ONE_BWD: IF Sen_Bwd THEN _cylState := CYL_AT_BWD; ELSE _cylState := CYL_IDLE; END_IF; CFG_TWO: IF Sen_Fwd AND NOT Sen_Bwd THEN _cylState := CYL_AT_FWD; _lastKnownState := CYL_AT_FWD; ELSIF Sen_Bwd AND NOT Sen_Fwd THEN _cylState := CYL_AT_BWD; _lastKnownState := CYL_AT_BWD; ELSIF NOT Sen_Fwd AND NOT Sen_Bwd THEN _cylState := CYL_INTERMEDIATE; END_IF; END_CASE; ELSIF Cmd_Fwd AND Cmd_Bwd THEN // Both commands simultaneously → command conflict (programming error) // Treat same as conflict fault _faulted := TRUE; _faultCode := FAULT_CONFLICT; Fault_Conflict := TRUE; _cylState := CYL_FAULTED; END_IF; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 7 — SAFE OUTPUT GATING ░░ Only energise actuator outputs when healthy and enabled. This is the key safety pattern: raw command passes through only if the FB is not faulted. ═══════════════════════════════════════════════════════════════════════════*) CylCmd_FwdSafe := Cmd_Fwd AND NOT _faulted AND Enable; CylCmd_BwdSafe := Cmd_Bwd AND NOT _faulted AND Enable; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 8 — STATUS BOOL OUTPUTS ░░ ═══════════════════════════════════════════════════════════════════════════*) AtFwd := (_cylState = CYL_AT_FWD); AtBwd := (_cylState = CYL_AT_BWD); Intermediate := (_cylState = CYL_INTERMEDIATE); Moving := (_cylState = CYL_MOVING_FWD) OR (_cylState = CYL_MOVING_BWD); Fault_Active := _faulted; FaultCode := _faultCode; CylState := _cylState; (*═══════════════════════════════════════════════════════════════════════════ ░░ SECTION 9 — HMI TEXT GENERATION ░░ Pre-formatted strings the HMI can display directly. String 80 chars max. Use CylName to identify which cylinder. ═══════════════════════════════════════════════════════════════════════════*) // ── Status text ─────────────────────────────────────────────────────────── CASE _cylState OF CYL_IDLE: HMI_StatusText := CONCAT(CylName, ': Idle / No command'); CYL_MOVING_FWD: HMI_StatusText := CONCAT(CylName, ': Moving → FWD ...'); CYL_AT_FWD: HMI_StatusText := CONCAT(CylName, ': At FWD position ✓'); CYL_MOVING_BWD: HMI_StatusText := CONCAT(CylName, ': Moving ← BWD ...'); CYL_AT_BWD: HMI_StatusText := CONCAT(CylName, ': At BWD position ✓'); CYL_INTERMEDIATE: HMI_StatusText := CONCAT(CylName, ': INTERMEDIATE — between sensors'); CYL_FAULTED: HMI_StatusText := CONCAT(CylName, ': *** FAULT — see fault message ***'); ELSE: HMI_StatusText := CONCAT(CylName, ': Unknown state'); END_CASE; // ── Fault text ──────────────────────────────────────────────────────────── CASE _faultCode OF FAULT_NONE: HMI_FaultText := ''; FAULT_FWD_TIMEOUT: HMI_FaultText := CONCAT(CylName, ': FWD TIMEOUT — check cylinder, solenoid and FWD sensor'); FAULT_BWD_TIMEOUT: HMI_FaultText := CONCAT(CylName, ': BWD TIMEOUT — check cylinder, solenoid and BWD sensor'); FAULT_CONFLICT: HMI_FaultText := CONCAT(CylName, ': SENSOR CONFLICT — FWD + BWD both active. Check wiring/sensors'); FAULT_LOST: HMI_FaultText := CONCAT(CylName, ': LOST POSITION — cylinder moved without command. Check mechanics'); ELSE: HMI_FaultText := CONCAT(CylName, ': Unknown fault'); END_CASE; END_FUNCTION_BLOCK (*═══════════════════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════════════════ FB_CylFaultCollector Aggregates faults from up to 8 cylinder monitors into one summary word. The welding sequencer reads CylFaultAny to trigger a machine pause or stop. HMI reads CylFaultWord bitmask + individual fault strings for alarm display. ═══════════════════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════════════════*) FUNCTION_BLOCK "FB_CylFaultCollector" { S7_Optimized_Access := 'TRUE' } VERSION : 0.1 VAR_INPUT //── FAULT INPUTS FROM UP TO 8 CYLINDER MONITORS ───────────────────────── Cyl1_Fault : Bool; Cyl2_Fault : Bool; Cyl3_Fault : Bool; Cyl4_Fault : Bool; Cyl5_Fault : Bool; Cyl6_Fault : Bool; Cyl7_Fault : Bool; Cyl8_Fault : Bool; //── FAULT CODES (from each FB_CylinderMonitor.FaultCode) ──────────────── Cyl1_FaultCode : Int; Cyl2_FaultCode : Int; Cyl3_FaultCode : Int; Cyl4_FaultCode : Int; Cyl5_FaultCode : Int; Cyl6_FaultCode : Int; Cyl7_FaultCode : Int; Cyl8_FaultCode : Int; //── FAULT TEXTS (from each FB_CylinderMonitor.HMI_FaultText) ──────────── Cyl1_FaultText : String[80]; Cyl2_FaultText : String[80]; Cyl3_FaultText : String[80]; Cyl4_FaultText : String[80]; //── MACHINE RESPONSE CONFIGURATION ────────────────────────────────────── // For each fault type, define machine response: // 0 = Warning only (continue running, amber lamp) // 1 = Pause machine (Cmd_Pause equivalent) // 2 = Stop machine (Cmd_Stop equivalent) Response_Timeout : Int; // Response for timeout faults (codes 1, 2) Response_Conflict : Int; // Response for conflict fault (code 3) Response_Lost : Int; // Response for lost position (code 4) END_VAR VAR_OUTPUT //── AGGREGATE OUTPUTS (wire to FB_WeldSequencer inputs) ────────────────── CylFaultAny : Bool; // OR of all faults — any cylinder faulted CylFaultWord : Word; // Bitmask: bit0=Cyl1...bit7=Cyl8 Cmd_MachinePause : Bool; // → connect to FB_WeldSequencer.Cmd_Pause Cmd_MachineStop : Bool; // → connect to FB_WeldSequencer.Cmd_Stop Cmd_Warning : Bool; // Amber warning lamp (non-stopping fault) //── FIRST FAULT REPORTING (for HMI alarm banner) ───────────────────────── FirstFaultCode : Int; // Code of first active fault FirstFaultText : String[80]; // Text of first active fault ActiveFaultCount : Int; // How many cylinders currently faulted END_VAR VAR _faultBits : Array[1..8] OF Bool; _faultCodes : Array[1..8] OF Int; _i : Int; _faultCount : Int; _firstFound : Bool; _anyPause : Bool; _anyStop : Bool; _anyWarn : Bool; _requiredResponse : Int; END_VAR BEGIN // ── Collect all faults into arrays ──────────────────────────────────────── _faultBits[1] := Cyl1_Fault; _faultCodes[1] := Cyl1_FaultCode; _faultBits[2] := Cyl2_Fault; _faultCodes[2] := Cyl2_FaultCode; _faultBits[3] := Cyl3_Fault; _faultCodes[3] := Cyl3_FaultCode; _faultBits[4] := Cyl4_Fault; _faultCodes[4] := Cyl4_FaultCode; _faultBits[5] := Cyl5_Fault; _faultCodes[5] := Cyl5_FaultCode; _faultBits[6] := Cyl6_Fault; _faultCodes[6] := Cyl6_FaultCode; _faultBits[7] := Cyl7_Fault; _faultCodes[7] := Cyl7_FaultCode; _faultBits[8] := Cyl8_Fault; _faultCodes[8] := Cyl8_FaultCode; // ── Reset aggregates ────────────────────────────────────────────────────── CylFaultWord := 0; CylFaultAny := FALSE; _faultCount := 0; _firstFound := FALSE; FirstFaultCode := 0; FirstFaultText := ''; _anyPause := FALSE; _anyStop := FALSE; _anyWarn := FALSE; // ── Loop all 8 slots ────────────────────────────────────────────────────── FOR _i := 1 TO 8 DO IF _faultBits[_i] THEN CylFaultAny := TRUE; _faultCount := _faultCount + 1; // Set bitmask bit (bit 0 = Cyl1) CylFaultWord := WORD_TO_WORD(CylFaultWord OR SHL(IN := WORD#16#0001, N := UINT_TO_INT(INT_TO_UINT(_i - 1)))); // First fault capture for HMI banner IF NOT _firstFound THEN _firstFound := TRUE; FirstFaultCode := _faultCodes[_i]; CASE _i OF 1: FirstFaultText := Cyl1_FaultText; 2: FirstFaultText := Cyl2_FaultText; 3: FirstFaultText := Cyl3_FaultText; 4: FirstFaultText := Cyl4_FaultText; ELSE: FirstFaultText := 'Cylinder fault (see fault word)'; END_CASE; END_IF; // Determine required machine response for this fault code CASE _faultCodes[_i] OF 1, 2: _requiredResponse := Response_Timeout; 3: _requiredResponse := Response_Conflict; 4: _requiredResponse := Response_Lost; ELSE: _requiredResponse := 2; // Unknown → Stop END_CASE; CASE _requiredResponse OF 0: _anyWarn := TRUE; 1: _anyPause := TRUE; 2: _anyStop := TRUE; END_CASE; END_IF; END_FOR; ActiveFaultCount := _faultCount; Cmd_MachinePause := _anyPause AND NOT _anyStop; // Stop takes priority over pause Cmd_MachineStop := _anyStop; Cmd_Warning := _anyWarn AND NOT _anyPause AND NOT _anyStop; END_FUNCTION_BLOCK