Learn PLCs free
Programming Examples10 min read1,961 words

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.

PPI
PLC Programming IO Editorial Team
Sourced guidance with documented review and correction standards

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.

Automation engineer tracing sensor and actuator states on a generic PLC conveyor training bench with a blank monitor
Generated editorial photograph: a physical I/O review at a training bench. It does not imitate a vendor IDE or claim that the visible hardware executes the examples below.

Structured Text guide contents

  1. Execution model
  2. Syntax essentials
  3. Complete state-machine example
  4. Functions and function blocks
  5. Defensive programming
  6. Arrays and bounded loops
  7. Vendor differences
  8. 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 task cycle from input read through ordered statements and persistent state to physical output update
Deterministic explainer: a generic cyclic task model. Direct I/O access, task scheduling and output update timing vary by controller and configuration.

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:

  1. := is assignment; = is equality comparison in the IEC-style syntax.
  2. Boolean names should describe the state being true. ProtectionHealthy is easier to review than an ambiguous Overload.
  3. 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;
Structured Text conveyor state sequence from Idle through Starting and Running to a defined Faulted recovery state
Deterministic explainer: the principal sequence states. The code also includes a controlled stopping state, timeouts and unknown-state recovery.

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 ELSE branch 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.

Structured Text function-block interface separating commands and equipment feedback from command, state and diagnostic outputs
Deterministic explainer: a reviewable function-block boundary. Exact parameter syntax, instance declarations and method support differ by vendor.

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.

Defensive Structured Text checklist for explicit output defaults, bound checks, transition timeouts and unknown-state recovery
Deterministic explainer: four practical review controls for ordinary PLC logic. Safety-related software follows the certified platform process and applicable functional-safety lifecycle.

Use these patterns:

  1. Default then override. Clear ordinary commands before the state-specific branch.
  2. Range-check indices. Never trust recipe or HMI indices before array access.
  3. Bound loops. A PLC task cannot tolerate open-ended work hidden inside a loop.
  4. Time out handshakes. Define what happens when the next device never responds.
  5. Separate validation from conversion. A successful type conversion does not mean the source value is plausible.
  6. Detect stale data. Communications status, sequence numbers or timestamps may be needed at integration boundaries.
  7. Handle the unknown state. A CASE ... ELSE branch 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

Structured Text portability matrix comparing compiler conversions, runtime tasks and function-block library behavior
Deterministic explainer: portability must be proven at compiler, runtime and library boundaries; common IEC syntax alone is not acceptance evidence.

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

Continue with the PLC programming languages guide, state-machine glossary, PLC scan-cycle guide and practical PLC examples.

#StructuredText#STProgramming#PLCProgramming#IEC61131-3#StateMachine
Share this article:

Related Articles