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:

  • A valve that defaults to OPEN could flood a tank
  • A motor contactor that defaults to ON could start a conveyor with someone inside the machine
  • A heater that retains its last state could overshoot while the temperature sensor is still initializing
  • 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_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 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_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 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_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

        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_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

    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_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

    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.