The short answer
State Machines in PLC Programs
A state machine writes a sequence as a set of named states, exactly one of which is active, with explicit transitions between them. Each state defines what the outputs do while it is active and what condition moves the machine to the next state. The pattern replaces the seal-in rungs and interlock chains that grow unmaintainable, makes the current step visible on the HMI, and forces the programmer to decide what happens on a fault, a stop, and a power loss in every state.
Key points
- One state active at a time, held in one integer or enumeration.
- Outputs are decided by the state; transitions are decided by conditions. Keep the two apart.
- Every state has a way out on fault, on stop, and on timeout.
- Publish the state number and a description to the HMI. That is the troubleshooting tool.
- Decide what happens on power loss: restart, resume, or go to a safe state.
The problem state machines solve
A backwash sequence, a generator transfer, a chemical batch, a well pump startup with its pump-to-waste period: each is a series of steps that depend on time and on conditions. Written as ladder with seal-in bits, each step is a rung that latches when the previous step is done and unlatches when its own completion is seen, plus the interlocks, plus the fault handling, plus the reset. By the twelfth step nobody can say with confidence which bits are set, and a fault mid-sequence leaves latches in a combination that was never tested.
A state machine replaces all of that with one variable, the current state, and a structure that makes each state explicit. The program can be in state 30, Combined Wash, and nothing else. What the outputs do in state 30 is written in one place. What moves the sequence to state 40 is written in one place. What happens in state 30 on a fault is written in one place. The HMI shows the number and the name.
The structure
- State variable
- An integer or enumeration, one per machine, retentive if the sequence must survive a power cycle. Numbered with gaps, 10, 20, 30, so a state can be inserted later without renumbering.
- States
- Each has a name, a description, and a purpose. Idle, Drain, Air Scour, Combined Wash, Rinse, Return to Service, Aborting, Fault. Idle and Fault always exist.
- Outputs by state
- What each output does in each state, written as a function of the state, not as a latch. In state 20 the air scour blower runs; in states where it should not, it does not. Because outputs are derived from state, they cannot be left on by a forgotten unlatch.
- Transitions
- The condition that moves from one state to the next: a timer expired, a level reached, a valve confirmed open, an operator acknowledgment. Written per state, and the only place the state variable is changed.
- Timeouts
- Each state that waits for something has a maximum time. A valve that does not confirm open in 60 seconds moves the machine to Fault, not to waiting forever.
- Fault and abort paths
- From any state, a fault condition goes to the Fault state, and a stop request goes to Aborting, which returns the equipment to a safe configuration and then to Idle. These transitions are written once, above the per-state logic.
(* Global exits: evaluated before the per-state logic, every scan. *)
IF Fault_Any THEN
State := 900; (* Fault *)
ELSIF Stop_Request AND State > 0 AND State < 800 THEN
State := 800; (* Aborting *)
END_IF;
CASE State OF
0: (* Idle *)
IF Backwash_Request AND Permissives_OK THEN
State := 10; Step_Timer := T#0s;
END_IF;
10: (* Close influent, drain to wash level *)
IF Influent_Valve_Closed AND Level < Wash_Level THEN
State := 20; Step_Timer := T#0s;
ELSIF Step_Timer > T#5m THEN
State := 900; Fault_Code := 10;
END_IF;
20: (* Air scour *)
IF Step_Timer > Air_Scour_Time THEN
State := 30; Step_Timer := T#0s;
END_IF;
30: (* Combined air and water wash *)
IF Step_Timer > Combined_Time THEN
State := 40; Step_Timer := T#0s;
END_IF;
40: (* Water rinse until turbidity clears *)
IF Wash_Turbidity < Rinse_Limit AND Step_Timer > Min_Rinse_Time THEN
State := 50; Step_Timer := T#0s;
ELSIF Step_Timer > Max_Rinse_Time THEN
State := 900; Fault_Code := 40;
END_IF;
50: (* Return to service *)
IF Influent_Valve_Open AND Effluent_Valve_Open THEN
State := 0;
END_IF;
800: (* Aborting: close wash valves, stop blower, then idle *)
IF Wash_Valves_Closed AND NOT Blower_Running THEN
State := 0;
END_IF;
900: (* Fault: hold safe until reset *)
IF Fault_Reset AND NOT Fault_Any THEN
State := 0;
END_IF;
ELSE
State := 900; Fault_Code := 999; (* unknown state *)
END_CASE;
(* Outputs derived from state. Nothing latches. *)
Air_Scour_Blower_Cmd := (State = 20) OR (State = 30);
Wash_Water_Valve_Cmd := (State = 30) OR (State = 40);
Influent_Valve_Cmd := (State = 0) OR (State = 50);The shape is the whole lesson. Exits that apply everywhere are at the top. Each state changes the state variable and nothing else. Outputs are computed from the state at the bottom, so there is nothing to unlatch and nothing left on when the sequence leaves a state by any route. The ELSE catches a state value that should not exist, which happens after a program edit or a memory fault.
The same pattern in ladder
Where the site standard requires ladder, the pattern survives. One integer holds the state. A rung per transition compares the state and the condition and moves a new value into the integer. A rung per output compares the state and drives the coil. It is more rungs than the CASE statement and it is still one variable, one place per transition, and outputs that follow the state. What it must not become is a set of seal-in bits with the state number added as decoration.
Power loss and restart
The one decision a state machine forces that latch logic lets a programmer avoid is what happens on a power cycle. Three answers are common, and the control narrative picks one per sequence.
| Behavior | How | Where it fits |
|---|---|---|
| Restart from Idle | State variable not retentive, or first-scan logic sets it to 0 | Sequences that are safe and cheap to start over: a pump startup, a valve exercise |
| Resume where it stopped | State variable retentive; timers and step data retentive too | Long sequences where restarting wastes product or time: a batch, a long backwash; only if every state is safe to resume into |
| Go to a safe state and wait | First-scan logic moves any active state to Aborting or a Hold state that needs an operator | Anything where resuming blind could be unsafe: chemical feeds, sequences with manual steps |
Publishing the state
The state number is the single most useful troubleshooting tag in a sequence. Publish it, and publish a text description of the current state and the reason the machine is waiting: state 40, Rinse, waiting for turbidity below 5 NTU, 3:20 elapsed of 10:00 maximum. A sequence that says why it is waiting does not need someone to open the program. Log every state change with a timestamp to the historian; the record of when each step started and ended is the commissioning evidence and the maintenance history.
Frequently asked questions
- Should I use a sequential function chart instead?
- SFC is the graphical form of the same idea and some platforms implement it well. It excels at simple linear sequences and at showing the active step. It becomes hard to read with many exits and parallel branches, and online troubleshooting depends on the platform. A CASE statement in structured text is portable and explicit; SFC is a fine choice where the platform and the site are comfortable with it.
- How do I handle two things happening at once?
- Two state machines. A filter with a backwash sequence and a separate flow control loop is two machines that exchange status. Trying to express parallel activity inside one state machine produces the branching mesh that the pattern was meant to avoid.
- Where do the interlocks go?
- Permissives gate the transition out of Idle. Interlocks that must stop the sequence go into the fault condition and produce the transition to Fault from every state. Interlocks that protect a single output stay on that output, after the state logic, so an output the state calls for is still blocked when its interlock says so.
- The sequence is stuck in a state. How do I find out why?
- Read the published wait reason and the step timer. If the machine does not publish them, look at the transition condition for that state in the program and evaluate each term. The fix after the event is to add the wait reason so the next time does not need the program.
Related topics
- Structured TextThe text language of IEC 61131-3: syntax, control structures, how it fits alongside ladder, the conventions that keep it readable for the next person, and the mistakes that make structured text a liability in a plant that troubleshoots online.
- Interlocks and PermissivesA permissive lets you start. An interlock stops you. Confusing the two produces equipment that either will not run or will not protect itself.
- Retentive MemoryWhat survives a power cycle in a controller and what does not, how retention is backed up by battery, capacitor, or nonvolatile storage, what should be retentive, and how retained data gets lost.
- Sequences of OperationWriting the ordered, step-by-step description of what a system does: states, transitions, timeouts, and the format that lets a programmer implement it and a tester verify it without interpretation.
- Ladder Logic FundamentalsRungs, contacts, coils, and the relay conventions the language inherited — plus the patterns you will meet in almost every industrial program.
- The Watchdog TimerWhat the scan watchdog protects against, why it faults the controller instead of pausing it, what makes a scan run long, and why raising the limit is the wrong first response.
Direct contact
Have a controls question?
Reach Eric Sullivan directly about anything on this site, a controls or automation topic, or one of his personal projects.