A PLC program is a loop you never write
Picture a conveyor in a packing hall. An operator presses a green button, a 0.75 kW motor starts, cartons move. Behind the cabinet door a PLC — maybe a Siemens S7-1500, an Allen-Bradley CompactLogix or a CODESYS soft-PLC on an industrial PC — is running a program that decides whether that motor contactor gets 24 V. The program is a few lines of Structured Text. What makes it a PLC program, and not just code, is how it runs.
A desktop program starts, does its work and ends. A PLC program never ends. The controller runs it again and again, typically every 1 to 100 milliseconds, for as long as the machine has power. Each pass is called a scan (or a cycle), and every scan has the same three steps:
- Read inputs. The CPU copies the state of every input terminal — buttons, sensors, analog transmitters — into memory. This copy is the process image.
- Execute the program. Your Structured Text runs once, top to bottom, using those copied values.
- Write outputs. The values your program left in the output variables are copied out to the output terminals: contactors, valves, lamps.
Then it starts again. You do not write this loop; the controller owns it. You write what happens in step 2, and you write it knowing it will happen again in a few milliseconds with fresh inputs.
That single idea explains most of what surprises new PLC programmers. A line of ST is not "run once when the button is pressed". It is run on every scan, whether or not anything changed. Keep that picture in your head and the rest of this course will make sense.
Your first program
Here is a complete Structured Text program. It is the electrical equivalent of a switch wired straight to a lamp — but the wire runs through the CPU.
PROGRAM Main
VAR
Switch : BOOL; (* input: a selector switch on the panel *)
Lamp : BOOL; (* output: a 24 V pilot lamp *)
END_VAR
Lamp := Switch;
END_PROGRAMThis box is a live program, not a picture. It runs on a Structured Text engine inside this page, one scan every 100 milliseconds of virtual time, just like a controller. Flip Switch on the right and watch Lamp follow it on the next scan. The online-watch table shows what an engineer sees when they connect to a real PLC: every variable and its current value.
Read the program from top to bottom:
PROGRAM Main…END_PROGRAMwraps the whole program and gives it a name. Real projects have several programs and the controller's task configuration decides which ones run and how often. Here there is one, and it runs every scan.VAR…END_VARis the declaration section. Every variable the program uses must be declared here with a name and a type.BOOLis the type for a signal that is eitherTRUEorFALSE.(* ... *)is a comment. The controller ignores it; the next engineer will not.Lamp := Switch;is the only statement. The symbol:=means assign: compute the value on the right, store it in the variable on the left. The semicolon ends the statement.
In a real project, Switch and Lamp would be mapped to physical terminals — %I0.0 and %Q0.0 on a Siemens CPU, Local:1:I.Data.0 on a Rockwell controller, a linked variable in TwinCAT. The mapping is configuration, not logic, and it varies by vendor. The logic itself is the line you just ran, and it is the same on all of them. That portability is the reason IEC 61131-3 exists.
What "every scan" really means
The program above looks like it does nothing until the switch moves. In fact it runs ten times a second here, and up to a thousand times a second on a fast controller. Every scan it copies Switch into Lamp again, even when neither changed. Let a program count its own scans and you can see the loop directly:
PROGRAM Main
VAR
ScanCount : DINT := 0; (* starts at zero when the PLC starts *)
Seconds : REAL;
END_VAR
ScanCount := ScanCount + 1;
Seconds := DINT_TO_REAL(ScanCount) * 0.1; (* this engine scans every 0.1 s *)
END_PROGRAMScanCount := ScanCount + 1; reads the current value, adds one and stores the result. Because the statement runs on every scan, the count climbs by one per scan for as long as the program runs. Use the pause and Step one scan buttons in the header to advance a single scan at a time; that is the most useful debugging habit you will learn in this course.
Two details matter here:
:= 0in the declaration is an initial value. It is applied when the controller starts the program, not on every scan. If it were applied every scan, the counter would never pass one.DINTis a 32-bit integer. A 16-bitINTwould overflow at 32,767 scans — under an hour at 100 ms, under a minute at 1 ms. Choosing types is the subject of the next chapter.
Order matters inside one scan
Within a scan, statements run in the order they are written, and each one sees the results of the lines above it. This program copies A into B before incrementing A:
PROGRAM Main
VAR
A : INT := 0;
B : INT := 0;
END_VAR
B := A; (* B gets A's value from the end of the previous scan... *)
A := A + 1; (* ...then A moves on *)
END_PROGRAMStep it one scan at a time. After every scan B is exactly one behind A, because B was assigned before A changed. Swap the two lines in the editor (the program rebuilds when you stop typing) and B catches up with A. Nothing about the variables changed — only the order.
This is not an academic point. Suppose one line calculates a fill target and a later line starts a valve when the weight is below the target. Put them in the wrong order and the valve decision uses last scan's target. At 10 ms per scan that is usually harmless; on the scan where a recipe changes it is not. The rule is simple: calculate before you decide, decide before you command. Most well-organised ST programs read in exactly that order.
Inputs are a snapshot, outputs are a promise
Because inputs are copied once at the start of the scan, your program sees a frozen picture of the machine for the whole scan. If a photo-eye flickers in the middle of your program's execution, your program will not see it until the next scan. That is a feature: every line in one scan agrees about what the inputs were.
Outputs work the other way round. Writing Lamp := TRUE; does not switch the lamp at that moment. It changes a value in memory, and the terminal follows when the outputs are written at the end of the scan. So if a program writes an output twice in one scan, only the last value reaches the terminal:
PROGRAM Main
VAR
Fault : BOOL; (* input from a drive *)
RunLamp : BOOL; (* output *)
END_VAR
RunLamp := TRUE; (* an earlier part of the program says "on" *)
RunLamp := RunLamp AND NOT Fault; (* a later part has the final word *)
END_PROGRAMWith Fault off, the lamp is on. Switch Fault on and the lamp never lights — not even for an instant — because the terminal only ever receives the final value. Engineers use this deliberately (a "last writer wins" safety override at the end of a program) and accidentally (two people writing the same output in different routines). In chapter 21 you will see why every output should have exactly one owner.
Reading a program like the controller does
When you meet an unfamiliar ST program, read it the way the controller executes it. Start at the declarations and note which variables are inputs, which are outputs and which are internal memory. Then take one scan with concrete input values and walk down the statements, writing each variable's new value as you go. Do it twice: once for the scan where an input changes, once for the scan after. Most bugs show up on one of those two scans.
That habit is exactly what the live examples on these pages do for you. The Step one scan button is the controller's loop with a pause in it. Use it whenever an example surprises you.
A few rules of the language are worth fixing in memory now:
- Case does not matter for keywords and names.
Lamp,LAMPandlampare the same variable. Pick one style and keep it; this course usesPascalCasefor variables and capitals for keywords. - Whitespace does not matter. Line breaks and indentation are for people.
Lamp:=Switch;is legal; it is also harder to read. - Every statement ends with a semicolon. Missing semicolons are the most common build error you will see.
- Comments are written
(* like this *)and can span lines. Most environments also accept// to the end of the line. - Names start with a letter or underscore and contain letters, digits and underscores. No spaces, no accented characters on most targets.
Where your program lives on a real controller
The single PROGRAM Main in these examples hides a little structure that every real project has. A task tells the controller how to run programs: cyclically every 10 ms, as fast as possible, or when an event fires. One or more programs are assigned to each task. Programs call functions and function blocks (chapters 17 and 18) to avoid repeating themselves. Vendors name these differently — Siemens has OBs, FBs and FCs, Rockwell has tasks, programs and routines, CODESYS and TwinCAT follow the IEC names closely — but the scan you just watched is the same idea everywhere.
The scan time also matters more than beginners expect. A 10 ms task cannot react faster than 10 ms plus the input filter on the module, which is often 3 ms by default. A sensor pulse shorter than a scan can be missed entirely. You will meet this again with edges in chapter 8 and with fast counters in chapter 11.
What to take into the next chapter
You now know the shape every Structured Text program shares: declarations, then statements, wrapped in a PROGRAM. More importantly, you know how it runs. The controller reads inputs, executes your statements in order and writes outputs, over and over. Initial values happen once; statements happen every scan. Later lines see earlier lines' results. Outputs only leave the CPU at the end of the scan.
Every chapter from here builds on that loop. The next one is about the variables themselves: which types exist, how much they can hold and how to choose the right one for a signal before it overflows on a Friday night.