PLC Programming (IEC 61131-3)

Programmable logic controllers run factories and machines. Learn PLC hardware (CPU, I/O, racks, remote I/O), the scan cycle, the five IEC 61131-3 languages (Ladder, Function Block Diagram, Structured Text, Instruction List, Sequential Function Chart), contacts and coils, seal-in circuits, timers (TON/TOF/TP), counters, edge detection, analog scaling, state machines, and good practice — with a Python model of a PLC scan.

A PLC is a rugged industrial computer that reads inputs (push-buttons, sensors), executes a program in a fast repeating cycle, and writes outputs (motors, valves, lamps). It replaced relay cabinets in the 1970s and now controls everything from packaging lines to water plants.

IEC 61131-3 standardises five programming languages so engineers can move between vendors (Siemens TIA Portal, Rockwell Studio 5000, Codesys, Schneider, Beckhoff TwinCAT, Mitsubishi, Omron). Ladder logic still looks like the relay drawings it replaced.

An analogy

A PLC is like a very fast, very reliable security guard doing rounds: every few milliseconds he checks every door (inputs), consults his rulebook (program) and switches lights on or off (outputs) — then starts the round again.

PLC hardware and the scan

A PLC has a CPU, power supply, digital inputs (24 V DC sensors, buttons), digital outputs (relays, transistors to contactors and solenoids), analog I/O (4–20 mA, 0–10 V, RTD, TC), and communication modules. It repeatedly: copies inputs to an image table, executes the program, writes outputs, and handles communications — typically every 1–50 ms. A watchdog stops outputs if the scan hangs.

The five IEC 61131-3 languages

Pick the language to suit the task:

Building blocks

Contacts and coils implement Boolean logic; seal-in circuits hold states; set/reset (latch/unlatch) coils; timers (TON on-delay, TOF off-delay, TP pulse) and counters (CTU/CTD/CTUD); edge detection (R_TRIG) for one-shot actions; comparison and math blocks; function blocks with instance memory for reusable devices (motor, valve) — the basis of modular, object-like PLC code (ISA-88/ISA-106 style).

Good practice and safety

Structure programs as state machines (CASE in ST, SFC) instead of scattered latches; use one place per output write; add alarms and first-out logic; version control and simulation/virtual commissioning before site work. Safety functions (E-stops, light curtains) belong in safety PLCs/relays certified to IEC 61508/ISO 13849 — not in standard PLC code. Cybersecurity (IEC 62443): disable unused services, protect programming access, segment networks.

Formulas

Worked example: Response time

Given: Input filter 3 ms, scan 10 ms, relay output 10 ms

Find: Worst-case response.

Answer: 33 ms

Worked example: Analog scaling

Given: Raw 0–27648 = 0–10 bar, raw = 13824

Find: Pressure.

Answer: 5.0 bar

Worked example: Counter

Given: CTU with PV = 12 counts bottles on a conveyor

Find: When is Q true?

Answer: On the 12th bottle → start case packer, then reset

Motor start/stop with delayed fan — Ladder and Structured Text

(* Ladder equivalent:
   |--[ Start ]--+--[/ Stop ]--[/ Overload ]------------( Motor )--|
   |--[ Motor ]--+                                                  |
   |--[ Motor ]-----------------[ TON FanDelay PT:=T#5s ]--( Fan )--|
*)
PROGRAM MotorControl
VAR
    Start      AT %IX0.0 : BOOL;      (* NO push-button *)
    Stop       AT %IX0.1 : BOOL;      (* wired NC in the field -> TRUE when healthy *)
    Overload   AT %IX0.2 : BOOL;      (* thermal overload tripped *)
    Motor      AT %QX0.0 : BOOL;
    Fan        AT %QX0.1 : BOOL;
    FanDelay   : TON;
END_VAR
    Motor := (Start OR Motor) AND Stop AND NOT Overload;    (* seal-in *)
    FanDelay(IN := Motor, PT := T#5S);
    Fan := FanDelay.Q;
END_PROGRAM

Illustrative IEC 61131-3 code (addresses %IX/%QX vary by vendor). The stop button is wired normally-closed so that a broken wire stops the motor — fail-safe wiring. The same logic is shown as a ladder rung in the comment.

Simulating the PLC scan in Python

# Python model of a PLC scan: start/stop seal-in motor + TON timer for a fan
class TON:
    def __init__(self, pt): self.pt, self.et, self.q = pt, 0.0, False
    def __call__(self, inp, dt):
        self.et = min(self.et + dt, self.pt) if inp else 0.0
        self.q = inp and self.et >= self.pt
        return self.q

motor = False
fan_timer = TON(pt=5.0)                 # fan starts 5 s after the motor
scan = 0.1                              # 100 ms scan time
events = {1.0: ("start", True), 1.2: ("start", False), 12.0: ("stop", True), 12.1: ("stop", False)}
start = stop = False
for n in range(0, 150):
    t = round(n * scan, 1)
    if t in events:
        name, val = events[t]
        if name == "start": start = val
        else: stop = val
    motor = (start or motor) and not stop     # rung 1: seal-in circuit
    fan = fan_timer(motor, scan)              # rung 2: on-delay timer
    if t in (0.0, 1.0, 3.0, 6.0, 6.1, 12.0, 12.1):
        print(f"t={t:4.1f}s start={int(start)} stop={int(stop)} motor={int(motor)} fan={int(fan)} ET={fan_timer.et:.1f}")
# t= 0.0s start=0 stop=0 motor=0 fan=0 ET=0.0
# t= 1.0s start=1 stop=0 motor=1 fan=0 ET=0.1
# t= 3.0s start=0 stop=0 motor=1 fan=0 ET=2.1
# t= 6.0s start=0 stop=0 motor=1 fan=1 ET=5.0
# t= 6.1s start=0 stop=0 motor=1 fan=1 ET=5.0
# t=12.0s start=0 stop=1 motor=0 fan=0 ET=0.0
# t=12.1s start=0 stop=0 motor=0 fan=0 ET=0.0

The motor keeps running after the start button is released (seal-in), the fan follows 5 s later, and the stop button drops both. Here stop is modelled as a normally-open button for simplicity. Each loop iteration is one scan: logic is evaluated top to bottom, once per cycle.

Common mistakes

Applications

Key takeaways

Going further