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.

What functional safety asks of the PLC program

Functional safety means a Safety Instrumented System (SIS) performs its safety function correctly when a demand comes. IEC 61508 (general), IEC 62061 (machinery) and IEC 61511 (process industry) set the requirements for the whole safety lifecycle, from hazard analysis through design, coding and testing to maintenance.

This article covers the PLC programming patterns used in safety-rated applications. A real safety system needs certified hardware (a safety PLC) and formal verification. Every automation engineer should still know the patterns themselves.

Safety integrity levels (SIL)

A SIL states how much risk reduction a safety function must deliver. The higher the SIL, the lower the allowed probability of a 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 is the probability of failure on demand. PFH is the probability of a dangerous failure per hour.

Fail-safe design

Design safety logic so that the most likely failure mode leads to the safe state. In practice:

  • De-energize to trip (energize to permit).
  • Wire to the closed-circuit principle, so a wire break gives the safe state.
  • Use redundant inputs with discrepancy monitoring.
  • Dual-channel input processing

    SIL 2 and above typically require redundant sensing: two independent sensors watching the same process condition. The PLC must compare the two channels and detect when they disagree:

    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)
    

    What the discrepancy timer catches

    Without discrepancy detection, a single sensor failure can go unnoticed. The system then runs on one channel of protection, which violates the SIL rating. The timer allows for normal switching tolerance, since two contacts never close at exactly the same instant, and still catches a genuine fault.

    Safe shutdown sequencing

    A safety shutdown is more than "turn everything off." Equipment has to be de-energized in the correct order, or the shutdown itself causes 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;
    

    Sequencing rules

  • Isolate fuel and energy first. This is always the highest priority.
  • Keep cooling running through the shutdown, because thermal damage is a secondary hazard.
  • Run the purge cycle before restart. It is mandatory for combustion systems (NFPA 86 for ovens and furnaces, NFPA 85 for boilers).
  • Allow reset only once the trip condition has cleared, so the plant cannot restart into an unsafe state.
  • Emergency stop (E-stop) logic

    ISO 13850 covers emergency stop design, including the stop category an E-stop uses. This is 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 that online diagnostics detect. A higher DC helps you reach 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 you command an output, check that 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

    You can detect a failed sensor by checking for out-of-range values. A 4-20mA sensor reads below 4mA on a 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 checks that the PLC program is still executing. The PLC toggles a bit and an external safety relay monitors the pulse. If the program hangs, the pulse stops and the relay opens:

    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
    

    Software development requirements by SIL

    IEC 61508 Part 3 sets software development requirements according to SIL:

    This is a simplified reading. IEC 61508-3 itself grades each technique per SIL as recommended (R) or highly recommended (HR), and you must justify leaving out an HR technique; "Required" below marks those.

    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 testing-RecommendedRequired
    Independent verification-RecommendedRequired

    Coding rules for safety ST

    Keep each block to one purpose, name every tag for what it measures, and bound every loop. The pressure monitor below follows those rules and trips on a channel fault as well as on high pressure:

    // 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) is the safest default, because any single failure causes a trip. You accept the higher spurious trip rate as the price of safety.

    2oo3 (two-out-of-three) gives the best balance of safety and availability. Critical process plants use it where a spurious trip is also hazardous, for example a refinery emergency shutdown.

    Before you use any of this on a machine

  • Safety PLC programming requires certified hardware. Standard PLCs (S7-1200, CompactLogix) are not suitable for safety functions. Use a safety-rated controller (S7-1500F, GuardLogix, PSSuniversal).
  • Real safety projects require formal verification. The patterns here are educational. Production safety logic must go through hazard analysis (LOPA/SIL determination), independent review and proof testing.
  • Develop safety application software to IEC 61508-3 and have it validated by a competent person or a TÜV-certified engineer.
  • Never bypass a safety function, even temporarily. Document every defeat condition and implement it with a time limit and an audit trail.
  • You can try these safety logic patterns in the online ST editor. For the alarm handling that complements a safety system, see the alarm management tutorial.