diff --git a/s7-1500/cylinder_monitor/DEMO_WeldCellIntegration.scl b/s7-1500/cylinder_monitor/DEMO_WeldCellIntegration.scl new file mode 100644 index 0000000..1c442d8 --- /dev/null +++ b/s7-1500/cylinder_monitor/DEMO_WeldCellIntegration.scl @@ -0,0 +1,297 @@ +(*═══════════════════════════════════════════════════════════════════════════════ + DEMO_WeldCellIntegration.scl + ───────────────────────────────────────────────────────────────────────────── + Demonstrates FB_CylinderMonitor instances for all 4 sensor configurations + wired into the FB_WeldSequencer from the welding cell demo. + + CYLINDERS IN THIS DEMO: + ┌──────┬───────────────────────┬──────────────┬────────────────────────────┐ + │ Inst │ Cylinder │ SensorConfig │ Notes │ + ├──────┼───────────────────────┼──────────────┼────────────────────────────┤ + │ DB1 │ Clamp Cylinder │ ONE_FWD (1) │ Only clamp-closed sensor │ + │ DB2 │ Weld Head │ TWO (3) │ Extended + retracted sensor │ + │ DB3 │ Part Ejector │ ONE_BWD (2) │ Only retracted (home) sensor│ + │ DB4 │ Gas Purge Valve │ NONE (0) │ No position sensor, timer │ + └──────┴───────────────────────┴──────────────┴────────────────────────────┘ + + RESPONSE CONFIGURATION: + Timeout faults → PAUSE machine (operator can clear & resume) + Conflict faults → STOP machine (requires Reset before restart) + Lost position → STOP machine (requires Reset before restart) + + CALL ORDER in OB (important — monitors must execute BEFORE sequencer): + 1. FB_CylinderMonitor instances (DB1..DB4) + 2. FB_CylFaultCollector (DB_CylFaults) + 3. FB_WeldSequencer (DB_WeldSeq) +═══════════════════════════════════════════════════════════════════════════════*) + +//───────────────────────────────────────────────────────────────────────────── +// INSTANCE DB DECLARATIONS (create these in TIA Portal project tree) +// +// DB_CylClamp : FB_CylinderMonitor +// DB_CylHead : FB_CylinderMonitor +// DB_CylEject : FB_CylinderMonitor +// DB_CylGas : FB_CylinderMonitor +// DB_CylFaults : FB_CylFaultCollector +// DB_WeldSeq : FB_WeldSequencer +//───────────────────────────────────────────────────────────────────────────── + +//═════════════════════════════════════════════════════════════════════════════ +// CYLINDER 1 — CLAMP (ONE_FWD: only clamp-closed sensor at FWD end) +// ───────────────────────────────────────────────────────────────────────── +// Physical setup: +// Proximity sensor mounted at closed (extended) jaw position ONLY. +// No sensor at open position — we assume open if no clamp command active. +// +// Error logic: +// Cmd_Fwd (clamp) active → Sen_Fwd (clamp closed) not confirmed in 2 s +// → FaultCode 1 (FWD timeout) → machine PAUSE +// Sen_Fwd unexpectedly active with no command? → Not detected (ONE_FWD limit) +// +// Advance in sequencer: +// Step 2 advance condition: DB_CylClamp.AtFwd +//═════════════════════════════════════════════════════════════════════════════ +"DB_CylClamp"( + CylName := 'Clamp Cylinder', + SensorConfig := 1, // ONE_FWD + T_Timeout := T#2S, // 2 second fault timeout + Cmd_Fwd := "DB_WeldSeq".Act_Clamp, // Sequencer output drives FWD + Cmd_Bwd := NOT "DB_WeldSeq".Act_Clamp AND NOT "DB_WeldSeq".Seq_Faulted, + // Bwd inferred: not clamped & not faulted + Cmd_Reset := "PB_Reset", + Sen_Fwd := %I0.2, // Sen_ClampClosed hardware input + Sen_Bwd := FALSE, // No sensor at open position + Enable := "DB_WeldSeq".Seq_Running OR "DB_WeldSeq".Seq_Paused +); + +//═════════════════════════════════════════════════════════════════════════════ +// CYLINDER 2 — WELD HEAD (TWO sensors: extended + retracted) +// ───────────────────────────────────────────────────────────────────────── +// Physical setup: +// Sensor at fully extended (weld position) = Sen_Fwd → %I0.3 +// Sensor at fully retracted (safe position) = Sen_Bwd → %I0.4 +// +// Error logic: +// Extend cmd → Sen_Fwd not confirmed in 3 s → FaultCode 1 → PAUSE +// Retract cmd → Sen_Bwd not confirmed in 3 s → FaultCode 2 → PAUSE +// Both sensors TRUE simultaneously → FaultCode 3 → STOP +// Sensors both drop with no command active → FaultCode 4 (lost) → STOP +// +// Advance in sequencer: +// Step 3 advance: DB_CylHead.AtFwd +// Step 9 advance: DB_CylHead.AtBwd +// +// NOTE: Extend and Retract are separate solenoid outputs (bistable valve) +// CylCmd_FwdSafe → Act_HeadExtend solenoid +// CylCmd_BwdSafe → Act_HeadRetract solenoid +//═════════════════════════════════════════════════════════════════════════════ +"DB_CylHead"( + CylName := 'Weld Head', + SensorConfig := 3, // TWO sensors + T_Timeout := T#3S, // 3 seconds (head travel takes longer) + Cmd_Fwd := "DB_WeldSeq".Act_HeadExtend, + Cmd_Bwd := "DB_WeldSeq".Act_HeadRetract, + Cmd_Reset := "PB_Reset", + Sen_Fwd := %I0.3, // Sen_HeadAtWeldPos + Sen_Bwd := %I0.4, // Sen_HeadRetracted + Enable := "DB_WeldSeq".Seq_Running OR "DB_WeldSeq".Seq_Paused + OR "DB_WeldSeq".Seq_Stopping +); + +// ── Wire safe outputs back to physical DQs ─────────────────────────────── +%Q0.1 := "DB_CylHead".CylCmd_FwdSafe; // Act_HeadExtend (gated) +%Q0.2 := "DB_CylHead".CylCmd_BwdSafe; // Act_HeadRetract (gated) + +//═════════════════════════════════════════════════════════════════════════════ +// CYLINDER 3 — PART EJECTOR (ONE_BWD: only home/retracted sensor) +// ───────────────────────────────────────────────────────────────────────── +// Physical setup: +// The ejector fires forward (extends) to push the part out. +// Sensor only at retracted (home) end — confirms ejector is clear/home. +// No sensor at extended (ejected) position. +// +// Error logic: +// Retract cmd (home return) → Sen_Bwd not confirmed in 1.5 s +// → FaultCode 2 (BWD timeout) → PAUSE +// Eject (FWD) cmd: no FWD sensor, so no timeout — just fires blind. +// After timer completes in sequencer, retract command issued. +// +// Advance in sequencer: +// Step 10 advance uses Sen_UnclampConf (%I0.7) which is separate. +// After cycle complete, this FB confirms ejector is home before next cycle. +//═════════════════════════════════════════════════════════════════════════════ +"DB_CylEject"( + CylName := 'Part Ejector', + SensorConfig := 2, // ONE_BWD + T_Timeout := T#1500MS, + Cmd_Fwd := "DB_WeldSeq".Act_PartEject, + Cmd_Bwd := NOT "DB_WeldSeq".Act_PartEject + AND "DB_WeldSeq".Seq_Complete, + Cmd_Reset := "PB_Reset", + Sen_Fwd := FALSE, // No sensor at ejected position + Sen_Bwd := %I0.7, // Ejector home sensor (shared / Sen_UnclampConf area) + Enable := TRUE // Always enabled +); + +//═════════════════════════════════════════════════════════════════════════════ +// CYLINDER 4 — GAS PURGE VALVE (NONE: no position sensors) +// ───────────────────────────────────────────────────────────────────────── +// Physical setup: +// Simple solenoid valve — open or closed. +// No position feedback — valve assumed open when energised. +// No fault can be generated from position sensors (none exist). +// +// Error logic: +// NONE: the FB passes the command through, reports AT_FWD when commanded +// On/Off confirmed only by timer in the sequencer (Steps 4, 6, 8). +// +// NOTE: A pressure switch or flow sensor could be added here in future. +// Connect it to Sen_Fwd and change SensorConfig to ONE_FWD. +//═════════════════════════════════════════════════════════════════════════════ +"DB_CylGas"( + CylName := 'Gas Purge Valve', + SensorConfig := 0, // NONE + T_Timeout := T#500MS, // Not used with NONE config (no timers start) + Cmd_Fwd := "DB_WeldSeq".Act_GasValve, + Cmd_Bwd := FALSE, // Single-acting valve — spring return + Cmd_Reset := "PB_Reset", + Sen_Fwd := FALSE, + Sen_Bwd := FALSE, + Enable := TRUE +); + +//═════════════════════════════════════════════════════════════════════════════ +// FAULT COLLECTOR — Aggregates all 4 cylinder faults +// Outputs feed directly into the sequencer Cmd_Pause / Cmd_Stop inputs +//═════════════════════════════════════════════════════════════════════════════ +"DB_CylFaults"( + //── Fault bits ─────────────────────────────────────────────────────────── + Cyl1_Fault := "DB_CylClamp".Fault_Active, + Cyl2_Fault := "DB_CylHead".Fault_Active, + Cyl3_Fault := "DB_CylEject".Fault_Active, + Cyl4_Fault := "DB_CylGas".Fault_Active, + Cyl5_Fault := FALSE, + Cyl6_Fault := FALSE, + Cyl7_Fault := FALSE, + Cyl8_Fault := FALSE, + + //── Fault codes ─────────────────────────────────────────────────────────── + Cyl1_FaultCode := "DB_CylClamp".FaultCode, + Cyl2_FaultCode := "DB_CylHead".FaultCode, + Cyl3_FaultCode := "DB_CylEject".FaultCode, + Cyl4_FaultCode := "DB_CylGas".FaultCode, + Cyl5_FaultCode := 0, + Cyl6_FaultCode := 0, + Cyl7_FaultCode := 0, + Cyl8_FaultCode := 0, + + //── HMI fault texts ─────────────────────────────────────────────────────── + Cyl1_FaultText := "DB_CylClamp".HMI_FaultText, + Cyl2_FaultText := "DB_CylHead".HMI_FaultText, + Cyl3_FaultText := "DB_CylEject".HMI_FaultText, + Cyl4_FaultText := "DB_CylGas".HMI_FaultText, + + //── Machine response policy ─────────────────────────────────────────────── + // Timeout → PAUSE (1): operator can check & reset, then resume + // Conflict → STOP (2): two sensors TRUE = hardware fault, needs investigation + // Lost pos → STOP (2): cylinder moved undetected = mechanical issue + Response_Timeout := 1, + Response_Conflict := 2, + Response_Lost := 2 +); + +//═════════════════════════════════════════════════════════════════════════════ +// WELDING SEQUENCER — Wired with cylinder feedback and fault injection +//═════════════════════════════════════════════════════════════════════════════ +"DB_WeldSeq"( + //── Mode & commands ───────────────────────────────────────────────────── + Mode_Auto := %I2.0, + Mode_Incr := %I2.1, + Cmd_Start := %I1.4, + Cmd_Reset := "PB_Reset", + + // ── PAUSE: operator button OR cylinder fault requesting pause ────────── + Cmd_Pause := %I1.5 + OR "DB_CylFaults".Cmd_MachinePause, + + // ── STOP: operator button OR cylinder fault requesting stop ─────────── + Cmd_Stop := %I1.6 + OR "DB_CylFaults".Cmd_MachineStop, + + Cmd_EStop := %I1.2, + Cmd_Incr := %I1.7, + Cmd_SkipStep := %I2.2, + StopCategory := %MW10, + + //── Safety inputs ──────────────────────────────────────────────────────── + Safety_DoorClosed := %I1.1, + Safety_EStopOK := %I1.0, + + //── Sensors — using GATED outputs from cylinder monitors where possible ── + // Sequencer only sees "AtFwd/AtBwd" from healthy, confirmed cylinders. + // If cylinder monitor is faulted, AtFwd/AtBwd → FALSE → step never + // advances → watchdog trips → sequencer also faults. Belt & braces. + + Sen_HomeFwd := %I0.0, // No cylinder monitor (robot) + Sen_HomeBwd := %I0.1, + + // Step 2 clamp — uses cylinder monitor's AtFwd (confirmed clamp closed) + Sen_ClampClosed := "DB_CylClamp".AtFwd, // ← From monitor, not raw DI + + // Steps 3 & 9 — weld head FWD/BWD (from monitor) + Sen_HeadAtWeldPos := "DB_CylHead".AtFwd, // ← Confirmed extended + Sen_HeadRetracted := "DB_CylHead".AtBwd, // ← Confirmed retracted + + Sen_ArcDetect := %I0.5, + Sen_ArcOff := %I0.6, + + // Step 10 unclamp (ejector home or unclamp sensor) + Sen_UnclampConf := "DB_CylEject".AtBwd, // ← Ejector home confirmed + + //── Actuator outputs ───────────────────────────────────────────────────── + // NOTE: Act_HeadExtend and Act_HeadRetract go through the head cylinder + // monitor's gated outputs (see %Q assignments above). + // Act_Clamp goes direct here — the monitor checks it on its own scan. + Act_Clamp => %Q0.0, + Act_HeadExtend => "DB_CylHead_CmdFwd_Raw", // Internal tag; monitor gates it to %Q0.1 + Act_HeadRetract => "DB_CylHead_CmdBwd_Raw", // Internal tag; monitor gates it to %Q0.2 + Act_GasValve => %Q0.3, + Act_WeldEnable => %Q0.4, + Act_Unclamp => %Q0.5, + Act_PartEject => %Q0.6, + + //── Status outputs ─────────────────────────────────────────────────────── + Seq_Running => %Q1.0, + Seq_Paused => %Q1.1, + Seq_Faulted => %Q1.2, + Seq_Complete => %Q1.3, + ActiveStep => %MW20, + SeqState => %MW22, + FaultCode => %MW24, + StepAdvanceReady => %M5.0, + IncrWaiting => %M5.1, + CycleCount => %MD30 +); + +//═════════════════════════════════════════════════════════════════════════════ +// HMI WIRING SUMMARY +// Wire these tags to your WinCC/Comfort panel for operator guidance: +// +// TAG SOURCE HMI ELEMENT +// ───────────────────────────────────────────────────────────────────────── +// DB_WeldSeq.ActiveStep → %MW20 Step indicator (1-10) +// DB_WeldSeq.SeqState → %MW22 State text list +// DB_WeldSeq.FaultCode → %MW24 Sequencer alarm +// DB_CylFaults.FirstFaultText First fault banner text +// DB_CylFaults.CylFaultWord Cylinder fault bitmask (overview) +// DB_CylFaults.ActiveFaultCount Number of active faults +// DB_CylClamp.CylState Clamp position indicator +// DB_CylClamp.HMI_StatusText Clamp status string +// DB_CylHead.CylState Head position indicator +// DB_CylHead.HMI_StatusText Head status string +// DB_CylEject.CylState Ejector status +// DB_CylHead.Moving Moving animation trigger +// DB_CylFaults.Cmd_Warning → %Q1.4 Amber warning lamp +// DB_WeldSeq.Seq_Faulted → %Q1.2 Red fault lamp +// DB_WeldSeq.IncrWaiting → %M5.1 "Press INCR" flashing indicator +//═════════════════════════════════════════════════════════════════════════════ diff --git a/s7-1500/cylinder_monitor/FB_CylinderMonitor.scl b/s7-1500/cylinder_monitor/FB_CylinderMonitor.scl new file mode 100644 index 0000000..ce5695f --- /dev/null +++ b/s7-1500/cylinder_monitor/FB_CylinderMonitor.scl @@ -0,0 +1,581 @@ +(*═══════════════════════════════════════════════════════════════════════════════ + 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 diff --git a/s7-1500/cylinder_monitor/README_CylinderMonitor.md b/s7-1500/cylinder_monitor/README_CylinderMonitor.md new file mode 100644 index 0000000..6387df0 --- /dev/null +++ b/s7-1500/cylinder_monitor/README_CylinderMonitor.md @@ -0,0 +1,529 @@ +# FB_CylinderMonitor — S7-1500 SCL Universal Cylinder Supervisor + +> **Platform:** Siemens S7-1500 | TIA Portal V18+ | SCL +> **Blocks:** `FB_CylinderMonitor` · `FB_CylFaultCollector` +> **Companion to:** [`FB_WeldSequencer`](../README.md) + +A drop-in cylinder monitoring library for any pneumatic or hydraulic actuator. Handles all real-world sensor configurations — zero, one, or two position sensors — generates direction-aware timeout faults, detects sensor conflicts and lost positions, gates actuator outputs for safety, and feeds structured fault data directly to an HMI and machine sequencer. + +--- + +## Table of Contents + +1. [Why This Exists](#1-why-this-exists) +2. [Files](#2-files) +3. [Sensor Configuration Options](#3-sensor-configuration-options) +4. [Fault Logic Per Configuration](#4-fault-logic-per-configuration) +5. [FB_CylinderMonitor — Interface](#5-fb_cylindermonitor--interface) +6. [FB_CylFaultCollector — Interface](#6-fb_cylfaultcollector--interface) +7. [Output Gating Pattern](#7-output-gating-pattern) +8. [Integration with FB_WeldSequencer](#8-integration-with-fb_weldsequencer) +9. [Demo: Welding Cell (4 cylinders)](#9-demo-welding-cell-4-cylinders) +10. [HMI Integration](#10-hmi-integration) +11. [TIA Portal Setup](#11-tia-portal-setup) +12. [Timing Reference](#12-timing-reference) +13. [Fault Code Reference](#13-fault-code-reference) +14. [Adapting for Other Applications](#14-adapting-for-other-applications) +15. [Safety Notes](#15-safety-notes) + +--- + +## 1. Why This Exists + +Every cylinder in a machine needs the same boilerplate logic: + +- Did it reach position in time? If not → fault +- Are both sensors TRUE at once? That's wiring or hardware failure → fault +- Did it leave position without being commanded? → fault +- What should the machine do when it faults — pause or stop? +- What should the operator see on the HMI? + +Writing this 20+ times per machine is error-prone and inconsistent. `FB_CylinderMonitor` encapsulates it once, handles all sensor topologies, and gives you clean `AtFwd`/`AtBwd` booleans that replace raw sensor reads in your step advance conditions. + +--- + +## 2. Files + +``` +📁 CylinderMonitor/ +├── FB_CylinderMonitor.scl # FB_CylinderMonitor + FB_CylFaultCollector +├── DEMO_WeldCellIntegration.scl # Integration demo (4 cylinders + sequencer) +└── README_CylinderMonitor.md # This file +``` + +--- + +## 3. Sensor Configuration Options + +Set `SensorConfig` (INT) on each FB instance: + +| Value | Constant | Sensor Setup | Typical Use | +|---|---|---|---| +| `0` | `CFG_NONE` | No sensors | Valves, no-feedback actuators | +| `1` | `CFG_ONE_FWD` | Sensor at FWD (extended) end only | Clamps, grippers — only care about "closed" | +| `2` | `CFG_ONE_BWD` | Sensor at BWD (retracted) end only | Ejectors — only care about "home" | +| `3` | `CFG_TWO` | Sensor at both ends | Weld heads, positioners, gates | + +### Which configuration to use? + +``` +Q: Does the cylinder need to confirm both extended AND retracted? + YES → CFG_TWO (3) + +Q: Only need to know it's closed/extended? + YES → CFG_ONE_FWD (1) + +Q: Only need to know it's home/retracted? + YES → CFG_ONE_BWD (2) + +Q: No sensor at all (timer-only advance)? + YES → CFG_NONE (0) +``` + +--- + +## 4. Fault Logic Per Configuration + +### CFG_NONE (0) — No faults possible from position + +``` +Cmd_Fwd ON ──────────────────────────────────▶ CylCmd_FwdSafe ON + CylState = AT_FWD (trusted) +No sensors → no timeout → no conflict +Advance logic: use sequencer's step timer +``` + +### CFG_ONE_FWD (1) — Fault on FWD only + +``` +Cmd_Fwd ON ──┬──────────────────────▶ CylCmd_FwdSafe ON + │ Sen_Fwd? ──TRUE────▶ AtFwd = TRUE ✓ (timer reset) + │ └─FALSE───▶ T_Timeout running + │ │ + │ ▼ T_Timeout elapsed + │ FaultCode = 1 (FWD TIMEOUT) + │ Fault_Active = TRUE + │ CylCmd_FwdSafe → FALSE (gated off) + │ +Cmd_Bwd ON ──▶ No BWD timeout possible (no BWD sensor) + CylState reports AT_BWD when FWD sensor clears +``` + +### CFG_ONE_BWD (2) — Fault on BWD only + +``` +Cmd_Bwd ON ──┬──────────────────────▶ CylCmd_BwdSafe ON + │ Sen_Bwd? ──TRUE────▶ AtBwd = TRUE ✓ (timer reset) + │ └─FALSE───▶ T_Timeout running + │ ▼ T_Timeout elapsed + │ FaultCode = 2 (BWD TIMEOUT) + │ +Cmd_Fwd ON ──▶ No FWD timeout (no FWD sensor) + CylState reports AT_FWD when BWD sensor clears +``` + +### CFG_TWO (3) — Full fault coverage + +``` +Cmd_Fwd ON ──▶ Sen_Fwd? ─TRUE──▶ AtFwd ✓ timer cancelled + └─FALSE─▶ Timeout FWD → FaultCode 1 + +Cmd_Bwd ON ──▶ Sen_Bwd? ─TRUE──▶ AtBwd ✓ timer cancelled + └─FALSE─▶ Timeout BWD → FaultCode 2 + +BOTH sensors TRUE simultaneously → FaultCode 3 (CONFLICT) → immediate fault +No cmd + was at known pos + both sensors go FALSE → FaultCode 4 (LOST) +Both Cmd_Fwd AND Cmd_Bwd TRUE → FaultCode 3 (command conflict) +``` + +--- + +## 5. FB_CylinderMonitor — Interface + +### VAR_INPUT + +| Name | Type | Description | +|---|---|---| +| `CylName` | `String[32]` | Human name, e.g. `'Clamp Cylinder'` — used in HMI text | +| `SensorConfig` | `Int` | 0=NONE, 1=ONE_FWD, 2=ONE_BWD, 3=TWO | +| `T_Timeout` | `Time` | Max time for cylinder to reach position | +| `Cmd_Fwd` | `Bool` | Extend / close command | +| `Cmd_Bwd` | `Bool` | Retract / open command | +| `Cmd_Reset` | `Bool` | Rising edge clears fault | +| `Sen_Fwd` | `Bool` | Forward / extended position sensor | +| `Sen_Bwd` | `Bool` | Backward / retracted position sensor | +| `Enable` | `Bool` | FALSE = ignore commands, hold state | + +### VAR_OUTPUT + +| Name | Type | Description | +|---|---|---| +| `CylCmd_FwdSafe` | `Bool` | **Gated FWD output** — connect to DQ | +| `CylCmd_BwdSafe` | `Bool` | **Gated BWD output** — connect to DQ | +| `AtFwd` | `Bool` | Position confirmed at FWD | +| `AtBwd` | `Bool` | Position confirmed at BWD | +| `Intermediate` | `Bool` | Between positions (TWO config, no cmd) | +| `Moving` | `Bool` | Command active, not yet confirmed | +| `Fault_Active` | `Bool` | Any fault present | +| `Fault_FwdTimeout` | `Bool` | Specific fault bit | +| `Fault_BwdTimeout` | `Bool` | Specific fault bit | +| `Fault_Conflict` | `Bool` | Sensor conflict fault bit | +| `Fault_Lost` | `Bool` | Lost position fault bit | +| `FaultCode` | `Int` | Numeric code (0–4) | +| `CylState` | `Int` | State code (0–6) | +| `HMI_StatusText` | `String[80]` | Ready-to-display status | +| `HMI_FaultText` | `String[80]` | Ready-to-display fault message | + +--- + +## 6. FB_CylFaultCollector — Interface + +Aggregates up to 8 cylinder monitor faults into one summary. Wire its outputs to the sequencer's `Cmd_Pause` and `Cmd_Stop`. + +### Key Outputs + +| Name | Type | Description | +|---|---|---| +| `CylFaultAny` | `Bool` | OR of all cylinder faults | +| `CylFaultWord` | `Word` | Bitmask — bit 0 = Cyl1, bit 7 = Cyl8 | +| `Cmd_MachinePause` | `Bool` | **Wire to sequencer `Cmd_Pause`** | +| `Cmd_MachineStop` | `Bool` | **Wire to sequencer `Cmd_Stop`** | +| `Cmd_Warning` | `Bool` | Amber lamp — non-stopping fault | +| `FirstFaultText` | `String[80]` | First fault message for HMI banner | +| `ActiveFaultCount` | `Int` | Number of cylinders currently faulted | + +### Machine Response Configuration + +| Input | Value | Effect | +|---|---|---| +| `Response_Timeout` | `0` | Warning only — machine keeps running | +| `Response_Timeout` | `1` | Pause machine | +| `Response_Timeout` | `2` | Stop machine (requires Reset) | +| `Response_Conflict` | `0/1/2` | Same options | +| `Response_Lost` | `0/1/2` | Same options | + +**Recommended defaults:** + +```scl +Response_Timeout := 1; // Pause — timeout may be temporary, operator can check +Response_Conflict := 2; // Stop — two sensors both TRUE = hardware fault +Response_Lost := 2; // Stop — cylinder moved without command = mechanical issue +``` + +--- + +## 7. Output Gating Pattern + +The core safety pattern of this FB is **output gating**. Instead of writing: + +```scl +// ❌ Unsafe — raw command goes direct to DQ +%Q0.1 := Act_HeadExtend; +``` + +You write: + +```scl +// ✅ Safe — passes through monitor, gated off on fault +"DB_CylHead"( + Cmd_Fwd := Act_HeadExtend, + ... +); +%Q0.1 := "DB_CylHead".CylCmd_FwdSafe; // Only energises if healthy +``` + +When a fault occurs (timeout, conflict, lost position): + +1. `CylCmd_FwdSafe` → `FALSE` immediately (same scan as fault detection) +2. Solenoid valve de-energised → cylinder stops +3. `Fault_Active` → `TRUE` → collector → sequencer `Cmd_Pause` or `Cmd_Stop` +4. `HMI_FaultText` → operator message describes exactly what failed + +--- + +## 8. Integration with FB_WeldSequencer + +The two FBs communicate in both directions: + +``` +FB_CylinderMonitor FB_WeldSequencer +──────────────────── ───────────────────────────── +AtFwd ─────────────────────────────▶ Sen_ClampClosed (step 2 advance) +AtFwd ─────────────────────────────▶ Sen_HeadAtWeldPos (step 3 advance) +AtBwd ─────────────────────────────▶ Sen_HeadRetracted (step 9 advance) +Fault_Active + └──▶ FB_CylFaultCollector + Cmd_MachinePause ──────────▶ Cmd_Pause (OR with operator PB) + Cmd_MachineStop ──────────▶ Cmd_Stop (OR with operator PB) + +FB_WeldSequencer FB_CylinderMonitor +───────────────────────────── ──────────────────── +Act_HeadExtend ─────────────────────▶ Cmd_Fwd +Act_HeadRetract ────────────────────▶ Cmd_Bwd +Seq_Running OR Seq_Paused ──────────▶ Enable +``` + +### Step Advance with Monitor vs Raw Sensor + +| Without Monitor | With Monitor | +|---|---| +| `_stepAdvReady := Sen_ClampClosed` | `_stepAdvReady := "DB_CylClamp".AtFwd` | +| Advances even if clamp timed out | Will not advance if monitor is faulted | +| No timeout detection | FaultCode 1 triggers machine pause | + +Using `AtFwd`/`AtBwd` from the monitor instead of raw sensor inputs means a faulted cylinder **also prevents step advance**, so the sequencer's own watchdog timer provides a belt-and-braces fallback catch. + +--- + +## 9. Demo: Welding Cell (4 Cylinders) + +The demo file `DEMO_WeldCellIntegration.scl` shows all four configurations side by side: + +| Instance | Cylinder | Config | Timeout | Fault Response | +|---|---|---|---|---| +| `DB_CylClamp` | Clamp | ONE_FWD | 2 s | Pause | +| `DB_CylHead` | Weld Head | TWO | 3 s | Pause (timeout) / Stop (conflict) | +| `DB_CylEject` | Part Ejector | ONE_BWD | 1.5 s | Pause | +| `DB_CylGas` | Gas Valve | NONE | n/a | n/a | + +### Execution Order (critical) + +In your OB (OB1 or OB30), call in this order: + +``` +1st → "DB_CylClamp"(...) FB_CylinderMonitor +2nd → "DB_CylHead"(...) FB_CylinderMonitor +3rd → "DB_CylEject"(...) FB_CylinderMonitor +4th → "DB_CylGas"(...) FB_CylinderMonitor +5th → "DB_CylFaults"(...) FB_CylFaultCollector +6th → "DB_WeldSeq"(...) FB_WeldSequencer +``` + +Monitors run first so that `AtFwd`/`AtBwd` and `Fault_Active` values are fresh when the sequencer evaluates step advance conditions and Cmd_Pause/Stop in the same scan. + +--- + +## 10. HMI Integration + +### Recommended Screen Objects + +#### Cylinder Overview Faceplate (one per cylinder) + +| Element | Tag | Type | +|---|---|---| +| Status text | `DB_CylClamp.HMI_StatusText` | Text field | +| Fault text | `DB_CylClamp.HMI_FaultText` | Text field (red, visible on fault) | +| State indicator | `DB_CylClamp.CylState` | Bar/value with colour map | +| Moving animation | `DB_CylClamp.Moving` | Animated cylinder graphic | +| At FWD lamp | `DB_CylClamp.AtFwd` | Green indicator | +| At BWD lamp | `DB_CylClamp.AtBwd` | Green indicator | +| Fault lamp | `DB_CylClamp.Fault_Active` | Red indicator | + +#### CylState Colour Map (for bargraph or rectangle) + +| Value | Colour | Label | +|---|---|---| +| 0 | Grey | Idle | +| 1 | Yellow | Moving FWD | +| 2 | Green | At FWD ✓ | +| 3 | Yellow | Moving BWD | +| 4 | Green | At BWD ✓ | +| 5 | Orange | Intermediate | +| 6 | Red | FAULTED | + +#### Global Alarm Banner + +``` +Tag: DB_CylFaults.FirstFaultText +Visible: DB_CylFaults.CylFaultAny = TRUE +Background: Red +Example text: "Weld Head: FWD TIMEOUT — check cylinder, solenoid and FWD sensor" +``` + +#### Fault Bitmask Display (overview bar) + +``` +Tag: DB_CylFaults.CylFaultWord (WORD) +Display as: 8 individual bit lamps +Bit 0 = Clamp Bit 1 = Head Bit 2 = Ejector Bit 3 = Gas ... +``` + +#### Operator Guidance Text Logic + +For each cylinder fault, the HMI_FaultText already tells the operator exactly what to check: + +``` +FaultCode 1 → "Clamp Cylinder: FWD TIMEOUT — check cylinder, solenoid and FWD sensor" +FaultCode 2 → "Weld Head: BWD TIMEOUT — check cylinder, solenoid and BWD sensor" +FaultCode 3 → "Weld Head: SENSOR CONFLICT — FWD + BWD both active. Check wiring/sensors" +FaultCode 4 → "Weld Head: LOST POSITION — cylinder moved without command. Check mechanics" +``` + +No lookup table needed — the text is generated inside the FB using the `CylName` you configured. + +--- + +## 11. TIA Portal Setup + +### Create Instance DBs + +For each cylinder, right-click project tree → **Add new block** → **Data Block** → Instance DB of `FB_CylinderMonitor`: + +``` +DB_CylClamp : FB_CylinderMonitor +DB_CylHead : FB_CylinderMonitor +DB_CylEject : FB_CylinderMonitor +DB_CylGas : FB_CylinderMonitor +DB_CylFaults : FB_CylFaultCollector +``` + +### Initial Parameter Setup (in startup OB or first-scan block) + +```scl +// Set cylinder names and configs once at startup +// (or set directly in instance DB initial values in TIA Portal) + +"DB_CylClamp".CylName := 'Clamp Cylinder'; +"DB_CylClamp".SensorConfig := 1; +"DB_CylClamp".T_Timeout := T#2S; + +"DB_CylHead".CylName := 'Weld Head'; +"DB_CylHead".SensorConfig := 3; +"DB_CylHead".T_Timeout := T#3S; + +"DB_CylEject".CylName := 'Part Ejector'; +"DB_CylEject".SensorConfig := 2; +"DB_CylEject".T_Timeout := T#1500MS; + +"DB_CylGas".CylName := 'Gas Purge Valve'; +"DB_CylGas".SensorConfig := 0; +``` + +Alternatively, set initial values directly in the instance DB's data view in TIA Portal — these persist through power cycles. + +--- + +## 12. Timing Reference + +| Cylinder Type | Recommended T_Timeout | Notes | +|---|---|---| +| Fast pneumatic (< 50 mm) | `T#1S` | Small bore, short stroke | +| Standard pneumatic | `T#2S` | Most clamps, grippers | +| Long-stroke pneumatic | `T#3S`–`T#5S` | Weld heads, gates | +| Hydraulic | `T#5S`–`T#15S` | Slower by nature | +| Spring-return valve | N/A (NONE config) | No position sensor | + +**Rule of thumb:** Set timeout to 3× the expected travel time. Fast enough to catch real faults, slow enough to avoid nuisance trips on normal variation. + +--- + +## 13. Fault Code Reference + +| Code | Name | Cause | HMI Message | Machine Action | +|---|---|---|---|---| +| `0` | No fault | Normal | *(empty)* | None | +| `1` | FWD timeout | Sen_Fwd not TRUE within T_Timeout after Cmd_Fwd | `"[Name]: FWD TIMEOUT — check cylinder, solenoid and FWD sensor"` | Pause (configurable) | +| `2` | BWD timeout | Sen_Bwd not TRUE within T_Timeout after Cmd_Bwd | `"[Name]: BWD TIMEOUT — check cylinder, solenoid and BWD sensor"` | Pause (configurable) | +| `3` | Conflict | Sen_Fwd AND Sen_Bwd both TRUE, or Cmd_Fwd AND Cmd_Bwd both TRUE | `"[Name]: SENSOR CONFLICT — FWD + BWD both active. Check wiring/sensors"` | Stop (configurable) | +| `4` | Lost position | Was at known position; both sensors dropped with no command | `"[Name]: LOST POSITION — cylinder moved without command. Check mechanics"` | Stop (configurable) | + +--- + +## 14. Adapting for Other Applications + +### Add a pressure/flow sensor as virtual FWD confirmation + +```scl +// Gas valve with pressure feedback +"DB_CylGas"( + CylName := 'Gas Valve', + SensorConfig := 1, // ONE_FWD + T_Timeout := T#1S, + Cmd_Fwd := Act_GasValve, + Sen_Fwd := PressureSwitch_OK, // Add this DI + ... +); +``` + +### Motor or servo (use as virtual "cylinder" with limit switches) + +```scl +"DB_CylSlide"( + CylName := 'Transfer Slide', + SensorConfig := 3, + T_Timeout := T#4S, + Cmd_Fwd := SlideMotor_Fwd, + Cmd_Bwd := SlideMotor_Rev, + Sen_Fwd := LS_SlideForward, + Sen_Bwd := LS_SlideReverse, + ... +); +``` + +### Changing timeout at runtime + +```scl +// Speed-dependent timeout (slow mode vs normal mode) +IF SlowMode THEN + "DB_CylHead".T_Timeout := T#8S; +ELSE + "DB_CylHead".T_Timeout := T#3S; +END_IF; +``` + +Note: `T_Timeout` is read by the TON each scan — changes take effect immediately on the next timer start. + +### More than 8 cylinders + +Extend `FB_CylFaultCollector` array declarations from `[1..8]` to `[1..N]` and add corresponding `VAR_INPUT` slots. + +--- + +## 15. Safety Notes + +> ⚠️ `FB_CylinderMonitor` is a **diagnostic and convenience layer**. It is NOT a safety function and should NOT be used as the sole means of protecting personnel from cylinder movement. + +### This FB provides (diagnostic/operational layer): +- Timeout detection → pause or stop production +- Sensor conflict → immediate output gate + machine stop +- HMI guidance text for operator troubleshooting +- Output gating (software level only — not certified) + +### This FB does NOT replace (safety layer): +- **Hardware safety valves** (5/2 safety valve with spring return for E-Stop) +- **Dual-channel safety relay** monitoring for guard interlocks +- **Safety PLC** (S7-1500F) for PLd/SIL2 applications +- **Mechanical hard stops** preventing overtravel +- **Pressure relief valves** on hydraulic systems + +### Minimum safety architecture: + +``` +E-Stop ──▶ Safety Relay ──▶ Safety Valve ──▶ Cylinder (power removed in hardware) + │ + └──▶ Safety_EStopOK input on FB_WeldSequencer (software layer) + +Door open ──▶ Safety Switch ──▶ Safety Relay + │ + └──▶ Safety_DoorClosed input on FB_WeldSequencer +``` + +The cylinder monitor's `CylCmd_FwdSafe` / `CylCmd_BwdSafe` outputs control directional valves only. Power removal for E-Stop must be done at the hardware safety relay level, not by this FB. + +--- + +## Changelog + +| Version | Date | Notes | +|---|---|---| +| 0.1 | 2025 | Initial release — 4 sensor config types, 4 fault codes, HMI text | + +--- + +## Licence + +MIT — free to use and adapt. + +--- + +*For the full welding cell sequencer (10 steps, AUTO/INCR mode, stop categories), see [`README.md`](../README.md).*