All posts
structured-text5 min read

Structured Text Programming: The Complete Guide for CODESYS

A practical guide to IEC 61131-3 Structured Text programming in CODESYS. Covers syntax, data types, function blocks, best practices, and common patterns every automation engineer should know.

PLC Assist Team

Structured Text Programming: The Complete Guide for CODESYS

Structured Text (ST) is the most powerful programming language in the IEC 61131-3 standard. While Ladder Diagram and Function Block Diagram are great for simple logic, ST is where you go when you need complex algorithms, data processing, or maintainable code.

This guide covers everything you need to know to write effective Structured Text in CODESYS.

Why Structured Text?

Compared to graphical PLC languages, ST offers:

  • Readability: Complex logic is easier to follow in text than in nested ladder rungs
  • Reusability: Function blocks and functions can be parameterized and reused across projects
  • Version control: Text files can be tracked with Git, diffed, and merged
  • Expressiveness: Loops, arrays, structs, enums, and string handling are straightforward
  • AI compatibility: AI tools can generate and analyze text-based code far more effectively than graphical languages

Basic Syntax

Variables and Data Types

Every program, function block, or function starts with variable declarations:

PROGRAM Main
VAR
    bMotorRunning : BOOL := FALSE;
    nCounter      : INT := 0;
    rTemperature  : REAL;
    sMessage      : STRING(80) := 'System OK';
    tDelay        : TIME := T#500MS;
    dtTimestamp   : DATE_AND_TIME;
END_VAR

CODESYS supports all IEC 61131-3 types plus extensions:

| Type | Description | Example | |------|-------------|---------| | BOOL | Boolean | TRUE, FALSE | | INT | 16-bit signed integer | -32768 to 32767 | | DINT | 32-bit signed integer | -2147483648 to 2147483647 | | REAL | 32-bit float | 3.14 | | LREAL | 64-bit float | 3.14159265358979 | | STRING | Character string | 'Hello' | | TIME | Duration | T#1S, T#500MS | | WORD | 16-bit bitstring | 16#FF00 |

Variable Sections

FUNCTION_BLOCK FB_Motor
VAR_INPUT     // Written by caller
    bEnable : BOOL;
END_VAR

VAR_OUTPUT    // Read by caller
    bActive : BOOL;
END_VAR

VAR_IN_OUT    // Read/write by caller
    nSharedCounter : INT;
END_VAR

VAR           // Internal (private)
    tonDelay : TON;
END_VAR

VAR PERSISTENT  // Survives power cycle
    nTotalStarts : DINT;
END_VAR

Control Flow

IF / ELSIF / ELSE

IF rTemperature > 80.0 THEN
    bAlarmHigh := TRUE;
    bHeaterOn := FALSE;
ELSIF rTemperature < 20.0 THEN
    bAlarmLow := TRUE;
    bHeaterOn := TRUE;
ELSE
    bAlarmHigh := FALSE;
    bAlarmLow := FALSE;
END_IF

CASE Statement

Perfect for state machines:

CASE nState OF
    0: // Idle
        IF bStart THEN
            nState := 10;
        END_IF

    10: // Starting
        bMotorCmd := TRUE;
        tonStartDelay(IN := TRUE, PT := T#2S);
        IF tonStartDelay.Q THEN
            nState := 20;
        END_IF

    20: // Running
        IF bStop OR bFault THEN
            nState := 30;
        END_IF

    30: // Stopping
        bMotorCmd := FALSE;
        tonStopDelay(IN := TRUE, PT := T#1S);
        IF tonStopDelay.Q THEN
            nState := 0;
        END_IF
END_CASE

FOR Loop

VAR
    i : INT;
    arValues : ARRAY[0..9] OF REAL;
    rSum : REAL := 0.0;
END_VAR

FOR i := 0 TO 9 DO
    rSum := rSum + arValues[i];
END_FOR

WHILE and REPEAT

// WHILE -- check before each iteration
WHILE nIndex < nArraySize AND NOT bFound DO
    IF arData[nIndex] = nTarget THEN
        bFound := TRUE;
    END_IF
    nIndex := nIndex + 1;
END_WHILE

// REPEAT -- execute at least once
REPEAT
    nRetries := nRetries + 1;
    bSuccess := SendCommand();
UNTIL bSuccess OR nRetries >= 3
END_REPEAT

Function Blocks: The Heart of ST

Function blocks are reusable, stateful components. Think of them as classes in object-oriented programming.

Basic Function Block

FUNCTION_BLOCK FB_Debounce
VAR_INPUT
    bSignal : BOOL;
    tDebounceTime : TIME := T#50MS;
END_VAR

VAR_OUTPUT
    bDebounced : BOOL;
END_VAR

VAR
    tonOn  : TON;
    tonOff : TON;
END_VAR

// Implementation
tonOn(IN := bSignal, PT := tDebounceTime);
tonOff(IN := NOT bSignal, PT := tDebounceTime);

IF tonOn.Q THEN
    bDebounced := TRUE;
ELSIF tonOff.Q THEN
    bDebounced := FALSE;
END_IF

Using a Function Block

VAR
    fbDebounce1 : FB_Debounce;
    bRawInput   : BOOL;
    bCleanInput : BOOL;
END_VAR

fbDebounce1(bSignal := bRawInput, tDebounceTime := T#100MS);
bCleanInput := fbDebounce1.bDebounced;

Common Patterns

State Machine with Enum

Using enumerations makes state machines much more readable:

TYPE E_MachineState :
(
    IDLE,
    INITIALIZING,
    RUNNING,
    PAUSED,
    ERROR,
    STOPPING
);
END_TYPE

VAR
    eState : E_MachineState := E_MachineState.IDLE;
END_VAR

CASE eState OF
    E_MachineState.IDLE:
        // ...
    E_MachineState.RUNNING:
        // ...
END_CASE

Rising and Falling Edge Detection

VAR
    rtrigStart : R_TRIG;
    ftrigStop  : F_TRIG;
END_VAR

rtrigStart(CLK := bStartButton);
ftrigStop(CLK := bStopButton);

IF rtrigStart.Q THEN
    // Start button was just pressed
END_IF

IF ftrigStop.Q THEN
    // Stop button was just released
END_IF

Timer Patterns

VAR
    tonDelay  : TON;   // On-delay timer
    tofDelay  : TOF;   // Off-delay timer
    tpPulse   : TP;    // Pulse timer
END_VAR

// TON: Output goes TRUE after input has been TRUE for PT
tonDelay(IN := bSensor, PT := T#5S);
IF tonDelay.Q THEN
    // Sensor has been active for 5 seconds
END_IF

// TOF: Output stays TRUE for PT after input goes FALSE
tofDelay(IN := bMotorRunning, PT := T#10S);
bCoolingFan := tofDelay.Q;  // Fan runs 10s after motor stops

// TP: Output is TRUE for exactly PT after rising edge
tpPulse(IN := bTrigger, PT := T#200MS);
bPulseOutput := tpPulse.Q;  // 200ms pulse

Best Practices

1. Use Meaningful Names

// Bad
VAR
    x : BOOL;
    t1 : TON;
    v : REAL;
END_VAR

// Good
VAR
    bConveyorRunning : BOOL;
    tonStartupDelay  : TON;
    rMotorSpeed      : REAL;  // RPM
END_VAR

2. Use Constants

VAR CONSTANT
    MAX_MOTOR_SPEED    : REAL := 1500.0;  // RPM
    OVERCURRENT_LIMIT  : REAL := 25.0;    // Amps
    STARTUP_DELAY      : TIME := T#3S;
END_VAR

3. Comment the Why, Not the What

// Bad: "Set bValve to TRUE"
bValve := TRUE;

// Good: "Open bypass valve to prevent pressure buildup during startup"
bValve := TRUE;

4. One State Machine Per Function Block

Don't put multiple state machines in the same FB. Each FB should have one clear responsibility.

5. Initialize Your Variables

Always set default values. Uninitialized variables default to 0/FALSE, but being explicit prevents surprises:

VAR
    nState       : INT := 0;
    rSetpoint    : REAL := 25.0;
    bAutoMode    : BOOL := FALSE;
END_VAR

Getting Started with CODESYS ST

  1. Install CODESYS from the CODESYS Store (free for development)
  2. Create a new Standard Project and select your target device
  3. Add a POU (Program Organization Unit) with language "Structured Text"
  4. Write your code in the implementation section
  5. Build and deploy to your PLC or simulation

If you want AI assistance while writing Structured Text, PLC Assist connects directly to your CODESYS project and provides intelligent code suggestions, generation, and review -- all without your code leaving your machine.


PLC Assist is an AI-powered engineering assistant for industrial automation. Write Structured Text faster with AI assistance. Get started free.