Traffic Light PLC Programming: State Machine Example
Build a six-state traffic-light PLC program with timers, all-red clearance, safe transitions, Structured Text, an I/O map and a reproducible test matrix.
How do you program a traffic light with a PLC?
Model each legal signal phase as one state, attach a timer to that state, and permit only a defined transition to the next state. For a two-road training intersection, use six states: north–south green, north–south yellow, all red, east–west green, east–west yellow and all red. Derive all lamp outputs from the active state and add a final conflict check that forces every direction red if incompatible greens are ever requested.
This pattern teaches sequence control, timers, state machines, interlocks and fault recovery. It is an educational simulation, not a deployable public-road signal controller.
Safety boundary: training logic is not a traffic controller
Public-road signal timing and controller operation are governed by applicable national, state and local requirements. The US Federal Highway Administration’s Traffic Signal Timing Manual explains that timing should be developed with engineering judgment and applicable policy. The Manual on Uniform Traffic Control Devices defines requirements for traffic-control devices.
This example deliberately excludes:
- certified conflict-monitor hardware;
- redundant output monitoring;
- pedestrian timing calculations;
- local yellow-change and red-clearance requirements;
- emergency and railroad preemption;
- flash transfer, cabinet monitoring and communications;
- accessibility and traffic-engineering approval.
Never connect this tutorial to roadside signals. Use a simulator or a low-voltage training rig with isolated indicator lamps.
Requirements for the training intersection
The example controls two non-overlapping movements:
- North–south (NS)
- East–west (EW)
Each movement has red, yellow and green outputs. The simplified sequence is:
| State | NS lamps | EW lamps | Example training time | Exit condition |
|---|---|---|---|---|
NS_GREEN |
Green | Red | 10 s | State timer done |
NS_YELLOW |
Yellow | Red | 3 s | State timer done |
ALL_RED_1 |
Red | Red | 2 s | State timer done |
EW_GREEN |
Red | Green | 10 s | State timer done |
EW_YELLOW |
Red | Yellow | 3 s | State timer done |
ALL_RED_2 |
Red | Red | 2 s | State timer done |
These times are arbitrary simulation values, not recommended road timings. FHWA notes that phase timing includes green, yellow change and red-clearance intervals, and that applicable policies and site geometry matter.
I/O map
Use readable tags rather than hard-coded addresses:
| Tag | Type | Direction | Purpose |
|---|---|---|---|
RunRequest |
BOOL | Input | Starts automatic cycling |
StopRequest |
BOOL | Input | Requests the stopped/all-red state |
ResetRequest |
BOOL | Input | Acknowledges a cleared training fault |
NsRed, NsYellow, NsGreen |
BOOL | Output | North–south lamps |
EwRed, EwYellow, EwGreen |
BOOL | Output | East–west lamps |
State |
ENUM/INT | Internal | Active legal phase |
NextState |
ENUM/INT | Internal | Calculated successor state |
StateTimer |
TON | Internal | Dwell time for active state |
ConflictFault |
BOOL | Internal | Incompatible green request detected |
On a training rig, use momentary pushbuttons for start, stop and reset. Treat an emergency stop as a separate safety function; do not label an ordinary PLC stop input “E-stop.”
Program architecture
Keep the program in five layers:
- Input normalization — debounce buttons and establish operating request.
- Transition logic — calculate the next state from the current state and timer.
- State memory — update the state once per scan.
- Output decode — derive lamps from the state.
- Final conflict guard — force all red and latch a fault if conflicting greens are requested.
This separation makes the sequence testable. A timer cannot directly energize a lamp in one rung while a second rung changes the state unexpectedly.
Complete Structured Text example
The code below uses IEC-style pseudocode. Adapt enum syntax and timer calling conventions to your target PLC.
TYPE TrafficState :
(
STOPPED,
NS_GREEN,
NS_YELLOW,
ALL_RED_1,
EW_GREEN,
EW_YELLOW,
ALL_RED_2,
FAULTED
);
END_TYPE
VAR
State : TrafficState := STOPPED;
NextState : TrafficState := STOPPED;
StateTimer : TON;
StateTime : TIME;
ConflictFault : BOOL;
END_VAR
(* 1. Select the dwell for the current state *)
CASE State OF
NS_GREEN, EW_GREEN: StateTime := T#10s;
NS_YELLOW, EW_YELLOW: StateTime := T#3s;
ALL_RED_1, ALL_RED_2: StateTime := T#2s;
ELSE StateTime := T#0s;
END_CASE;
StateTimer(
IN := RunRequest
AND NOT StopRequest
AND (State <> STOPPED)
AND (State <> FAULTED),
PT := StateTime
);
(* 2. Calculate one legal successor *)
NextState := State;
IF StopRequest THEN
NextState := STOPPED;
ELSIF ConflictFault THEN
NextState := FAULTED;
ELSE
CASE State OF
STOPPED:
IF RunRequest THEN NextState := ALL_RED_1; END_IF;
NS_GREEN:
IF StateTimer.Q THEN NextState := NS_YELLOW; END_IF;
NS_YELLOW:
IF StateTimer.Q THEN NextState := ALL_RED_1; END_IF;
ALL_RED_1:
IF StateTimer.Q THEN NextState := EW_GREEN; END_IF;
EW_GREEN:
IF StateTimer.Q THEN NextState := EW_YELLOW; END_IF;
EW_YELLOW:
IF StateTimer.Q THEN NextState := ALL_RED_2; END_IF;
ALL_RED_2:
IF StateTimer.Q THEN NextState := NS_GREEN; END_IF;
FAULTED:
IF ResetRequest AND NOT ConflictFault THEN
NextState := STOPPED;
END_IF;
ELSE
NextState := FAULTED;
END_CASE;
END_IF;
(* 3. Change state; the timer must restart for a new state *)
IF NextState <> State THEN
State := NextState;
StateTimer(IN := FALSE, PT := StateTime);
END_IF;
(* 4. Safe defaults, then decode the active state *)
NsRed := TRUE; NsYellow := FALSE; NsGreen := FALSE;
EwRed := TRUE; EwYellow := FALSE; EwGreen := FALSE;
CASE State OF
NS_GREEN:
NsRed := FALSE; NsGreen := TRUE;
NS_YELLOW:
NsRed := FALSE; NsYellow := TRUE;
EW_GREEN:
EwRed := FALSE; EwGreen := TRUE;
EW_YELLOW:
EwRed := FALSE; EwYellow := TRUE;
END_CASE;
(* 5. Independent conflict guard *)
ConflictFault := NsGreen AND EwGreen;
IF ConflictFault THEN
NsRed := TRUE; NsYellow := FALSE; NsGreen := FALSE;
EwRed := TRUE; EwYellow := FALSE; EwGreen := FALSE;
END_IF;
Important timer detail
Many PLC TON implementations retain Q for the rest of the scan in which their input is removed. Reset the timer explicitly when the state changes, or use a state-entry pulse and one timer instance per state. Confirm behavior in your target runtime rather than assuming every dialect executes the same way.
Ladder-logic implementation pattern
Use one state bit or integer state register. A clean ladder project has:
- one rung/network for the timer associated with the active state;
- one transition rung for each legal edge;
- one first-scan/default-state rung;
- output rungs that decode state;
- a final conflict/fault rung;
- explicit stop and reset behavior.
Avoid cascading TON.DN bits through lamp output rungs. That design becomes difficult to pause, reset and test because timing, transitions and outputs are coupled.
See PLC scan cycle explained, TON timer programming and the ladder-logic tutorial.
Optional vehicle demand
After the fixed-time sequence passes, add demand without compromising the clearance states:
(* Minimum NS green has elapsed.
Extend while NS demand exists, but not beyond MaxGreen. *)
LeaveNsGreen :=
MinGreenTimer.Q
AND (
EwVehicleDemand
OR MaxGreenTimer.Q
);
Keep minimum green, maximum green and clearance timing separate. A missing detector must have a documented fallback. Do not let a demand input jump directly from one green to the conflicting green.
Stop, power-up and recovery behavior
Define these before programming:
- On power-up, enter
STOPPEDwith all red. - Start request enters an all-red state before the first green.
- Stop request forces all red.
- An invalid state enters
FAULTED. - Reset returns to
STOPPED, not directly to green. - No automatic restart occurs merely because power or an input returns.
For an educational lamp panel, these choices are conservative and easy to verify.
Reproducible acceptance test
Run the following on a simulator or isolated trainer. Record actual state, timer elapsed time and six output bits for every step.
| ID | Test | Expected result |
|---|---|---|
| T01 | Power up with RunRequest = FALSE |
STOPPED; both red lamps on |
| T02 | Apply run request | Enters all red before any green |
| T03 | Complete a full cycle | States occur in the documented order |
| T04 | Observe every normal state | No state requests both greens |
| T05 | Stop during each green/yellow | Both directions become red |
| T06 | Force an invalid state in the simulator | Program enters FAULTED; both red |
| T07 | Reset while conflict remains | Reset is rejected |
| T08 | Clear conflict then reset | Returns to STOPPED, not green |
| T09 | Remove and restore run request | No state is skipped |
| T10 | Cycle power with run request true | Follows documented restart policy |
Invariants to monitor
These Boolean assertions should always be true:
NOT (NsGreen AND EwGreen)
NOT (NsGreen AND NsYellow)
NOT (EwGreen AND EwYellow)
NsRed OR EwRed
State is one of the declared enum values
On a real safety-critical application, software assertions are not a substitute for independent certified protection. Here they make the training exercise deterministic.
Practise this exact scenario
PLC Simulation Software includes a browser-based traffic-light scenario for practising timer, state and output logic without roadside equipment.
Ownership disclosure: PLC Programming and PLC Simulation Software are operated by the same publisher. The product is a training simulator, not an approved traffic-signal controller.
Open the traffic-light PLC simulator and reproduce T01–T10. Save screenshots of the state sequence and fault response as portfolio evidence.
Key takeaways
- One legal phase should equal one explicit state.
- Timers control dwell; transition logic controls allowed movement.
- Decode all outputs from state with safe defaults.
- Add an independent incompatible-output assertion.
- Test startup, stop, invalid state and reset—not only normal cycling.
- Treat all example times as simulation values.
- Use the applicable traffic-engineering standards and approved hardware for any real intersection.


