Problem Details
An inherited PLC program stops progressing inside an initialization routine identified in the project as qa8_init. The controller is running (no hard fault reported by the operator), but the machine never leaves the initialization phase: downstream sequence steps never execute, and the startup handshake never completes.
The available evidence is limited to that symptom description and the later statement that the issue was resolved. No controller family, firmware revision, hardware configuration, or block source was published. This article therefore documents a diagnostic method that applies to any initialization block that stalls, not a claim about which specific defect existed in that project.
qa8_init is a user-assigned symbol. Treat it as a routine/block name in the original project, not as a vendor-defined system object. Do not assume it maps to a documented manufacturer function until you have opened the offline archive and confirmed the block type and language.What "hangs" can actually mean
| Observed behavior | Likely mechanism | First check |
|---|---|---|
| Cyclic execution continues, sequence step never advances | Wait condition inside init never becomes true | Monitor the step number and the transition condition online |
| Controller stops or faults shortly after start | Scan-time watchdog exceeded by a loop inside init | Read the diagnostic buffer / fault log timestamp |
| Init runs once then repeats forever | First-scan or "init done" latch is non-retentive or cleared | Check retentive attribute of the done flag |
| Init blocks only with real hardware, not in simulation | Missing/absent I/O module or comm partner | Compare configured vs. present hardware |
Root Cause Candidates
An initialization routine that never completes almost always fails on one of five classes of condition. Work them in this order, because each later class costs more time to test.
- Unsatisfied wait/transition condition. Init code commonly waits on a feedback bit, a homing-complete flag, a valid setpoint range, or an operator acknowledge. If that source no longer exists in the current machine, the condition is permanently false.
- Missing or reconfigured hardware. Legacy programs frequently address I/O, drives, or intelligent modules that were removed or re-slotted. Init routines that poll a module status word will spin forever if the module is absent, and I/O data may be forced to a substitute value rather than reported as an error.
- Communication handshake never establishes. Serial/fieldbus init sequences that expect a response frame from a device (drive, HMI, weighing head, remote rack) will block if the partner is off, mis-addressed, or running a different protocol setting.
- Uninitialized or stale retentive memory. After a memory clear, battery loss, or restore from an incomplete archive, a state machine can start at an invalid step number that has no defined exit transition. Conversely, a retained "init done" flag can be stuck set or stuck cleared.
-
Blocking loop inside the block. A
WHILE/REPEATor jump-back construct whose exit condition depends on external data will consume the entire scan and can trip the cycle-time watchdog rather than simply waiting.
Diagnostic Procedure
- Archive first. Upload the running program and configuration and store it read-only before you change anything. On legacy projects the controller is often the only complete copy; comments and symbols may exist only in the offline project, so also locate any archived project files.
- Read the controller diagnostic buffer / fault table. Record every entry with its timestamp and the sequence relative to power-up. A watchdog/cycle-time entry points to case 5. An I/O access or module-fault entry points to case 2. No entries at all points to cases 1, 3, or 4.
- Confirm the controller mode and scan is actually running. Toggle a free memory bit with a simple test rung or watch a free-running counter. If the counter increments, the CPU is scanning and the problem is logical, not a stopped CPU.
-
Instrument the block. Add a monitor of the internal step/state variable of
qa8_initto a watch table or trend. Note the exact step it parks on. This single value converts a vague "hangs" into a specific unmet transition. - Expand that one transition. Cross-reference every operand in the stuck transition condition. For each operand answer: who writes it, from which block, and is that writer still called? A writer left in an unused block or in code disabled by a jump is a common legacy failure.
- Verify the physical source. For each input operand, force nothing yet - measure at the terminal, check the module LED, and confirm the address maps to the slot you think it does. Re-slotted racks silently shift addresses on many platforms.
- Check the hardware configuration against reality. Compare the configured module list with the installed modules, including firmware/revision keying and any "substitute value" behavior configured for failed inputs.
- Check comm partners. For any device the init sequence talks to, confirm node address, baud rate, parity/stop bits or bus profile, and termination. Capture the exchange if a protocol analyzer or the controller's own comm status words are available.
- Test the transition in isolation. With the machine safely de-energized where appropriate, force or simulate only the single suspect condition and confirm the state machine advances to the next step. If it advances and then stalls again, repeat the procedure at the new step - legacy init routines commonly have several stale dependencies.
Corrective Actions by Cause
| Cause | Correction | Do not do this |
|---|---|---|
| Feedback device removed from machine | Remove the dependency from the transition, or re-add a properly rated feedback device | Permanently force the bit or bypass a safety-related feedback |
| Wrong I/O addressing after rack change | Correct the hardware configuration and re-map addresses; verify with a cross-reference | Patch the symbol table only, leaving the physical map inconsistent |
| Comm partner not answering | Restore the partner's address/protocol settings; add a bounded timeout with an alarm instead of an infinite wait | Delete the handshake entirely and assume the device is ready |
| Invalid retained state | Add a startup validity check that forces the state machine to a defined home step when the retained step is out of range | Clear all retentive memory on a live machine without knowing what else is retained |
| Blocking loop trips watchdog | Convert the loop to a scan-based state machine with a timeout timer | Raise the cycle-time watchdog to hide the loop |
Make the failure self-reporting
Any wait step in an init routine should carry a timeout and a diagnostic word. Pseudocode, adapt to your platform's language:
// Step wait with bounded timeout
IF InitStep = 8 THEN
StepTimer(IN := TRUE, PT := T#10s);
IF Feedback_QA8 THEN
StepTimer(IN := FALSE);
InitStep := 9;
ELSIF StepTimer.Q THEN
InitFaultCode := 8; // step number that timed out
InitFaulted := TRUE; // drive to HMI alarm
END_IF;
END_IF;
With this pattern the next occurrence reports "init timed out at step 8" instead of an unexplained stall, which is the difference between a five-minute fix and a shift-long hunt.
Verification
- Power-cycle the controller cold (not just a warm restart) and confirm the init routine runs to completion within its expected time.
- Repeat with the machine in its worst realistic starting state - mid-cycle stop, e-stop reset, partial product in the machine - since init routines are usually only tested from a clean state.
- Confirm the diagnostic buffer / fault table is clear of new entries after the restart.
- Verify no forces or online edits remain active, and that the online program matches the offline project (run a compare).
- Record the resolved cause, the changed operands, and the date in the project header comment. On legacy code, an undocumented fix becomes the next engineer's identical outage.
- Archive the corrected project to two locations and note the controller firmware revision that the archive was verified against.
Preventive Notes for Legacy Projects
- Before touching logic, confirm you can restore what is currently running. An upload without the original symbol/comment file is a poor recovery point.
- Cross-reference is the highest-value tool in an unknown project: it identifies dead writers, duplicate coils/multiple writers, and orphaned blocks faster than reading code sequentially.
- Never leave an unbounded wait in initialization code. Every wait needs a timeout, a fault code, and an HMI message.
- Document the physical prerequisites of the init sequence (which sensors, which comm partners, which home positions) alongside the code, so a removed device is caught at design review rather than at start-up.
- Keep the hardware configuration in the project synchronized with the installed rack after any module replacement, including revision keying.
Why does my PLC program hang in the initialization block but the CPU stays in RUN?
The CPU is still scanning; a transition condition inside the init state machine never becomes true. Monitor the internal step variable, identify the step it parks on, then cross-reference every operand in that step's transition to find which writer or input no longer exists.
How do I tell a logic stall from a watchdog trip?
Read the controller diagnostic buffer or fault table. A cycle-time/watchdog entry means a blocking loop is consuming the scan; no entries with a still-incrementing free-running counter means the code is waiting logically and the fault is in the transition condition.
Is it safe to force the bit that the init routine is waiting on?
Only as a temporary diagnostic, with the machine isolated and under a documented procedure - forcing can command motion or defeat an interlock. Remove all forces and verify the force table is empty before returning to production.
The init routine works in simulation but hangs on the real machine. Where do I look?
Look at hardware and communications: absent or re-slotted I/O modules, mismatched module revision keying, and comm partners with wrong node address or baud/parity settings. Simulation satisfies those handshakes that real hardware does not.
How do I stop this from recurring after I fix it?
Replace every unbounded wait with a timer-bounded step that sets a fault code equal to the step number and raises an HMI alarm, then archive the corrected project and record the cause and changed operands in the project header.