Debugging, Simulation, and Testing PLC Programs
Structured Text (ST) lets you express complex logic clearly, but PLC code still runs inside the scan/task model you learned earlier: inputs are sampled, code executes, outputs update. That execution model creates debugging pitfalls that look like “random” behavior if you approach PLCs like event-driven software.
This article shows practical, IEC 61131-3-aligned techniques for:
Debugging ST logic online
Simulating I/O and processes safely
Testing Functions and Function Blocks (FBs) so changes don’t break machinesWe will keep the ideas vendor-neutral, because every PLC IDE has different buttons, but the underlying methods are consistent.
What makes PLC debugging different from typical software debugging
Cyclic execution changes what “cause and effect” means
In many PLCs, your code does not react instantly to an input change. Instead:
Inputs are sampled at the start of the task cycle
Your ST code runs using that sampled image
Outputs are updated after logic executionSo a signal that changes between scans is only visible on the next scan. This is why issues like missed pulses and double-counting happen.
State is everywhere (often on purpose)
PLCs rely heavily on memory across scans:
FB instances keep internal variables
Timers and counters store elapsed time and counts
Latches and state machines intentionally “remember”Debugging often means figuring out who owns the state and when it changes.
The plant is part of the program
Your PLC program is connected to:
Real sensors that bounce, flicker, or fail
Actuators that have delays, inertia, and feedback
Safety systems that may remove power regardless of softwareA correct program can still look wrong if the physical system is not behaving as assumed.
!A mental model showing where timing and state issues usually originate.
A practical debugging workflow for ST projects
When something is wrong, you want a repeatable method that works under commissioning pressure.
Start with boundaries: I/O mapping and signal conditioning
Before blaming “logic”, verify what the program is actually seeing.
Confirm the raw input tag changes when the device changes
Confirm any inversion, scaling, or debouncing is correct
Confirm you are not reading the wrong channel or wrong global tagA common organization that helps debugging (from earlier articles):
I/O image and conditioning in one place
Device FBs as single owners of device behavior
Sequence/state logic deciding what to requestThen check ownership: who writes the final command
Many bugs are actually “multiple writers” problems:
Two POUs write the same output
A state machine sets a command, but a device FB overwrites it
A manual/HMI path and an auto path fight each otherRule:
Each physical output should have one obvious final writerFinally check time: edges, timers, and scan rate assumptions
If the behavior is intermittent, suspect timing:
A pulse is shorter than the task period
A counter counts multiple scans because it lacks edge detection
A TON resets because IN drops briefly due to noiseOnline debugging techniques (and what to be careful about)
Most PLC IDEs support online monitoring features. Used well, they can shorten troubleshooting dramatically.
Watch/monitor variables
Watching variables helps answer:
Is the input really TRUE in the PLC?
Did the permissive drop for one scan?
Is the state machine in the state you think it is?Good things to watch together:
Raw input, conditioned input, command, final output
State enum value (or state number) and transition conditions
Timer IN, Q, and ET
Counter CU, CV, and QCross-reference and “who writes this?”
If your IDE supports cross-reference, use it to locate:
All reads of a signal (who depends on it)
All writes to a signal (who controls it)This is the fastest way to find multi-writer bugs.
Breakpoints and single-cycle stepping (use carefully)
Some PLCs allow breakpoints or stepping in ST. If you stop a task, outputs may freeze in an unsafe state.
Safe guidance:
Prefer breakpoints in simulation or with outputs disabled
Avoid halting tasks on a running machine unless you have a safe planForcing I/O (powerful and risky)
A force overrides the real input or output value.
Uses:
Simulate a sensor to prove logic
Hold an output OFF during commissioningRisks:
You can defeat real-world conditions and create unsafe motion
You can “prove” logic that only works because the world is being overriddenPractical rules:
Use forces only with a clear procedure and permission
Label and document active forces
Remove forces before handing over a systemTraces, trends, and sequence-of-events
When a problem lasts only one scan, watching live is not enough. Many systems provide:
Traces or sampled logging of variables
Trends for analog values
Event logs with timestampsIf available, trace these together:
State
Key permissives
Command and output
Timer ET or counter CVThis often reveals the single scan where a permissive flickered.
Debugging timers, counters, and edge-related bugs
Timers: confirm you are driving IN the way you think
A TON measures continuous TRUE time on IN.
Common mistakes:
IN is not stable because it comes from a bouncing input
IN is computed from logic that changes during the start-up sequence
A state machine turns IN off when leaving a state, resetting the timer earlier than expectedA debugging pattern is to watch IN, Q, and ET at the same time.
Counters: count events, not levels
Most counters must be driven by a one-scan pulse (event). Otherwise a single sensor that stays TRUE for 5 scans counts as 5 parts.
Correct pattern:
Use R_TRIG (or equivalent) to generate a one-scan pulse
Drive CTU.CU with that pulseExample:
Debugging tip:
If CV increases “too fast”, check if CU is TRUE for multiple scans.State machines: make state visible and transitions explicit
If you use an enum state (recommended), debugging gets easier:
Watch State and the transition conditions
Ensure only one state sets the high-level commandA useful approach is to provide a status struct from your sequence FB:
State
LastTransitionReason (string or code)
StepTimerETEven if you don’t keep this in final production code, it is valuable during commissioning.
Simulation strategies: from simple to realistic
Simulation means running control logic against a model instead of a real machine. In PLC work, simulation can be as simple as toggling bits, or as advanced as a dynamic process model.
Software-only simulation (logic-level)
Goal:
Prove ST logic, sequencing, and interlocks without hardwareTypical methods:
Replace mapped I/O with simulated variables
Use an HMI screen or test panel to toggle inputs
Create a simple process model in ST (for example, tank level increases when a valve is open)A practical pattern is a simulation mode flag:
If simulation is enabled, inputs come from simulated variables
If not, inputs come from real I/OCaution:
Keep simulation code clearly separated so it cannot accidentally be left enabledHardware-in-the-loop (HIL)
Goal:
Run the real PLC against simulated sensors/actuators to test timing and integrationThis is common when the real machine is not available or is expensive to risk. Reference: Hardware-in-the-loop simulation
FAT and SAT mindset
Two common industrial test stages:
Factory Acceptance Test (FAT): test the system before shipment or before installation
Site Acceptance Test (SAT): test the system in the real installationSimulation often supports FAT, while SAT validates real-world wiring, noise, and process dynamics.
Testing ST code: what to test and how to structure it
Testing is simply making failures visible before production. Reference: Software testing
What you can unit-test easily: Functions
Functions are ideal for unit testing because they are stateless and return one value.
Examples to test:
Clamp, scale, conversions
Alarm limit calculations
Selection logic (min/max)Unit test concept reference: Unit testing
A basic unit-test idea:
Provide known inputs
Check the returned value against expected outputWhat you test with sequences of scans: Function Blocks
FBs have memory, so you test them as a timeline:
Apply inputs for N scans
Verify outputs and internal state after each phaseTypical FB tests:
Debounce FB: input flickers should not produce output
MotorControl FB: fault forces off; stop priority; minimum run time behavior
Counter wrapper FB: counts exactly once per sensor eventTest harness pattern (PLC-friendly)
A test harness is a small program/POU that:
Instantiates the FB under test
n- Drives its inputs with scripted steps
Records results into variables you can watch onlineSkeleton example:
Even without a full automated framework, this gives you a controlled way to reproduce a bug.
Design choices that make debugging and testing easier
Build observability into your design
Add variables that explain why something is off, not just that it is off.
Examples:
PermissiveOK as a named BOOL, not an inline expression
Faulted and FaultSourceCode
AutoRequestRun and ManualRequestRun before they are combinedThis aligns with the earlier course idea of separating:
Commands and permissives
Device behavior and sequencing policyPrefer enums for states and codes
Enums reduce “magic numbers” and make watch windows readable.
Keep timing and state close to the owner
From earlier articles:
Timers enforcing device behavior belong inside the device FB
Timers for sequence transitions belong in the sequence/state logicThis reduces hidden interactions and makes test harnesses simpler.
Make safe defaults explicit every scan
A reliable scan-based pattern:
Set safe defaults (outputs OFF)
Override in the correct state or when permissives are satisfiedThis prevents “sticky” outputs caused by forgotten assignments.
Common failure patterns and fast diagnostics
| Symptom | Likely cause | Fastest check |
|---|---|---|
| Counter jumps by more than 1 per part | No edge detection | Watch CU over several scans |
| Timer never finishes | IN not continuously TRUE | Watch IN and ET together |
| Output flickers | Two writers or permissive flicker | Cross-reference output writes, trace permissive |
| Sequence stuck in a state | Transition condition never TRUE | Watch state, transition BOOLs, and interlocks |
| Works in simulation, fails on machine | Noise, wiring, bounce, real delays | Compare raw vs conditioned signals, add debounce, trend inputs |
What you should be able to do after this article
Use online monitoring to trace a signal from raw input to final output
Identify and fix multi-writer issues by enforcing single ownership of outputs
Debug timer, counter, and edge problems by watching the right internal signals
Choose an appropriate simulation approach for your project risk level
Create a simple test harness to validate an ST Function or FB across multiple scans