PLC Safety Programming: SIL-Rated Logic & Safety Function Blocks in Structured Text

Design SIL-rated safety logic in Structured Text — covering dual-channel monitoring, safe shutdown sequences, diagnostic coverage, and IEC 62061 / IEC 61508 compliance patterns.

Functional Safety in PLC Programming

Functional safety is about ensuring that a Safety Instrumented System (SIS) correctly performs its intended safety function when demanded. Standards like IEC 61508 (general), IEC 62061 (machinery), and IEC 61511 (process industry) define requirements for the entire safety lifecycle — from hazard analysis through design, coding, testing, and maintenance.

This article focuses on the PLC programming patterns used in safety-rated applications. While real safety systems require certified hardware (safety PLCs) and formal verification, understanding these patterns is essential for every automation engineer.

Safety Integrity Levels (SIL)

SIL defines the required risk reduction of a safety function. Higher SIL = lower probability of dangerous failure:

SILPFD (Low Demand)PFH (High/Continuous)Risk ReductionExample Application
110⁻¹ to 10⁻²10⁻⁵ to 10⁻⁶ /h10–100×Light curtain guard stop
210⁻² to 10⁻³10⁻⁶ to 10⁻⁷ /h100–1,000×Emergency stop, burner management
310⁻³ to 10⁻⁴10⁻⁷ to 10⁻⁸ /h1,000–10,000×High-pressure relief, toxic gas shutdown
410⁻⁴ to 10⁻⁵10⁻⁸ to 10⁻⁹ /h10,000–100,000×Nuclear protection (rarely needed in manufacturing)
PFD = Probability of Failure on Demand. PFH = Probability of dangerous Failure per Hour.

Key Principle: Fail-Safe Design

Safety logic must be designed so that the most likely failure mode leads to the safe state. This means:

  • De-energize to trip (energize to permit)
  • Closed-circuit principle for wiring (wire break = safe state)
  • Redundant inputs with discrepancy monitoring
  • Dual-Channel Input Processing

    SIL 2 and above typically require redundant sensing — two independent sensors monitoring the same process condition. The PLC must compare both channels and detect discrepancies:

    FUNCTION_BLOCK FB_DualChannelInput
    VAR_INPUT
        Channel_A       : BOOL;     // Sensor 1 (NC contact)
        Channel_B       : BOOL;     // Sensor 2 (NC contact)
        DiscrepancyTime : TIME := T#500ms;  // Max allowed disagreement
    END_VAR
    VAR_OUTPUT
        SafeInput       : BOOL;     // TRUE = safe condition detected
        Discrepancy     : BOOL;     // Channels disagree too long
        DiagFault       : BOOL;     // Diagnostic fault detected
    END_VAR
    VAR
        DiscrepTimer    : TON;
        BothClosed      : BOOL;
        BothOpen        : BOOL;
        Disagreeing     : BOOL;
    END_VAR

    BothClosed := Channel_A AND Channel_B; BothOpen := NOT Channel_A AND NOT Channel_B; Disagreeing := NOT BothClosed AND NOT BothOpen;

    // Discrepancy timer: channels must agree within time limit DiscrepTimer(IN := Disagreeing, PT := DiscrepancyTime);

    IF DiscrepTimer.Q THEN // Channels have disagreed too long — diagnostic fault Discrepancy := TRUE; DiagFault := TRUE; SafeInput := FALSE; // Go to safe state on fault ELSE Discrepancy := FALSE; // Safe condition: BOTH channels indicate safe // (For NC contacts: both TRUE = guards closed / condition OK) SafeInput := BothClosed AND NOT DiagFault; END_IF;

    // DiagFault latches until explicit reset (not shown here)

    Why Discrepancy Monitoring Matters

    Without discrepancy detection, a single sensor failure could go undetected — the system would operate with only one channel of protection, violating the SIL rating. The discrepancy timer allows for normal switching tolerances (contacts don't close simultaneously) while catching genuine faults.

    Safe Shutdown Sequencing

    A safety shutdown isn't just "turn everything off." Equipment must be de-energized in the correct sequence to prevent hazards like water hammer, pressure surges, or thermal shock:

    FUNCTION_BLOCK FB_SafeShutdownSequence
    VAR_INPUT
        TripCommand     : BOOL;     // Safety system trip signal
        ResetCommand    : BOOL;     // Operator reset (after investigation)
        AllValvesClosed : BOOL;     // Feedback: all isolation done
        DrivesStopped   : BOOL;     // Feedback: all motors stopped
    END_VAR
    VAR_OUTPUT
        ShutdownActive  : BOOL;
        Step            : INT;
        FuelValveCmd    : BOOL;     // FALSE = close (safe)
        FeedPumpCmd     : BOOL;     // FALSE = stop (safe)
        CoolingPumpCmd  : BOOL;     // Keep running during shutdown!
        PurgeBlowerCmd  : BOOL;     // Run during purge cycle
        ReadyToRestart  : BOOL;
    END_VAR
    VAR
        StepTimer       : TON;
        R_Trip          : R_TRIG;
        R_Reset         : R_TRIG;
    END_VAR

    R_Trip(CLK := TripCommand); R_Reset(CLK := ResetCommand);

    IF R_Trip.Q THEN Step := 1; ShutdownActive := TRUE; ReadyToRestart := FALSE; END_IF;

    CASE Step OF 0: // NORMAL OPERATION ShutdownActive := FALSE; // Outputs controlled by normal process logic 1: // STEP 1: Close fuel valve IMMEDIATELY FuelValveCmd := FALSE; // De-energize = close FeedPumpCmd := TRUE; // Keep feed pump running briefly CoolingPumpCmd := TRUE; // Cooling must continue! PurgeBlowerCmd := FALSE; StepTimer(IN := TRUE, PT := T#5s); IF StepTimer.Q THEN StepTimer(IN := FALSE); Step := 2; END_IF; 2: // STEP 2: Stop feed pump after 5s delay FeedPumpCmd := FALSE; CoolingPumpCmd := TRUE; // Still cooling StepTimer(IN := TRUE, PT := T#30s); IF StepTimer.Q THEN StepTimer(IN := FALSE); Step := 3; END_IF; 3: // STEP 3: Start purge cycle (30s post-trip) PurgeBlowerCmd := TRUE; CoolingPumpCmd := TRUE; StepTimer(IN := TRUE, PT := T#120s); // 2-minute purge IF StepTimer.Q THEN StepTimer(IN := FALSE); Step := 4; END_IF; 4: // STEP 4: Purge complete, stop blower PurgeBlowerCmd := FALSE; CoolingPumpCmd := TRUE; // Cooling runs 10 min total StepTimer(IN := TRUE, PT := T#480s); // Remaining cool-down IF StepTimer.Q THEN StepTimer(IN := FALSE); Step := 5; END_IF; 5: // STEP 5: Shutdown complete — awaiting reset FuelValveCmd := FALSE; FeedPumpCmd := FALSE; CoolingPumpCmd := FALSE; PurgeBlowerCmd := FALSE; ShutdownActive := TRUE; IF R_Reset.Q AND NOT TripCommand THEN // Only allow reset when trip condition cleared ReadyToRestart := TRUE; Step := 0; ShutdownActive := FALSE; END_IF; END_CASE;

    Critical Sequencing Rules

  • Fuel/energy isolation FIRST — always the highest priority
  • Cooling continues through shutdown — thermal damage is a secondary hazard
  • Purge cycles — mandatory for combustion systems (NFPA 86) before restart
  • Reset requires trip condition cleared — prevents restart into an unsafe state
  • Emergency Stop (E-Stop) Logic

    IEC 13850 defines E-Stop categories. Here's a Category 1 implementation (controlled stop, then power removal):

    FUNCTION_BLOCK FB_EmergencyStop
    VAR_INPUT
        EStop_Ch1       : BOOL;     // NC contact, channel 1
        EStop_Ch2       : BOOL;     // NC contact, channel 2
        ResetButton     : BOOL;     // Momentary NO contact
        AllDrivesSafe   : BOOL;     // Speed = 0 feedback
    END_VAR
    VAR_OUTPUT
        SafetyOK        : BOOL;     // Master safety relay output
        Cat1StopActive  : BOOL;     // Controlled stop in progress
        EStopLatched    : BOOL;     // Latched trip state
    END_VAR
    VAR
        DualInput       : FB_DualChannelInput;
        StopTimer       : TON;
        R_Reset         : R_TRIG;
    END_VAR

    // Process dual-channel E-Stop inputs DualInput( Channel_A := EStop_Ch1, Channel_B := EStop_Ch2, DiscrepancyTime := T#200ms );

    R_Reset(CLK := ResetButton);

    // E-Stop pressed (NC contacts open = both FALSE) IF NOT DualInput.SafeInput OR DualInput.DiagFault THEN EStopLatched := TRUE; END_IF;

    IF EStopLatched THEN // Category 1: Allow controlled deceleration Cat1StopActive := TRUE; // After drives have stopped (or 10s max), remove power StopTimer(IN := TRUE, PT := T#10s); IF AllDrivesSafe OR StopTimer.Q THEN SafetyOK := FALSE; // Open safety relay END_IF; ELSE SafetyOK := TRUE; Cat1StopActive := FALSE; StopTimer(IN := FALSE); END_IF;

    // Manual reset: requires E-Stop released AND button press IF R_Reset.Q AND DualInput.SafeInput AND NOT DualInput.DiagFault THEN EStopLatched := FALSE; END_IF;

    Diagnostic Coverage and Proof Testing

    Diagnostic coverage (DC) is the fraction of dangerous failures detected by online diagnostics. Higher DC contributes to achieving the target SIL:

    Diagnostic TechniqueTypical DCST Implementation
    Input comparison (dual-channel)90-99%FB_DualChannelInput above
    Output readback (EDM)90-99%Compare command vs. feedback
    Watchdog timer60-90%Cyclic pulse monitored externally
    Valve partial stroke test60-90%Periodic partial close + position check
    Wire break detection60-90%Check for out-of-range analog

    Output Readback (External Device Monitoring)

    After commanding an output, verify the device actually responded:

    FUNCTION_BLOCK FB_OutputReadback
    VAR_INPUT
        Command         : BOOL;     // What we commanded
        Feedback        : BOOL;     // What actually happened
        MonitoringTime  : TIME := T#2s;  // Max response time
    END_VAR
    VAR_OUTPUT
        EDM_Fault       : BOOL;     // External Device Monitoring fault
    END_VAR
    VAR
        MismatchTimer   : TON;
        Mismatch        : BOOL;
    END_VAR

    // Check if command and feedback disagree Mismatch := Command XOR Feedback;

    MismatchTimer(IN := Mismatch, PT := MonitoringTime);

    IF MismatchTimer.Q THEN // Device did not respond within time — latch fault EDM_Fault := TRUE; // Requires manual reset (not shown) END_IF;

    Analog Input Diagnostics

    Detect sensor failures by checking for out-of-range values. A 4-20mA sensor reads below 4mA on wire break:

    FUNCTION_BLOCK FB_AnalogDiagnostic
    VAR_INPUT
        RawValue        : INT;      // 0-32767 from ADC
        WireBreakLow    : INT := 1000;   // Below this = wire break
        OverRangeHigh   : INT := 31000;  // Above this = over-range
    END_VAR
    VAR_OUTPUT
        ValidSignal     : BOOL;
        WireBreak       : BOOL;
        OverRange       : BOOL;
        EngValue        : REAL;     // Scaled 4-20mA → 0-100%
    END_VAR

    WireBreak := RawValue < WireBreakLow; OverRange := RawValue > OverRangeHigh; ValidSignal := NOT WireBreak AND NOT OverRange;

    IF ValidSignal THEN // Scale 4-20mA range (approx 6554-26214 in 0-32767 ADC) EngValue := (INT_TO_REAL(RawValue) - 6554.0) / 19660.0 * 100.0; // Clamp to 0-100% IF EngValue < 0.0 THEN EngValue := 0.0; END_IF; IF EngValue > 100.0 THEN EngValue := 100.0; END_IF; ELSE EngValue := 0.0; // Or last-known-good, depending on strategy END_IF;

    Safety PLC Watchdog Pattern

    A watchdog timer ensures the PLC program is executing correctly. The PLC toggles a bit each scan; an external safety relay monitors the pulse:

    FUNCTION_BLOCK FB_SafetyWatchdog
    VAR_OUTPUT
        WatchdogPulse   : BOOL;    // Connect to safety relay input
    END_VAR
    VAR
        PulseTimer      : TON;
        PulseState      : BOOL;
    END_VAR

    PulseTimer(IN := NOT PulseState, PT := T#250ms);

    IF PulseTimer.Q THEN PulseState := NOT PulseState; PulseTimer(IN := FALSE); END_IF;

    WatchdogPulse := PulseState;

    // If PLC program hangs, pulse stops → safety relay opens // Typical external relay: expects pulse within 500ms window

    Safety Software Development Requirements

    IEC 61508 Part 3 specifies software development requirements by SIL:

    RequirementSIL 1SIL 2SIL 3
    Structured programmingRecommendedRequiredRequired
    Defensive programmingRecommendedRequiredRequired
    Formal code reviewRecommendedRequiredRequired
    Unit testingRecommendedRequiredRequired
    Integration testingRequiredRequiredRequired
    Cyclomatic complexity limit≤ 10≤ 10
    Single entry/exit per blockRecommendedRequiredRequired
    No dynamic variablesRequiredRequiredRequired
    No recursionRequiredRequiredRequired
    No pointers (SIL 3)RecommendedRequired
    100% code coverage testingRecommendedRequired
    Independent verificationRecommendedRequired

    Coding Rules for Safety ST

    // GOOD: Single purpose, clear naming, bounded loops
    FUNCTION_BLOCK FB_SafePressureMonitor
    VAR_INPUT
        PV_Pressure_Ch1 : REAL;    // Channel 1 reading (bar)
        PV_Pressure_Ch2 : REAL;    // Channel 2 reading (bar)
        SP_HighHigh     : REAL;    // Trip setpoint (bar)
        SP_Deviation    : REAL;    // Max channel deviation
    END_VAR
    VAR_OUTPUT
        TripOutput      : BOOL;    // TRUE = trip (safe action)
        ChannelFault    : BOOL;
        VotedPressure   : REAL;    // Selected value for display
    END_VAR

    // Channel deviation check ChannelFault := ABS(PV_Pressure_Ch1 - PV_Pressure_Ch2) > SP_Deviation;

    // 1oo2 voting: trip if EITHER channel exceeds limit // (conservative — avoids missed trip on single sensor failure) TripOutput := (PV_Pressure_Ch1 >= SP_HighHigh) OR (PV_Pressure_Ch2 >= SP_HighHigh) OR ChannelFault; // Fault = trip (fail-safe)

    // Select higher value for display (conservative) IF PV_Pressure_Ch1 >= PV_Pressure_Ch2 THEN VotedPressure := PV_Pressure_Ch1; ELSE VotedPressure := PV_Pressure_Ch2; END_IF;

    Voting Architectures: 1oo1, 1oo2, 2oo3

    ArchitectureDescriptionSafe FailuresDangerous FailuresUse Case
    1oo1Single channelMay cause spurious tripMissed trip possibleSIL 1 basic
    1oo2Trip if either channel tripsHigher spurious trip rateVery low (both must fail)SIL 2–3
    2oo3Trip if 2 of 3 tripLow spurious (majority vote)Low dangerous (2 must fail)SIL 3, high availability
    2oo2Trip only if both tripLowest spurious rateHigher dangerous (1 failure masks)Avoid for safety
    1oo2 (One-out-of-Two): Safest default — any single failure causes a trip. Higher spurious trip rate is accepted as a safety trade-off.

    2oo3 (Two-out-of-Three): Best balance of safety and availability. Used in critical process plants where spurious trips are also hazardous (e.g., refinery emergency shutdown).

    Important Disclaimers

  • Safety PLC programming requires certified hardware — standard PLCs (S7-1200, CompactLogix) are NOT suitable for safety functions. Use safety-rated controllers (S7-1500F, GuardLogix, SafeLogix, PSSuniversal).
  • Real safety projects require formal verification — the patterns here are educational. Production safety logic must undergo hazard analysis (LOPA/SIL determination), independent review, and proof testing.
  • Certification matters — safety application software should be developed per IEC 61508-3 and validated by a competent person or TÜV-certified engineer.
  • Never bypass safety functions — even temporarily. Document all defeat conditions and implement them with time limits and audit trails.
  • Practice safety logic patterns in our online ST editor. For alarm handling that complements safety systems, see our alarm management tutorial.