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
- Install CODESYS from the CODESYS Store (free for development)
- Create a new Standard Project and select your target device
- Add a POU (Program Organization Unit) with language "Structured Text"
- Write your code in the implementation section
- 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.