Advanced
PLC State Machine Design Patterns in Structured Text
Design robust, maintainable PLC programs using state machine patterns in Structured Text — the professional approach to sequential control.
Why Use State Machines in PLC Programming?
Every complex PLC program eventually becomes a state machine — whether you design it that way or not. Without a deliberate state machine structure, programs devolve into tangled webs of interlocking booleans that are nearly impossible to debug.
Benefits of State Machine Design
Basic State Machine with CASE Statement
The simplest and most common PLC state machine uses an INT variable with a CASE statement:
PROGRAM SimpleStateMachine
VAR
State : INT := 0;
StartButton : BOOL := FALSE;
Sensor1 : BOOL := FALSE;
Sensor2 : BOOL := FALSE;
Cylinder1 : BOOL := FALSE;
Cylinder2 : BOOL := FALSE;
DoneFlag : BOOL := FALSE;
END_VARCASE State OF
0: // IDLE
Cylinder1 := FALSE;
Cylinder2 := FALSE;
DoneFlag := FALSE;
IF StartButton THEN
State := 10;
END_IF;
10: // EXTEND CYLINDER 1
Cylinder1 := TRUE;
IF Sensor1 THEN
State := 20;
END_IF;
20: // EXTEND CYLINDER 2
Cylinder2 := TRUE;
IF Sensor2 THEN
State := 30;
END_IF;
30: // RETRACT ALL
Cylinder1 := FALSE;
Cylinder2 := FALSE;
IF NOT Sensor1 AND NOT Sensor2 THEN
State := 40;
END_IF;
40: // DONE
DoneFlag := TRUE;
IF NOT StartButton THEN
State := 0;
END_IF;
END_CASE;
Why Number States 0, 10, 20...?
Using gaps (10, 20, 30) instead of sequential numbers (1, 2, 3) lets you insert new states later without renumbering everything. This is standard industrial practice.
Using ENUM Types for Readable States
For better readability and type safety, use an enumeration:
TYPE MachineState :
(
IDLE := 0,
LOADING := 10,
PROCESSING := 20,
UNLOADING := 30,
ERROR := 99
);
END_TYPEPROGRAM EnumStateMachine
VAR
State : MachineState := IDLE;
PrevState : MachineState := IDLE;
StartCmd : BOOL := FALSE;
LoadDone : BOOL := FALSE;
ProcessDone : BOOL := FALSE;
UnloadDone : BOOL := FALSE;
ErrorDetected : BOOL := FALSE;
END_VAR
// Track state changes
PrevState := State;
// Global error check — can interrupt any state
IF ErrorDetected THEN
State := ERROR;
END_IF;
CASE State OF
IDLE:
IF StartCmd THEN
State := LOADING;
END_IF;
LOADING:
// Activate loading mechanism
IF LoadDone THEN
State := PROCESSING;
END_IF;
PROCESSING:
// Run process
IF ProcessDone THEN
State := UNLOADING;
END_IF;
UNLOADING:
// Unload finished product
IF UnloadDone THEN
State := IDLE;
END_IF;
ERROR:
// Safe state — all outputs off
IF NOT ErrorDetected AND StartCmd THEN
State := IDLE;
END_IF;
END_CASE;
Pattern: State Machine with Timeouts
Real machines need timeouts to detect jammed cylinders, missing parts, or stalled processes:
PROGRAM TimedStateMachine
VAR
State : INT := 0;
StateTimer : TON;
TimeoutAlarm : BOOL := FALSE;
StartButton : BOOL := FALSE;
PartPresent : BOOL := FALSE;
ClampClosed : BOOL := FALSE;
Clamp : BOOL := FALSE;
END_VAR// Reset timer on state change (use one-shot detection)
StateTimer(IN := TRUE, PT := T#10s);
CASE State OF
0: // IDLE
StateTimer(IN := FALSE); // Reset timer
Clamp := FALSE;
TimeoutAlarm := FALSE;
IF StartButton AND PartPresent THEN
State := 10;
StateTimer(IN := FALSE);
END_IF;
10: // CLAMPING
Clamp := TRUE;
StateTimer(IN := TRUE, PT := T#5s);
IF ClampClosed THEN
State := 20;
StateTimer(IN := FALSE);
ELSIF StateTimer.Q THEN
TimeoutAlarm := TRUE;
State := 99;
END_IF;
20: // PROCESSING
StateTimer(IN := TRUE, PT := T#30s);
// ... process logic
IF StateTimer.Q THEN
TimeoutAlarm := TRUE;
State := 99;
END_IF;
99: // FAULT
Clamp := FALSE;
// Wait for operator reset
IF StartButton AND NOT TimeoutAlarm THEN
State := 0;
END_IF;
END_CASE;
Pattern: Parallel State Machines
Complex machines often have multiple subsystems running simultaneously. Use separate state variables:
PROGRAM ParallelStates
VAR
ConveyorState : INT := 0;
RobotState : INT := 0;
InspectionState : INT := 0;
// Handshake signals between subsystems
PartAtRobot : BOOL := FALSE;
RobotPickDone : BOOL := FALSE;
PartAtInspection : BOOL := FALSE;
InspectResult : BOOL := FALSE;
END_VAR// Each CASE block runs independently every scan
CASE ConveyorState OF
0: // ... conveyor logic
10: // ...
END_CASE;
CASE RobotState OF
0: // WAIT FOR PART
IF PartAtRobot THEN
RobotState := 10;
END_IF;
10: // PICKING
// ... robot pick logic
RobotPickDone := TRUE;
RobotState := 20;
20: // PLACING
// ...
END_CASE;
CASE InspectionState OF
0: // ...
10: // ...
END_CASE;
Pattern: Step/Transition (SFC-like)
You can implement a Grafcet/SFC-style pattern in pure Structured Text:
PROGRAM SFCStyle
VAR
Step : INT := 0;
StepTimer : TON;
// Transitions (conditions to move forward)
T1 : BOOL := FALSE; // Start condition
T2 : BOOL := FALSE; // Sensor reached
T3 : BOOL := FALSE; // Process complete
// Actions
Motor : BOOL := FALSE;
Heater : BOOL := FALSE;
Valve : BOOL := FALSE;
END_VARCASE Step OF
0: // Initial step — all off
Motor := FALSE;
Heater := FALSE;
Valve := FALSE;
IF T1 THEN Step := 1; END_IF;
1: // Step 1: Start motor
Motor := TRUE;
Heater := FALSE;
StepTimer(IN := TRUE, PT := T#2s);
IF StepTimer.Q AND T2 THEN
StepTimer(IN := FALSE);
Step := 2;
END_IF;
2: // Step 2: Heat
Motor := TRUE; // Keep running
Heater := TRUE;
IF T3 THEN Step := 3; END_IF;
3: // Step 3: Dispense
Heater := FALSE;
Valve := TRUE;
StepTimer(IN := TRUE, PT := T#5s);
IF StepTimer.Q THEN
StepTimer(IN := FALSE);
Step := 0; // Return to idle
END_IF;
END_CASE;
Best Practices for PLC State Machines
Practice State Machines Online
Build your own state machine in our free PLC simulator. Start with the basic CASE pattern, then add timeouts and error handling. Our lessons include step-by-step state machine exercises.