Startup Logic
PLC Power-Up Initialization & Startup Sequences: Safe Boot Logic in Structured Text
What happens in the first 5 seconds after power-up determines whether your machine starts safely or damages itself. Build bulletproof PLC initialization sequences with proven Structured Text patterns.
The Most Dangerous 5 Seconds
When a PLC powers up, there's a brief window where outputs might glitch, sensors haven't settled, and communication links aren't established. In that window:
The first rule of PLC startup: assume nothing is in a known state. Your initialization sequence must verify everything before enabling any output.
🔄 The Startup State Machine
Every robust PLC program starts with a startup sequencer that gates all normal operation:
TYPE StartupState : (
BOOT_INIT, // First scan — initialize variables
BOOT_IO_CHECK, // Verify I/O modules are online
BOOT_COMM_CHECK, // Verify network connections
BOOT_SENSOR_VALID, // Wait for sensor readings to stabilize
BOOT_SAFE_POSITION, // Move actuators to safe/home positions
BOOT_SELF_TEST, // Run diagnostic checks
BOOT_READY, // All checks passed — ready for operation
BOOT_FAILED // Startup failed — manual intervention needed
);
END_TYPEPROGRAM StartupManager
VAR
State : StartupState := BOOT_INIT;
BootComplete : BOOL := FALSE;
BootFailed : BOOL := FALSE;
BootErrorCode : DINT := 0;
BootErrorMsg : STRING[80] := '';
BootTimeElapsed : TIME;
MaxBootTime : TIME := T#30S;
tmrBootTimeout : TON;
tmrStepDelay : TON;
FirstScan : BOOL := TRUE;
END_VAR
// Global boot timeout — if startup takes too long, something is wrong
tmrBootTimeout(IN := (State <> BOOT_READY) AND (State <> BOOT_FAILED), PT := MaxBootTime);
IF tmrBootTimeout.Q THEN
State := BOOT_FAILED;
BootErrorCode := 9999;
BootErrorMsg := 'Boot timeout — startup exceeded maximum time';
END_IF;
📋 Phase 1: First-Scan Initialization
The first scan is special — it's where you set initial values and clear any retained variables that shouldn't persist across power cycles:
// Inside the BOOT_INIT state:
CASE State OF
BOOT_INIT:
// === CRITICAL FIRST-SCAN ACTIONS ===
// 1. Force all outputs to safe state
DO_AllMotors := FALSE;
DO_AllValves := FALSE;
DO_AllHeaters := FALSE;
AO_AllSpeeds := 0;
// 2. Clear one-shot commands that might have been retained
CMD_Start := FALSE;
CMD_AutoRun := FALSE;
CMD_BatchStart := FALSE;
// 3. Reset all function block instances
// (Call with Enable:=FALSE to force internal reset)
Motor1(Enable := FALSE);
Motor2(Enable := FALSE);
TempLoop1(Enable := FALSE);
// 4. Initialize counters and accumulators
ProductionCount := 0;
ShiftRuntime := T#0S;
// 5. Load configuration from retained memory
IF RetainedConfig.IsValid THEN
ActiveRecipe := RetainedConfig.LastRecipe;
UnitConversion := RetainedConfig.Units;
ELSE
// First-ever boot or corrupted retain — use defaults
ActiveRecipe := 1;
UnitConversion := 0; // Metric
END_IF;
State := BOOT_IO_CHECK;
🏷️ Warm Start vs. Cold Start
Cold start: PLC was completely powered off — all non-retained variables are reset Warm start: PLC was stopped and restarted — retain variables preserved
TYPE RestartType : (
RESTART_COLD, // Full power cycle — initialize everything
RESTART_WARM, // Stop/Start — retain some context
RESTART_FAULT // Recovered from CPU fault
);
END_TYPEFUNCTION FC_DetectRestartType : RestartType
VAR
RetainMarker : DINT := 0; // Non-retained
RetainedMarker : DINT; // RETAIN variable
END_VAR
VAR RETAIN
RetainedMarker : DINT := 0;
BootCounter : DINT := 0;
END_VAR
BootCounter := BootCounter + 1;
IF RetainedMarker = 16#DEADBEEF THEN
// Retained memory intact — warm start
FC_DetectRestartType := RESTART_WARM;
ELSE
// Retained memory was cleared — cold start
RetainedMarker := 16#DEADBEEF;
FC_DetectRestartType := RESTART_COLD;
END_IF;
🔌 Phase 2: I/O Module Validation
Before trusting any input, verify that the I/O modules are actually online and responding:
// Inside BOOT_IO_CHECK state:
BOOT_IO_CHECK:
// Check I/O module status (platform-specific diagnostic registers)
// Siemens: Module status in system DB
// Allen-Bradley: Module fault bits in controller properties
// CODESYS: IoConfig device state
ioCheckResult := FC_ValidateIOModules();
CASE ioCheckResult OF
0: // All modules OK
State := BOOT_COMM_CHECK;
1: // Non-critical module missing (e.g., spare I/O)
BootErrorMsg := 'Warning: Non-critical I/O module offline';
// Continue with degraded capability
State := BOOT_COMM_CHECK;
2: // Critical module missing
BootErrorCode := 2001;
BootErrorMsg := 'Critical I/O module not responding';
State := BOOT_FAILED;
END_CASE;
FUNCTION FC_ValidateIOModules : INT
VAR
ModulesExpected : INT := 8;
ModulesOnline : INT := 0;
CriticalMask : WORD := 16#003F; // Modules 0–5 are critical
OnlineMask : WORD := 16#0000;
i : INT;
END_VAR// Platform-specific: read module status bits
// This example uses a status word where each bit = one module
FOR i := 0 TO ModulesExpected - 1 DO
IF ( module i is online ) TRUE THEN
OnlineMask := OnlineMask OR SHL(WORD#16#0001, i);
ModulesOnline := ModulesOnline + 1;
END_IF;
END_FOR;
// Check if all critical modules are present
IF (OnlineMask AND CriticalMask) = CriticalMask THEN
IF ModulesOnline = ModulesExpected THEN
FC_ValidateIOModules := 0; // All modules OK
ELSE
FC_ValidateIOModules := 1; // Non-critical module missing
END_IF;
ELSE
FC_ValidateIOModules := 2; // Critical module missing
END_IF;
📡 Phase 3: Communication Validation
Network devices take time to boot. Wait for them, but don't wait forever:
BOOT_COMM_CHECK:
// Check each communication link
commCheckDone := TRUE;
// VFD on PROFINET
IF NOT VFD_Drive1.IsOnline THEN
commCheckDone := FALSE;
tmrStepDelay(IN := TRUE, PT := T#10S);
IF tmrStepDelay.Q THEN
BootErrorCode := 3001;
BootErrorMsg := 'VFD Drive 1 not responding on PROFINET';
State := BOOT_FAILED;
END_IF;
END_IF;
// Remote I/O on EtherNet/IP
IF NOT RemoteIO_Panel2.IsOnline THEN
commCheckDone := FALSE;
tmrStepDelay(IN := TRUE, PT := T#10S);
IF tmrStepDelay.Q THEN
BootErrorCode := 3002;
BootErrorMsg := 'Remote I/O Panel 2 not responding';
State := BOOT_FAILED;
END_IF;
END_IF;
// SCADA — non-critical, continue without it
IF NOT SCADA_Connection.IsOnline THEN
// Log warning but don't fail boot
BootErrorMsg := 'SCADA offline — running in local mode';
END_IF;
IF commCheckDone THEN
tmrStepDelay(IN := FALSE);
State := BOOT_SENSOR_VALID;
END_IF;
🌡️ Phase 4: Sensor Stabilization
Analog sensors need time to stabilize after power-up. Thermocouples, pressure transmitters, and flow meters can read garbage for 1–5 seconds:
FUNCTION_BLOCK FB_SensorStabilizer
VAR_INPUT
RawValue : REAL;
StableWindow : REAL := 2.0; // Must stay within this range
StableTime : TIME := T#3S; // For this duration
PlausibleMin : REAL := -50.0;
PlausibleMax : REAL := 500.0;
END_VAR
VAR_OUTPUT
IsStable : BOOL := FALSE;
StableValue : REAL;
IsPlausible : BOOL := FALSE;
END_VAR
VAR
tmrStable : TON;
refValue : REAL;
initialized : BOOL := FALSE;
END_VAR// Check if reading is physically plausible
IsPlausible := (RawValue >= PlausibleMin) AND (RawValue <= PlausibleMax);
IF NOT IsPlausible THEN
IsStable := FALSE;
tmrStable(IN := FALSE);
RETURN;
END_IF;
// Initialize reference on first plausible reading
IF NOT initialized THEN
refValue := RawValue;
initialized := TRUE;
END_IF;
// Check if value is stable (within window of reference)
IF ABS(RawValue - refValue) <= StableWindow THEN
tmrStable(IN := TRUE, PT := StableTime);
ELSE
// Value drifted — reset with new reference
refValue := RawValue;
tmrStable(IN := FALSE);
END_IF;
IsStable := tmrStable.Q;
StableValue := RawValue;
BOOT_SENSOR_VALID:
// Run stabilizers for critical sensors
stabTemp1(RawValue := AI_Temperature1, StableWindow := 2.0, StableTime := T#3S);
stabPress1(RawValue := AI_Pressure1, StableWindow := 0.5, StableTime := T#2S);
stabLevel1(RawValue := AI_Level1, StableWindow := 3.0, StableTime := T#2S);
IF stabTemp1.IsStable AND stabPress1.IsStable AND stabLevel1.IsStable THEN
State := BOOT_SAFE_POSITION;
END_IF;
// Individual sensor failure messages
IF NOT stabTemp1.IsPlausible THEN
BootErrorMsg := 'Temperature sensor 1 — reading not plausible';
END_IF;
🏠 Phase 5: Safe Positioning
Move actuators to their safe starting positions before enabling automatic operation:
BOOT_SAFE_POSITION:
// Close all valves
Valve_Inlet(CMD_Close := TRUE);
Valve_Outlet(CMD_Close := TRUE);
Valve_Drain(CMD_Close := TRUE);
// Home all axes (if servo-controlled)
IF NOT Axis1_Homed THEN
fbHome_Axis1(Axis := Axis1, Execute := TRUE, Velocity := 10.0);
IF fbHome_Axis1.Done THEN
Axis1_Homed := TRUE;
fbHome_Axis1(Execute := FALSE);
ELSIF fbHome_Axis1.Error THEN
BootErrorCode := 5001;
BootErrorMsg := 'Axis 1 homing failed';
State := BOOT_FAILED;
END_IF;
END_IF;
// Verify all actuators reached safe state
allSafe := Valve_Inlet.IsClosed
AND Valve_Outlet.IsClosed
AND Valve_Drain.IsClosed
AND Axis1_Homed;
IF allSafe THEN
State := BOOT_SELF_TEST;
END_IF;
🧪 Phase 6: Self-Diagnostics
Run automated checks to verify the system is healthy:
FUNCTION_BLOCK FB_SelfTest
VAR_INPUT
Execute : BOOL;
END_VAR
VAR_OUTPUT
Done : BOOL;
Passed : BOOL;
TestID : INT;
FailMessage : STRING[80];
END_VAR
VAR
Step : INT := 0;
TestCount : INT := 0;
PassCount : INT := 0;
END_VARIF NOT Execute THEN Step := 0; Done := FALSE; RETURN; END_IF;
CASE Step OF
0: // TEST 1: Verify analog input range
TestCount := TestCount + 1;
TestID := 1;
IF AI_Temperature1 > -40.0 AND AI_Temperature1 < 300.0 THEN
PassCount := PassCount + 1;
Step := 1;
ELSE
FailMessage := 'Self-test: Temperature 1 out of range at boot';
Step := 99;
END_IF;
1: // TEST 2: Verify output feedback matches command
TestCount := TestCount + 1;
TestID := 2;
// Briefly pulse an output and check feedback
// (Only for outputs with feedback wiring)
DO_TestOutput := TRUE;
Step := 2;
2: // Check feedback after one scan
IF DI_TestFeedback THEN
DO_TestOutput := FALSE;
PassCount := PassCount + 1;
Step := 3;
ELSE
DO_TestOutput := FALSE;
FailMessage := 'Self-test: Output feedback mismatch';
Step := 99;
END_IF;
3: // TEST 3: Verify retained data integrity
TestCount := TestCount + 1;
TestID := 3;
IF RetainedConfig.Checksum = FC_CalcChecksum(RetainedConfig) THEN
PassCount := PassCount + 1;
Step := 4;
ELSE
FailMessage := 'Self-test: Retained data checksum mismatch';
// Not fatal — use defaults
PassCount := PassCount + 1;
Step := 4;
END_IF;
4: // ALL TESTS COMPLETE
Done := TRUE;
Passed := (PassCount = TestCount);
99: // TEST FAILED
Done := TRUE;
Passed := FALSE;
END_CASE;
🚦 Phase 7: Ready for Operation
BOOT_READY:
BootComplete := TRUE;
BootFailed := FALSE;
// Now enable normal operation
// The main program checks BootComplete before running any sequence
BOOT_FAILED:
BootComplete := FALSE;
BootFailed := TRUE;
// All outputs forced to safe state
DO_AllMotors := FALSE;
DO_AllValves := FALSE;
DO_AllHeaters := FALSE;
// Display error on HMI
// Wait for operator to fix issue and request re-boot
END_CASE;
Gating Normal Operation
// In your main process program:
PROGRAM ProcessControl
VAR
startup : StartupManager;
END_VARIF NOT startup.BootComplete THEN
// DON'T RUN ANYTHING — startup hasn't completed
// Optionally show boot progress on HMI
RETURN;
END_IF;
// === Normal operation starts here ===
// Only reached after ALL boot checks pass
🔄 Power Failure Recovery
What if power is lost mid-process? A recovery sequence determines what to do based on where the process was interrupted:
TYPE ProcessCheckpoint : (
CP_IDLE,
CP_FILLING,
CP_HEATING,
CP_REACTING,
CP_COOLING,
CP_DRAINING,
CP_COMPLETE
);
END_TYPEVAR RETAIN
LastCheckpoint : ProcessCheckpoint := CP_IDLE;
LastBatchID : DINT := 0;
LastStepParams : RecipeStep;
CheckpointValid : BOOL := FALSE;
END_VAR
// After boot completes, check for interrupted process:
IF startup.BootComplete AND FC_DetectRestartType() = RESTART_COLD THEN
IF CheckpointValid AND LastCheckpoint <> CP_IDLE AND LastCheckpoint <> CP_COMPLETE THEN
// Process was interrupted!
CASE LastCheckpoint OF
CP_FILLING:
// Safe to resume — verify weight and continue
RecoveryAction := 'Resuming fill from checkpoint';
CP_HEATING:
// Check actual temperature — may need to re-ramp
IF ActualTemp > (LastStepParams.TargetTemp - 10.0) THEN
RecoveryAction := 'Near setpoint — resuming hold timer';
ELSE
RecoveryAction := 'Temperature dropped — restarting ramp';
END_IF;
CP_REACTING:
// CRITICAL: reaction may be in unknown state
RecoveryAction := 'OPERATOR DECISION REQUIRED — reaction interrupted';
// Don't auto-resume — require operator confirmation
CP_DRAINING:
// Safe to resume draining
RecoveryAction := 'Resuming drain sequence';
END_CASE;
END_IF;
END_IF;
// Save checkpoint at each major step transition:
LastCheckpoint := CP_HEATING;
LastStepParams := CurrentStep;
CheckpointValid := TRUE;
// These RETAIN variables survive power loss
Summary
| Phase | Purpose | Timeout | | INIT | Force outputs safe, clear commands, load config | Immediate | | IO_CHECK | Verify all I/O modules are online | 5s | | COMM_CHECK | Verify network devices are responding | 10s | | SENSOR_VALID | Wait for analog readings to stabilize | 5–10s | | SAFE_POSITION | Move actuators to home/safe positions | 15s | | SELF_TEST | Automated diagnostic checks | 5s | | READY | Enable normal operation | — |
| Principle | Implementation | | First scan safety | Force all outputs OFF before checking anything | | Warm vs. cold detection | RETAIN marker variable (0xDEADBEEF pattern) | | Sensor stabilization | Plausibility check + stability window timer | | Graceful degradation | Non-critical failures log warnings but don't block boot | | Power failure recovery | RETAIN checkpoints + operator-confirmed resume for critical steps | | Boot timeout | Global watchdog prevents infinite startup loops |
The startup sequence is your machine's immune system. It runs once, takes a few seconds, and protects against every power-up scenario you'll encounter in 20 years of production. Skip it, and you're gambling with every power cycle.