learn · South Africa
Ladder logic basics — read and write your first rung
A practical ladder logic basics tutorial. Contacts, coils, branches, the scan cycle, and a first start-stop rung — written for South African PLC learners.
Ladder logic basics is the part of PLC work that almost every learner thinks they understand after one afternoon and almost nobody actually does. The notation looks simple — two vertical lines, a few horizontal rungs, contacts that look like switches, coils that look like relays — and that simplicity is exactly what trips people up. The shapes are easy. The semantics underneath them are not. This tutorial walks through the small set of rules that, once they are reflexes, lets you read any ladder program on any brand, and write your own start-stop rung that actually works the first time you download it.
We will keep the examples concrete. Real instructions, real addresses, real ASCII rungs you can copy into the simulator and watch run. By the end of the page you should be able to look at a printed ladder program from a Siemens TIA Portal export, a Rockwell Studio 5000 PDF or a CODESYS printout and read it like prose.
Try the simulator →The two power rails — left and right
Open any ladder program and the first thing you see is two vertical lines running down the page. Those are the power rails. The left rail is conventionally labelled L1 — the live, the source, the place power "comes from". The right rail is labelled N — the neutral, the return, the place power "goes to". Each horizontal line between them is a rung. Power is imagined to flow left to right across a rung when the conditions on that rung are true.
This is a metaphor. The PLC processor is not actually pushing 24 V DC across your program. The CPU is reading bits from the input image table, evaluating Boolean expressions one rung at a time, and writing bits to the output image table. Nothing on the silicon resembles a relay. The ladder shape is a leftover from the 1970s when PLCs replaced banks of physical relays on car-plant wiring panels, and the engineers who built the first PLCs deliberately kept the visual language so the existing electricians on the shop floor could read it.
Why does this matter for a learner in 2026? Because the metaphor has a sharp edge. The "power flow" picture is helpful for reading individual rungs but it misleads you about what happens between rungs. Between rungs, there is no power flow. There is only the scan cycle, which we get to in section five, and the scan cycle is the rule that actually decides what your program does. Hold the metaphor loosely. It is a teaching tool, not a model of the machine.
A useful habit while you are learning: read each rung as a sentence. "If the start button is pressed AND the stop button is not pressed, energise the motor coil." That sentence is the rung. The shapes are just notation for the sentence.
Contacts — XIC and XIO
A contact is the input side of a rung — the condition that has to be true for power to "flow" through it. There are two basic contacts and you spend ninety percent of your time using them.
The first is XIC — Examine If Closed. The shape is --| |--. It evaluates true when the bit it points at is 1, false when the bit is 0. Despite the name, it has nothing to do with whether the physical button on the panel is pressed. The "closed" in the name refers to the conceptual relay coil being energised, which closes the contact, which lets power through.
The second is XIO — Examine If Open. The shape is --|/|--. It evaluates true when the bit is 0, false when the bit is 1. It is the logical inversion of XIC. Newcomers sometimes call it a "normally closed contact" because that is its physical-relay origin, but in the program it is just NOT bit.
Here is where almost every new PLC programmer trips. The state of the bit and the state of the field device are two different things, and the wiring decides the relationship between them. Take an emergency stop button. Industrial safety practice says wire it normally-closed: the button physically holds the circuit closed when nothing is wrong, and pressing the mushroom head breaks the circuit. That means when the plant is healthy, the input bit reads 1 (current is flowing in the input loop). When somebody slaps the e-stop, the bit reads 0. So in your ladder, you draw the e-stop as an XIC contact — --| |-- — because you want the rung to be true when the bit is 1, which is when the e-stop is not pressed. The shape on the screen is the opposite of the device on the panel. Get this wrong and your motor will only run when the e-stop is mashed, which is funny exactly once.
Vendor naming differs but the idea is identical:
- Allen-Bradley / Rockwell — XIC and XIO. The names are mnemonic and the symbols are
--| |--and--|/|--. - Siemens TIA Portal — Normally Open (N.O.) and Normally Closed (N.C.) contacts. Same shapes, different vocabulary. Siemens call them by the relay metaphor.
- CODESYS / IEC 61131-3 — the standard does not give the contacts a vendor name. They are just "contact" and "negated contact" and the symbols are
--| |--and--|/|--.
If you can read XIC, you can read N.O. If you can read XIO, you can read N.C. The rule "true when bit is 1" versus "true when bit is 0" is the rule that matters. The brand vocabulary is a translation layer.
Coils — OTE, OTL, OTU
A coil is the output side of a rung — what gets written when the rung evaluates true. There are three coil types you will use constantly, and using the wrong one is the second most common rookie bug.
OTE — Output Energise. The standard, non-retentive output coil. Symbol --( )--. It writes the rung's logical result directly to its bit every scan. If the rung is true, the bit is 1. If the rung goes false on the next scan, the bit is 0. The output follows the rung. This is what you want for ninety percent of motor and lamp outputs.
OTL — Output Latch. Symbol --(L)--. When the rung is true, it sets the bit to 1. When the rung goes false, it does not clear the bit. The bit stays 1 until something else clears it. On Siemens this is the S (Set) instruction. On CODESYS the function block equivalent is SR or RS.
OTU — Output Unlatch. Symbol --(U)--. When the rung is true, it clears the bit to 0. When the rung is false, it does nothing. On Siemens this is the R (Reset) instruction.
OTL and OTU come as a pair. An OTL without a matching OTU somewhere in your program is a bug. Always. The bit gets set, nothing ever clears it, and the moment you cycle power on a non-retentive memory area or fail to handle the first-scan case on a retentive one, your program does something nobody can explain. New techs use OTL when they should use OTE because they have not yet internalised the seal-in pattern, which is the next section. As a rule: if you can write the same logic with an OTE and a seal-in branch, prefer OTE. Reach for OTL/OTU only when the latched state genuinely needs to survive logic transitions across multiple rungs — for example, a fault flag that you want to hold until an operator presses a reset pushbutton.
The Siemens vocabulary is S and R, and the CODESYS / IEC 61131-3 standard formalises the same idea inside the SR (set-dominant) and RS (reset-dominant) function blocks where you wire the set and reset conditions explicitly. The behaviour is the same across vendors. The names change.
Series and parallel — AND vs OR
Two contacts in series on the same horizontal line are an AND. Both have to be true for the rung to be true.
| StartPB StopPB Motor
|---| |--------|/|--------------------( )---|
Read that as: "If StartPB is pressed AND StopPB is not pressed, energise Motor." Series is AND. Note the StopPB is drawn as XIO --|/|-- because we want "stop is not pressed" to be true for the motor to run.
Two contacts in parallel — branches off the main rung path that rejoin — are an OR. Either branch being true makes the rung true.
| ManualSwitch Lamp
|---| |---+--------------------------( )---|
| |
| AutoEnable TimerDone |
|---| |---------| |-------------------+
Read that as: "If ManualSwitch is pressed OR (AutoEnable is true AND TimerDone is true), energise Lamp." Parallel branches are OR. AND and OR combine freely. A single rung can have a series of three contacts in one branch, a parallel pair in the next, and a final series contact after the rejoin. The Boolean expression you read out of the rung is just the algebra you would write in any other language: (A AND B) OR (C AND D AND E). Ladder is one of five IEC 61131-3 languages and the IEC 61131-3 standard is the cross-vendor reference for what these expressions mean. The other languages — Function Block Diagram, Structured Text, Sequential Function Chart, Instruction List — are just different syntaxes for the same Boolean and arithmetic semantics.
The scan cycle — the part newcomers skip and regret
Here is the part where most beginner PLC books wave their hands and most beginners pay for it later. The PLC does not run your ladder program the way a Python script runs. It runs a scan cycle that repeats every few milliseconds, and understanding that cycle is the difference between a ladder programmer who writes working code and one who writes code that mysteriously misbehaves for a year before they finally read about scan order.
A typical scan cycle goes:
- Input scan. The CPU copies the state of every physical input terminal into the input image table — a region of memory holding the current value of every input bit. After this step, the inputs are frozen for this scan. If you wiggle a sensor mid-scan, the CPU will not see the change until the next input scan.
- Program scan. The CPU executes your ladder rungs in order, top to bottom, left to right, branch by branch. Each rung evaluates against the input image table and the current values of internal bits, and writes its result to the output image table — not directly to the physical outputs.
- Output scan. The CPU copies the output image table to the physical output terminals. Lamps light up. Motors energise. Valves move.
- Housekeeping. Communication with the HMI, diagnostics, watchdog refresh, network handling.
- Repeat.
A typical scan time is between one and ten milliseconds for a small to mid-size program on a current CPU like a Siemens S7-1500 or an Allen-Bradley CompactLogix 5380. Big programs with heavy motion or large arrays can push 20 ms or more. The scan time is reported live by the CPU and you can read it from the diagnostics page in the IDE.
Two consequences of the scan cycle that catch every newcomer:
Last write wins. If you have two rungs that both write to the same output coil — say Motor is the OTE on rung 5 and Motor is also the OTE on rung 12 — the value at the end of the program scan is whatever rung 12 wrote. Rung 5's value is overwritten before the output scan ever happens. This is why the rule is one coil, one rung. Two coils with the same address on different rungs is a bug. The IDE will usually warn you. Believe the warning.
Top-to-bottom matters within a scan, not across scans. A rung lower down the program reads the values that earlier rungs in the same scan wrote to internal memory. So ordering inside a scan does affect the result, but the resolution is one scan. Across scans, everything refreshes. This is why timer and counter behaviour can feel surprising when you first watch them — they are evaluated once per scan, with their accumulator updated based on the scan time, not against wall-clock time.
If you want the deep version of this, the Wikipedia ladder logic article covers the scan model and the historical relay-replacement origin clearly enough to be worth reading once.
Your first rung — start-stop with seal-in
Now we put the rules together. The classic first ladder program — the "hello world" of PLC work — is the three-wire start-stop motor circuit. It is the rung you will see drawn on a whiteboard at every interview, the one every textbook opens with, and the one you will reuse a thousand times over a career. Here it is:
| StartPB StopPB Motor
|---| |----+----|/|------------------------( )---|
| |
| Motor |
|---| |----+
Three contacts and one coil. Read it as a sentence: "If (StartPB is pressed OR Motor is already running) AND StopPB is not pressed, energise Motor."
Walk through what happens scan by scan. At the start, every bit is 0. The operator presses StartPB. On the next scan, the input scan reads StartPB as 1. The program scan evaluates the rung: StartPB is true, StopPB is true (because the NC physical wiring means the bit is 1 when the button is not pressed, and we drew it as XIO so it evaluates true on 1... wait, that is wrong for an NC-wired stop button). Read carefully — this is exactly the wiring trap from section two.
Let us say it correctly. The stop pushbutton is wired NC: the physical button passes 24 V to the input when the operator is not pressing it, so StopPB reads 1 in the rest state. We want the rung to be true when the operator is not pressing stop, so we draw StopPB as XIC — --| |-- — not as XIO. The rung becomes:
| StartPB StopPB Motor
|---| |----+----| |-------------------------( )---|
| |
| Motor |
|---| |----+
Now scan by scan. Rest state: all zero except StopPB which is 1 (NC button, not pressed, current flowing in the input loop). Operator presses StartPB. Input scan reads StartPB 1, StopPB 1. Program scan: StartPB true, OR branch lower path is false (Motor still 0), top branch passes through, StopPB true, rung true, Motor coil energises to 1. Output scan writes Motor 1 to the physical output, the motor contactor pulls in.
Operator releases StartPB. Input scan reads StartPB 0. Program scan: top branch StartPB false, but the parallel branch contact Motor is now 1 (we wrote it last scan), so the OR resolves true. StopPB still 1. Rung still true. Motor stays at 1. The seal-in is doing its job — the motor's own output bit feeds back into the rung and holds the rung true after the start button is released.
Operator presses Stop. NC stop button breaks current to the input. Input scan reads StopPB 0. Program scan: StopPB now drawn as XIC, evaluates false on 0, rung is false regardless of either branch, Motor goes to 0. Output scan drops the contactor. Motor stops. On the next scan StartPB is 0, Motor is 0, the seal-in branch is 0, rung stays false, motor stays off. The cycle is complete.
Why must the seal-in be drawn XIC (NO contact of Motor) and not XIO? Because we are sealing in the active state. The Motor bit is 1 when running. We want the seal-in branch to pass when the motor is running, so we evaluate true on 1, so XIC. If you accidentally drew XIO, the rung would only stay sealed when the motor was off, which is exactly the opposite of what you want — and a real bug we have watched a learner fight for forty minutes before they spotted it.
Common newcomer mistakes
Five mistakes that catch almost every new PLC programmer. Watch for them in your own code.
Stop button wired NO instead of NC. A stop button must be wired normally-closed for safety reasons — if the wire breaks or the terminal comes loose, you want the input to read 0 and the motor to stop, not the opposite. Wire it NO and a broken wire becomes an unstoppable motor. This is the lesson safety standards are built on.
Two output coils for the same address. Last write wins, every scan. The first coil's effect is invisible. The IDE may warn you. The fix is always to consolidate the logic onto one rung with the conditions OR-ed together.
Expecting top-to-bottom order to matter mid-scan. It does not, in any way that matters across scans. Rung 5 reads what rung 4 wrote during the same scan, but next scan everything refreshes from inputs. New techs sometimes try to "cascade" logic by relying on intra-scan ordering and write programs that work in simulation but fail intermittently on hardware. Stay clear of clever ordering tricks.
Latched coil never unlatched on power-up. OTL writes a 1 and leaves it there. If the latched bit is in retentive memory, it survives a power cycle. If your program has no first-scan handler that explicitly clears the latched bit on startup, the plant comes back online with stale state and a motor command from yesterday afternoon. Always handle first-scan initialisation when you use latches.
Ignoring first-scan initialisation generally. The first scan after a CPU goes from STOP to RUN is special — many internal bits are in their cold state, retentive memory may or may not be populated depending on the CPU and the memory area, and timers may have stale accumulators. Use the first-scan bit (S:FS on Allen-Bradley, M1.0 or the system memory byte on Siemens, _FirstScan on Sysmac, the SystemInfo.OnlineChange patterns on CODESYS) to clear, reset and initialise before the main logic gets a vote.
There are more mistakes than these five, but if you write the start-stop rung once a week for a month and consciously fall into and climb out of each of these traps, you will have built the reflex layer that working PLC programmers operate on.
Practice it in the simulator
Reading about ladder logic is half the work. The other half is writing rungs, breaking them, and watching what fails. Open our browser simulator and load the start-stop preset. The start_stop_motor preset has the three-wire seal-in rung pre-drawn — start NO, stop NC, motor coil with the seal-in branch. Run it. Press the start input. Watch the motor bit go to 1 and stay there. Release the start. Watch it stay 1. Press the stop. Watch it drop to 0.
Now break it on purpose. Change the stop contact from XIC to XIO. Run it again. The motor will start when you press stop. Fix it. Now duplicate the motor coil onto a second rung lower down with a different condition. Watch one of the coils get ignored. Delete the duplicate. Now replace OTE with OTL on the motor coil and try to stop the motor — you cannot, because there is no OTU. Add an OTU on a stop rung. Now it works again, but ugly. Convert it back to OTE with seal-in. Compare.
That sequence — twenty minutes of breaking and fixing — teaches more about ladder logic basics than two hours of reading. The simulator is built for exactly this kind of practice: change a contact, hit run, watch the bits, undo. No wiring, no panel, no risk to a real motor.
When the start-stop pattern feels automatic, the next tutorial in this series moves to timers and the TON / TOF / RTO instructions, and from there into counters, latching sequences, and your first state machine. Build the reflexes here first. Every later pattern leans on them.
Try the simulator →