IEC 61131-3 Structured Text: A Practical Guide for Modern PLC Programming
Ladder logic was invented for electricians in 1969. It maps relay circuits to a screen. For simple start/stop logic, it's fine. For anything with math, loops, state machines, or data processing, it becomes a mess of tangled rungs that nobody — including the person who wrote it — can understand six months later.
Structured Text (ST) is the IEC 61131-3 language that looks and works like a modern programming language. If you've written Python, JavaScript, or C, ST will feel familiar. If you're new to PLC programming but comfortable with code, start with ST — not ladder logic. This guide covers everything you need to write your first ST program, with real examples you can run today using Voltrus PLC on macOS.
What Is IEC 61131-3 Structured Text?
IEC 61131-3 is the international standard for PLC programming languages. It defines five languages. Structured Text is the high-level, text-based one. Think of it as Pascal or a simplified C designed specifically for industrial control:
- Text-based — you type code, not draw rungs. Version control works. Copy-paste works. Search-and-replace works.
- Block structured — code is organized into PROGRAMs, FUNCTION_BLOCKs, and FUNCTIONs. Each is a self-contained unit with local variables and interfaces.
- Cyclic execution — unlike Python scripts that run top-to-bottom once, ST programs run in a continuous scan loop: read inputs → execute logic → write outputs → repeat. This is fundamental to how PLCs work.
- Strongly typed — variables have explicit types (
BOOL,INT,REAL, etc.). Type mismatches are caught at compile time, not when the motor spins the wrong direction.
The other IEC 61131-3 languages — Ladder Diagram (LD), Function Block Diagram (FBD), Sequential Function Chart (SFC), Instruction List (IL) — each have their place. But ST is the one that scales to complex programs and the one that software-native engineers reach for first.
ST Syntax in 10 Minutes
Variables and Types
Every variable must be declared before use. Declarations go in a VAR...END_VAR block at the top of a PROGRAM or FUNCTION_BLOCK:
PROGRAM MotorControl
VAR
start_button : BOOL; (* true/false *)
stop_button : BOOL;
motor_run : BOOL;
speed_sp : REAL := 0.0; (* floating point, initialized *)
counter : INT := 0; (* signed 16-bit integer *)
temperature : REAL;
alarm_active : BOOL := FALSE;
END_VAR
Common data types:
BOOL— true/falseINT,DINT,SINT— signed integers (16-bit, 32-bit, 8-bit)UINT,UDINT— unsigned integersREAL,LREAL— floating point (32-bit, 64-bit)TIME— time duration (e.g.,T#500ms,T#2s,T#1m30s)STRING— text stringsARRAY [0..9] OF INT— fixed-size arrays
Conditional Logic: IF / ELSIF / ELSE
IF temperature > 80.0 THEN
cooling_fan := TRUE;
alarm_active := TRUE;
ELSIF temperature > 60.0 THEN
cooling_fan := TRUE;
alarm_active := FALSE;
ELSE
cooling_fan := FALSE;
alarm_active := FALSE;
END_IF
Nested IFs work as you'd expect. Parentheses around conditions are optional — ST uses AND, OR, NOT, and XOR instead of &&, ||, !.
Multi-Way Branch: CASE
CASE pump_state OF
0: (* idle *)
pump_run := FALSE;
valve_open := FALSE;
1: (* starting *)
valve_open := TRUE;
2: (* running *)
pump_run := TRUE;
3: (* stopping *)
pump_run := FALSE;
ELSE
pump_state := 0; (* fallback *)
END_CASE
Loops: FOR, WHILE, REPEAT
(* FOR loop — known iteration count *)
FOR i := 0 TO 9 DO
readings[i] := 0;
END_FOR
(* WHILE loop — condition at start *)
WHILE count < max_count DO
count := count + 1;
END_WHILE
(* REPEAT loop — condition at end, always runs at least once *)
REPEAT
result := result * 2;
steps := steps - 1;
UNTIL steps = 0
END_REPEAT
Timers: The Heartbeat of PLC Programs
Timers are the single most-used function blocks in PLC programming. Every motor start has a delay. Every alarm has a debounce. Every sequence has a timeout. IEC 61131-3 defines three standard timer types:
TON — Timer On-Delay
Turns output ON after a delay when input goes TRUE. The most common timer — used for startup delays, sequential operations, and debouncing.
(* Turn on an output 2 seconds after the input goes true *)
my_timer(IN := start_signal, PT := T#2s);
IF my_timer.Q THEN
output := TRUE;
END_IF
If start_signal goes FALSE before 2 seconds, the timer resets. ET (elapsed time) shows current progress. Q is TRUE when elapsed time ≥ preset time.
TOF — Timer Off-Delay
Turns output OFF after a delay when input goes FALSE. Used for cooling fans that run after the motor stops, or lights that stay on after the door closes.
(* Keep fan running 30 seconds after motor stops *) my_tof(IN := motor_running, PT := T#30s); fan := my_tof.Q;
TP — Timer Pulse
Generates a pulse of fixed duration when triggered. Used for one-shot operations — open a valve for exactly 5 seconds, regardless of how long the trigger signal stays high.
(* Open valve for exactly 5 seconds on trigger *) my_tp(IN := trigger, PT := T#5s); valve_open := my_tp.Q;
Counters: CTU, CTD, CTUD
Counters track events — parts produced, cycles completed, errors detected. IEC 61131-3 defines three types:
(* Count up — increment on rising edge of CU *)
part_counter(CTU, CU := sensor_on, PV := 1000);
IF part_counter.Q THEN
batch_full := TRUE; (* Q is TRUE when CV >= PV *)
END_IF
(* CTD — count down from preset *)
(* CTUD — combined up/down counter *)
Edge Detection: R_TRIG and F_TRIG
PLCs scan continuously. Without edge detection, a button press that's TRUE for 10 scans would trigger your logic 10 times. Edge detectors fire only on the transition:
(* R_TRIG — detect rising edge (FALSE → TRUE) *)
button_edge(R_TRIG, CLK := start_button);
IF button_edge.Q THEN
cycle_count := cycle_count + 1; (* increments exactly once per press *)
END_IF
(* F_TRIG — detect falling edge (TRUE → FALSE) *)
stop_edge(F_TRIG, CLK := stop_button);
IF stop_edge.Q THEN
motor_run := FALSE;
END_IF
Real-World Example: Motor Start/Stop with Overload Protection
Here's a complete motor control program in Structured Text. It handles start/stop buttons, a motor contactor output, an overload input, an auto-reset delay, and a trip counter:
PROGRAM MotorStarter
VAR
(* I/O *)
start_btn : BOOL; (* normally-open pushbutton *)
stop_btn : BOOL; (* normally-closed pushbutton *)
overload : BOOL; (* thermal overload relay *)
contactor : BOOL; (* motor contactor output *)
(* Internal *)
motor_run : BOOL;
reset_timer : TON;
start_edge : R_TRIG;
stop_edge : R_TRIG;
trip_count : INT := 0;
END_VAR
(* Edge detection *)
start_edge(CLK := start_btn);
stop_edge(CLK := NOT stop_btn); (* NC contact — TRUE when pressed *)
(* Start logic — latch on, unlatch on stop or overload *)
IF start_edge.Q AND NOT overload THEN
motor_run := TRUE;
END_IF;
IF stop_edge.Q OR overload THEN
motor_run := FALSE;
END_IF;
(* Auto-reset after 5 second cooldown if no overload *)
reset_timer(IN := NOT motor_run AND NOT overload, PT := T#5s);
IF reset_timer.Q THEN
(* ready to restart *)
END_IF;
(* Count overload trips *)
IF overload THEN
trip_count := trip_count + 1;
END_IF;
(* Output *)
contactor := motor_run;
END_PROGRAM
This program runs every scan (typically every 10-100ms). Each scan: check inputs → run logic → update outputs. The edge detectors ensure the start button increments the trip counter once per press, not once per scan.
ST vs Ladder Logic: When to Use Each
This is the wrong question. The right question is: what's the best language for this specific task? Modern PLCs support multiple languages in the same project. Use ST for the complex logic, ladder for the simple I/O mapping, and SFC for the sequence orchestration.
But if you're starting fresh and you have any programming experience: start with ST. It's more expressive, more maintainable, and version-control friendly. You can always add ladder diagrams later for the specific rungs where they add clarity.
Try Structured Text on Your Mac
Voltrus PLC is a native macOS PLC IDE with full IEC 61131-3 Structured Text support. Write ST in a modern CodeMirror 6 editor with syntax highlighting, compile it with the built-in compiler, and run it on the softPLC runtime — all on your Mac. No Windows, no hardware required.
The editor supports autocomplete for keywords, types, and standard functions. The diagnostics panel shows compile errors with line numbers. The watch table lets you monitor variables in real-time as the scan runs. And the Modbus TCP driver lets your ST program talk to real devices.
Frequently Asked Questions
What is IEC 61131-3 Structured Text?
Structured Text (ST) is one of the five programming languages defined by IEC 61131-3, the international standard for PLC programming. It's a high-level, text-based language with syntax similar to Pascal. It supports conditional logic (IF/THEN/ELSE, CASE), loops (FOR, WHILE, REPEAT), arithmetic, function calls, and function blocks. ST is the preferred language for complex control logic, math, and data processing in PLCs.
What are the five IEC 61131-3 languages?
The five IEC 61131-3 languages are: Structured Text (ST) — high-level text language; Ladder Diagram (LD) — relay-style graphical; Function Block Diagram (FBD) — node-based data flow; Sequential Function Chart (SFC) — state machine diagrams; Instruction List (IL) — deprecated low-level assembly-like language.
Is Structured Text better than ladder logic?
ST is better for complex logic, math, loops, and data processing. Ladder logic is better for simple boolean sequences and maintenance electricians tracing rungs. Most modern PLCs support both, and the best approach is mixing languages within a project — ST for the complex calculations, ladder for the simple I/O rungs.
Can I learn Structured Text without a PLC?
Yes. Voltrus PLC includes a built-in softPLC runtime that runs on your Mac. You can write, compile, and run ST programs entirely in software — no PLC hardware required. The watch table lets you monitor variables, and when you're ready, the Modbus TCP driver connects to real devices.
Write Structured Text on Your Mac
Voltrus PLC is a native macOS IEC 61131-3 Structured Text editor with compiler, softPLC runtime, and Modbus I/O. Learn ST, test programs, and connect to real devices — all from your Mac. v1.0 available now — $29.99 one-time.
Download for Mac