Here is the job. Where would you start?
An operator puts a carton at the near end of a belt. There is a photoeye there, PE1, and another at the far end, PE2. The request is short:
“Press Start to bring one carton to the far sensor. Stop there. Wait for the next cycle.”

You know how to write an IF. You might already know how a contact and a coil work. But the request has not told you which IF or which rung to write. That is the part we will practise.
Start on paper. Write Q_Belt in the middle. It is the output that asks the drive to run the belt. Around it, leave space for four answers: what allows it, what keeps it going, what finishes it, and what interrupts it. Those answers will become your program. You can learn the spelling of the instructions as you go.
The workbench above is this exact machine. Open it and leave the first draft's rules off. Change inputs, advance a scan, and look at the carton. Later, add each rule and watch the code change. You are not trying to guess an answer from four options. You are deciding what the machine is allowed to do and testing that decision.
The first twenty minutes
Imagine sitting down with an empty editor and that one-sentence request. Resist the urge to type IF Start. You are missing several decisions, and syntax will not make them for you.
Put a line under each phrase of the request. Beside it, write the question it leaves unanswered. This is what an experienced programmer is doing when they appear to be taking a long time to write a short program.
| Words in the brief | What you need to ask | Decision for this teaching job |
|---|---|---|
| Press Start | Hold it, or press and release it? | A fresh press requests one complete transfer. |
| One carton | How do we know a carton is available? | PE1 must be active when the request is accepted. |
| To the far sensor | What counts as completion? | PE2 becomes active while the accepted move is in progress. |
| Stop there | What if arrival and Stop appear together? | Stop wins. Record an interrupted cycle, not a successful delivery. |
| Wait | What if Start is still held when the carton is removed? | Stay stopped. Removal is not a new command. |
| Next cycle | Who says the station is ready again? | Acknowledge delivery, establish pickup, release Start, then accept a new press. |
These answers are chosen requirements, not properties of PLCs. A different customer could choose a different acknowledgement process. The professional habit is to expose the decision, agree it, and test it. Do not hide it inside a convenient rung.
Now put an asterisk beside anything that the physical equipment cannot prove. Our two photoeyes can show that a carton is at either end. They cannot identify the carton, measure its exact position between sensors, prove that a belt is not slipping, or tell you whether somebody removed it halfway through. Keep those limits on the page. They tell you where a timeout can detect missing progress and where another sensor might be necessary.
If you cannot finish one of those sentences, write a question in the design record. That is progress. A guessed answer that makes the program compile is still a guessed machine.
First: name what you can command
For this small job there is one ordinary control output: the belt-run request. Write its name, destination and meaning.
| Name | Goes to | TRUE means | FALSE means |
|---|---|---|---|
| Q_Belt | Drive run input | Request belt motion | Remove the run request |
Notice the word request. It is possible to request motion while the drive is faulted, the belt is jammed or the motor is disconnected. A command does not prove that anything moved. Later we will supervise the drive's actual response. For now, do not label this variable “MotorRunning”.
This first row prevents a common muddle: mixing an action, an observation and a permission into one Boolean called “Motor”. You need all three meanings. Give them different names.
Second: find the facts that justify the command
Point to the real thing that supplies each fact. For our teaching station:
| Fact | Where it comes from | Why we need it |
|---|---|---|
| AtPickup | PE1 | A carton is available when we accept a cycle |
| AtDrop | PE2 | The requested move has reached its destination |
| DriveReady | Drive status | The drive can accept an ordinary run request |
| StartButton | Operator input | Someone requests a cycle |
| StopButton | Operator input | Someone requests an interruption |
We have chosen teaching assumptions: a carton starts at PE1, both sensors detect the carton when TRUE, and DriveReady remains supervised during travel. Your real machine might use different wiring or different readiness semantics. Put those differences in the signal map; do not quietly compensate for them halfway down the sequence.
What if PE2 does not exist? Then “stop at PE2” is not a decision the program can make. A timer cannot conjure that observation. You would need another way to establish position—perhaps an encoder or a different sensor—or the requirement would need to change. Finding that gap now is more useful than writing twenty rungs around an imagined input.
Third: decide what must be remembered
Ask the operator to press and release Start. Should the belt stop as soon as the finger leaves the button? For this job, no. The press requests a complete move. Something must remember that the request was accepted.
Compare these two jobs carefully:
| Job | What determines the output? | Useful starting pattern |
|---|---|---|
| Light a lamp while a sensor is active | Present conditions | A Boolean expression or a rung |
| Run while an operator holds a jog button | Present request and permissives | A gated Boolean expression |
| Accept Start and finish a move after release | An accepted request plus later evidence | Memory, with a defined clearing rule |
| Clamp, extend, retract, then release | Several remembered phases with different evidence | A state sequence |
Do not use a state machine because it sounds professional. Use one when naming the phases makes the decision easier to explain. Our one-carton job benefits from four names because the motor can be off for three different reasons: waiting for a request, finished, or interrupted.
Here is a quick proof. After a carton arrives, remove it from PE2. The sensor becomes FALSE. Should the belt run? No: the accepted job is finished. At an earlier moment, while a carton was travelling toward PE2, that same FALSE input meant “keep moving”. The sensor value alone cannot answer the question. The history matters.
Fourth: name the states and write on the arrows
Use these states for the exercise:
| State | What it remembers | Belt request |
|---|---|---|
| IDLE | No move has been accepted | Off |
| MOVING | A move was accepted and has not finished | On, subject to its continuous permissives |
| DELIVERED | The arrival was observed | Off |
| INTERRUPTED | The accepted work did not finish normally | Off |
Write an arrow from IDLE to MOVING. On it, put the conditions that permit acceptance: a fresh Start event, a carton at pickup, an available drive, no arrival already active, and no Stop request.
Write an arrow from MOVING to DELIVERED. Put AtDrop on that arrow. This is the observation that proves normal completion.
Write an arrow from MOVING to INTERRUPTED. Put Stop, loss of drive readiness, or missing arrival after five seconds on it. Five seconds is the chosen limit of this teaching station. A real limit needs a travel distance, speed range, acquisition delay and a reason for the permitted margin.
The diagram is incomplete until you can leave DELIVERED and INTERRUPTED. Delivery needs acknowledgement and a new carton at pickup. Interruption needs a recovery procedure that reconciles the physical carton, then an eligible reset. Neither clearing a sensor nor removing a fault is a new Start request.
The workbench simplifies recovery: release Start and Stop, restore drive readiness, move the carton back to pickup, then issue a new reset edge. That is a declared model procedure, not a universal machine-recovery rule. A real interrupted carton may need removal, inspection or disposal.
Fifth: turn the design into three passes
Now the editor has a specific job. Write the program in three passes.
Pass 1 reads events and updates time. Detect a new Start press. Detect acknowledgement and reset events. Call the travel timer every evaluation, including evaluations where its enable is FALSE.
Pass 2 decides one next state. Begin by assuming the state remains unchanged. Evaluate the current state's exit conditions in their agreed priority order. Store the result as the next state.
Pass 3 assigns the physical output once. Combine the state's request with the conditions that must remain true. Do not scatter writes to Q_Belt through every phase.
The final output decision is short because the earlier design did its work:
Q_Belt := (State = 10) AND DriveReady
AND NOT AtDrop AND NOT StopButton;
In this example 10 is the numeric value assigned to MOVING. The workbench shows the preceding code that reaches and leaves that state. A target project can use a named enumeration if the controller supports it; the decision does not depend on the chosen number.
Read the expression aloud: “Request the belt while the accepted move is active, the drive is ready, arrival has not been detected, and Stop is absent.” Every phrase points back to a requirement. If you cannot explain a term that way, investigate it before adding another one.
Make the smallest wrong program fail
Try this tempting shortcut:
Q_Belt := StartButton AND NOT AtDrop;
Press Start, let the carton leave pickup, then release Start. The belt stops between sensors. The shortcut forgot the accepted job.
Try the opposite shortcut:
Q_Belt := NOT AtDrop;
It runs before anyone has asked for a cycle. It also starts again when a delivered carton is removed. It has no idea what “one carton per request” means.
These examples are useful because each failure names missing information. The first needs remembered intent. The second needs both intent and a completion state. You do not fix either by adding a mysterious delay.
In the workbench, switch on only the memory rule and run the four tests. Then add Stop priority, fresh-Start acceptance and the arrival watchdog. Read the changed source after each addition. Which condition appeared? In which pass? Which test now distinguishes the two drafts?
Keep five artifacts beside the program
You do not need an elaborate document for this exercise. You do need five small artifacts another person can read:
- The output contract: what the command physically requests.
- The rule list: acceptance, continuous permission, normal completion and interruption.
- The signal map: where the necessary evidence actually comes from.
- The state-and-transition table: remembered purpose and the conditions on each arrow.
- The test histories: inputs over time and the expected state and output.
These are the route from a machine sentence to code. You will refine them throughout the book. A second actuator, a network peer or an analog measurement adds work to the route; it does not remove the route.
Ordinary control examples here are teaching models. Protective safety functions, electrical stopping arrangements and real recovery procedures need their own engineering and validation. Keep that boundary visible when moving from a browser exercise to equipment.
The complete first program
Here are the decisions assembled into one program. Trace each assignment back to the artifacts above. The declarations start in INTERRUPTED, so a cold start requires the declared recovery check before a fresh Start can be accepted. The interactive workbench begins at an already established IDLE condition to let you compare drafts quickly; that is a different starting condition, not an automatic restart policy.
The listing uses local teaching signals. Bind and verify physical I/O in the target project. State values are non-retained here; an actual controller's startup and retention configuration must match the agreed recovery design. This is ordinary control, not a protective safety function.
PROGRAM Transfer
VAR
StartButton, StopButton, DriveReady : BOOL;
AtPickup, AtDrop, AckButton, ResetButton : BOOL;
StartPulse, AckPulse, ResetPulse : BOOL;
StartWasDown, AckWasDown, ResetWasDown : BOOL := TRUE;
State : INT := 90;
NextState : INT;
Q_Belt : BOOL := FALSE;
TravelWatch : TON;
END_VAR
(* PASS 1: events and elapsed time, evaluated every scan *)
StartPulse := StartButton AND NOT StartWasDown;
StartWasDown := StartButton;
AckPulse := AckButton AND NOT AckWasDown;
AckWasDown := AckButton;
ResetPulse := ResetButton AND NOT ResetWasDown;
ResetWasDown := ResetButton;
TravelWatch(IN := State = 10, PT := T#5s);
(* PASS 2: one decision about remembered work *)
NextState := State;
CASE State OF
0: (* IDLE *)
IF StartPulse AND DriveReady AND AtPickup
AND NOT AtDrop AND NOT StopButton THEN
NextState := 10;
END_IF;
10: (* MOVING: priority is visible in the order *)
IF StopButton OR NOT DriveReady THEN
NextState := 90;
ELSIF AtDrop THEN
NextState := 20;
ELSIF TravelWatch.Q THEN
NextState := 90;
END_IF;
20: (* DELIVERED *)
IF AckPulse AND AtPickup AND NOT AtDrop
AND NOT StopButton THEN
NextState := 0;
END_IF;
90: (* INTERRUPTED *)
IF ResetPulse AND DriveReady AND AtPickup
AND NOT AtDrop AND NOT StopButton
AND NOT StartButton THEN
NextState := 0;
END_IF;
ELSE
NextState := 90;
END_CASE;
State := NextState;
(* PASS 3: exactly one writer of the physical request *)
Q_Belt := (State = 10) AND DriveReady
AND NOT AtDrop AND NOT StopButton;
END_PROGRAM
Read the decisions, not just the instructions
The three previous-button values begin TRUE. A held button at startup therefore cannot masquerade as a fresh press. After the program has observed a released button, a later press can produce an edge. Those detectors run even when the sequence is interrupted.
The timer is called before selecting the next state. Its enable uses the state at the start of this evaluation. An accepted move first enables timing on the following evaluation. Include that ordering, task period and timer resolution when deriving a real travel limit. Five seconds here is an exercise parameter, not an assertion about a real machine's stopping performance.
NextState := State means “keep doing the same work unless a listed transition wins”. The CASE statement selects one branch using the current state, so returning from INTERRUPTED to IDLE does not also run the IDLE branch in the same evaluation. Reset is not Start.
The output uses the updated State and repeats the continuous permissions. It does not use AtPickup, because the carton normally leaves pickup during travel. Putting AtPickup in that final expression would interrupt a correct move as soon as it began.
Prove three things before you add anything
| Input history | Expected result | What a different result would expose |
|---|---|---|
| Establish recovery; press and release Start before arrival. | MOVING remains active and Q_Belt stays TRUE. | Missing remembered intent or an acceptance-only condition incorrectly used continuously. |
| While moving, present Stop and arrival together. | INTERRUPTED; Q_Belt FALSE. | An incorrect priority or another writer of the output. |
| Keep Start held through arrival, remove the carton, load another and acknowledge. | Return to IDLE without motion. Release and press Start to request a new cycle. | A level request accidentally standing in for a fresh event. |
The next chapters develop fault records, device feedback, modes and richer recovery. Do not silently assume this compact first program already contains them. Its value is that you can explain every line and show exactly which contract it implements.
Try it
A customer changes our station: “The operator should jog the belt forward only while holding Jog. A carton at PE2 must block forward jogging. Auto mode should still deliver one carton per Start.”
Write the first five artifacts. You do not need to implement a complete mode system yet. Identify the two internal requests, the one physical output owner, which request needs memory, and where the mode conditions belong. Give a test in which Jog is held while Auto is selected.
Work through the answer
The two internal requests are AutoRunRequest and JogRequest. Q_Belt remains the one physical command. AutoRunRequest belongs to the remembered Moving phase. JogRequest depends on the current button and Manual mode; it does not need to remember a released button.
JogRequest := ManualMode AND JogButton;
AutoRunRequest := AutoMode AND (State = 10);
Q_Belt := (AutoRunRequest OR JogRequest)
AND DriveReady AND NOT AtDrop
AND NOT StopButton;
This is an output-arbitration excerpt, not the whole mode design. The actual mode logic must prevent simultaneous ManualMode and AutoMode and define what happens to an accepted automatic move when the operator changes mode.
The test starts in Auto with no accepted cycle. Hold Jog and keep all physical permissives true. Q_Belt must remain FALSE, because the manual request is gated by ManualMode. That one negative test exposes an ungated jog branch.
Now move to the behaviour contract. We will make the rule list precise enough that two programmers can implement it without inventing different machines.