Chapter 4 / 32 · Part 1 · First contact

Boolean logic and interlocks

AND, OR, NOT and XOR as machine permissives: seal-in circuits, fail-safe stops and interlocks you can read aloud.

Four operators run most machines

At the end of chapter 1 you wrote Horn := DoorOpen AND NOT MaintKey; and it read like the rule it implemented: the horn sounds when the door is open and the key is not on. This chapter is about writing all of your logic that way. A large part of any machine program is boolean logic (whether a motor may start, whether a valve may open, whether a press may close), and good boolean logic in ST reads aloud like the sentence in the functional specification.

There are four operators:

  • NOT A is TRUE when A is FALSE. In ladder, a normally-closed contact.
  • A AND B is TRUE only when both are TRUE. Contacts in series. & is an accepted alternative spelling.
  • A OR B is TRUE when at least one is TRUE. Contacts in parallel.
  • A XOR B (exclusive or) is TRUE when exactly one is TRUE, in other words when the two disagree. Ladder has no single contact for it.

Their complete behaviour fits in one table:

ABA AND BA OR BA XOR BNOT A
FALSEFALSEFALSEFALSEFALSETRUE
FALSETRUEFALSETRUETRUETRUE
TRUEFALSEFALSETRUETRUEFALSE
TRUETRUETRUETRUEFALSEFALSE

Flip the two inputs below through all four combinations and check every row:

PROGRAM Main
VAR
    A, B   : BOOL;   (* inputs *)
    AndQ   : BOOL;
    OrQ    : BOOL;
    XorQ   : BOOL;
    NandQ  : BOOL;   (* NOT (A AND B) *)
END_VAR

AndQ  := A AND B;
OrQ   := A OR B;
XorQ  := A XOR B;
NandQ := NOT (A AND B);

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: A, B.

A boolean expression is re-evaluated on every scan, exactly like the arithmetic of the previous chapter. AndQ is not "set" when both inputs come on and "reset" later; it simply is A AND B, recomputed ten times a second here. You will not need an IF for most of the logic in this chapter, and the programs are better for it.

From relay contacts to Boole and Shannon

The algebra is older than electricity in factories. George Boole published the rules of a logic with two values in 1854, in An Investigation of the Laws of Thought. Augustus De Morgan, his contemporary, gave his name to the two inversion rules later in this chapter. For eighty years it stayed with the logicians, until Claude Shannon's master's thesis at MIT, published in 1938 as A Symbolic Analysis of Relay and Switching Circuits, showed that series and parallel relay contacts obey exactly Boole's AND and OR. From then on a relay panel could be designed on paper and simplified with algebra before anyone picked up a screwdriver. That is the lineage of the ladder diagram, and of every line in this chapter.

Ladder and ST, rung by rung

If you have read ladder, every boolean expression maps onto a rung, and the mapping is mechanical: AND is contacts in series, OR is a parallel branch, NOT is a normally-closed contact, and the assignment is the coil at the right-hand end.

LadderStructured Text
NO contact AA
NC contact ANOT A
A and B in seriesA AND B
A in parallel with BA OR B
Coil QQ := ...;
A branch around a series pairparentheses: (A AND B) OR C

The motor starter you will meet later in this chapter looks like this as a rung:

     Start         StopOK      OverloadOK            Motor
 ----| |-----+------| |-----------| |-----------------( )----
             |
     Motor   |
 ----| |-----+

and like this in ST: Motor := (Start OR Motor) AND StopOK AND OverloadOK;. The parallel branch becomes the parentheses. Whatever sits to the right of the branch point is outside them, in series with the whole branch.

Two things do not translate one to one. Ladder has no XOR contact, so an XOR becomes two parallel paths, (A AND NOT B) OR (NOT A AND B). And ladder shows you the parentheses as geometry; in ST you have to write them, which is the subject of the next section. When you convert a rung by hand, trace each path from the left rail to the coil, write each path as an AND of its contacts, and join the paths with OR. The result is correct by construction, if not always the shortest form, and a later section shows how to shorten it.

Precedence: NOT, then AND, then XOR, then OR

When you combine operators, IEC 61131-3 applies them in a fixed order: NOT binds most tightly, then AND, then XOR, then OR. Comparisons such as > and = bind more tightly than all of the boolean operators except NOT. That is why Level > 80.0 AND PumpRunning works without parentheses.

So A OR B AND C means A OR (B AND C), the same way 1 + 2 * 3 means 1 + (2 * 3). Thinking of AND as multiplication and OR as addition is the old trick for remembering it, and it is exact: with TRUE as 1 and FALSE as 0, AND behaves like multiplication.

The seal-in: start, stop and memory in one line

The most famous circuit in industrial control is the motor starter with a seal-in (or holding) contact. Press Start and the contactor pulls in; an auxiliary contact on the contactor itself bridges the Start button, so the motor keeps running after you let go. Press Stop and the circuit breaks. In ST the whole thing is one expression in which the output appears on both sides:

PROGRAM Main
VAR
    Start      : BOOL;           (* input: green push button, NO contact *)
    StopOK     : BOOL := TRUE;   (* input: red push button, NC contact - TRUE when NOT pressed *)
    OverloadOK : BOOL := TRUE;   (* input: motor overload relay, NC - TRUE when healthy *)
    Motor      : BOOL;           (* output: contactor coil *)
END_VAR

Motor := (Start OR Motor) AND StopOK AND OverloadOK;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: Start, StopOK, OverloadOK.

Read it aloud: the motor runs if (Start is pressed or it is already running) and Stop is not pressed and the overload is healthy. Press Start and release it; the motor stays on, because on the next scan Motor is still TRUE from the scan before and holds itself in. Switch StopOK off (the button pressed) and it drops out; switch it back on and the motor stays off until you press Start again. Hold Start and press Stop together: Stop wins, because it is outside the parentheses. That priority is the reason for the shape. Stop must always win.

The variable Motor is doing something new here: it is reading its own previous value. Because variables keep their value between scans, the scan-to-scan memory comes for free. Chapter 8 covers latches, SR and RS blocks and the question of what a program should remember; the seal-in is the version you will see in every motor starter.

Look at what the seal-in remembers across a power cut, too. Motor is a normal VAR, so after a restart it is FALSE and the motor stays off until someone presses Start. That is the behaviour you want, and it is one reason the course warned against retaining run commands in chapter 2. On a Logix controller, where every tag keeps its value through a power cycle, the same line would bring Motor back TRUE; Logix programs clear such tags in a first-scan routine using S:FS, or seal in on a contactor feedback input instead of the output tag.

Fail-safe: why stop buttons are normally closed

Look at the declarations again. StopOK is TRUE when the button is not pressed. That is how stop circuits are wired everywhere. A stop button uses a normally-closed (NC) contact, so current flows through it when all is well and stops flowing when it is pressed. The PLC input sees 24 V as "OK to run".

The reason is what happens when something breaks. Wires come loose, terminals corrode, connectors get pulled during maintenance. With an NC contact, a broken wire looks exactly like a pressed button: the input goes FALSE and the machine stops. With a normally-open (NO) stop button wired to an input meaning "stop pressed", a broken wire looks like nobody is pressing it, and you only find out when someone needs to stop the machine and cannot.

The program below runs two identical conveyors, one with an NO stop button and one with an NC button. CableCut simulates the wire to both buttons coming off its terminal:

PROGRAM Main
VAR
    Start       : BOOL;   (* input: start button (both conveyors) *)
    StopPressed : BOOL;   (* input: the operator presses stop *)
    CableCut    : BOOL;   (* input: the stop cable comes loose *)
    InStopNO    : BOOL;   (* what the PLC terminal sees with an NO button *)
    InStopNC    : BOOL;   (* what the PLC terminal sees with an NC button *)
    ConveyorNO  : BOOL;   (* output: wired the wrong way *)
    ConveyorNC  : BOOL;   (* output: wired fail-safe *)
END_VAR

(* The field wiring: a cut cable gives 0 V either way *)
InStopNO := StopPressed AND NOT CableCut;
InStopNC := NOT StopPressed AND NOT CableCut;

ConveyorNO := (Start OR ConveyorNO) AND NOT InStopNO;
ConveyorNC := (Start OR ConveyorNC) AND InStopNC;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: Start, StopPressed, CableCut.

Start both conveyors and stop them with StopPressed: both behave. Now start them, switch CableCut on and try to stop them. ConveyorNC stopped the moment the cable came off. ConveyorNO keeps running and cannot be stopped from the button at all. The same thinking applies to every signal whose loss should mean "stop": guard door switches, pressure-OK switches, overloads, low-level cut-outs. Name such inputs for their healthy state (StopOK, GuardClosed, LubePressureOK) so the logic reads correctly and nobody inverts them by mistake.

Fail-safe beyond the stop button

The stop button is the case everyone learns. The same question applies to every input and output: when this signal is lost (a broken wire, a dead sensor, a blown fuse, a module pulled from the rack), which way should the machine go? Design so that loss of signal produces the safe or at least the harmless reaction. Process engineers call this de-energise to trip: the healthy state is the energised one, and anything that removes the energy produces the trip.

A high-level switch on a tank is the classic example. If it is wired so that the input is TRUE when the tank is full, a broken wire reads as "not full" and the fill valve keeps filling until the tank overflows. Wired the other way, with the input TRUE while the level is below the switch, a broken wire reads as "full" and the filling stops. The second arrangement gives a nuisance stop, which someone will investigate. The first gives a spill, which someone will clean up. Name the input for its healthy state, HighLevelClear rather than HighLevel, and the logic shows which way it fails.

Sensors that switch electronically have the same choice built in. A photo-eye usually has a light-operate / dark-operate selector or a wiring option, and a proximity switch comes in NO and NC versions as well as PNP and NPN. Here is a failure that takes a morning to find. A through-beam photo-eye that detects jams on a conveyor fails and is replaced from stores with the same model, set to the other mode. The input is now TRUE when the beam is blocked instead of when it is clear. The program, written for "beam clear is TRUE", now sees a permanent jam the moment the belt stops, and none while it runs. No code changed, and nothing in the program is wrong. The fix is the switch on the sensor, and the lesson is to record the operating mode of every sensor in the I/O list, next to the address.

Outputs deserve the same thought. A spring-return valve that closes when its solenoid is de-energised fails closed when the PLC output, the fuse or the cable fails. That is right for a fuel valve and wrong for a cooling-water valve on a reactor jacket, which should fail open. That is a hardware decision, made with the process engineer, but you need to know which you have, because it decides whether CoolingValve := TRUE means "open" or "close" in your code.

Interlocks and permissives that read aloud

Two words appear in every functional specification. A permissive is a condition that must be true before something may start. An interlock is a condition that stops something, or prevents it, whenever it is true, usually to protect equipment. In code both end up as booleans in the same expression, and the job is to make that expression readable by the process engineer who wrote the specification.

The way to do it is to give every condition a name, and to build the final command from names rather than raw inputs:

PROGRAM Main
VAR
    RunRequest         : BOOL;   (* input: auto sequence wants the pump *)
    SuctionValveOpen   : BOOL;   (* input: limit switch on suction valve *)
    TankHighLevel      : BOOL;   (* input: high-level switch in the receiving tank *)
    PumpFault          : BOOL;   (* input: drive fault *)
    LowSuctionPressure : BOOL;   (* input: pressure switch, pump would run dry *)
    StartPermissive    : BOOL;
    RunInterlock       : BOOL;
    TransferPump       : BOOL;   (* output *)
END_VAR

StartPermissive := SuctionValveOpen AND NOT TankHighLevel;
RunInterlock    := PumpFault OR LowSuctionPressure OR TankHighLevel;
TransferPump    := RunRequest AND StartPermissive AND NOT RunInterlock;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: RunRequest, SuctionValveOpen, TankHighLevel, PumpFault, LowSuctionPressure.

The last line now reads: run the transfer pump when it is requested, it is permitted and it is not interlocked. Anyone can check that against the specification. The two lines above it each answer one question, and each named variable shows up in the online watch, so when the pump will not start the maintenance technician can see which condition is missing instead of decoding a twelve-term expression. On a real HMI those intermediate booleans are the "start conditions" screen.

Notice that TankHighLevel appears in both. That is deliberate: it blocks a start and stops a running pump. Duplication of this kind is fine; what you must avoid is a condition that appears in neither because everyone assumed it was in the other.

(This example keeps the input names short. Wired fail-safe as the previous section recommends, the high-level switch would arrive as TankHighLevelClear and appear without the NOT.)

On larger plants the specification often comes as a cause and effect matrix: one row per cause (a switch, an alarm, a trip), one column per effect (a pump, a valve), and a mark where a cause acts on an effect. It maps onto code in exactly this shape. Each column becomes one interlock expression, the OR of the causes marked in that column, and each line of code can be checked against one column of the matrix. Review it that way, column by column, with the matrix printed beside the listing.

Mutual exclusion: forward and reverse

Some outputs must never be on together. A reversing motor has a forward contactor and a reverse contactor; if both close, they short two phases of the supply. Two valves that fill a tank from different product lines must not both be open, or the products mix. The logic is the same each time: each output is allowed only when the other is off, and a request for both at once is refused.

PROGRAM Main
VAR
    FwdRequest  : BOOL;   (* input: sequence or jog asks for forward *)
    RevRequest  : BOOL;   (* input: sequence or jog asks for reverse *)
    FwdFeedback : BOOL;   (* input: auxiliary contact of the forward contactor *)
    RevFeedback : BOOL;   (* input: auxiliary contact of the reverse contactor *)
    BothAsked   : BOOL;
    MotorFwd    : BOOL;   (* output: forward contactor coil *)
    MotorRev    : BOOL;   (* output: reverse contactor coil *)
END_VAR

BothAsked := FwdRequest AND RevRequest;
MotorFwd  := FwdRequest AND NOT BothAsked AND NOT RevFeedback;
MotorRev  := RevRequest AND NOT BothAsked AND NOT FwdFeedback;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: FwdRequest, RevRequest, FwdFeedback, RevFeedback.

Ask for forward, then for both: everything drops, because a program that receives two contradictory requests should do neither. Now ask for reverse while FwdFeedback is still on, as it would be for a few tens of milliseconds while the forward contactor drops out, or for ever if its contacts have welded. Reverse is refused until the forward contactor has really opened. Using the feedback rather than the other output is the important part: the output only says what the PLC asked for, while the auxiliary contact says what the contactor did.

This interlock protects the equipment, and it is not the only one. The control circuit of every reversing starter also has a hardwired electrical interlock (an NC auxiliary contact of each contactor in series with the coil of the other), and factory-built reversing assemblies add a mechanical interlock between the two contactors. The PLC logic sits on top of those, catches a request error early and reports it. Never remove the hardwired interlock because "the PLC already does it". A forced output, a wrong download or a failed output card would then be enough to short two phases.

De Morgan's laws: two ways to say the same thing

Two identities let you rewrite any expression with NOT in front of parentheses:

Thisis the same as
NOT (A AND B)NOT A OR NOT B
NOT (A OR B)NOT A AND NOT B

In words: "not (both)" is "at least one is missing"; "not (either)" is "neither". The same rule often comes to you phrased one way and reads better the other way. "The line may run if no zone has a fault" is NOT (Zone1Fault OR Zone2Fault OR Zone3Fault), which is also NOT Zone1Fault AND NOT Zone2Fault AND NOT Zone3Fault: three NC contacts in series, which is exactly how an electrician would wire it.

De Morgan is also how you check an inversion. Suppose Ready := GuardClosed AND AirOK; and you need the alarm for "not ready". The tempting NotReady := NOT GuardClosed AND AirOK; is wrong: NOT binds to GuardClosed only, so this is TRUE only when the guard is open and the air is fine. You want NOT (GuardClosed AND AirOK), which De Morgan turns into the readable NOT GuardClosed OR NOT AirOK: the guard is open, or the air is gone. When you invert a rule, invert every term and swap AND with OR. Missing one of those two steps is the common bug.

Simplifying and checking an expression

De Morgan's laws are two of a small set of identities that let you rewrite boolean expressions without changing what they do. These are the ones you will use:

IdentityExample of where it shows up
A AND TRUE = A, A OR FALSE = Aa bypass bit left at its default
A AND FALSE = FALSE, A OR TRUE = TRUEa forced or commented-out permissive that disables the whole line
A AND NOT A = FALSE, A OR NOT A = TRUEa condition that can never, or always, be met
A OR (A AND B) = Aabsorption: the B term does nothing
(A AND B) OR (A AND NOT B) = AB is irrelevant to the result
(A AND B) OR (A AND C) = A AND (B OR C)factoring out a shared condition

The absorption rule is the one that finds bugs. Here is a line of the kind you find in a program that has been edited by several people over several years:

(* Static listing: from a migrated program *)
Run := (Auto AND Request AND NOT Fault)
    OR (Auto AND Request AND NOT Fault AND Override);

The second path is the first path with one more condition in series, so by absorption it can never make Run TRUE when the first path is FALSE. Whoever added it believed Override let the machine run through a fault. It never did. The line reduces to Run := Auto AND Request AND NOT Fault;, and the real question, which goes back to the process engineer, is whether an override was meant to exist at all and what it should bypass.

When you simplify an expression, prove that the two versions agree before you replace one with the other. With four inputs there are only sixteen combinations, and the comparison = between two BOOLs does the checking for you:

PROGRAM Main
VAR
    Auto, Request, Fault, Override : BOOL;   (* inputs *)
    Original   : BOOL;
    Simplified : BOOL;
    Same       : BOOL;   (* TRUE while both versions agree *)
END_VAR

Original   := (Auto AND Request AND NOT Fault)
           OR (Auto AND Request AND NOT Fault AND Override);
Simplified := Auto AND Request AND NOT Fault;
Same       := Original = Simplified;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: Auto, Request, Fault, Override.

Walk the inputs through all sixteen combinations and Same never drops. Now change Simplified to something plausible but wrong, such as Auto AND (Request OR Override) AND NOT Fault, and find the combination where Same goes FALSE. This trick is worth keeping for real work: paste the old and new expressions side by side in a scratch program, and step through the truth table before the change goes to site. For more than five or six inputs, write the cases as a test instead (chapter 23).

Simplify for the reader, not for the CPU. A modern controller evaluates a twelve-term expression in well under a microsecond, so the shortest form is only better if it is also the clearest. The named permissives and interlocks of the pump example are longer than a single expression and much easier to maintain.

From a truth table to code

When a rule is easier to state as a list of situations than as a sentence, write the truth table first and the code second. Take a tank with two level switches. LowCovered is TRUE when liquid covers the low switch. HighClear is TRUE when the level is below the high switch, wired fail-safe as described above, so a broken wire reads as "high". Two inputs give four situations:

LowCoveredHighClearWhat it meansFill pumpDrain pump
FALSETRUEbelow the low switchallowednot allowed: it would run dry
TRUETRUEnormal working rangeallowedallowed
TRUEFALSEfullnot allowedallowed
FALSEFALSEhigh covered but low dry: impossiblenot allowednot allowed

The last row cannot happen in a healthy tank. Liquid cannot reach the high switch without passing the low one. It means a switch or its wiring has failed, and since you cannot know which, the safe reaction is to stop both pumps and raise an alarm. Every pair of sensors that measure the same thing at different points has an impossible combination like this, and checking for it costs one line. (A switch can also fail in a way that looks plausible, such as a low switch stuck "covered" in a tank that is really empty. Catching that needs a time comparison against the pumps, which comes with timers in chapter 9.)

Each output column becomes an expression. Read the rows where it is "allowed" and join them with OR, or, often shorter, describe the rows where it is not and invert. The challenge below asks you to do exactly that.

Does the right-hand side run? Short-circuit evaluation

If the left side of an AND is FALSE, the result is FALSE whatever the right side is. A compiler can skip evaluating the right side, and many programming languages promise that it will; C's && does. IEC 61131-3 does not make that promise. The 2003 edition says boolean expressions may be evaluated only as far as necessary to determine the result, which allows the short cut without requiring it.

Most of the time you cannot tell the difference. It shows up in two places. The first is a guard on the left that protects the right:

(* Static listing: only safe if the right side is skipped when Index is out of range *)
Ok := (Index <= 10) AND (Table[Index] > 0);

On a platform that evaluates both sides, Table[11] is read when Index is 11, and that can fault the controller or read whatever lies after the array. The second is a function call with a side effect on the right, such as a function that counts its calls or writes a log entry: whether it runs on a given scan now depends on the compiler.

The vendors differ, which is the reason to write code that does not care. CODESYS and TwinCAT evaluate both sides of AND and OR, and offer AND_THEN and OR_ELSE as vendor operators that do short-circuit. Other platforms optimise in their own way. Write every boolean expression so each operand is valid and harmless on its own; when one condition really must protect another, use an IF (chapter 5), which states the order outright. Chapter 14 meets the same problem in a loop condition.

Voting and discrepancy

Where a single sensor failing would be dangerous or expensive, plants install three and vote. A 2-out-of-3 (2oo3) trip fires when at least two of the three agree: one faulty switch can neither trip the plant on its own nor prevent a genuine trip. There is no special operator for it; you list the pairs:

Trip := (A AND B) OR (A AND C) OR (B AND C);

XOR is the natural operator for the other half of the job: noticing that redundant signals disagree. ChannelA XOR ChannelB is TRUE exactly when two channels that should match do not. In a real plant you would only alarm the discrepancy after it has persisted for a few hundred milliseconds (switches never change at precisely the same scan) and that delay is a timer, coming in chapter 9. Because XOR sits between AND and OR in the precedence order, which almost nobody remembers, always wrap it in parentheses.

XOR has one more trap with three inputs. "Exactly one of them" is easy to write as A XOR B XOR C, and that expression means something else.

Logical or bitwise: one operator, two jobs

AND, OR, XOR and NOT work on bit strings as well as on BOOLs. On a BOOL they are logical operators. On a WORD they work on all sixteen bit positions at once, each position independently: 16#00F0 AND 16#0FF0 is 16#00F0, and NOT 16#00FF is 16#FF00. The operator is the same and the type of the operands decides which job it does.

Programmers who come from C or C# expect two sets of operators, && for logic and & for bits, and in ST there is only one. & is simply another spelling of AND. The type system keeps the two jobs apart: Running AND StatusWord mixes a BOOL with a WORD and will not build on a strict compiler. That is a useful error, because the intent of such a line is never clear. If you want to know whether a word has any bit set, compare it: StatusWord <> 0. If you want one bit, select it: StatusWord.3.

Logix uses the same operators on its integer types (there are no WORD types in Studio 5000), so AND between two DINTs is a 32-bit bitwise AND. Siemens SCL and CODESYS also allow bitwise operators on integers in many cases. The next section uses them on WORDs, which is where they belong.

Bit masks on WORDs

This is how you test several flags in a packed status word at once. AND keeps the bits set in both operands, OR combines them, NOT inverts all sixteen. A mask is a constant with ones in the positions you care about:

PROGRAM Main
VAR
    AlarmWord : WORD := 16#0000;   (* input: 16 alarm bits from a remote valve terminal *)
    TripMask  : WORD := 16#00FF;   (* bits 0-7 stop the line, bits 8-15 are warnings *)
    TripBits  : WORD;
    LineTrip  : BOOL;
    Warning   : BOOL;
END_VAR

TripBits := AlarmWord AND TripMask;
LineTrip := TripBits <> 0;
Warning  := (AlarmWord AND NOT TripMask) <> 0;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: AlarmWord.

Type 4 into AlarmWord (bit 2) and the line trips. Type 256 (bit 8, 16#0100) and you get a warning only. Type 260 and you get both. The mask documents the classification in one constant instead of sixteen OR terms. Keep masks for exactly this kind of packed data from drives, valve terminals and protocol registers; for your own signals, separate named BOOL variables are clearer.

Work one by hand to see what the hardware does. With AlarmWord = 260 (16#0104):

BinaryHex
AlarmWord0000 0001 0000 010016#0104
TripMask0000 0000 1111 111116#00FF
AlarmWord AND TripMask0000 0000 0000 010016#0004
NOT TripMask1111 1111 0000 000016#FF00
AlarmWord AND NOT TripMask0000 0001 0000 000016#0100

Each column of bits is an independent AND. Setting, clearing, toggling and shifting bits get a full chapter later, in chapter 25.

A real burner trip of this kind is a safety function. It is engineered in a safety PLC or a certified burner management system, to IEC 61511 or the applicable burner standard, and the 2oo3 voting is configured there. The standard PLC version above is how you learn the logic, and how a standard PLC would vote three switches for an equipment-protection alarm.

Reviewing boolean logic

A review of interlock logic is worth doing with the specification open, one output at a time. For each output, find the single line that assigns it and read it aloud as a sentence; if you cannot, it needs named intermediate conditions. Check that every stop, trip and interlock sits outside the parentheses of any seal-in, so it wins over Start. Check that every input whose loss should stop something is named and wired for its healthy state, and that the I/O list says so, sensor modes included. Look for every mix of AND and OR without parentheses, and every XOR with more than two operands. Look for a guard on the left of an AND that protects the right, and move it into an IF. For outputs that must exclude each other, check that the interlock reads the other device's feedback and that the drawings show the hardwired interlock as well. And check that nothing in the standard program is presented as the protection for a person; that belongs to the safety system, and the program's job is to monitor it.

What to take into the next chapter

AND, OR, NOT and XOR are enough to write most of a machine's protective logic, and an expression assigned every scan is often clearer than any IF. Every rung of ladder maps onto one, with the parallel branches as parentheses. NOT binds first, then AND, then XOR, then OR, and you write the parentheses anyway. The seal-in (Start OR Motor) AND StopOK keeps its stop conditions outside the parentheses so Stop always wins. Signals whose loss should stop something are wired to fail safe and named for their healthy state, and mutual exclusion reads the other device's feedback, on top of a hardwired interlock. Permissives and interlocks get names of their own so the final line reads like the specification. De Morgan and absorption let you rewrite a rule without changing it, and a comparison of the two versions proves you did not. Do not rely on the right side of an AND being skipped, and remember that a chain of XORs counts parity.

Expressions compute a value every scan. Sometimes, though, the program has to choose between doing one thing and doing another: open the valve in one way in manual and another in auto, or do nothing at all. That is the job of IF, ELSIF and ELSE, and the next chapter starts with the trap that catches everyone who first moves logic like this seal-in into an IF.

Practise this chapter