Safety
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:
| SIL | PFD (Low Demand) | PFH (High/Continuous) | Risk Reduction | Example Application |
|---|---|---|---|---|
| 1 | 10⁻¹ to 10⁻² | 10⁻⁵ to 10⁻⁶ /h | 10–100× | Light curtain guard stop |
| 2 | 10⁻² to 10⁻³ | 10⁻⁶ to 10⁻⁷ /h | 100–1,000× | Emergency stop, burner management |
| 3 | 10⁻³ to 10⁻⁴ | 10⁻⁷ to 10⁻⁸ /h | 1,000–10,000× | High-pressure relief, toxic gas shutdown |
| 4 | 10⁻⁴ to 10⁻⁵ | 10⁻⁸ to 10⁻⁹ /h | 10,000–100,000× | Nuclear protection (rarely needed in manufacturing) |
Key Principle: Fail-Safe Design
Safety logic must be designed so that the most likely failure mode leads to the safe state. This means:
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_VARBothClosed := 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_VARR_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
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 Technique | Typical DC | ST Implementation |
|---|---|---|
| Input comparison (dual-channel) | 90-99% | FB_DualChannelInput above |
| Output readback (EDM) | 90-99% | Compare command vs. feedback |
| Watchdog timer | 60-90% | Cyclic pulse monitored externally |
| Valve partial stroke test | 60-90% | Periodic partial close + position check |
| Wire break detection | 60-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_VARWireBreak := 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_VARPulseTimer(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:
| Requirement | SIL 1 | SIL 2 | SIL 3 |
|---|---|---|---|
| Structured programming | Recommended | Required | Required |
| Defensive programming | Recommended | Required | Required |
| Formal code review | Recommended | Required | Required |
| Unit testing | Recommended | Required | Required |
| Integration testing | Required | Required | Required |
| Cyclomatic complexity limit | — | ≤ 10 | ≤ 10 |
| Single entry/exit per block | Recommended | Required | Required |
| No dynamic variables | Required | Required | Required |
| No recursion | Required | Required | Required |
| No pointers (SIL 3) | — | Recommended | Required |
| 100% code coverage testing | — | Recommended | Required |
| Independent verification | — | Recommended | Required |
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
| Architecture | Description | Safe Failures | Dangerous Failures | Use Case |
|---|---|---|---|---|
| 1oo1 | Single channel | May cause spurious trip | Missed trip possible | SIL 1 basic |
| 1oo2 | Trip if either channel trips | Higher spurious trip rate | Very low (both must fail) | SIL 2–3 |
| 2oo3 | Trip if 2 of 3 trip | Low spurious (majority vote) | Low dangerous (2 must fail) | SIL 3, high availability |
| 2oo2 | Trip only if both trip | Lowest spurious rate | Higher dangerous (1 failure masks) | Avoid for safety |
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
Practice safety logic patterns in our online ST editor. For alarm handling that complements safety systems, see our alarm management tutorial.