01 — WHY STRUCTURE

How unstructured PLC code fails on a real plant, what a POU actually is, and why a change needs one home.

Estimated 20 minutes.

Everything in this track sits on one loop

A controller does exactly three things and then does them again: it copies every input into memory, runs your code from the first line to the last, then copies the results out to the output cards. The TryPLC engine runs that loop every 100 ms; a ControlLogix or TwinCAT task typically runs it every 10. Nothing you write escapes it.

Read, solve, write — then again

Your program never talks to a field device directly. It reads a snapshot taken at the top of the scan and writes into an image that is pushed out at the bottom — which is exactly why the LAST thing written to an output is the only thing the wire ever sees.

Flat code is not wrong. It is unbounded.

A single main routine with everything in it runs perfectly well, and plenty of profitable plants are built that way. The problem is not correctness on day one. It is that nothing in the file limits where a change can reach, so every future edit has to be argued out in your head instead of enforced by the language.

Scan Detective: two statements, one valve

This is the real ST engine. One flat program, one variable written by two different people. Run it, then drag the guard interlock above the fill logic and run it again — same values, different plant.

The same collision, in both languages

Neither language is protecting you here. The only thing keeping that valve shut is the order two engineers happened to type their code in, nineteen weeks apart.

What does the solenoid actually see?

FV_401_Cmd is written on line 41 by the fill logic and again on line 96 by the guard interlock. The guard opens mid-fill, so line 41 wants TRUE and line 96 wants FALSE. What does the physical solenoid do during that scan?

  • Chatters — it sees both values every scan
  • Follows line 96, because outputs are written to the cards once, at the end of the scan
  • Follows line 41 — the first write wins and later writes are ignored
  • Nothing: the compiler rejects a variable written twice

Failure one: the copy that missed the change order

Six fillers, six copies of the same eighty-line fill section, because copying was faster than designing. CR-4471 asked for a two-second dribble at the end of every dose. The edit took twenty minutes and was applied to five of them. Filler 6 kept overfilling for three weeks: a six-hour night shift to find it, 1 800 litres scrapped, and two pallets on QA hold.

The rung that exists six times

Neither version is badly written. The cost is not in the rung, it is in the fact that there are six of them and the language has no idea they are related.

Failure two: two people, one global, one scan

GVL_Plant.FV_401_Cmd was written by the process engineer in week 12 and by the safety integrator in week 31. Neither knew about the other, because a global is visible to every line in the project and owned by none of them. It ran correctly for four months — the two statements only disagree while the guard is open mid-fill — and it took two shifts and a scope on the solenoid before anyone thought to read the scan order.

Failure three: the handover

Main.st is 4 027 lines and the engineer who wrote it left in March. The customer wants a second filler. Find every place you would have to edit — first in the flat program, then in the structured one.

A POU is a container with a boundary

IEC 61131-3 calls the things you write Program Organisation Units, and it gives you exactly three: PROGRAM, FUNCTION and FUNCTION_BLOCK. Alongside them sit two things that are not POUs at all — a GVL, which declares shared variables and holds no code, and a TYPE (a DUT in TwinCAT), which declares a shape. Five containers, and every artefact in a project belongs to exactly one.

The five containers, with real file names

TwinCAT extensions shown. CODESYS calls the entry point PLC_PRG and the shapes DUTs; Studio 5000 calls programs and routines something else again — the three code containers underneath are the same standard.

POU Sorter: file the project

Six artefacts out of a real filling-line project. Drag each one into the container the standard gives it. Wrong drops bounce back and tell you why — nothing resets.

Why can only one of these be a FUNCTION?

Scale_4_20mA takes raw counts and two range limits and gives back engineering units. TON takes IN and PT and gives back Q and ET. Both look like "values in, values out". Only one of them can be a FUNCTION. Which reason is the real one?

  • TON is a vendor block, and vendor blocks are always FUNCTION_BLOCKs
  • TON has to remember when its input went TRUE, and a FUNCTION keeps nothing between calls
  • TON takes a TIME input, and FUNCTIONs cannot accept TIME
  • Scale_4_20mA executes faster, so it does not need an instance

One place to change

Every construct in the fourteen lessons after this one — functions, function blocks, the VAR sections, encapsulation, methods, interfaces, inheritance, composition — exists to do one job: give a change exactly one place to live, and make the language enforce it instead of your memory. That is the whole argument. The syntax is just how it is spelled.

The seventh filler

Next quarter the line grows a seventh filler. Today the fill logic exists once per filler, copied. Which of these actually reduces what that change order costs?

  • Copy filler 6's section and rename the tags carefully
  • Add a comment header to each copy listing the other five
  • Move the fill logic into one FUNCTION_BLOCK and declare a seventh instance of it
  • Move every filler tag into one GVL so at least the data is in one place

What you learned

  • The scan cycle is the ground truth: read every input, run every line top to bottom, write every output, then repeat — forever.
  • Outputs reach the field once per scan, so the last write to a variable is the only one the hardware ever sees. Statement order is not style.
  • IEC 61131-3 gives you three code containers — PROGRAM, FUNCTION, FUNCTION_BLOCK — plus GVLs for shared data and TYPEs for shapes.
  • A FUNCTION forgets between calls; a FUNCTION_BLOCK remembers, which is exactly why TON is a FUNCTION_BLOCK and not a FUNCTION.
  • Flat code fails three ways: the copy that missed the change order, the global two people write in one scan, and the routine nobody can hand over.
  • The cost is measured in change orders and handovers, not in keystrokes — six edits instead of one, twenty-six hours instead of fifteen minutes.
  • Everything that follows exists to give a change exactly one place to live.

WHY STRUCTURE — done

  • You can prove on a running engine that statement order alone decides whether a guard interlock works.
  • You can name the five containers in an IEC 61131-3 project and say what belongs in each, including which two are not POUs.
  • You can put an hours figure on what unstructured code costs, per change order and per handover.