Structured Text PLC Programming: Syntax & Example
Learn IEC 61131-3 Structured Text with a complete state-machine example, scan-cycle model, function-block boundaries, portability checks and tests.
Structured Text (ST) is the textual PLC language in IEC 61131-3. It is well suited to state machines, calculations, arrays and reusable functions, but it still executes inside a configured PLC task. Correct ST therefore depends on scan order, persistent function-block state, bounded loops, explicit outputs and tests on the target runtime—not only on valid syntax.
IEC published IEC 61131-3:2025 Edition 4 on 22 May 2025. It defines the syntax and semantics of ST, LD and FBD, plus SFC elements and controller configuration concepts. The standard is a language baseline; it does not make vendor libraries, data layouts, task behavior or project files interchangeable.
Structured Text guide contents
- Execution model
- Syntax essentials
- Complete state-machine example
- Functions and function blocks
- Defensive programming
- Arrays and bounded loops
- Vendor differences
- Testing checklist
For a language-level overview first, use the Structured Text glossary. For the broader PLC model—field I/O, scan scheduling and matched LD/FBD behavior—start with the PLC programming guide.
Structured Text execution model
An ST routine does not run continuously just because it looks like conventional source code. A controller executes it when the configured task invokes its program or routine. The observable result depends on:
- when inputs are sampled or read;
- where the ST routine sits in task/program order;
- which variables persist between calls;
- when output data reaches the physical module;
- how long the task may run before a watchdog or scheduling fault; and
- what happens during download, mode change, restart and communications loss.
Assignments run in statement order within the routine. That means a later statement can overwrite an earlier assignment in the same call. Make this intentional: assign safe defaults once, then apply the state-specific commands.
Structured Text syntax essentials
The common core includes assignments, expressions, conditional statements, CASE, and bounded loops. Variables are strongly typed, but the exact available types and implicit conversion rules still need the target compiler's documentation.
VAR
StartRequest : BOOL;
StopRequest : BOOL;
ProtectionHealthy : BOOL;
SpeedSetpointPct : REAL;
SpeedCommandPct : REAL;
END_VAR
IF StopRequest OR NOT ProtectionHealthy THEN
SpeedCommandPct := 0.0;
ELSIF StartRequest THEN
SpeedCommandPct := LIMIT(0.0, SpeedSetpointPct, 100.0);
END_IF;
Three details matter:
:=is assignment;=is equality comparison in the IEC-style syntax.- Boolean names should describe the state being true.
ProtectionHealthyis easier to review than an ambiguousOverload. - Library signatures can differ. Confirm the exact
LIMIT, timer, conversion and string functions supplied by the target platform.
IF or CASE?
Use IF / ELSIF / ELSE for a small priority decision. Use CASE when one named machine state should own the outputs and legal transitions. Deeply nested IF statements often hide priority and recovery paths.
Timer instances
Timers are function blocks with internal state. Declare an instance, call it every relevant scan, and read its outputs. Do not assume a timer behaves like a stateless function.
VAR
StartTimeout : TON;
END_VAR
StartTimeout(IN := State = STATE_STARTING, PT := T#3s);
IF StartTimeout.Q AND NOT MotorFeedback THEN
FaultCode := FAULT_START_TIMEOUT;
State := STATE_FAULTED;
END_IF;
The generic example shows intent. Timer type names, input/output names, reset behavior and retentivity must be checked on the selected runtime.
Complete Structured Text state-machine example
The example below controls an ordinary conveyor sequence. It is not safety logic. A safety-rated system must independently remove or prevent hazardous motion according to the validated design.
TYPE ConveyorState :
(
STATE_IDLE,
STATE_STARTING,
STATE_RUNNING,
STATE_STOPPING,
STATE_FAULTED
);
END_TYPE
VAR
State : ConveyorState := STATE_IDLE;
StartEdge : BOOL;
StopRequest : BOOL;
ResetEdge : BOOL;
PermissivesHealthy : BOOL;
MotorFeedback : BOOL;
MotorCommand : BOOL;
FaultActive : BOOL;
FaultCode : UINT;
StartTimeout : TON;
StopTimeout : TON;
END_VAR
(* Safe ordinary-control defaults for this scan. *)
MotorCommand := FALSE;
FaultActive := FALSE;
StartTimeout(IN := State = STATE_STARTING, PT := T#3s);
StopTimeout(IN := State = STATE_STOPPING, PT := T#5s);
CASE State OF
STATE_IDLE:
IF StartEdge AND PermissivesHealthy THEN
State := STATE_STARTING;
END_IF;
STATE_STARTING:
MotorCommand := PermissivesHealthy;
IF StopRequest OR NOT PermissivesHealthy THEN
State := STATE_STOPPING;
ELSIF MotorFeedback THEN
State := STATE_RUNNING;
ELSIF StartTimeout.Q THEN
FaultCode := 1; (* Start feedback timeout *)
State := STATE_FAULTED;
END_IF;
STATE_RUNNING:
MotorCommand := PermissivesHealthy;
IF StopRequest OR NOT PermissivesHealthy THEN
State := STATE_STOPPING;
ELSIF NOT MotorFeedback THEN
FaultCode := 2; (* Feedback lost while commanded *)
State := STATE_FAULTED;
END_IF;
STATE_STOPPING:
IF NOT MotorFeedback THEN
State := STATE_IDLE;
ELSIF StopTimeout.Q THEN
FaultCode := 3; (* Stop feedback timeout *)
State := STATE_FAULTED;
END_IF;
STATE_FAULTED:
FaultActive := TRUE;
IF ResetEdge AND NOT MotorFeedback AND PermissivesHealthy THEN
FaultCode := 0;
State := STATE_IDLE;
END_IF;
ELSE
FaultCode := 999; (* Unknown-state recovery *)
State := STATE_FAULTED;
END_CASE;
Download the complete ST example and its acceptance-test CSV. Treat the file as platform-neutral teaching code: import or translate it only after checking the target compiler and I/O mapping.
Why the example is structured this way
- Outputs receive explicit defaults before the state logic.
- Every transition has a readable condition.
- A start command is different from running feedback.
- Starting and stopping have separate timeouts.
- Reset is accepted only from a known stopped condition.
- The
ELSEbranch catches an invalid state value. - The example avoids hidden retained run bits.
Practice the sequence in the browser before translating it to a target controller. Simulation helps prove state logic; it does not validate wiring, safety, timing or controller-specific behavior.
Functions and function blocks
| Program organization unit | State between calls? | Typical use |
|---|---|---|
| Function | Normally no instance state | Conversion or calculation with a returned value |
| Function block | Yes, per instance | Timer, valve object, motor object or sequence module |
| Program | Runtime/project dependent organization | Scheduled application logic and coordination |
A function block is useful when a machine object needs state, but its interface should stay small and diagnosable. Prefer explicit request, permissive, feedback and reset inputs over dozens of global tags.
Function-block review questions
- Is each instance called exactly where the task design expects?
- Are outputs assigned for disabled and faulted states?
- Can an HMI command bypass a permissive?
- Are retained and non-retained fields documented?
- Does the diagnostic distinguish command, feedback, timeout and configuration faults?
- Can the block be tested without energizing real outputs?
Defensive Structured Text patterns
PLC code must remain predictable when values are bad, feedback is late or a sequence enters an unexpected state.
Use these patterns:
- Default then override. Clear ordinary commands before the state-specific branch.
- Range-check indices. Never trust recipe or HMI indices before array access.
- Bound loops. A PLC task cannot tolerate open-ended work hidden inside a loop.
- Time out handshakes. Define what happens when the next device never responds.
- Separate validation from conversion. A successful type conversion does not mean the source value is plausible.
- Detect stale data. Communications status, sequence numbers or timestamps may be needed at integration boundaries.
- Handle the unknown state. A
CASE ... ELSEbranch provides a defined recovery path.
Do not describe ordinary defensive ST as safety-rated simply because it drives an output to false. Functional-safety logic requires the selected certified platform, safety requirements, verification and validation process.
Arrays and bounded loops
Arrays make repeated calculations compact, but the task still performs every iteration. Measure the worst case on the target CPU and task.
VAR
Samples : ARRAY[0..9] OF REAL;
Sum : REAL;
Average : REAL;
Index : INT;
END_VAR
Sum := 0.0;
FOR Index := 0 TO 9 DO
Sum := Sum + Samples[Index];
END_FOR;
Average := Sum / 10.0;
Check the declared bounds, counter type and loop limit. CODESYS explicitly documents a counter-overflow pitfall when the loop end equals the maximum value of a small integer type. Do not assume every compiler handles overflow, CONTINUE, or malformed bounds the same way.
For larger data sets, distribute the work across scans, use a bounded index state, or move non-control analytics to an appropriate external service. Whether that is necessary depends on the task period, controller load and required response—not on a generic line-count rule.
Structured Text vendor differences
Siemens SCL
Siemens calls its Structured Text implementation Structured Control Language (SCL). TIA Portal supports SCL blocks on applicable SIMATIC controllers. Confirm the target CPU, firmware and STEP 7 version because supported instructions and block behavior vary.
Rockwell Logix Structured Text
Studio 5000 Logix Designer provides Structured Text routines and documents its syntax, data types and instruction behavior in versioned online help. Rockwell's implementation is not a drop-in CODESYS project: tags, routines, Add-On Instructions, task structure and controller instruction support belong to the Logix environment.
CODESYS ST and ExST
CODESYS documents both ST and its Extended Structured Text (ExST) additions. An extension supported in one CODESYS target or compiler version should not be assumed portable to another toolchain. Build, download and test on the selected device description and runtime.
| Boundary | What may differ |
|---|---|
| Data types | Supported widths, strings, aliases, layouts and conversions |
| Task model | Priority, period, watchdog, event tasks and I/O update |
| Libraries | Timer behavior, string functions, motion, communications and diagnostics |
| Retentivity | Declaration, memory region, download and restart behavior |
| Project structure | Program, routine, block and namespace organization |
| Online changes | What can be edited, tested, accepted or rolled back |
Structured Text vs Ladder Logic
Choose the language from the task and maintainers rather than calling one language universally better.
| Requirement | Usually favors ST | Usually favors Ladder |
|---|---|---|
| Array or recipe processing | Yes | No |
| State-machine transitions | Often | Sometimes |
| Dense calculations | Yes | No |
| Simple permissive chain | Sometimes | Often |
| Online electrical-style tracing | No | Often |
| Reusable text-based algorithm | Yes | Sometimes |
Mixed-language projects are normal where the platform supports them. The important part is a shared tag model, reviewable interfaces and matched acceptance tests.
Structured Text test and commissioning checklist
Run the example with the physical output isolated or in an approved test environment.
| Test | Action | Expected evidence |
|---|---|---|
| Normal start | Start edge with all permissives | Starting then Running after feedback |
| Start timeout | Hold feedback false | Fault code 1 and command removed |
| Stop request | Stop while running | Command removed and Stopping entered |
| Stop timeout | Hold feedback true | Fault code 3 and Faulted state |
| Lost permissive | Remove a permissive while running | Command removed; recovery path recorded |
| Invalid state | Force only in an approved test | ELSE branch moves to Faulted |
| Held start | Keep request true through recovery | No unintended restart if edge logic is required |
| Controller restart | Controlled mode/power cycle | Defined state and outputs, no unintended movement |
Record:
- controller, firmware and engineering-tool versions;
- task period, priority and watchdog;
- project checksum or version identifier;
- initial and retained values;
- test inputs, observed transitions and timestamps;
- any differences between simulator and target runtime; and
- reviewer approval plus unresolved deviations.
Primary references
- IEC 61131-3:2025 Edition 4 publication record
- CODESYS ST editor
- CODESYS FOR statement and overflow caution
- Rockwell Automation Structured Text syntax
- Rockwell Automation Structured Text editor
- Siemens TIA Portal standardization guide
Continue with the PLC programming languages guide, state-machine glossary, PLC scan-cycle guide and practical PLC examples.


