TwinCAT 3 Tutorial: Ladder Logic Editor

This chapter is part of the TwinCAT 3 Tutorial.

In TwinCAT 3, the ladder logic editor shares a lot of functionality with the function block diagram editor. In some ways, the function block editor is just the ladder logic editor without contacts and coils. All of the function blocks available in the function block diagram editor are also available in the ladder logic editor, so I consider the ladder logic language to be a super-set of the function block diagram language.

Ladder logic, however, requires the concept of a “rung”, which is just power (represented as a BOOL signal) entering from the left, and connecting all of the language elements together within the rung (a.k.a “network” in TwinCAT 3).

The EN Input and ENO Output

Here is what the ADD function looks like in the function block diagram editor:

01 Add in FBD

Here is what it looks like in the ladder logic editor:

02 Add in LD

Notice that the editor added the EN input and the ENO output automatically. The EN input is an enable input. The instruction is only executed if the EN input is true. Here is a disabled ADD instruction (the EN input is FALSE), and you can see that the ADD instruction isn’t being executed because the Result is still 0:

03 Add in LD Disabled

While the EN input is true, the instruction executes:

04 Add in LD Enabled

Please note that the contact before the ADD instruction is generally unnecessary if you want it to execute all the time:

05 Add in LD No Contact Before

The ENO output is for continuing the rung to the right, so you can chain multiple instructions together like this:

06 Chained Instructions with ENO

However, once you start using longer variable names, doing this can cause rungs that are very wide and don’t wrap very nicely, so as a matter of ladder logic programming style, a rung like this should generally be split into two rungs:

07 Split instructions to multiple rungs

While the novice programmer often assumes that a program with fewer rungs is better, the only thing that should guide your choice is readability. One good rule of thumb is that any instruction that modifies a variable should be the end of a rung. Ladder logic programmers implicitly assume that inputs are on the left and outputs are on the right. In the example above, the ADD instruction modifies the Result variable, so we should stop the rung there and continue the logic in a new rung.

The Rung as RValue and LValue

Take a look at this typical rung made up of only contacts and coils:

08 RValue vs LValue

I highlighted a vertical line with a red circle around it. The vertical line separates two parts of the rung: the RValue and LValue. These names are actually confusing because the RValue is everything to the left of the vertical line, and the LValue is everything to the right of it. Why is this backwards? In a traditional programming language like C, consider a statement like this:

int a = 5 + 3;

Everything on the left of the equals sign is the LValue and everything on the right is the RValue. The LValue is the part being modified (the result of the assignment) and the RValue is the expression being evaluated. It’s just that in ladder logic, this is reversed. The assigned variable (the coil) is on the right and the expression (the inputs) are on the left.

What does this mean in practice? Basically it means that everything on the left of the vertical line will be evaluated first, and then everything on the right will be assigned. Only coils can go on the right, and coils can’t go on the left.

That might seem a bit restrictive if you’re coming from Allen-Bradley, where you can put a coil instruction anywhere you like. In practice, this is only a minor adjustment to your thinking.

Automatic Conversion Between Function Block Diagram and Ladder Logic

One interesting feature of TwinCAT 3 is that it can convert between function block diagram and ladder logic. If you copy the rung above and paste it into the function block diagram editor, you get this:

09 LD Converted to FBD

Interestingly, if you take this ADD instruction in function block diagram:

10 Add Instruction in FBD

…and then you paste it into the ladder logic editor, you’ll get this:

11 Add Instruction from FBD pasted into LD

Why didn’t it include an EN input and an ENO output when it converted it to ladder logic? As I said, the ladder logic language in TwinCAT 3 is a super-set of the function block diagram language. The ladder logic editor can support any valid function block diagram construction.

If you right click on a rung and choose Insert ADD Box from the context menu, you’ll get an ADD instruction with the EN input and ENO output. However, if you right click (typically on an empty rung) and select Insert Empty Box from the context menu, you’ll get a box without the EN input and ENO output. Then you can just type the name of the function you want at the top of the box:

12 Insert Empty Box and change to ADD

However, this presents a problem. The ladder logic editor won’t allow you to connect the output of the ADD instruction to a variable. You can insert another empty box and use a GT (greater than) instruction to compare it, which gives you a BOOL output, which you can then connect to a coil. What you should be able to do is right click and choose Insert Assignment, but this option isn’t enabled in the ladder logic editor. I actually believe this is a bug in the editor, and that it might be fixed in a future version. In the mean time, you can actually build your network in the function block diagram editor, copy it, and paste it into the ladder editor, and it will compile and run just fine.

Blocks with Variable Numbers of Inputs

Some blocks allow you to add extra inputs. For example, an ADD function can have 3 or more inputs:

28 ADD Function with 3 Inputs

To add an input, right click on the block and select Append Input from the context menu.

Contacts and Coils

Let’s take a look at the ladder logic specific instructions in more detail.

The Coil

Here is the coil instruction:

13 The Coil

The coil instruction does one thing: it assigns the result of the RValue expression (the result of the logic on the left) to the coil variable.

In other PLCs, the coil instruction does a second thing: during program startup (sometimes referred to as the pre-scan), it sets the variable to FALSE. This is an important property of coils in ladder logic: they are expected to default to off after a program start.

In TwinCAT 3, persistence is controlled by whether or not you declare a variable as a persistent variable. In real life, coils turn off when they lose power, so an experienced ladder logic programmer (or an electrician) looking at a rung like this:

Sealed in Run Coil

…will expect the Run coil to drop out if the program restarts (particularly due to the machine being turned off and back on again). If you make the Run variable persistent, then you will break this expectation. There is a principle of programming called The Principle of Least Astonishment. Don’t make a coil variable persistent because it breaks this principle. Use a Set/Reset instruction pair instead (explained later).

Similarly, as a matter of programming style, do not use the same variable in two different coil instructions. The editor and compiler will allow you to do this, but it’s considered bad ladder logic style. If you find that you have logic that requires you to do this, consider writing that logic in structured text language instead. Ladder logic should mimic physical relays whenever possible, and using the same variable in two coil instructions breaks this mental equivalency.

The Negated Coil

If you have a regular coil and you select it and press the slash key (“/”), it will change it into a Negated Coil:

14 The Negated Coil

This is an example of an instruction that was probably included because it was easy to add, but should never have been included in the language. It’s easy to explain what this does: during program execution, if the rung input condition is TRUE, it sets the variable to FALSE, and if the input condition is FALSE then it sets the variable to TRUE. The problem is that there’s no real-world counterpart to a negated coil.

Logically this:

15 Negated Coil Example

…is equivalent to this:

16 Negated Coil Example Equivalent to

The latter two-rung logic is the preferred form. Please avoid use of the negated coil instruction. One of the purposes of ladder logic is to allow electricians to debug it. The negated coil is not intuitive to an electrician and even an experienced ladder logic programmer will frown when they see it.

Set and Reset

While a coil should revert to “off” when the program is restarted, it’s sometimes preferable to remember the state of a variable through a program restart. This is the purpose of Set and Reset instructions:

17 Set and Reset Coils

In the physical world, we would use a latching relay. The latching relay has two inputs called Set and Reset (or Latch and Unlatch). Energizing the Set input will move an internal solenoid to change the relay to the “on” position. A spring mechanism will hold it in this position, so it will remember its state even after power is removed. Likewise, energizing the Reset input will move the solenoid the other way, to the “off” position.

Note that in TwinCAT 3, to get the variable to retain its state, you will have to make it a persistent variable. The important thing to note is that when a ladder logic programmer sees a pair of Set/Reset instructions, they will expect this to be a persistent variable, so please don’t break this expectation.

Normally Open and Closed Contacts

The Normally Open Contact:

18 The Normally Open Contact

…sets the rung output condition to the logical “and” of the rung input condition and the state of the contact’s variable. In the physical world, a normally open contact is a switch that only allows power to pass if the coil variable is in the “on” state. The normal state of a coil is off (de-energized) which means a normally open contact is normally “open” (i.e. not conducting).

The Normally Closed Contact:

19 The Normally Closed Contact

…sets the rung output condition to the logical “and” of the rung input condition and the negated state of the contact’s variable. In the physical world, a normally closed contact is a switch that only allows power to pass if the coil variable is in the “off” state.

The TwinCAT 3 ladder logic editor has a handy shortcut: to toggle a contact between normally open and normally closed, but select it and press the slash (“/”) key.

Positive and Negative Transitions

TwinCAT 3 supports Positive and Negative Transition contacts:

20 Positive and Negative Transition Contacts

What these instructions do is generate a one-scan pulse on a positive or negative transition of the given variable, and then “and” the pulse with the rung input condition. In other PLCs this type of instruction is sometimes referred to as a “differentiate up/down”, “rising/falling edge”, or a “one-shot rising/falling” instruction. It’s important to note that these TwinCAT 3 instructions generate pulses based on the transitions of the variable (SomeInput1 in this example) and not based on the rung input condition. (There are built-in function blocks called R_TRIG and F_TRIG that will look for transitions on their inputs.)

Internally, the Positive and Negative transition contacts operate by implicitly storing a copy of the variable and comparing the current value against the value from the last scan to determine if a transition occurred.

It’s worth noting that the internal variable copy seems to be initialized to FALSE on a program start. This means that if you use a Positive transition contact with a variable that is already TRUE when the program starts, the instruction will generate a pulse on the first program scan. This may be unexpected.

For instance, if you have a persistent variable, and used a Positive transition instruction on it, and it was TRUE when the program shut down and restarted, most programmers wouldn’t expect the Positive transition instruction to generate a pulse when the program started. Unfortunately you need to be aware of this and program accordingly. If it’s important, you can use the built-in R_TRIG function block and make the function block instance itself persistent. This will save the state of the internal memory variable through the program shutdown and restart.

For some added fun, the Negative transition instruction doesn’t behave this way because the internal state is initialized to FALSE so when it’s used on a variable that’s already FALSE on a program restart, the instruction doesn’t see a transition. (Ultimately I think this is a bug, and that the Positive transition instruction’s internal variable should have been initialized to TRUE to avoid this inconsistent and surprising behavior. It’s possible that Beckhoff may “fix” it in the future, but they might be hesitant because it could constitute a breaking change in a few programs.)

Using Function Blocks in Ladder Logic

In TwinCAT 3, a function block has inputs, outputs, and internal state. The obvious example is a timer or a counter. TwinCAT 3 has two timer instructions: a delay-on timer (TON) and a delay-off timer (TOF). Here’s an example of a TON instruction:

21 TON Example

What this does is create a variable (SomeInput1TMR.Q) where the on is delayed by 200 ms. The timer will still turn off immediately when SomeInput1 turns off. On the other hand, a TOF instruction turns on immediately when the input turns on, but delays turning off:

22 TOF Example

A delay-on timer is more common, but a delay-off timer is typically used when you have a fast pulse (like a quick button-push or brief sensor) and you want to elongate that into a longer, more consistent pulse width.

What differentiates a function block from a function is that a function block can store state internally. In the case of a timer, the obvious internal state is the Elapsed Time (ET) output. Since function blocks store internal state, TwinCAT 3 needs somewhere to put it, so you have to declare a variable. The variable is entered above the function block:

23 TON with Variable Highlighted

You have to declare this function block variable just like any other variable in your program:

24 TON Variable Declaration

I find that most function blocks are declared in the program variables, not in global variables, though there are some exceptions. Where you declare them is up to you and the needs of your application.

In addition to timers, you can also declare counters. Here is a count-up counter (CTU):

25 CTU Example

The count-up timer will start at zero (the CV output means current value) and will add one to the current value on each rising edge of the count-up (CU) input. The counter will turn on the Q output if the CV value is greater than or equal to the PV value. However, please note that the counter will continue counting even after the Q output is on. This is different than counters in some other PLCs. Turning on the RESET input causes the CV output to change to zero and the counter won’t respond to the CU input. Note that if you set PV to zero, the counter still functions but the Q output will always be on.

The opposite of the count-up timer is the count-down timer (CTD):

26 CTD Example

As you can see, instead of a RESET input, the count-down timer has a LOAD input. When the LOAD input turns on, it sets the current value (CV) output to the value of the preset (PV) input. Each rising edge on the count-down (CD) input will decrement one from the CV output value. The counter will count down to zero, but won’t continue counting below zero (it can’t because the CV output is an unsigned variable). The Q output will turn on if the CV output is zero.

Note that both the CTU and CTD counters are 16-bit counters (WORD variable), so their max values are 65535. You can, of course, write your own counter function blocks if you need one that can count higher.

Note that TwinCAT 3 also offers the up/down counter (CTUD) which is a combination of the CTU and CTD counters:

27 CTUD Example

As you can see, you can RESET the counter (to zero), LOAD the counter (to the preset value) and count it up or down. The counter is “reset dominant” meaning if both the RESET and LOAD inputs are on at the same time, the RESET input takes priority. Like the count-up counter, it can happily count above the preset value, but like the count-down counter it can’t count down below zero. The QU output turns on if CV is greater than or equal to PV, and the QD output turns on if CV equals zero.

The Many Faces of the MOVE Function

The simple MOVE function:

29 MOVE Example

It copies the value on the left to the variable on the right. The input on the left can be any expression:

30 MOVE Example with Add

Here’s a useful case where you have an angle in degrees and you want to take the COS of it, which expects an angle in radians:

31 MOVE Example with COS and Conversion to Radians

…which is equivalent to this:

32 COS Example

The latter is probably more readable since COS is what you’re “doing” and the expression in the input is just a conversion. Here’s another typical use of the MOVE function to do unit conversions:

33 MOVE Example Convert inch to mm

Note that a purist would say that the value 25.4 needs to be declared in a constant, like INCH_TO_MM. Personally, for well-known conversions, I find that using the value 25.4 is simple and readable. Obviously you shouldn’t be using 3.14 for the value of PI because (a) it’s wrong(er) than than value available in the built-in constant and (b) the constant is shorter and more readable.

If you really don’t want to use a constant, consider creating a function, like in_to_mm(). This is both readable, short, and precise.

It’s also a very good idea to put the unit name in your variable name, such as Length_mm or Length_inch. In the United States and many facilities in Canada the operators will still want to see distances shown in inches, but Beckhoff motion control and servo products all use mm exclusively, so this type of conversion is commonplace. Putting the units in the variable name will help you keep track and make errors more obvious.

Variable Type Conversions

TwinCAT 3 has a full suite of built-in unit conversion functions. Some conversions can be done implicitly, like converting from an integer value to a floating point value, but you will still get a compiler warning. To get rid of the warning, or to do a conversion from floating point to integer, use an explicit conversion, like this:

34 Unit Conversion

Summary

The ladder logic editor in TwinCAT 3 is a powerful super-set of the function block diagram editor. It allows you to mix standard contacts and coils with the entire library of built-in function blocks, plus any new functions or function blocks you write.

Remember to always focus on writing readable logic. In particular, make sure you’re writing logic that an electrician can read, follow, and understand. The ability for an electrician to understand ladder logic is one of the main reasons that we write in ladder logic to begin with. Always keep that in mind.




This chapter is part of the TwinCAT 3 Tutorial. Continue to the next chapter: Writing your own Functions and Function Blocks.