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 first 5 seconds after power-up
For a short while after a PLC powers up, outputs can glitch, sensors have not settled and communication links are not yet established. In that window:
The first rule of PLC startup is to assume nothing is in a known state. Your initialization sequence has to verify everything before it enables any output.
The sequence below is machine logic. It does not replace the safety circuit: protecting someone inside the machine is the job of the safety relay or safety PLC. The startup sequencer keeps the process logic from running on outputs, sensors and links in an unknown state.
A startup state machine that holds off normal operation
A well-built PLC program starts with a startup sequencer, and nothing in normal operation runs until it finishes:
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_TYPE
PROGRAM 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 where you set initial values and clear any retained variables that should not survive a power cycle:
// 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
On a cold start the retained memory is cleared as well, so every variable goes back to its initial value. On a warm start the RETAIN variables keep their values and only the non-retained variables are reset. The function below tells the two apart with a marker value kept in retained memory:
TYPE RestartType : (
RESTART_COLD, // Full power cycle — initialize everything
RESTART_WARM, // Stop/Start — retain some context
RESTART_FAULT // Recovered from CPU fault
);
END_TYPE
FUNCTION 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 you trust any input, check that the I/O modules are 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
Drives, remote I/O and SCADA links take time to boot. Wait for them, but put a limit on the wait:
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 settle after power-up. Thermocouples, pressure transmitters and flow meters can read garbage for 1–5 seconds, so each critical reading has to pass a plausibility check and then stay inside a window for a set time:
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 the actuators to their safe starting positions before you enable automatic operation. Here that means closing the valves and homing the servo axis:
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
Automated checks then confirm the analog range, output feedback and retained data before the machine is declared ready:
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_VAR
IF 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
The last two states either release the machine or hold every output off and wait for the operator:
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;
Holding the main program until boot completes
The main process program returns early on every scan until BootComplete is TRUE:
// In your main process program:
PROGRAM ProcessControl
VAR
startup : StartupManager;
END_VAR
IF 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
If power is lost in the middle of a process, a recovery sequence decides what to do from the step that was interrupted. Some steps can resume on their own; an interrupted reaction waits for an operator decision:
TYPE ProcessCheckpoint : (
CP_IDLE,
CP_FILLING,
CP_HEATING,
CP_REACTING,
CP_COOLING,
CP_DRAINING,
CP_COMPLETE
);
END_TYPE
VAR 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
Phase timeouts and design rules at a glance
| 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 | None |
| 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 |