BECKHOFF-Twincat 3 Tutorial

May 5, 2017 | Author: Jorge Andril | Category: N/A
Share Embed Donate


Short Description

BECKHOFF - Tutorial of TwinCAT v3...

Description

TwinCAT 3 Tutorial This is an in-depth tutorial on Beckhoff’s TwinCAT 3 PC-based automation software.

Twin cats? The tutorial is organized into a chapter format and is meant to be read like a book. Start here with the Table of Contents:

1. Introduction 2. Quick Start 3. Structuring PLC Data 4. Persistent Variables 5. Structuring PLC Logic 6. Multiple virtual PLCs 7. Ladder Logic Editor 8. Writing your own Functions and Function Blocks 9. Structured Text 10. Building an HMI in .NET 11. Introduction to Motion Control 12. Introduction to TwinSAFE 13. The Scope View 14. Part Tracking

1 - TwinCAT 3 Tutorial: Introduction In the control system industry we’re used to slow progress. PLCs lag behind PC technology by ten years or more, and we pay an outrageous premium for PLC hardwarecompared to the technology sitting on our desktop in our offices. Beckhoff has always pushed PC-based control systems based on commodity hardware like Intel x86 processors and Ethernet chips. With TwinCAT 3, their flagship automation software now supports multi-core processing, which leaves traditional PLC technology so far behind it’s not even funny.

On all new TwinCAT 3 PLC programs I start, I set the task to run every 0.5 ms by default, and I could run most of them much faster. Not only does the PLC logic execute every 0.5 ms, but the EtherCAT I/O bus for a reasonably large machine can also run at this speed. When you compare this with a traditional PLC with typical logic scan times (plus I/O refresh times) in the tens of milliseconds, you can see that TwinCAT 3 with EtherCAT is far better suited to any high speed process than your typical PLC. If you think scan times don’t matter, consider a conveyor line running at around 1 m/s (completely reasonable) and you have to fire an output as a specific point on the conveyor passes a given location (measured by an encoder). The difference between a 1 ms reaction time and a 10 ms reaction time is the difference between 1 mm and 10 mm accuracy.

The TwinCAT 3 solution also offers many advantages over the traditional PLC/HMI/Laptop combination we’re familiar with:



The development environment runs on the same machine as the PLC logic, so you don’t need a separate laptop. Going online is as simple as opening the development environment and clicking the Go Online button.



The HMI runs on the same machine as the PLC, so HMI-to-PLC communication responds incredibly fast. Transferring data between the TwinCAT 3 runtime and the HMI is as fast as a memory copy instruction. I’ve been able to transfer hundreds of kilobytes of data from a PLC array to a .NET application in a fraction of a second.



If you’re into writing your own HMI software (for example in .NET), the driver for the protocol (called ADS) is included for free.



The TwinSAFE safety editor is integrated into the development environment, so mapping signals between your safety program and your PLC program is trivial, and safety inputs can also be double-mapped both to the safety program, and to your PLC program for monitoring and alarming.



You can use source control applications like git, mercurial or subversion, and they can even integrate with the development environment.

Not only does TwinCAT 3 offer outstanding performance, but you can download it, install it, and try it out indefinitely for free! When you try to activate your first TwinCAT 3 PLC program, you’ll be prompted to generate a 7 day trial license. When that 7 day trial license expires, if you’re not done testing, you can just generate a new 7 day trial licensewithout reinstalling. Obviously you wouldn’t be able to run this in a production environment if you had to do this every 7 days, but the trial is a fully functional version of TwinCAT 3. Want to try something on your test bench before you commit to implementing it in production? Just install the trial version on an old PC and test it out. I’ve been a control system programmer for over 15 years, and I’m also a .NET programmer, but I’m a ladder logic programmer first and foremost. If you’re a control system programmer in North America, you’re likely familiar with AllenBradley’s (or Rockwell Software’s) RSLogix family of control software. There is no doubt that RSLogix has set the standard for ladder logic editors, and to be completely honest, no other editor comes close to the user-friendliness of their development environment, and I’m including TwinCAT 3.

Beckhoff is a German company and ladder logic just doesn’t seem to be popular on their side of the pond. TwinCAT 3’s ladder editor is a major improvement over the one in TwinCAT 2, but it still doesn’t have the polish you’d be used to if you’re coming from the RSLogix world. I guarantee you’ll be frustrated the first time you try it, but that’s why I’m writing this tutorial. In my experience, programming ladder logic in TwinCAT 3 can be just as productive as in RSLogix, and it doesn’t need to be painful. Also, keep in mind that TwinCAT 3 is very new and Beckhoff continues to support and improve it.

If you’re familiar with RSLogix 5000, you may find some of my examples familiar. I’ve tried to organize my TwinCAT 3 projects in a way that’s similar to how most RSLogix 5000 programmers would organize their projects. That made the transition easier for me, and I hope it will for you too.

2 - TwinCAT 3 Tutorial: Quick Start This “Quick Start” is actually rather long, but it’s going to take you through downloading, installing, configuring, programming, building, activating, going online, forcing, and even making online changes to a TwinCAT 3 PLC program. When you’re done you will have a basic understanding of the TwinCAT 3 system and how it works.

Prerequisites TwinCAT 3 can run on a plain-vanilla copy of Windows 7. As of June 2016, Beckhoff says TwinCAT 3 is fully supported on Windows 10 (with TwinCAT version 3.1.4020.0).

One of Beckhoff’s main product lines is industrial PCs and if you buy their industrial PCs, you’ll get a discount on the TwinCAT 3 license. Also, the license cost scales with the processing power of the PC. Their most expensive license tier is for “3rd party hardware”, which means any PC you didn’t buy from them. I have no opinion on whether you should buy a Beckhoff industrial PC or a commercial PC. One thing to keep in mind is that Beckhoff’s top of the line industrial PC won’t have quite as much processing power as the newest commercial PCs on the market today, so if you’re really concerned about performance, you’ll probably go with a commercial PC. On the other hand, industrial PCs are hardened against tougher environmental conditions like dirt and temperature, and they come in nicer form factors for mounting inside of a control panel. They also take a 24V input rather than running from the AC mains. Whatever you do, make sure you invest in a good UPS as well.

BIOS Settings If you use a 3rd party PC, you may have to change the following settings in the BIOS:



Turn off Hyper-Threading (Intel Core-i7 specifically)



Turn on Intel Virtualization Technology Extensions (VT-x), which is required for the 64-bit version of TwinCAT 3

Optional: I/O Whether or not you’re hooking up to a legacy system, or starting fresh, I highly recommend using an EtherCAT I/O bus. The performance is phenomenal compared to any other bus on the market today, including any other Ethernet-based technology, and even SERCOS. The price is also quite reasonable because the technology is based on commodity Ethernet hardware.

If you do want to go with EtherCAT, you can’t just use any Ethernet card in the PC as your bus master. It has to be an Intel chip, as that’s the only chipset that Beckhoff’s EtherCAT bus master seems to support. While you can buy a compatible card from Beckhoff, I’ve also had success with commercial off-the-shelf Ethernet cards. Your mileage may vary. For a list of compatible Ethernet chips, do a Google search forbeckhoff ethercat compatible cards or visit the following URL:http://infosys.beckhoff.com/english.php?content=content/1033/tcsystemmanager/reference/ethercat/html/ethercat_ supnetworkcontroller.htm As of this moment, an Intel Gigabit CT PCI-E Network Adapter EXPI9301CTBLK is a reasonably priced commodity card that works well. If you want to try TwinCAT 3 without any hardware attached, you can do that too, and you won’t need an EtherCAT master at all.

If you do happen to need to interface to legacy I/O buses like DeviceNET, rather than installing a DeviceNET bus master card in the PC itself, I highly recommend starting with an EtherCAT bus to an EtherCAT bus coupler such as an EK1100, and then buying one of the many different EtherCAT-to-whatever bridges that Beckhoff sells, as this is usually less expensive in the short run, and lets you expand your system with lower cost (and higher performance) EtherCAT I/O in the future.

Optional: Visual Studio 2010 Professional If you’re already a .NET programmer then you may already have Visual Studio 2010 Professional installed. If that’s the case, TwinCAT 3 will actually install as an extension to VS2010. If you don’t have it installed, don’t worry; TwinCAT 3 will install the Visual Studio Shell instead.

You may wonder if you can use the free versions of Visual Studio such as Visual C# 2010 Express edition or Visual Basic .NET 2010 Express Edition. While you can have them installed, and you can use them to write programs that communicate with the TwinCAT 3 runtime, TwinCAT 3 won’t install as an extension to these products.

Downloading TwinCAT 3 The TwinCAT 3 installer is free to download, but it requires registration. Beckhoff periodically releases new versions, so to get the very latest, go to their product web page:

http://www.beckhoff.com/english.asp?download/tc3-downloads.htm Click the TE1xxx | Engineering link under the Software section. Click on the TwinCAT 3.1 – eXtended Automation Engineering (XAE) link. Note that XAE includes the runtime (XAR) as well. Once you register you’ll receive an email with download instructions. Download the .zip file and extract it to some temporary directory.

Installing TwinCAT 3 In the directory where you unzipped the downloaded TwinCAT 3 archive, locate the “exe” file and double-click it.

Follow the directions. Use the default options. When it prompts you about the Visual Studio 2010 shell, check the box to install it:

You will have to restart before it completes.

Create Your First TwinCAT 3 Project Now that you have TwinCAT 3 installed, check in your system tray for a new icon:

Just for reference, the color of the icon (blue) means the TwinCAT 3 runtime (PLC/motion controller/etc.) is in Config mode. That’s similar to “program mode” on an Allen-Bradley PLC. When the runtime is started the icon will be green. Red means it’s stopped.

Right-clicking on the icon will display a system menu:

Select TwinCAT XAE from the system menu. (The “E” stands for “Engineering.” That’s the programming environment.) After the splash screen you’ll see the Visual Studio Start Page:

Click the New Project+ link (highlighted) on the left side of the page. That will display theNew Project dialog:

I’ve only installed TwinCAT 3 without installing Visual Studio 2010 Professional, so you can only see one project type: TwinCAT XAE Project. If you have Visual Studio 2010 Professional installed, you’ll see all the regular project types,

plus this new TwinCAT XAE Project type. Make sure you’ve selected this type. Then give your project a suitable Name (see the highlighted field above). When you change the project name, the solution name will change to match. For our purposes they can be the same name. I chose “TwinCAT 3 Tutorial.” Click OK. This could take a minute. It’s building a new TwinCAT 3 project from a template. When it’s complete you’ll see a screen like this:

All of the files for this project are organized in the Solution Explorer on the right. Here’s a brief explanation of each section: The top node in the tree is the “Solution.” This is actually a Visual Studio construct, not a TwinCAT 3 object. In Visual Studio, a Solution is a group of Visual Studio Projects and an associated “build order.” In our case there’s only one Visual Studio Project: TwinCAT 3 Tutorial. If you’re using Visual Studio 2010 Professional, you would be able to add additional projects under the solution, such as a C# or VB.NET project (perhaps an HMI or a data collection application?). This would be convenient simply because you could keep everything in one place and even use the integrated source control addins to manage everything in one environment. Under the project you have:



SYSTEM: this section manages the runtime including the licenses. You can configure how many PC processor cores to use, which PLC or motion tasks run on each core, and what percentage of each core’s processing power to allocate to each task. Note that it’s possible to connect to runtimes on other computers, but for now we’ll only be concerned with the local runtime.



MOTION: this is where you add motion control tasks, and assign axes to each task. These tasks do the work of closing the control loops for you. Your PLC programs typically interface with the motion tasks using standard motion function blocks and the motion tasks do the heavy lifting of controlling the actual servo drives, etc. When you commission a new axis (setting up your motion parameters like speed, acceleration, deceleration, etc., you interact directly with the objects under the MOTION section of the tree).



PLC: this is where you add PLC projects. Each PLC project lets you define variables (memory assignment) and PLC logic.



SAFETY: if you’re using an EL6900 Safety PLC slice or the new TwinCAT 3 safety runtime (a certified safety PLC that runs on the PC itself), you can edit the safety logic here. This is handy because you can create connections between the PLC and the Safety logic for monitoring and status.



C++: one of the big new features or TwinCAT 3 is the ability to write C++ code that executes directly in the runtime.



I/O: this is where you configure your I/O network, and then create your mappings between all of the different modules. For example, if you define an input in the PLCmodule, you have to map it to a physical input in the I/O module.

TwinCAT 3 Solution File Structure One of the interesting things about TwinCAT 3 compared to other PLC programming environments is that it stores the “solution” in a collection of files and directories rather than in a single monolithic file. If you open the directory where you asked it to create your solution, you’ll see the following:

The TwinCAT 3 Tutorial.sln file is your “solution” file, and it corresponds to the top level node in your solution explorer in the right hand side of your TwinCAT XAE window. TheTwinCAT 3 Tutorial folder corresponds to the TwinCAT 3 Tutorial project under the solution. If you double click on that folder, you’ll see a file called TwinCAT 3 Tutorial.tsproj:

If you’re inquisitive, you can open this file with Notepad (or any text editor) and look at the contents. You’ll see that it’s just an XML file. When you start to add a PLC program or a Safety program in the editor, you’ll then see new folders created here, and theTwinCAT 3 Tutorial.tsproj file will include references to these child projects.

Adding a PLC Project

In TwinCAT 3, a “PLC” is like a “virtual” PLC. You can run multiple virtual PLCs on a single computer. This might actually be useful if you had a single PC controlling multiple machines. Each machine could be controlled by a single virtual PLC, which might be a good way to manage the complexity of a large system. You can also separate virtual PLCs by CPU core, so if your scan time started to get too high, you could break out part of that logic and run it in a separate virtual PLC and schedule the task that runs that second PLC on a different CPU core. Two virtual PLCs can still send data back and forth by mapping outputs on one PLC to inputs on the other PLC, and vice-versa.

The simplest case is to create a single PLC project. Let’s do that now. Right-click on the PLC node in the Solution Explorer (highlighted):

From the context menu that appears, select Add New Item+ to display the following wizard:

Enter a name for your new virtual PLC in the Name box (highlighted above). I chose “PLC1” but you may want to pick a more descriptive name, like the name of the machine or cell that this PLC will control. Standard PLC Project is the default template, so leave that selected and click the Addbutton in the bottom right. TwinCAT 3 will build a new PLC project for you from a template. This could take a few seconds to a few minutes depending on the speed of your computer. When it’s complete, you’ll see your new PLC in the Solution Explorer:

It gets a little confusing to talk about the “PLC Project” now because there are so many nodes in the tree, so from now on I’ll call the nodes by their name: PLC1 or PLC1 Project. Most of the interesting parts are under the collapsed PLC1 Project node, so click on the triangle next to that node to expand it:

Let’s go through them one at a time to explain their meaning:



External Types: you should never have to go in here, so don’t worry about it.



References: this is where you add references to external libraries (either ones that are included in TwinCAT 3, such as motion control libraries, or ones that you write yourself, which we’ll explain later).



DUTs, GVLs, POUs, and VISUs: these are just convenience folders that are created by TwinCAT 3, and it’s where you’re supposed to create your Data Unit Types, Global Variable Lists, Program Organization Units, and Visualizations, respectively. What do those mean?

o

Data Unit Type (DUT): this is similar to a structure or a user-defined type in other programming languages. It allows you to group a few pieces of data together into a single composite piece of data.

o

Global Variable List (GVL): as the name suggests, this is a list of variables (memory locations) that will be accessible from everywhere in this PLC (but not other PLCs) and can be accessed from your HMI.

o

Program Organization Unit (POU): contains logic (such as ladder logic, function block diagram logic, structured text, etc.) and associated local variables that are only accessible from inside that logic. There are 3 types of POUs: Programs, Functions, and Function Blocks, which will be explained later.

o

Visualization (VISU): these are your HMI screens. TwinCAT 3 has a built-in HMI system that takes advantage of the fact that you’re already running TwinCAT 3 on a computer with a screen. This is an optional add-on (sometimes called a “supplement”). You have to pay extra for the visualization license if you want to use it in a production machine, but in my experience it’s a reasonable price. Note that you can also use 3rd party HMI/SCADA systems with TwinCAT 3, and you can write your own .NET program. The 3rd party solutions typically use OPC for communication, and .NET programs use Beckhoff’s free DLL that lets you communicate directly with the runtime using their proprietary ADS communication library.

When you created the PLC project, TwinCAT 3 automatically created the first POU for you (a Program called MAIN). It did this so that it could automatically create a Task for you. To explain what this means, I’ve expanded a few more nodes in the Solution Explorer tree:

I’ve highlighted the important nodes, above. Under SYSTEM > Tasks, there is a new Task called PlcTask. A Task is what gets scheduled to run on the TwinCAT 3 runtime (that is, you can’t run a Program directly, you have to create a Task, and the Task then runs your Program). The Task has certain attributes, like how often to run. Double-click on the PlcTask node under the SYSTEM > Tasks node, and you’ll see this properties page for the Task:

Note the two highlighted fields above next to the Cycle ticks field. By default the task is configured to run once every 10 cycle ticks, and that corresponds to once every 10 ms. That means the TwinCAT 3 runtime itself is actually configured to “wake up” once per millisecond, and every 10 times it wakes up, it executes this Task. You can then configure what this Task does when it runs. TwinCAT 3 has already done this by linking PlcTask to the MAIN program in PLC1. That means our program called MAIN in PLC1 will be executed once every 10 ms.

Configuring the Real-Time All of the editing we’ve been doing in TwinCAT 3 XAE is a normal Windows program, and Windows is a general-purpose operating system, not a real-time operating system. The runtime component of TwinCAT 3 (called XAR) has to run under real-time conditions so that it won’t be pre-empted by other tasks on the computer while it’s busy executing machine control logic.

The TwinCAT 3 runtime pre-empts normal Windows programs by running in “ring 0” execution mode. In Windows there are only two execution modes: ring 0 and ring 3. Ring 0 is “kernel” mode, which is where all drivers and some internal windows code runs, and ring 3 is “user” mode, which is where normal windows programs run.

The runtime registers a timer interrupt to force execution of the runtime at a regular interval (called the clock tick). Interrupt routines run under ring 0, or kernel mode, so the runtime has access to the full resources of the computer. The runtime then executes Tasks, and each Task can do things like run PLC Programs, do Motion Control, interface with the EtherCAT I/O bus, or manage communications with the HMI. These interrupts pre-empt ring 3 (user-mode) programs so the runtime always gets priority over normal Windows programs, like the TwinCAT 3 editor or your HMI.

To configure the real-time behavior of TwinCAT 3, double-click on the SYSTEM > Real-Time node in the Solution Explorer (highlighted):

That will display the Real-Time configuration window:

Notice that the “Available CPUs” shows only one CPU and it’s allocated to Windows. This is actually incorrect, because my computer is a Core i3 with 4 logical cores. Click theRead from Target button to make TwinCAT 3 read the actual configuration from the PC:

Now it correctly shows 4 cores. The cores are listed in the top list box, numbered 0, 1, 2, and 3. This configuration allows you to allocate cores between Windows, the TwinCAT 3 runtime, or both (or neither). I have found that mixing Windows and TwinCAT 3 on the same core might cause problems (at least in earlier versions of TwinCAT 3), so if you have a 4core machine, I recommend allocating 2 cores for Windows and 2 cores for TwinCAT 3 runtimes. Changing this allocation requires a reboot, so save everything first.

Warning: only use the core isolation feature on Windows 10 if you have build 4020.0 or later. The “core isolation” feature is not compatible with Windows 10 in earlier versions. As far as I have been told, prior to build 4020.0, TwinCAT 3 won’t be able to see the cores set to “Other” and the only way to fix it is to use the “Reset this PC” feature of Windows 10. If you have Windows 7, to change the core allocation, first click the Set on target button and the following window will pop up:

Here, we just set the number of cores available to Windows, and the remainder will be available to the TwinCAT 3 runtime. Change the number 4 to a 2, and then click the Setbutton:

Click OK to the confirmation dialog box:

Then click OK to the Reboot question (save and close everything except TwinCAT 3 first):

Hang on while the system reboots. Once it has rebooted, open the TwinCAT 3 solution again (either by double-clicking on the TwinCAT 3 Tutorial.sln file wherever you saved it, or by opening TwinCAT 3 XAE again and selecting it from the Recent Files list. Now double-click on the SYSTEM > Real-Time node in the Solution Explorer again, and the real-time configuration window will look like this:

Notice that CPUs 0 and 1 still say “Windows” but cores 2 and 3 now say “Other”. Now we want to configure the TwinCAT 3 runtime to use cores 2 and 3. Under the RT-CPUcolumn, check the checkboxes next to cores 2 and 3, and uncheck the checkbox next to core 0:

Notice that the CPU Limit column showed 80% next to core 0 when it was checked but shows 100% next to cores 2 and 3. That’s because when the TwinCAT 3 runtime has to share a core with Windows, we have to limit how much CPU time

it can use, so that Windows still has some time to run user programs. However, when the runtime has a core all to itself, it can use to up 100% of the CPU time on that core.

The lower grid allows you to configure which tasks run on which core. Currently there are only 2 tasks: PlcTask and PlcAuxTask. The first is the task that was created when we created the PLC1 project. The second is an automatically created “housekeeping” task. For now, both are set to run on core 2, which is a bit wasteful of core 3, but in most real applications there’s a good chance of using two real-time cores. If your application has any motion control, it’s typical to run the motion control task on a separate core, and if you had more than one virtual PLC, you can choose to run them on different cores to balance the load. Next, check the Base Time columns in the grids above. By default, the TwinCAT 3 runtime “wakes up” every 1 millisecond and checks if it needs to execute any Tasks. You can optionally change the Base Time in the upper grid. 1 millisecond is actually the slowest option. I find that 0.5 ms (500 us) works quite well for most PLC tasks. Note that this is much faster than you’ll be used to if you come from a more traditional PLC brand. In the lower grid, you can see that even though PlcTask is running on core 2 with a 1 ms base time, it only runs once every 10 Cycle Ticks, so it effectively only runs once every 10 ms. I normally set this to 1 Cycle Tick so the PLC task would run every time the runtime wakes up. Note that your I/O refresh is tied to the speed of the Task using the I/O. That means if you set your PlcTask to run every 1 ms, then any I/O mapped to that Task also has to refresh once every 1 ms. The EtherCAT I/O bus is actually fast enough to do this even with a rather large number of devices on the bus. To change the PlcTask so that it runs every 1 ms, double click on the SYSTEM > Tasks > PlcTask node in the Solution Explorer and change the Cycle ticks from 10 to 1:

Edit the PLC Logic Add a Global Variable List Let’s assume we’re going to program a grinding machine. Perhaps there’s a grinding wheel that needs to be turned on and off via a motor starter. Let’s start by declaring some variables for our PLC to interface with the outside world.

Under the PLC1 project, right click on the GVLs folder (highlighted):

From the context menu that appears, choose Add > Global Variable List+ A dialog box will appear where you can enter a name for your new Global Variable List. Let’s enter a suitable name for this list, like Grinder:

Then click the Open button. This will do 2 things: it will add a new Global Variable List in the GVLs folder, and it will open the new Grinders GVL for editing. Your PLC1 project will now look like this in the Solution Explorer:

Rand the main editing area will now have a window open for editing the GVL:

An empty Global Variable List isn’t very useful, so let’s add some variables. You’ll have to type the following exactly correctly between the VAR_GLOBAL and END_VAR lines:

Note that I’ve created 3 variables, all of which are of type BOOL. The first variable (GrindingWheelMS) will represent the output for the motor starter that turns on the grinding wheel. The grinding wheel will run as long as this variable is on. The other two variables represent the inputs for start and stop pushbuttons (PBs) for the grinding wheel. We haven’t actually declared them as real outputs or inputs yet, but we’re going to come back to that later. A BOOL value in TwinCAT 3 can take on two values: TRUE and FALSE. These variables can be used anywhere in the PLC program. It’s possible to create two Global Variable Lists that both have a variable of the same name. In that case, if you used that variable name in your program, when you tried to build your PLC

project, the compiler will complain

that the name is “ambiguous”. In that case you will have to prefix the variable name with the name of your Global Variable List. Due to this possibility, I always use the fully-qualified name (including the Global Variable List name) whenever I use a global variable in my logic, and I will do that in the rest of this tutorial. This is optional (and unnecessary) if you only have one GVL.

Adding a Ladder Logic Program Now that we have some variables to control, let’s write some logic to control them. Right click on the POUs folder in the Solution Explorer and choose Add > POU+ In

the Add

POU dialog

that

pops

up,

enter

a

name

for

the

new

Program

(RunGrindingWheel),

choose Program under Type, and at the bottom underImplementation Language, choose Ladder Logic Diagram (LD):

Then click the Open button. Your new Program will show up under the POUs folder in theSolution Explorer, and the Program will be opened for editing in the main window:

The editing screen is split into two parts. At the top is your list of variable declarations. These are similar to the variable declarations in a Global Variable List, but any variables declared here can only be used within this Program. It’s a useful place to declare variables for storing temporary values or helper coils. In order to write a simple start/stop rung, we won’t need to declare any local variables.

The bottom part of the screen is where you write the ladder logic. The numeral “1” indicates that it has already added a blank rung #1 for you. Let’s start by adding a coil to this rung. You can right click on the rung and choose Insert Coil

from

the context menu, or you can click the coil icon on the ladder logic toolbar at the top of the screen:

If you don’t see that toolbar, it might be hidden. Try right clicking on the blank toolbar spaces at the top of the screen and selecting the TwinCAT PLC FBD/LD/IL toolbar. A third option is to click on the Toolbox vertical tab on the far left of the screen and drag and drop the coil icon from the toolbox over to the empty rung. Any of those options will result in a new coil being added to your empty rung:

Notice that the coil has three question marks over it. This is where you have to enter the name of the variable you want to assign to this coil. Type the fully-qualified name of the grinding wheel motor starter variable into this field: Grinder.GrindingWheelMS:

Now let’s add the start button. The easiest way is to click on the rung right where the small tick-mark is in the center of the rung, and then right-click and choose Insert Contact from the context menu. Now you will see a contact inserted on the left of the rung:

Then enter the fully-qualified variable name for the start pushbutton: Grinder.StartGrindingWheelPB:

That will turn the motor starter on when the start pushbutton turns on, but of course it won’t stay on unless we add a sealin circuit. To add a branch around the start button contact, select the contact, right click, and choose Insert Contact Parallel (below) from the context menu:

Edit the three question marks over the new parallel contact and change it toGrinder.GrindingWheelMS so that the coil seals itself in:

Now the motor starter will stay on after the start pushbutton input turns off, but it will keep running forever. Finally let’s add the stop pushbutton into the circuit as a normally closed contact. Select the section of rung between the branch on the left and the coil on the right (where the small tickmark is). Right click on this section of rung, and choose Insert Negated Contact from the context menu:

Finally, replace the three question marks above the normally closed contact with the fully-qualified variable name of the stop pushbutton: Grinder.StopGrindingWheelPB:

Finally, our start/stop circuit is complete! Remember to click the Save All button on the toolbar (it looks like a stack of three floppy disks).

Calling the Ladder Logic Program from MAIN There’s one last thing we have to do before we can run our program. When the PlcTaskruns, it will only execute the MAIN program, not our new RunGrindingWheelprogram. To get our program to execute every 1 ms, we need to “call” it from the MAINprogram. Double click on the MAIN program in the POUs folder of the Solution Explorer:

That will open the program in the Structured Text editor:

This looks very similar to the RunGrindingWheel program when we first opened it, but in this case, everything in the bottom is in Structured Text (ST) language instead of Ladder Diagram (LD). If you want, you can delete the MAIN program and replace it with a new MAIN program that uses Ladder Diagram. You have to remember to link thePlcTask to this new MAIN program because when you delete the old one it will lose the link. However, since the MAIN program’s only job is to call other programs (in our example), it’s simple enough to do this in Structured Text. Change line 1 of the MAIN program to the following:

That is, it’s just the name of the program you want to call, following by an open and a closed bracket, and finally a semicolon. All statements in Structured Text end in a semi-colon. You could add other program calls below this one on new lines if you wanted. The program will call them once, in sequence, every time it runs (which is once every millisecond).

Downloading and Going Online Build the PLC Project It’s a good idea to “build” the PLC project before you try to download it. This will tell you if you have any syntax errors or misspelled variable names. In the Solution Explorer, right click on the PLC1 Project node and choose Build from the context menu:

This could take a minute while it compiles the PLC project. At the bottom of the screen, there’s a tab called Output, and it will show you the progress of the compiler:

Take a look at the last line to see if there were any errors. You want it to say “1 succeeded” and “0 failed”. If it failed to build, you should look for error messages earlier in the output messages. Double-clicking on the error message should take you to the site of the error, or close to it.

Activate the Configuration Now that our PLC project compiled successfully, the next step is to “activate the configuration”. What this does is:

1.

Stop the TwinCAT 3 runtime (all running programs will halt, and I/O will turn off)

2.

Apply the real-time configuration to the runtime (number of cores, tasks, etc.)

3.

Copy the compiled program(s) to the runtime

4.

Apply the “mapping” configuration (we will discuss mapping later)

5.

Restart the TwinCAT 3 runtime in run mode

Note that when the runtime starts, it won’t automatically load the PLC program unless we “activate it as the boot project”, and it won’t start executing the PLC program unless we configure it to “autostart the boot project”. Do these two things by right clicking on thePLC1 node in the Solution Explorer:

Rand then choose Activate Boot Project+ from the context menu, and then do it again and choose Autostart Boot Project. Now go to the top menu and choose TwinCAT > Activate Configuration from the menu. You will have to confirm this action with the following dialog box:

Click the OK button. Since we haven’t purchased a runtime license for this machine, it’s going to ask us if we want to generate “trial licenses”:

Click the Yes button. Then it will ask you to enter a captcha-type randomly generated group of characters to prevent you from automating the generation of trial licenses. Enter the letters in the text box underneath exactly as they appear (yours will be different than mine):

Then click the OK button. This generates a “trial” license which will expire after 7 days. Don’t worry too much about exceeding this 7-day limit though, as you can just generate another 7-day license when that happens. You will have to purchase an actual license only when you deploy to a production system. After generating the trial license, it will ask you if you want to restart in run mode:

Click the OK button. You should see the TwinCAT icon in the system tray (if it’s visible) turn from blue (config mode) to red (stopped) and then finally to green (running). If that happens, the TwinCAT 3 runtime is running in run mode on your PC! If you look at the PLC1 node in the Solution Explorer window, you will also notice that it now displays a green rectangle, indicating that the PLC program is running:

Go Online Like most other modern PLCs, TwinCAT 3 supports online debugging. To see this in action, start by double-clicking on the RunGrindingWheel program so that it’s open in the editor’s main window. Then select PLC > Login from the main menu:

That puts the system into “online” mode and you can view the current state of the logic. Power is represented by the blue lines, and the status of the contacts and coils are represented by the blue rectangles in the middle of each. As you can see above, theGrinder.GrindingWheelMS coil is currently off, as are the start and stop pushbuttons.

Write Values to Variables (Online) Even though the pushbuttons and motor starter variables aren’t connected to anything physical, the code is running. We can watch the state of the motor starter variable change by writing new values to the pushbutton variables. Let’s start by simulating

someone

pressing

the

start

pushbutton.

To

do

that,

we’re

going

to

write

the

value TRUEto Grinder.StartGrindingWheelPB. The first step is to queue a value to write. Hover your mouse over the center of theGrinder.StartGrindingWheelPB contact and double-click the small rectangle in the center:

Next to the contact you can now see a queued write value (“TRUE“). You can change the queued write value to FALSE by double-clicking it again, and remove the queued write value entirely by double-clicking it a third time. When you’re ready to write the queued values to the PLC variables, choose PLC > Write values to all online applications from the drop-down menu at the top of the screen. You will immediately see the state of the rung change to reflect the new value:

As you can see, simulating the start pushbutton turning on caused the logic to turn on the Grinder.GrindingWheelMS motor starter variable. Now let’s simulate releasing the start pushbutton by writing the value FALSE to the start pushbutton:

Rand write the values by choosing PLC > Write values to all online applications from the drop-down menu at the top of the screen:

You can see that the motor starter variable (coil) stayed on. To finish the example, let’s simulate someone pressing the stop pushbutton by writing the value TRUE to theGrinder.StopGrindingWheelPB contact:

Writing the value then turns off the motor starter coil:

Writing a value to a variable is a one-time operation, so if you tried to write the valueTRUE to the motor starter coil, it won’t appear to have any effect, because 1 millisecond later, the program logic will execute and change the value back to FALSE. If you want to override the logic, you need to use the “force.”

Force Variables (Online) Forcing a variable is similar to writing a variable, except that the force overrides the logic of the program. For instance, we can force the motor starter coil to TRUE even though the logic is trying to keep it turned off. Start by double-clicking on the small rectangle in the middle of the motor starter coil:

You can see that we’ve queued the value TRUE, just like if we were going to do a variable write. However, in this case we’re going to choose PLC > Force values to all online applications from the menu at the top of the screen:

Forced variables are indicated with a small “F” in a red box next to the contact or coil that is forced. These variables will stay forced until you choose PLC > Unforce all values on all online applications from the menu at the top of the screen. You will also be asked if you want to unforce existing forces if you logout (go offline).

Using the TwinCAT PLC Toolbar We’ve been going through the PLC menu at the top of the screen for all of the online functions, but there is also a toolbar that can give you quick access to these functions. It looks like this:

If you don’t see it, try right right-clicking on the toolbar area near the top of the screen and making sure that the TwinCAT PLC option is checked. If it’s already checked and you can’t see it, then you might have too many toolbars on one line, and that would force it to shrink the toolbar to almost nothing, like this:

If that’s the case, drag and drop the shrunk toolbar onto a new empty toolbar line and it will expand fully for you.

Making Online Changes to PLC Programs TwinCAT 3 supports online changes to PLC program logic and variables.

TwinCAT 3 does not support online changes to the mapping between your PLC program and your physical I/O, so if you want to add or remove I/O, or you want to change the mapping between your PLC I/O and your physical I/O, you have to

use TwinCAT > Activate Configuration from the top menu, and you will then have to restart the runtime, which will restart your PLC programs. This will cause your machine to stop. The process for making an online change to a PLC program is:

1.

Make sure you are offline (logged out).

2.

Edit your PLC program logic and variables.

3.

Login (at the prompt, select Login with online change).

4.

If you’ve edited a variable (for instance, changed the type), it will warn you that the variable will be moved to a new memory location, and ask if you want to continue. This is safe to do unless you have an external program (like an HMI or a data collection system) reading or write variables in the PLC, in which case you may want to shut them down before continuing.

5.

Click Yes when it asks if you want to “update the boot project” (if you say No then your changes will be lost if you restart the runtime or reboot the computer).

Your changes will be applied without stopping the PLC program, and without any interruption to the machine.

Now let’s make a change to our logic. Stop buttons on machines are sometimes wired using a normally closed contact instead of a normally open contact (so the input is on when nobody is pushing the button and the input turns off when someone pushes it). The reason for this is so that the machine will stop if the wiring to the stop button is disconnected (and it also allows you to chain stop buttons in series). Let’s change our grinding wheel start/stop logic to work with a normally closed stop pushbutton. First, make sure that you are offline. Choose PLC > Logout from the menu if you haven’t done so already. Here is the original logic:

Left-click to select the contact in the middle (Grinder.StopGrindingWheelPB):

TwinCAT 3 has a handle shortcut for changing a normally closed contact to a normally open contact, or vice-versa: just press the slash key (“/”). Now your logic will look like this:

Login to the PLC by choosing PLC > Login from the menu. TwinCAT 3 will notice that the logic has changed and will ask you what you want to do:

Leave Login with online change selected and click OK. Next it will ask you if you want to update the boot project:

Click Yes. After that you will be online with the PLC, and your changes will be applied:

Notice that the Grinder.StopGrindingWheelPB contact is now a normally open contact, meaning the input has to turn off to stop the grinding wheel.

Designating PLC Inputs and Outputs When we built our example PLC program above, we didn’t actually define which variables were inputs and which were outputs, so as far as the runtime was concerned, all of the variables were internal variables and the PLC didn’t interact with any physical I/O.

Designating a variable as an input or output only makes it available for mapping to a physical input or output during the mapping phase (after we’ve configured our I/O devices, which will be covered later).

We designate a variable as an input or output by modifying the variable declaration. Here is the original list of variables in the Grinder Global Variable List:

First let’s designate the GrindingWheelMS variable as an output. Change the variable declaration to the following:

As you can see, I’ve added AT %Q* directly after the variable name. The Q means output. You can designate the other two variables as inputs like this:

This is the same, but I’ve replaced the Q with an I meaning “input.” I know this is a weird syntax, but it’s a throwback to processors where you could (or had to) explicitly map variables to an input or output memory area. In this case, the asterisk means “I don’t care where you put it, just put it in the input or output map accordingly.” Now you have to “build” the PLC project to see these variables show up on the PLC’s input or output map. Right-click on the PLC1 Project node in the Solution Explorer and choose Build from the context menu:

After the project is compiled, you will now see two new nodes under the PLC1 Instancenode:

Expand the inputs and outputs by clicking on the small triangle next to each node:

Now you can see the variables that you designated as inputs and outputs are available here for mapping. Normally you will map these variables to physical I/O, but they can also be mapped to I/O in the motion control task, the Safety program, or even to I/O from another PLC. That’s how you would be able to interlock two PLC programs together virtually without

running any physical wiring. The runtime takes care of copying the data between each task, or between tasks and physical I/O.

Beware that if you change the name or type of a variable that has been designated as an input or output, TwinCAT 3 will unmap that variable from any existing mappings, and will only give you a rather easy-to-miss message in the output window when the PLC project builds. When you activate the configuration and restart, you may be left wondering why your output won’t turn on, or why your start button isn’t working. Note that when you’ve mapped a variable, the icon will change to include a small arrow, so you can tell which ones are mapped:

(All I did here was map the PLC output Grinder.GrindingWheelMS back to PLC input Grinder.StartGrindingWheelPB, which is a rather silly thing to do in real life.) Whew! That concludes the Quickstart chapter of this tutorial! You’ve now covered all the basics of downloading, installing, configuring, programming, going online, forcing, and making online changes to your PLC program in TwinCAT 3. That’s a lot of new information, but I hope it gives you a good enough introduction to feel like you can experiment and play around with the system some more. When you’re ready, you can move forward in the tutorial with more advanced topics.

3 - TwinCAT 3 Tutorial: Structuring PLC Data TwinCAT 3 gives you a lot of options about how to organize the data (whether boolean, integer, floating point, string, or user defined data) in your PLC project. As with any PLC project, you want your data structures to follow naturally from the actual machine it’s controlling. I think we can all agree that Infeed.Conveyor.Running is easier to understand than bit[328].

Global vs. Local Variables If you’re coming from the RSLogix 5000 world then you’re probably already familiar with the concept of global vs. local variables. A ControlLogix has “controller scope” and “program scope” tags. Essentially “controller scope” tags are global (because every program can see them) and “program scope” is local because only the program they’re defined in can see them. It’s also pretty common to come across PLC projects where all the tags are declared in “controller scope”.

TwinCAT 3 also has global variables and local variables. Global variables live in “Global Variable Lists” and Local variables live inside programs, function blocks and functions (a.k.a “POUs”). As for what should go where, that’s ultimately your decision, but here are the rough guidelines I use:



Inputs and Outputs: Global



HMI Buttons and Indicators: Global



HMI Alarms: Global



Faults: Global



Interlocks: Global



Everything Else: Local

What do I mean by “Interlocks”? When you divide up your program, you typically divide it into modules that physically match your machine, so you might have a program for the Infeed Conveyor, one for the a Pick & Place, and one for a Robot. Most of your Infeed Conveyor logic shouldn’t care about the Pick & Place logic, except that you might want to prevent the Infeed Conveyor from running if the Pick & Place isn’t out of the way of the conveyor. In that case, you probably want to create a globalPickAndPlace.ClearOfInfeedConveyor variable. The Pick & Place logic is responsible for setting this using a coil, and the Infeed Conveyor logic uses it to prevent running the conveyor. This is an “interlock” signal and since two different programs need to access it, then it’s a good candidate for a global variable. You’ll also notice that everything accessible from an HMI is also global. This is just my preference. You don’t have to do thatR an HMI can access local variables, but since an HMI-accessed variable is kind of like an interlock, I like to give them Global status. When someone is reading or modifying your PLC logic they’ll tend to make the assumption that Local variables are only accessed, wellR locally. That means when they want to make a change, they’ll only check for usages

of the variable within that local scope. Checking the HMI for references to that variable is much harder. That’s why I suggest making HMI-accessed variables Global and also indicating with a comment that they are accessed from the HMI.

Global Variable Lists When you created your PLC project in TwinCAT 3, the wizard automatically creates aGVLs folder:

“GVL” stands for “Global Variable List.” It’s Beckhoff’s intention that you should put all your global variable lists in the GVLs folder, but I think that’s incorrect. The organization and structure of your PLC project should mimic the physical and logical organization of your machine. Let’s continue our example of a machine with an infeed conveyor followed by a pick & place. If those are two major components of the machine, then that’s a logical way to break down our logic and data. Let’s create two folders under our project called InfeedConveyor and PickAndPlacerespectively. Start by right clicking on the PLC Project node and selecting Add -> New Folder from the context menu. That will create a new folder called NewFolder1 and you can change the name to InfeedConveyor:

You can do the same to add a PickAndPlace folder:

I want to stop here and talk about folder ordering. First of all, TwinCAT 3 will order the folders and items in a folder alphabetically. Secondly, TwinCAT 3 has a bug where it will only re-order them after you save your project, close the solution and open it again. Initially they show up based on the original folder name, which was NewFolder1(hopefully this is fixed in a later version). Since the folders represent the major components of our machine, we want the ordering to flow logically from the way the machine works. If the machine works by parts being introduced to the machine on the infeed conveyor, and are then processed by the pick & place, then it makes the most sense to have the InfeedConveyor folder come before PickAndPlace folder. It just so happens that “I” comes before “P” in the alphabet, but this is only a happy coincidence. That’s why I suggest prefixing each folder with a number so you can enforce a logical ordering:

I used two digit numbers because that will allow it to sort correctly from 01 through 99. Don’t worry about needing to insert one later as renumbering even 10 or 20 folders doesn’t take very long.

Now let’s add a Global Variable List for the infeed conveyor. Right click on the01_InfeedConveyor folder and select Add -> Global Variable List+ from the context menu. That will display the Add Global Variable List dialog. Enter InfeedConveyorin the Name text box and click the Open button:

Now you’ll see an empty global variable list in the editor:

Now we have to imagine some global variables that our imaginary conveyor might have. I think it needs an output to turn on the motor (we’ll call that “Run”), a through-beam sensor across the end of the conveyor to sense when a part reaches the pick position (we’ll call that the “NoPart” input) and maybe this conveyor needs to signal to the pick & place that the conveyor is stopped (we’ll call that the “Stopped” interlock). Here’s what a global variable list with those 3 variables would look like:

Let’s look at the first variable:

NoPart AT %I* : BOOL; (* Through-beam sensor *) The line starts with the variable name (NoPart). Typical variable naming rules apply, so you can’t start the name with a number, and you can’t have spaces or special characters in the name, except an underscore. Here I’ve used the PascalCase way of writing the variable, by sticking capitalized words together. You don’t have to use PascalCase, but you should pick a consistent way of naming your variables and stick with it. After the variable name is an optional element that defines the variable as an input or output (AT %I*). The “I” means input and a “Q” means output. Adding this element will force the variable to show up in the PLC project’s I/O map which makes it available for linking to physical I/O. Inputs are set by the input scan before your logic runs, and outputs are set by your

logic, but copied to the physical outputs during the next output scan. (Technically the input an output scans happen at the same time.) A colon (:) separates the declaration of the variable from the variable type (BOOL). Here are the common types you’ll use, though there are more:



BOOL: A boolean value (TRUE or FALSE)



BYTE: An 8-bit unsigned integer value (0 to 255) aka USINT



SINT: An 8-bit signed integer value (-128 to 127)



WORD: A 16-bit unsigned integer value (0 to 65,535) aka UINT



INT: A 16-bit signed integer value (−32,768 to 32,767)



DWORD: A 32-bit unsigned integer value (0 to 4,294,967,295) aka UDINT



DINT: A 32-bit signed integer value (−2,147,483,648 to 2,147,483,647)



ULINT: A 64-bit unsigned integer value (0 to 18,446,744,073,709,551,615), new in TwinCAT 3



LINT: A 64-bit signed integer value (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), new in TwinCAT 3



REAL: A 32-bit floating point value



LREAL: A 64-bit floating point value



STRING: An ASCII null-terminated string, default 80 characters



STRING(n): An ASCII null-terminated string, with max length “n” characters



TIME: A 32-bit time value (typically used in timers)

A note on how much memory each one takes: for anything that says “8-bit”, “16-bit”, “32-bit” or “64-bit” then the memory usage is 1 byte, 2 bytes, 4 bytes, or 8 bytes respectively. A BOOL could be represented by a single bit, but takes a full byte (this is for performance reasons). There is also a rarely used data type called BIT which uses a single bit, and can have the values 0 and 1, but this is not equivalent to a BOOL. ASTRING takes 81 bytes (80 for the characters and 1 for the null terminator) and aSTRING(n) takes n+1 bytes. Beckhoff’s Infosys site has more information about TwinCAT 3 Data Types. After the data type, there is a terminating semi-colon (;). The compiler stops reading here.

After the semi-colon you can put an optional comment ((* Through-beam sensor *)). There are two ways to insert comments: you can enclose them in bracket-asterisks, as I did, or you can preface them with a double-slash (//). Note that comments will show up as tool-tips when you hover your mouse over any use of this variable in your logic, so they are helpful. The other thing that shows up in a tool-tip over a variable is the data type (BOOL, INT, etc.). Long before we had helpful features like this in our IDE, programmers invented a system for embedding the type of the variable in the variable name called Hungarian Notation. You will often see TwinCAT code examples, even in the official documentation, that use

Hungarian Notation (such as bIsTRUE : BOOL;). This is now discouraged thanks to smarter and more helpful editors. In general you should avoid Hungarian Notation in your TwinCAT 3 programs. When you refer to global variables in your logic, you should use the fully qualified name of the variable (including the Global Variable List name) such asInfeedConveyor.Run. Technically it will let you omit the Global Variable List name and just use the variable name (Run). This is a throw-back to TwinCAT 2 where there was only one Global Variable List. You don’t want to run into a situation where it gets confused with another global variable called Run in another Global Variable List (OutfeedConveyor.Run would be an obvious one). If you happen to do this, the compiler will give you an error telling you that you have an ambiguous variable name and it will force you to use the fully qualified name. I suggest using fully qualified names for all global variables.

Adding Structure(s) A conveyor is a pretty simple piece of equipment and I doubt you’d want to break up the structure of the InfeedConveyor Global Variable List into smaller pieces, but for a larger part of the machine you might want to. For instance, the pick & place might have two positions (Pick and Place). Rather than having variables such asPickAndPlace.InPickPosition it’s a bit nicer to havePickAndPlace.Pick.InPosition (I realize some of the reasons why it’s nicer might not be obvious at this point, but trust me). You can accomplish this with TwinCAT 3’s Structure, which is a kind of Data Unit Type, a.k.a. “DUT”. In the RSLogix 5000 world, this is called a “UDT” meaning “user defined type.” Let’s start by creating a Structure for the pick & place’s Pick position. Begin by right clicking on the 02_PickAndPlace folder and selecting Add -> DUT+, which will open the Add DUT dialog window. Enter PickAndPlace_Pick in the Name text box. Make sure the Structure radio button is selected in the Type group box. Then click theOpen button:

That will generate a new (empty) structure in the editor window:

Any variables we add here will show up prefixed with PickAndPlace.Pick. in their fully qualified name. Add an InPosition variable like this:

Now let’s create a new PickAndPlace Global Variable List. Right click on the 02_PickAndPlace folder, use Add -> Global Variable List+ and after creating it, add this variable:

Now we’ve created a variable called PickAndPlace.Pick.InPosition. Note that you can also define inputs and outputs inside your structure (by adding AT %I* orAT %Q* respectively).

Arrays It’s often advantageous to declare a numbered list of variables, and for this we traditionally use arrays. TwinCAT 3 has full support of arrays of any type. The syntax for declaring an array variable looks like this:

To use this array in your logic, it would look like this: ExampleGlobalVariableList.SomeArrayVariable[1]. That would be the first element. The last element in the array would beExampleGlobalVariableList.SomeArrayVariable[10]. Note that the syntax ARRAY[lower..upper] OF allows you to define any lower or upper bounds you like. Unlike a language like C, your lower bound doesn’t have to be zero. The total number of elements in the array is upper - lower + 1. You can also define multi-dimensional arrays like this:

You could access the first element of the array like this: ExampleGlobalVariableList.SomeArrayVariable[1,50] and the last element as ExampleGlobalVariableList.SomeArrayVariable[10,55]. Caution: when some programmers graduated from RLogix 500 to RSLogix 5000 and were first introduced to tags, they just didn’t “get” it. They wanted their nice safeB3:0/5 or whatever. Since the concept of tags was confusing, they just created a big controller-scope array tag called bits and used bits[0], bits[1], bits[2], etc. in their logic. It was a bad idea, but doing the same thing in TwinCAT 3 is a really bad idea. At least in RSLogix 5000 you could add a useful comment for each element of the array but there is no method of doing that in TwinCAT 3 (because you’re not supposed to do that anyway). Your TwinCAT 3 program would be completely incomprehensible. I would also like to caution you against using arrays for things that aren’t naturally numbered, or which aren’t a good fit for fixed numbers. For instance let’s say you have an auto sequence of steps like:

1.

Run conveyor to sensor

2.

Pick part from conveyor

3.

Place part on fixture

4.

Return pick & place to home

Perhaps you would like to implement that auto sequence using the Step pattern. Good idea. Now you might think you want to create an array of variables for your steps, such as PickAndPlace.AutoSequence.Step[1]. Having done this in the past, I now think it’s a bad idea. It’s very common to have to modify your auto sequence to add or remove steps in the future. What if between your pick and place step you wanted to add a measurement step? It’s not so easy to add a step 2.5 when you’ve declared them as an array, and renumbering the steps in all your logic can be a real pain. I think it’s better to create variables like PickAndPlace.AutoSequence.PickStep andPickAndPlace.AutoSequence.PlaceStep. Then you can add and remove steps, or even re-order them, without cursing yourself later.

Conciseness and Readability You might notice that some of my variable names above end up very long. I agree that’s a problem. On the one hand, since the comments for each variable only show up if you hover your mouse over the variable, you shouldn’t make your variable names too cryptic or it will be hard for someone to understand your logic. On the other hand, if you make them

too long, you can’t fit enough of your ladder logic on the screen at once, and the logic itself becomes difficult to follow. There’s a trade-off to make in conciseness vs. readability.

I suggest making short codes or acronyms for your top level machine elements. For example, you could abbreviate InfeedConveyor as IFC and PickAndPlace toPP. When someone looks at your PLC project, they will still see 01_InfeedConveyor and 02_PickAndPlace as two folders, and inside each there will be Global Variable Lists IFCand PP respectively. Since you’re only doing this at the top level of your program, and it’s fairly easy for someone to see the relationship of the full name to the abbreviation, I think this is a good way to shorten your fully qualified variable names without sacrificing much readability. For the lower level variable names, make an effort to pick names that are still concise but convey the meaning to the reader. Yes, it takes practice and there’s no perfect answer, but effort really counts here.

4 - TwinCAT 3 Tutorial: Persistent Variables Traditional PLCs offer various forms of data persistence (so your PLC memory isn’t lost when the PLC loses power). Some use battery-backed SRAM, which maintains power to all or a portion of the memory even when power is removed from the PLC. Some use flash-memory technologies. Since TwinCAT 3 runs on PC hardware, and PC memory isn’t battery-backed up, it has a different mechanism for storing persistent variables.

By default, variables in TwinCAT 3 are not persistent. If you restart the runtime, or your PC loses power, then when your PLC program restarts the variables will revert to their default state. This is fine for many situations (we generally want our outputs to start in the off state anyway). However, there are times when we need to store machine state such as part presence or process information and we don’t want to lose it when the PC reboots. TwinCAT 3 allows you to explicitly define variables that are “persistent” for this purpose.

In this chapter I’ll explain how to create persistent variables, how TwinCAT 3 saves and restores them, and how to write a program to periodically save them to a file in case of a crash or power loss.

Declaring Persistent Variables For this example, I created a new Global Variable List called “PersistentExample”. Note that persistent variables don’t need to be global (they could be defined in a program or a function block, for example). By default a Global Variable List only has one section of declarations (called VAR_GLOBAL):

Anything declared inside of the VAR_GLOBAL section is not persistent. However, we can declare a section to be persistent like this:

Adding the PERSISTENT keyword after VAR_GLOBAL tells TwinCAT 3 that any variables in this section should be written to persistent storage when the runtime shuts down, and restored from persistent storage when the runtime restarts

(the data is actually stored to a file on disk, and I’ll explain more about the details later). I’ve declared two integer variables (one persistent and one non-persistent) as examples, and I’ve initialized them with default values (5 and 3 respectively). Now let’s login to the PLC, and apply this as an online change. You will now see an online view of this Global Variable List:

As you can see, both variables have been created in the PLC, and they have the values 5 and 3, respectively. Now I’m going to write new values to them. In the Prepared valuecolumn, enter the values 10 and 6, like this:

Now choose PLC > Write values to all online applications to overwrite the variable values in the PLC memory:

As you can see, the values have changed. Now we’re going to restart the runtime and see what happens. First choose PLC > Logout from the menu and then choose TwinCAT > Restart TwinCAT System to initiate a runtime restart. You will be asked to confirm:

Click OK to restart in Run Mode. You will see the TwinCAT icon in the system tray briefly turn red, and then turn back to green. When that’s done, go back online by choosingPLC > Login from the menu:

You can see that the persistent variable retained the value of 10 that we wrote to it, but the non-persistent variable reverted to the initialization value of 3 that we defined in the variable declaration. The written value of 6 was lost.

How Persistence Works TwinCAT 3 stores the persistent variables in a file on the computer’s hard drive. The sequence of when this happens is critical to understand to prevent you from losing your valuable persistent data. When you shut down the TwinCAT 3 runtime, here is how it saves your variables:

1.

The PLC program stops executing

2.

The runtime reads all of the persistent variables and writes them to a file

3.

The runtime stops

The persistent data file is stored in the C:\TwinCAT\3.1\Boot\Plc folder, and it will be named Port_851.bootdata. Each PLC instance on a runtime has an “ADS port number”. The default port numbers start at 851. You can change the port number, but that’s not important right now. When TwinCAT 3 restarts, here is how it restores your variables:

1.

The runtime loads the PLC program (including the default values of all variables)

2.

The runtime looks for a persistent data file on the hard drive, and if it finds one, it restores the saved values to the PLC variables

3.

It deletes the persistent data file from the hard drive

4.

The PLC program starts executing

If, for some reason, the runtime can’t find the persistent data file, then all of your persistent variables will be reset to their default values. This is actually very easy to demonstrate because if the runtime doesn’t get a chance to shutdown cleanly, it won’t write the persistent data file to the hard drive. Here are some ways that this can happen:



The PC loses power without doing a Windows shutdown



The runtime crashes (this shouldn’t happen, but it can happen)



The persistent data file can become corrupted

The recommended way to install a TwinCAT 3 system is to use a PC with some kind of battery backup, like an Uninterruptible Power Supply (UPS). You must connect the PC to the UPS (via USB cable, etc.) and use the software provided with the UPS to make sure that Windows does a clean shutdown if the UPS loses power for any appreciable length of time. However, this isn’t good enough.

Protecting Your Persistent Data Even if you use a UPS to provide Windows enough time to do a clean shutdown, there are still many ways that you can lose your persistent data (such as the UPS failing, the hard drive failing, or a runtime crash). Unless you can afford to lose the persistent data, you must do more to protect it.

While your PLC program is running, the only up-to-date copy of your persistent data is the actual values of the variables in your PLC memory. The persistent data file is only written when the runtime shuts down cleanly, and it deletes the persistent data file after reading it when the runtime restarts.

The first step in protecting your persistent data is to periodically write out the persistent data to disk while the program is running. Thankfully TwinCAT 3 provides a function block called WritePersistentData that does exactly this. You can manually instruct the runtime to write out the persistent data file from within the PLC program.

Create a SavePersistentVariables Program In this section I’ll describe how to write a generic program that handles saving the persistent variables regularly while your program is running. This program will make use of a function block called WritePersistentData that’s provided with TwinCAT 3 in the Tc2_Utilities library. This library wasn’t referenced automatically when you created the PLC program, so you’ll have to add a reference before you can use it. Start by finding the References folder under the PLC1 Project:

There are already 4 libraries referenced. These references were added when you created the project, and they provide some basic function blocks like timers, counters, and string functions. To add a reference to the Tc2_Utilities library, rightclick on theReferences folder and choose Add Library+ from the context menu. That will display theAdd Library window:

Now click on the System node (highlighted above) to expand it so you can see the system libraries:

Click on Tc2_Utilities (highlighted above) and then click the OK button. The window will close and you will now see the Tc2_Utilities library added under the References node in the Solution Explorer:

If you want to see a list of what is contained in a library, double-click on that library name in Solution Explorer and it will open the Library Manager window:

Select a library in the top portion of the window, and you’ll be able to browse the contents of the library in the bottom left corner. With the Tc2_Utilities library selected, expand thePOUs folder on the bottom left and expand the TwinCAT PLC folder beneath that to find the WritePersistentData function block. Click on the name of the function block to display a picture of the block on the right side:

You can click on the Documentation tab above the picture of the block to see more detailed information about the inputs and outputs:

Now we’re going to create a new Program POU called “SavePersistentVariables.” Start by right-clicking on the POUs folder in the Solution Explorer and choosing Add > POU+from the context menu. Enter the program name in the Add POU window and chooseProgram as the type and Ladder Logic Diagram as the implementation language:

Rthen click Open to create the program. You will now see the empty SavePeristentVariables program in the ladder logic editor:

Since WritePersistentData (from the library) is a function block, that means it has local state (stored in internal variables) and we have to allocate space for those state variables by declaring an “instance” of the function block as a variable in our program. (Note that the main difference between a Function and a Function Block is that a Function doesn’t have any internal state, so you can use a Function without first declaring an instance of it as a variable). If you’ve ever used a timer or a counter function block in another PLC, you will be familiar with the idea of allocating memory space for each instance of a timer or counter. Declare the function block variable as a local variable of the program, like this:

This is just like declaring a BOOL variable. WritePersistentDataFB is the name of the variable, and WritePersistentData is the type. In this case, the type is a data structure that the function block needs to store its internal state. To add the function block to rung 1, right click on the rung and choose Insert Empty Box+ from the context menu. Your rung will now look like this:

The three question marks inside the box are where you type the function block name, and the three question marks above the box are where you type the variable (or “instance”) name. Go ahead and type WritePersistentData inside the box andWritePersistentDataFB above it:

The editor recognizes the function block name and automatically adds the required input and output parameters to the block. We can start by filling in the NETID, PORT, andTMOUT inputs like this:

The NETID is an empty string (just two single quotes), which means that the AMS Net ID is the local target (i.e. the PC it’s currently running on). The PORT is 851 because that’s the ADS port number of the PLC1 program. The TMOUT input is set to 10 seconds, which is a maximum time we will allow for the operation before the function block will abort and set the ERR and ERRID outputs. Notice the format we used for specifying the time. This is a syntax defined in the IEC 61131-3 standard. It allows you to specify durations like this:



T#10ms (10 milliseconds)



T#5000ms (5000 milliseconds, or 5 seconds)



T#5s (5 seconds)



T#1m (1 minute)



T#1h30m (1 hour and 30 minutes)



T#2d1h (2 days and 1 hour)

The WritePersistentData function block initiates a save of the persistent data when the START input transitions from off to on (also called a “rising edge”). The BUSYoutput will turn on immediately and will stay on until an acknowledgement has been received (or the timeout expires). When the BUSY output turns off, if there was an error then the ERR output will turn on and the ERRID output will indicate the ADS error number (do a Google search for “ADS Return Codes” to see the list). Ideally we should save the persistent variables immediately after the PLC program starts, and then at regular intervals after that. We’re going to use two on-delay timers, one to trigger the initial write and a second to trigger the subsequent writes. TwinCAT 3 includes an on-delay function block called TON. It’s included in the standard library, which is already referenced. First you have to create the variables to hold the timer instance data:

Declare two timers called StartTON and IntervalTON. Now we want to insert a rung to put the first timer on (note that in TwinCAT 3, rungs are called Networks because the ladder diagram editor is also the function block diagram editor, and function block diagram programs are divided into networks). Right-click on the existing rung and chooseInsert Network from the context menu. The existing rung will become rung 2, and a new blank rung will be inserted as rung 1:

Now right-click on rung 1 and choose Insert Empty Box from the context menu. Inside the new box, where the three question marks are, type TON and press enter. Then where the three question marks are above the box, type StartTON and press enter. Your rung #1 should look like this:

The IN input of the timer controls when the timer is timing. The PT input is the “preset time” which is how long the timer will run before turning on the Q output. The ET output gives you the current “elapsed time” which is how long it has been since the IN input turned on. We want this timer to start timing right away, so replace the three question marks above the contact that feeds the IN input with the constant value TRUE. We want this timer to fire very shortly after the PLC starts, so enter the time constant T#1ms in place of the three question marks next to the PT input. That will make the timer Q output turn on one scan after the PLC starts (I did this to make sure theWritePersistentData function block will see a transition from off to on). Since we don’t need the ET output, right click on the timer and choose Remove unused FB call parameters from the context menu. That will remove both the Q and ET outputs. Now your timer rung should look like this:

The timer’s output Q will transition from FALSE to TRUE 1 ms after the program starts. Use the output to drive the WritePersistentData function block’s START input (highlighted):

Now we need to create another timer that will fire repeatedly after a certain interval. Insert another rung (between rungs #1 and #2), add another TON function block, this time using IntervalTON as the timer instance. Set the preset time to 1 minute. We want this timer to start after the initial startup timer is complete, and we want it to reset every time the WritePersistentData function block is busy:

Now modify rung #3 (the WritePersistentData rung) so that both timers will trigger the function block’s START input. Begin by selecting the StartTON.Q contact:

Now right click on the contact and choose Insert Contact Parallel (below) from the context menu:

Then replace the three question marks over the new parallel contact with the output of the second timer (IntervalTON.Q):

Finally, there remains one problem. The StartTON.Q variable will be on almost all the time, but we’re only interested in the rising edge. TwinCAT 3 allows you to change a contact from a normally open contact into a rising edge or falling edge (also called “differentiate up” and “differentiate down”) by selecting the contact and pressing the “P” or “N” keys, respectively. Select the StartTON.Q contact and press the “P” key:

Finally, we don’t want to use the ERR and ERRID outputs. Rather than making them disappear, just delete the three question marks next to those outputs and the compiler will just leave them disconnected for now. Eventually you should consider using the ERRoutput to create an alarm to notify someone that there’s a problem saving the persistent variables. Here is the complete program:

Here’s how it works:

1.

The StartTON timer fires almost immediately when the PLC starts, and that gives a single pulse to the START input.

2.

During the initial write process, the BUSY output is on, which preventsIntervalTON from running.

3.

When the initial write is complete, BUSY turns off and then IntervalTON starts running.

4.

One minute later the IntervalTON timer completes and turns on the Q output. This triggers the START input again, which makes the BUSY output turn on, which resets IntervalTON until the write is complete.

5.

When the write completes, we go back to step 3.

Before this program will run, you have to call it from your MAIN program. The QuickStartchapter explained how to call other programs from your MAIN program so I won’t repeat that here. Remember to login with an online change to start running this new program. When you execute this code, you can watch the C:\TwinCAT\3.1\Boot\Plcfolder and see the Port_851.bootdata file created, and then see the file timestamp update every minute after that. Now we have a copy of the persistent data on the hard drive that is at most about 1 minute old. That will protect us if there is a runtime crash or the UPS stops working but it still doesn’t protect us from a corrupt file or a hard drive that stops working. To make sure you can recover from these scenarios, you need to make backups of the persistent data file at regular intervals to another computer.

To restore a backup of your persistent data file:

1.

Restart your runtime in Config mode

2.

Replace the .bootdata file with the backed up one

3.

Restart your runtime in Run mode

Note that in the event of a crash, the restored persistent variables might be up to 1 minute old. That’s still a problem for part presence data, but at least you won’t lose all your setpoints. The persistent data saved when the runtime does a clean shutdown is the primary method for protecting the consistency of your persistent data. That means a good UPS connected to the PC with the USB cable and running the UPS software so it will do a clean shutdown in the event of a power loss is absolutely necessary for every production system using TwinCAT 3.

5 - TwinCAT 3 Tutorial: Structuring PLC Logic TwinCAT 3 gives you a lot of flexibility in organizing your PLC logic. In this chapter I’ll explain how to make use of this flexibility to organize your logic in a way that makes it easier for someone reading your program to find what they need.

The MAIN Program When you create your first TwinCAT 3 PLC project, the wizard will helpfully create aMAIN program for you:

The MAIN program is called by the PlcTask task on a schedule defined in the PlcTaskconfiguration. This is the point at which your program now has control of the runtime. From within MAIN, you have to call other programs that are part of your PLC project. Note that the MAIN program is inside of a POUs folder. This is only Beckhoff’s default output generated by its wizard. Right now you could move the MAIN program into theGVLs folder and PlcTask would still call it without a problem.

Folders In my programs I delete the DUTs, GVLs, POUs and VISUs folders and then I create my own folder structure that mimics the structure of the machine. For instance, if I was programming a multi-zone conveyor system, I would create folders called Zone01,Zone02, etc., and if I was programming a rotary assembly cell I would create folders such as RotaryTable, Station01, Station02, etc. Then, inside each folder I would put both the data structures and logic related

to that part of the machine. There’s no need to separate the data from the logic in separate folders:

Note: the folders sort themselves alphabetically. It just so happens that “R” comes before “S” so it all worked out nicely above, but usually you want to enforce an ordering scheme. In that case, you can add a numerical prefix, such as “01_” to the beginning of every folder name to make them show up in the order you want. Also note that the current version of TwinCAT 3 doesn’t seem to sort properly as you add new content. To get it to sort properly, save everything, close the solution, and open it again.

You can also put folders inside of folders. For instance, if you have a zone-based conveyor system inside of the infeed side of your automation cell, you can put all of the zone folders inside of an Infeed folder.

Programs Within your new folder structure, add programs to control each logical element or function of the machine. For instance in a zone-based conveyor system, it’s typical for each zone to have a “drive” which is the motor that turns the rollers and a “stop” which is an air-actuated plunger that pops up to prevent a pallet from moving past a point, and drops down out of the way to allow the pallet to go forward. In a system like that, I would expect to see logic like this:

Always try to imagine someone trying to troubleshoot this machine. In a normal factory there will be a maintenance team that handles the maintenance on several different machines. Nobody will know any particular machine to the depth that you know it while you’re programming it. An operator might call them because there’s a pallet stuck in zone 2 and it won’t leave. If they open up the project above, they can quickly navigate to theZone02 folder, open the Zone02_Stop program and find the output that drops the stop. If the output is on but the stop isn’t dropping, then they know there’s something wrong on the mechanical or electrical side with the stop, but if the output isn’t on in the program, then they can start working their way backwards through the logic trying to figure out why the program isn’t turning the output on. You might wonder why you wouldn’t create a single function block and call it 3 times, one for each zone. Good question. Remember that even though the conveyor system appears to have 3 zones and they all looks the same, you should expect the logic for each zone to be slightly different. Remember that these are 3 separate physical parts of the machine and even though we might like to imagine that they’re identical, they’re not. If zone 1 is where material gets introduced to the line, then it likely has some kind of interface logic for interlocking with whatever is feeding it, such as a light curtain or a robot. Similarly the 3rd zone might need to be interlocked with some kind of pick-and-place equipment. If you create a function block that can handle the logic for all 3 zones, then you’ve complicated the logic for no real gain.

It’s better to see if you can pull out components of each zone that are necessarily identical (such as the stop) and create a function block for just that component. However, even then I would caution you that these are 3 separate physical stops and you should expect the logic to have to work slightly differently for each one, even if it’s something as simple as a timer. Also, it’s common that someone in maintenance might need to bypass some condition in the logic either to recover from an unforeseen problem or to bypass a broken sensor until it can be replaced. It’s much more difficult to do this if all 3 components share the same logic, since any bypassing code will likely affect all 3 stops, when in reality you only want to bypass a single sensor. Ultimately it’s your decision, but I suggest defaulting to the copy/paste method rather than the function block method in most cases.

Calling Program from MAIN After you’ve created your programs, you need to “call” them once-and-only-once from your MAIN program. Here is what your MAIN program would look like in the conveyor example above:

Remember that there is only one “instance” of each program (unlike a function block like a timer where there can be more than one instance). That means there is only one copy of the local variables declared inside of a program. If you call a program twice, you are executing the logic a second time, and it’s going to affect your local program variables and any global variables referenced by that program. That’s almost certainly not what you want. Someone reading your project will assume that every program is called once, and typically in the order that the programs are listed in the solution explorer, so please work hard to make the order that they show up and the order that you call them in match. If they need to be called in a certain order, please rename your programs and folders so that the ordering is the same. Every time you create a new program you have to remember to call it from MAIN. If you don’t, the logic won’t execute and this can be difficult to debug. Thankfully, TwinCAT 3 will give you a hint that you made this mistake. As an example, I’m going to comment out the line where I call Zone02_Stop from the MAIN program:

The two forward slashes turn the line into a comment, which means anything after the slashes is ignored by the compiler. Now, right click on the PLC1 Project node in theSolution Explorer window and click Build from the context menu. After building, TwinCAT 3 will gray out any POUs that aren’t “linked,” which generally means any that aren’t called by your MAIN program:

I highlighted the grayed out node with a red arrow above. Note that you can defeat this helpful feature by forcing the compiler to link it even if it’s not called. Click on the grayed out node, and then look at the Properties window:

If you change the Always Link property from False to True, and then build the project, the node will no longer be grayed out. When you run the program it still won’t call this program, so you may wonder why this feature exists. Well, if the program isn’t linked, then that means the compiler didn’t even try to build it. There are certain cases where you might be trying to write some new logic and you don’t want to call it yet, but you do want to check if there are any compile errors. In that case you can set the Link Alwaysproperty to True, build the project, and any compile errors will show up in the error list at the bottom of the screen. Just remember to change the property back to false so you will remember that you’re not calling this logic yet.

Program Variables Each program has a variable declaration section at the top of it for local variables. Any variable that is only referenced by this program should go in the program’s local variables, not in a Global Variable List. That helps someone reading your program understand the “scope” of a variable. Global variables have more impact across your whole project and might even be referenced by your HMI program, but local variables should only be used by your program and modifying them should only have a local effect. Note: In the latest version of TwinCAT 3, you can actually read and write program local variables from the HMI. I advise against doing this, but if you do, please make sure that you at least comment these variables in your program to let other people know that they’re accessed by the HMI. Even global variables accessed by the HMI need to be commented. That’s because it’s easy to find all the places where a variable is used in your program: just change the variable name and do a build. You’ll get compile errors at every reference (and there’s also a cross reference feature too). Unfortunately there’s no simple way to find places where that variable is referenced in the HMI, so make sure you make it obvious. In addition to normal local variables, you can add a PERSISTENT variable section inside the variable declarations of a program, just like you would in a Global Variable List. The variables in

a VAR PERSISTENT block will be saved and

restored through a shutdown and a restart of the PLC runtime, just like persistent variables in a Global Variable List:

Less is More How much logic should you put in a single program? My general rule of thumb is “as little as you can get away with.” You want all of the logic in a program to be “tightly coupled.” That is, all of the logic in a single program should be highly related to the other logic in there. Conversely, you want the logic in different programs to be “loosely coupled.” That is, ideally you want all the logic in a program to stand on its own without interacting with the logic in other programs at all.

Obviously this isn’t always possible, but in practice you want to reduce the coupling between programs. Since the coupling between programs takes place through global variables, this means you want to separate your logic in such a way that the interfacing signals between programs are few and simple. If you have a conveyor feeding a pick-and-place robot, then PickAndPlaceClearOfConveyor is a reasonable global variable that’s set in the pick-and-place logic and used in the conveyor logic. The conveyor logic shouldn’t “care” how that signal is created (that’s the job of the pick-and-place logic). It just needs to know that it’s clear so the conveyor can move. In practice, I try to keep my programs between 1 and 12 rungs long, but that’s certainly not a hard and fast rule. Logic that needs to be longer should be longer. However, if your program logic really starts to grow, see if it’s really doing two things that could be separated out into two programs where just one or two global variables could handle the interaction between those two programs.

6 - TwinCAT 3 Tutorial: Multiple Virtual PLCs In the Windows Server world, the big trend in the last 10 or so years has been servervirtualization. That means we take one big beefy server and we run many “virtual servers” on that hardware. One physical server takes on the role of email server, file server, database server, etc., and we can logically think of each server as a separate entity because we can login to each one, see its disk drives, install programs, etc., but each virtual server shares the resources of the parent physical server. This revolution in the data center has also made its way into PLCs. The TwinCAT 3 runtime running on a PC is kind of running a “virtual” PLC. That PLC runs in “ring 0” of Windows and handles all the tasks that a traditional PLC would, like scanning the I/O, and solving logic. TwinCAT 3 lets you create multiple virtual PLCs and run them all in the same TwinCAT 3 runtime. You might wonder why you’d want to do this. Here are some reasonsR

Reason 1: Make use of Multiple Cores The primary reason touted by Beckhoff is that this is how you make use of more than one core in those fancy new multicore CPUs we can buy now. Modern CPUs in the Intel Core-i3, i5, and i7 series have 4 physical cores in the CPU, and each one can be executing a different program at the same time. However, the fundamental scheduled unit in Beckhoff, the “task”, can only run on a single core.

Even though the processor power available in a single core is much more than in a traditional PLC, EtherCAT is so fast that you may also be pushed to run your logic faster. In case you run out of CPU time, you now have the option of breaking your system up into multiple tasks that run on separate cores. In a typical system with a PLC and a motion controller, you can already do this by running your PLC task on one core and the motion control task on another. However, you still have the option of splitting your PLC logic into multiple virtual PLCs and running each one on a separate core so you can balance the loads.

Reason 2: Separation of I/O Let’s say you have a really big system with lots of I/O. The more I/O you have, the longer it can take to scan the whole bus. Some day you might have a case where you want to scan a small portion of the bus very quickly (a high speed input, for example) but the rest of the I/O can be scanned at some more leisurely pace. The I/O scan matches its rate to the PLC task tied to it. That means you can accomplish multiple I/O scan rates by making one virtual PLC to handle your high speed I/O and another to handle the rest of it. TwinCAT will handle scanning just that portion of the I/O that each PLC needs for its I/O map.

Reason 3: Flexibility In addition to allowing you to balance CPU load and I/O scan time, supporting multiple virtual PLCs also gives you the flexibility to move a virtual PLC to an entirely different physical machine.

Imagine you have three separate production cells (let’s call them OP10, OP20, and OP30). These form a production line where parts are transferred from one operation to the next. Currently the cells are beside each other in your facility, but they operate independently. Operators run parts through OP10, and they may or may not take the parts directly to OP20, or they might store them for a while and run them as batches. The point is that in the future the production department wants flexibility. They may want to install a transfer system to tie all the cells together, or they may want to batch the parts into bulk bins, possibly moving the cells away from each other physically. In today’s environment you may even see one of the operations contracted out to a lower tier supplier entirely, including moving the cell (say OP10) to their facility.

When you build and install this machine, if you separate the operations onto 3 virtual PLCs, you have the flexibility to run all 3 on a single physical PC (saving money). Later, when they want to move OP10 to another location, or even another facility, you just have to move the PLC and I/O for OP10 over to another PC (possibly a new PC, or possibly an existing PC in the new location). This is just a configuration change and a matter of plugging the EtherCAT cable for OP10 into a different EtherCAT master.

Reason 4: Modularity and Code Re-use We often try to design our machines with re-usable components. If you’re a machine builder and the machines you sell have an optional feature, such as an auto-pallet-changer on a CNC mill, why ship that logic on a machine where the customer didn’t order it? If you use a separate virtual PLC to control your pallet changer, then you just don’t include that virtual PLC on machines that don’t have it. You might also be able to offer your pallet changer as an add-on for other CNC mills, perhaps ones you didn’t even build, by running your virtual PLC on a low-cost shoebox style TwinCAT 3 box without the overhead of an industrial PC.

What if you already have a bunch of TwinCAT 3 machines in your facility and you want to add a new plant-wide data collection system to all of them? Create a single virtual PLC project that handles the data collection and run it on all your TwinCAT 3 machines. That’s a lot simpler than modifying the logic on every single machine, and it keeps the data collection logic nice and isolated from the machine control logic. The machine control virtual PLC would interface with the data collection virtual PLC over mapped I/O which creates a nice clean interface between the two.

Example: Create Multiple PLCs Start by creating a new TwinCAT XAE project called TwinCATMultiplePLCs:

In this example we’re going to imagine two separate physical work cells called OP10 and OP20 each controlled by a separate virtual PLC. There will be a single physical EtherCAT master (though you could connect each work cell to its own physical EtherCAT master if you want). At first these two PLCs won’t communicate with each other, but we’ll add interlocks between them at a later stage of the example.

Let’s start by creating the I/O. Under the I/O node, right click on Devices and select Add New Item+ from the context menu. That will open the Insert Device dialog. SelectEtherCAT Master as the new device and enter EtherCAT in the name box:

It will now try to attach this EtherCAT master to a physical device on your PC. If none exists (because you haven’t installed one), just click Cancel:

Now you will see your new EtherCAT master in the Solution Explorer:

Right click on the EtherCAT master and choose Add New Item+ from the context menu. That will display the Insert EtherCAT Device dialog:

It will helpfully auto-select the EK1100 head module, which is what we typically want. Enter OP10 as a name for this device in the name box. This will be the head module for the I/O in the OP10 panel. Click OK. Now follow the same procedure again, but this time add an EK1100 with the name OP20. You will see these two new devices under your EtherCAT master:

Now under each head module we want to create a single input card. Right click on theOP10 device and choose Add New Item+ from the context menu. The dialog will auto-select the last thing you selected. We want to add an EL1008 input card. The fastest way to select it in the list is to use the Search box. Type el1008 in the search box:

Enter a name for this card as well. I chose OP10 Inputs here, but you could choose something like OP10 In01 if you expect to add more cards. Click OK. Now follow the same procedure but for the OP20 device, to add a new card called OP20 Inputs. Your I/O should now look like this:

Now let’s add our virtual PLCs. Right click on the PLC node in the Solution Explorer and choose Add New Item+ from the context menu. From the dialog that pops up, chooseStandard PLC Project, enter OP10 in the name box, and click the Add button:

Wait for the wizard to create your new PLC project. Now repeat the process and create a second PLC project called OP20. You should see your two PLC projects under the PLCnode:

Now we need to create some inputs in our PLCs. I’ve already covered adding global variable lists earlier, so I’m only going to explain the broad steps here.

1.

Create a Global Variable List in the OP10 PLC called OP10

2.

Create a Global Variable List in the OP20 PLC called OP20

3.

Add a single BOOL input to each Global Variable List

4.

Right click on the OP10 Project node and choose Build from the context menu

5.

Right click on the OP20 Project node and choose Build from the context menu

Now each PLC project will have an input for linking to the physical I/O:

Now go back down to your I/O. Expand the OP10 Inputs device (the EL1008 card) and then expand the Channel 1 node underneath that:

Underneath the Channel 1 node you’ll see a single node called Input. Right click on theInput node and choose Change Link+ from the context menu. Now you’ll see the Attach Variable Input dialog:

Select the OP10.Input01 variable in the tree and click the OK button. Now you’ve “mapped” the PLC input to the actual physical I/O. Repeat this procedure to map theOP20.Input01 variable to the OP20 Inputs Channel 1 physical I/O. Now go up to the OP10 node under PLC, right click on it and choose Activate Boot Project+ Then do the same with the OP20 node under PLC. Also right click on the OP20 node and make sure that Autostart Boot Project is checked (the OP10 one should already be checked by default). Since this is a new project we’ve created, we have to configure our Real-Time again. Under the SYSTEM node, doubleclick on the Real-Time node to show the real-time settings:

Click the Read from Target button (highlighted above). Assuming you already completed the Quickstart section earlier, then it should read the following configuration:

There are 2 cores assigned to Windows and 2 cores assigned as “Other.” Under the RT-CPU column, check CPUs 2 and 3, and uncheck CPU 0:

Now if you try to Activate Configuration to start your PLCs, it won’t work because the EtherCAT master isn’t linked to a physical Ethernet card. You can try it if you want, but you’ll just get an error message when it tries to start the runtime in run mode. To get around this, right click on the EtherCAT master node and choose Disable from the context menu. Now you can go to the TwinCAT menu (at the top of the screen) and choose Activate Configuration from the drop down menu. Click through the dialog boxes, just like in the Quickstart, earlier. When it asks if you want to restart in run mode, say Yes.

Going Online You can still go online with each PLC, but it’s important to understand that you go online with each PLC separately. That means when you login with an online change, you’re making the change to that PLC only. There’s no way to simultaneously make online changes to 2 PLCs at the same time (however that should rarely ever be a problem).

ADS Ports By default, the first PLC listens on ADS port 851. You can see this by double-clicking on the OP10 node. Subsequent PLCs are assigned in order, so the OP20 PLC uses ADS port 852, and so on. This is important if you make use of the free ADS DLL to communicate with the PLC(s) from a Windows program.

Running the PLCs on Separate CPU Cores In the example above, there’s still only one task (called PlcTask) that executes both PLCs. Currently both PLCs are running on CPU 2 (i.e. core 2). If we want to run the two PLCs on separate CPU cores (say cores 2 and 3) then we need to have two PLC tasks. Under the SYSTEM node in the Solution Explorer, under the Tasks node double-click on the PlcTask node. In the name box, change the name of the task to OP10. Now right click on the Tasks node and choose Add New Item+ from the context menu. In the Insert Task dialog, enter OP20 as the new task name, and click OK.

That will add a new OP20 node under Tasks. Double-click on the OP20 node and change the priority to 21. By default, new PLC tasks are assigned a priority of 20. Two tasks can’t have the same priority, so while we’re leaving the OP10 task as priority 20, we’re assigning the OP20 task to a priority of 21. Now under the OP20 Project node, find the PlcTask node:

Notice that it’s still linked to the original OP10 task. Right click on the PlcTask node and and click Assign to Task from the context menu. That will display the Assign new Taskdialog box:

The only other option is the OP20 task, so click OK. Go back under the SYSTEM node in the Solution Explorer and double-click on the Real-Time node:

Notice that both the OP10 and OP20 tasks are listed in the bottom grid, but both are still assigned to CPU (core) 2. In the RT-CPU column of the bottom grid, in the OP20 row, click the drop-down box and change it to CPU 3:

Now choose Activate Configuration from the TwinCAT menu at the top of the screen, and restart in run mode when it asks you. Now the OP10 PLC is running on CPU (core) 2 and the OP20 PLC is running on CPU (core) 3. Click on the Online tab:

The two left-hand graphs are for CPU 2, and the right-hand graphs are for CPU 3. The top graphs show how much of the CPU you’re using (0% in this case because we didn’t run any logic). The bottom graphs show how the latency between when the timer interrupt fires (every 1 ms in our example) and when the task actually gets to run.

Connecting Virtual PLCs In the example above, each PLC operates independently of the other. OP10 is blissfully unaware of the existence of OP20 even though both share an EtherCAT master, and could even share an input mapped from the same physical input.

What if we want to connect these two PLCs together so they can send signals back and forth? This is as simple as defining an input variable in one PLC, an output variable in the other (of the same type) and linking them together the same as if you were linking these variables to physical I/O.

For example, in OP10‘s Global Variable List define a variable like this: ToOP20 AT %Q* : BOOL; Then in OP20‘s, define: FromOP10 AT %I* : BOOL; Right click on each of OP10 Project and OP20 Project and select Build from the context menu. Now link the output from OP10 to the input from OP20:

Right click on the OP10.ToOP20 PLC output (highlighted) and choose Change Link+from the context menu. Select OP20.FromOP10 in the Attach Variable dialog. Click OK. Select Activate Configuration from the TwinCAT menu and say yes to restarting in run mode. Now the OP10 PLC can send a signal to OP20. You can open both Global Variable Lists, go online with each one, and set the output variable to TRUE on the OP10 side and watch it change to TRUE on the OP20 side.

Sharing a Common Library It’s normal to have a list of functions or function blocks you’d like to use in all your programs. Rather than copying the logic manually into each PLC project, TwinCAT 3 offers the ability to create a library and reference it in your other projects.

Typically you would create this library in another separate solution, but I like to have everything in one solution if I can. Start by right clicking on the PLC node in the Solution Explorer and choosing Add New Item+ from the context menu. In this case select Empty PLC Project and give it the name CommonLibrary. Click Add.

The next thing you want to do is right click on the new CommonLibrary node and chooseDisable from the context menu. Now let’s add a function. Right click on the CommonLibrary Project node and select Add->POU from the context menu. In the dialog, set the name to DegreesToRadians, selectFunction as the type, REAL as the return type, and choose Structured Text (ST) as the language:

Then click Open. The formula for converting degrees to radians is to multiply by pi and then divide by 180. The mathematical constant PI is defined in a TwinCAT 3 library, which we will have to reference. Under CommonLibrary Project, right click on the Referencesnode and choose Add Library from the context menu. Expand the System section in the tree and choose the Tc2_System library and click OK. Now define the DegreesToRadians function:

Note that there’s an input (Degrees), and one line of logic.

Before we can save it as a library, we have to set some properties. Right click on theCommonLibrary Project node and choose Properties from the context menu. Now you’ll see the properties window. You have to fill in the 3 bolded fields: Company, Title, andVersion:

To access this function in the OP10 and OP20 PLCs, we first have to save this as a library, install it in the local library manager, and then reference it in both PLCs. Start by right-clicking on the CommonLibrary Project node and choosing Save as library and install+ from the context menu. It will ask you to save this as a .library file somewhere on disk. It really doesn’t matter where you save it, as it will install it in the library manager immediately afterwards. Now you can go under the OP10 Project node, right click on References and choose Add library+ from the context menu. Your new library will show up under (Miscellaneous). Select it and click OK. Do the same thing under the OP20 Project node. Now you can use the DegreesToRadians function in both virtual PLCs. If you want to modify the CommonLibrary then you have to remember to right click on theCommonLibrary Project node and choose Save as library and install+ from the context menu. The library manager will reload it in both of your PLC projects. You’ll still have to login with an online change on the actual PLC to send the new logic to the runtime. That concludes this section on multiple virtual PLCs. This is an advanced feature of TwinCAT 3, but it’s useful in many real-world applications.

7 - TwinCAT 3 Tutorial: Ladder Logic Editor 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:

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

Notice that the editor added the EN input and the ENO output automatically. The ENinput 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 ADDinstruction isn’t being executed because the Result is still 0:

While the EN input is true, the instruction executes:

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

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

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:

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:

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 theright 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 theRValue. The LValue is the part being modified (the result of the assignment) and theRValue 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:

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

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

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:

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 aGT (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:

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:

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:

Rwill 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:

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:

Ris equivalent to this:

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:

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 apersistent 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:

Rsets 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:

Rsets the rung output condition to the logical “and” of the rung input condition and thenegated 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:

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 alreadyFALSE 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:

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:

A delay-on timer is more common, but a delay-off timer is typically used when you have a fast pulse (like a quick buttonpush 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:

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

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):

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 CVoutput 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):

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:

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 theRESET 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:

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

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

Rwhich is equivalent to this:

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 MOVEfunction to do unit conversions:

Note that a purist would say that the value 25.4 needs to be declared in a constant, likeINCH_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 PIbecause (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, likein_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 asLength_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:

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.

8 - TwinCAT 3 Tutorial: Writing your own Functions and Function Blocks There are three types of Program Organization Units (POUs):



Programs



Function Blocks



Functions

When you use the wizard to create a new TwinCAT 3 PLC project, it will create a program for you called MAIN. TwinCAT 3 also comes with several libraries full of Function Blocks and Functions for you to use (the most common being timers, counters, and math functions like addition and subtraction). Let’s start by examining the differences between Functions and Function Blocks. The basic difference is that a function block can hold state, and a function cannot. For instance, a counter function block has to “remember” the value of the counter internally, and it must also remember the previous value of the increment input because it needs to detect a rising edge. To the programmer using a function or a function block, this means that an instance of a function block needs to be declared as a variable and referenced when you call it.

Another difference is that a function always returns a value. The ADD function returns a number, for instance. You have to declare a return type for a function when you define it. Most functions only return one value, but you can define a function that has more outputs. Here is an example of an ADD function:

Here is an example of a delay-on timer (TON) function block:

I’ve highlighted the variable above the TON block. As you can see, to use a function block you have to declare the variable to hold the internal state, but to use a function, you don’t. This means a function is easier to use, but the functionality is limited to what it can compute from the current inputs of the function.

A program is like a function block (it has internal state), but there is only one global copy of each program, so you can’t create “instances” of programs like you can with a function block.

You can’t use a function block (like a timer) inside of a function, because the timer’s state will be re-initialized very time the function is called. TwinCAT 3 will let you do it, but the timer won’t work.

Build a Maintenance Counter Function Block TwinCAT 3 includes counter function blocks in the standard library, but these have some limitations. The primary limitation is that the internal counter uses a 16-bit WORDvariable, so it can only count up to 65535. What if we wanted to create a maintenance counter for our machine that recorded the number of parts produced. It’s easy to imagine a machine that could produce millions of parts in its lifetime. A counter with a 32-bit value (DWORD) would solve this. Since this is a maintenance counter, we also want to make this value persistent so it doesn’t reset to zero when the program is restarted. We can do this by making the instance variable of the timer persistent, but we can also do it by making the internal counter persistent (which makes all instances of this counter persistent automatically).

Start by right clicking on the folder where you want to add your function block (typically a folder called Common, or Helpers, or Utilities, etc.). Then choose Add->POU+ from the context menu. In the Add POU dialog, enter MaintenanceCounter as the function block name and choose Function Block under Type. Finally, make sure Ladder Logic Diagram (LD) is selected under Implementation Language:

Click Open to create the function block:

Now define the inputs and outputs of the function block. I copied these from the count-up (CTU) function block, but I changed the PV and CV values from WORD (16-bit) toDWORD (32-bit) variables:

Now let’s add internal variables. Since we want these variables to be persistent, we need to change the variable declaration from VAR to VAR PERSISTENT. Then add two variables, as shown:

The RisingEdge variable is actually an instance of another function block, called anR_TRIG. This is a function block that detects a rising edge on an input. R_TRIG is actually defined in the IEC-61131-3 programming language standard for

automation systems. Internally it “remembers” the last state of the input value, and by making it persistent, it will remember that value even through a power cycle or program restart. The CurrentValue variable is just the internal representation of the CV output value. At the end of our function block, we’ll copy the CurrentValue to CV. Again, we’re making it persistent so the value won’t be reset on a power cycle or program restart. Now add the ladder logic for the MaintenanceCounter implementation:

In rung 1, the R_TRIG function block detects a rising edge on the CU input. In rung 2, it adds 1 to the counter value on the rising edge. Rung 3 zeros the counter on a reset, and since it executes after the ADD instruction, the RESET input will be “dominant”. That is, if the RESET input is on while the CU input turns on, the RESET input will keep the counter at zero. Rung 4 copies the internal value to the output, and rung 5 sets the Q(counter done) output based on the CV and PV values. I’m sure you can see some other interesting possibilities. For instance, if you needed a counter that stopped counting when the current value reached the preset value, you could easily make one. I’ll leave that as a homework exercise for you.

Build a Function to Convert Celsius to Fahrenheit Most industrial sensors will report the temperature in Celsius, but some US customers might want their machine to have setpoints entered in Fahrenheit. This is a good application for a function because the output (temperature in Fahrenheit) can be computed directly from the input (temperature in Celsius).

Start by right clicking on the folder where you want to add your new function. SelectAdd->POU... from the context menu. In the Add POU dialog, enterCelsiusToFahrenheit as the Name, choose Function under Type, and make sure to enter REAL in the Return type box. Make sure Ladder Logic Diagram (LD) is selected in the Implementation language drop down:

Click Open to create the empty function:

Note that the return type (REAL) is defined after the function name at the top. This is actually an implicitly declared output variable, where the variable name is the name of the function itself. At some point in the function you have to set this implicit variable to some value.

Now declare an input variable (TemperatureInCelsius):

The formula for converting from Celsius to Fahrenheit is to multiply by 9, divide by 5, and then add 32. Since we’re doing this in ladder logic, let’s break it down into 3 steps and use an internal (temporary) value. Add a variable called Temp:

Remember that any variables declared in a function will be re-initialized to their default values (typically zero or false) at the beginning of each call to the function. If you’re familiar with other languages like C/C++, BASIC, or Pascal, these are like local variables.

Now create the ladder logic to compute the temperature in Fahrenheit:

In rung 1, it takes the input temperature, multiplies by 9 and stores the result in the temporary variable. Rung 2 takes the temporary result and divides by 5, storing it back into the same temporary location. Rung 3 takes the result of rung 2 and adds 32, returning it as the result of the function.

Note that a purely mathematical function like this is better implemented in the Structured Text language, but this is a good introductory example of a function with local variables implemented in Ladder Logic. We can revisit this in a later section on Structured Text.

The VAR_IN_OUT Variable Type In the examples above, you can see three types of variable declarations: inputs (VAR_INPUT), output (VAR_OUTPUT) and local (VAR). There is another optional block you can add, called VAR_IN_OUT. In other languages like C/C++, etc., this other type would be called “pass by reference”. With the normal inputs and output types, the value of the input is copied into the function or function block and copied out of it into an output. For a BOOL or INT type, this happens very fast and it also prevents a function block from inadvertently modifying an input value (which would normally be unexpected by someone using a function block). However, there are cases where you want a function block to do something to a variable, or you don’t want to incur the overhead of copying a very large variable structure into a function block. In these cases you can use the VAR_IN_OUT type. As an example, look at this function block that increments a number:

You would call it like this:

Notice that the Number input has a little two-directional arrow next to it indicating that it’s an in/out variable.

Also note that we still had to declare an variable for this function block, even though the function block itself doesn’t have any internal state. If you were the user of this function block, it might be a bit annoying. We can actually use a trick to fix this. Instead of declaring Increment as a function block, we can declare it as a program:

A program’s internal state is global, but in this case it has no internal state. That means you can use it like this:

Note that you have to add the VAR_INPUT and VAR_IN_OUT blocks manually, since the wizard that creates a program POU only adds the VAR block. Using a VAR_IN_OUT block is an advanced topic, but it does come up enough that you need to be aware of it. For instance, let’s say you had a long list of measurements stored in an array and you wanted to compute the average. Copying the array into function could be an expensive operation (causing a higher than necessary scan time), but passing a reference to the array by declaring it as an in/out variable is as fast as passing an integer. As a matter of understanding, when you create a function block and you declare a variable for it to represent the internal state (like a timer variable), that internal state variable is implicitly being passed to the function block by reference, which is why the function block can read and write to that variable.

Usage The overall structure of your automation project should be broken up into programs, with one program representing one functional unit of your machine. The more you break it up into logical pieces, the easier it will be to find the part of the logic you’re looking for. Average programs might have 5 to 15 rungs of ladder logic, but this isn’t a hard and fast rule, and

you should group your logic however it makes the most sense for someone reading through your logic. Try to keep related things together and unrelated things apart.

Use functions and function blocks to build the “domain specific language” of your application. Use descriptive names for your functions and function blocks that clearly describe what they do. If you see the same 5 rungs of logic repeated over and over, that’s a good indication that perhaps you should be looking at creating a function or a function block (but only if it improves readability).

Functions and function blocks are particularly useful to contain identical bits of logic that are likely to change. Putting logic like that in a single place makes it easy to change. However, just because two bits of logic are identical now doesn’t mean they aren’t likely to diverge in the future. This is actually quite common in automation programming. You might have a conveyor system with 5 identical zones, but the customer may some day want to add some feature to zone 4 that you can’t anticipate. Making aConveyorZone function block seems like a great idea at the beginning, but may be a bad decision in the long run. Making these kinds of decisions can only be informed by experience. As a general rule, Idon’t think machine control logic (e.g. contacts and coils that make the machine start and stop) belongs in a re-used function or function block, but common elemental operations like logging an event, computing common formulas, or communicating with some piece of hardware should be contained in a re-usable POU. That’s because the physical and electrical configuration of the machine is likely to change on a piece-by-piece basis, but the way you log events, or compute an average, is only likely to change simultaneously across your entire project (or is unlikely to change at all). Note that functions and function blocks can access global variables too. For example, I would expect a function block that logs an event to access a global event array where the event history is kept. Accessing physical inputs is also fairly common. Be careful not to do something unexpected, such as setting a physical output (each output should be driven by exactly one rung in a program somewhere, not in a function block). Finally, since each POU can be implemented in a different programming language, using functions and function blocks is a great way to write parts of your project in a language that’s more appropriate to the task at hand.

9 - TwinCAT 3 Tutorial: Structured Text TwinCAT 3 includes all five IEC-61131-3 languages: Ladder Diagram, Structured Text, Function Block Diagram, Sequential Function Chart, and Instruction List. If you’re coming from the Allen-Bradley world then obviously Ladder Diagram is going to be your most comfortable language, but I expect you’ll also want to make use of Structured Text. In fact, Beckhoff themselves typically present Structured Text as the go-to language for programming in TwinCAT 3.

I prefer writing most of my programs in Ladder Diagram for the obvious reasons: ease of troubleshooting, and the ability of electricians to go online with the program and debug it. However, we can’t forget that old adage, “use the right tool for the job,” and there are times when Structured Text is the right tool, and Ladder Diagram is not.

Structured Text has similarities to Pascal or BASIC (at least after they removed the concept of line numbers from BASIC). The most applicable feature of Structured Text for us are LOOPs.

The FOR Loop Imagine for a moment that you have an array of a thousand REAL data values and you want to compute the average of those values. The formula is pretty simple: just add them up and divide by 1000. Obviously this presents some difficulty in Ladder Diagram, but in Structured Text, we can just use a FOR loop. Start by creating a new function. Call it AverageOf1000 and make sure you select a function with the return type of REAL, and Structured Text (ST) in the Implementation Language drop-down box:

Click Open. Now you’ll have an empty Structured Text function:

We could pass the array in as an input, but if you remember from the last section, that would mean copying the entire array every time this function is called, which could negatively impact the scan time. It’s better to pass large data structures like this by reference, which means we declare it as a VAR_IN_OUT variable:

Next declare some local variables: one to store the sum of the values, and another to be an index to hold where we’re pointing to in the array.

Now we can write our logic, which consists of a FOR loop and a division operation:

On line 1 it initializes the value of variable Sum to 0. Note that the := operator means assignment. It computes the expression on the right (the RValue) and stores it in the variable on the left (the LValue). Also note that each statement ends with a semi-colon. This is important and you’ll get a syntax error if you don’t include it (the exception is the semicolon at the end of line 4, which is optional, but frequently included in many Structured Text examples). Lines 2 and 4 define the FOR loop. Line 2 defines a loop index variable (called Indexin this case), followed by an assignment symbol (:=). This means the Index variable will take on the values from 1 to 1000 and BY 1 means it will count by 1. The lines between 2 and 4 are what will be executed with each value of Index. If you were to watch the runtime execute this logic, what you’d see is (roughly):



Set Index to 1



Execute line 3



Set Index to 2



Execute line 3



Set Index to 3



Execute line 3



Set Index to 4



Execute line 3



Set Index to 5



Execute line 3



R



Set Index to 999



Execute line 3



Set Index to 1000



Execute line 3

As you can see, loops can have a significant impact on scan time, especially as the number of iterations becomes high. If you’re running TwinCAT 3 on a modern PC, then 1000 iterations isn’t too bad, but executing a million iterations on a 2 GHz PC is likely going to take a minimum of 0.5 milliseconds, and that’s without doing anything in the loop. You have to be aware of this and program accordingly. If you’re averaging the list of the last 100 sensor readings, don’t even worry about it, but if you’re doing math-heavy computation on thousands of data points, be aware that it might be too much work to do in one scan time.

Line 5 takes the Sum and divides by 1000, assigning the result to the return value of the function. Note that I added a decimal point to the value 1000.0 and I did this to remind the reader that I’m dealing with floating point numbers here. This is a style choice. You don’t have to do it.

The WHILE Loop (and IF/THEN/ELSE Blocks) Another type of loop is the WHILE loop. Instead of executing a fixed number of times like a FOR loop, it can execute as long as some condition is true. For instance, let’s say we want to find the first index in an array where the value is greater than some value:

The purpose of this function is to search an array of 1000 values and return the first index where the value is greater than some Threshold. If it doesn’t find any values greater than the Threshold then it returns 0, which is an invalid index. Line 1 initializes a boolean flag, Found, to FALSE. Since this is a function, it’s not really necessary because the value would be initialized to false every time you call the function, but if this was a function block, then you’d want to include that line because the value would be retained from call to call. Line 2 initializes the Index variable to the first array index (1). Lines 3 and 9 define theWHILE loop. Lines 4 through 8 will be executed repeatedly as long as the expression in line 3 returns true. As you can see, we loop until either we find it, or the Index passes the upper bound of the array.

Lines 4 through 8 comprises an IF/THEN/ELSE block. If the expression in line 4 is true, then it executes line 5. If the expression on line 4 is false, then it executes line 7 instead. To demonstrate how this works, assume the values in the array are 25, 50, 75, 100, 125, etc. Also assume Threshold is 80. We would expect the function to return a value of 4. Here’s how the function executes:



Line 2 sets Index to 1



Line 3 evaluates to true because Found is false and Index is 1



Line 4 evaluates to false (25 is not greater than 80)



Line 7 sets Index to 2



Line 3 evaluates to true because Found is false and Index is 2



Line 4 evaluates to false (50 is not greater than 80)



Line 7 sets Index to 3



Line 3 evaluates to true because Found is false and Index is 3



Line 4 evaluates to false (75 is not greater than 80)



Line 7 sets Index to 4



Line 3 evaluates to true because Found is false and Index is 4



Line 4 evaluates to true (100 is greater than 80)



Line 5 sets Found to true



Line 3 evaluates to false because Found is true



Line 11 evaluates to true



Line 12 sets the return value of the function to 4 (because Index has the value 4)

While this is a perfectly reasonable function, there are also some problems with it.

First of all, the scan time is quite variable. The worst case scan time is when the value isn’t found, and it returns 0. In that case it iterates through the entire array. In the best case it returns 1. Variable scan times can lead to problems if the worst case is never tested, or if you have a lot of functions like this and there’s some diabolical case where all of them have to execute the worst case on the same scan, and you exceed your allowable scan time.

Secondly, the logic is complex. Some of you might be laughing at me for saying that. If you’re a PC programmer writing code in C or BASIC then the function above is actually quite simple, yet in PLC programming we have an abnormal emphasis on simplicity. We want logic that is obviously correct when we look at it, and the above function isn’t obviously correct unless you give it a significant amount of analysis. To analyse it you really have to “play computer” and walk through at least 2 different scenarios: one where the value is found, and one where it’s not found.

Earlier in this section I talked about expecting electricians to go online with our programs and do troubleshooting. An electrician can understand Ladder Diagram, and with a little bit of work they can probably understand the FOR loop example above, but there are going to be a lot of people who won’t be able to understand this example of a WHILEloop with IF/THEN/ELSE blocks. If you believe these people don’t have any business going online with a PLC, then suggest you should change your attitude. Automation is a team sport and we have no room on the team for big egos. Use the simplest logic you possibly can (not the shortest). If the machine you’re programming has 10 motors, don’t try to write the motor start/stop logic in Structured Text with a FOR loop. Don’t even make a function block and re-use it 10 times. Just write 10 different programs in Ladder Diagram and copy the logic. Sure they might share some common logic, like an OkToRunMotors coil that gets set in another program. Remember that these are 10 physically different motors and the conditions for starting and stopping them are likely to change over time. Recognize that and keep the logic separate. On the other hand, Structured Text is the right tool for the event-logging and recipe-handling logic of a program. An electrician logging into the PLC to understand why a motor isn’t starting isn’t going to be concerned with the event-logging module. Structured Text is also the right tool for manipulating data, such as a scan received from a barcode scanner or an RFID reader. Complex math is also more easily expressed in Structured Text.

Using the right tool for the job means taking more than the problem itself into account. Make sure you take the capabilities of your team and the customer’s capabilities into account too.

Don’t Loop on an Input A novice programmer will write this:

Notice how we’re looping on an input. An input is a real-world physical input. It only changes when an I/O scan happens. When the runtime executes this logic, it will enter the loop and potentially never exit, and none of the rest of your program will execute again. The machine will appear to freeze, outputs will stay in their last state, and bad things will happen. Simply, if you’re using an input as the conditional in a WHILE loop, then you don’t have a good understanding of how the PLC runtime works, and you need to stop and go back to the beginning. Most PLCs work by reading the physical inputs into memory, running the program logic, and copying the new values of the outputs to the actual physical outputs (that’s a simplification and not true of all PLCs, but it’s a good model to start with), and then doing it again and again. The amount of time it takes to do all that is your scan time, and we want the scan time to be as short as possible. Causing the program to enter a loop that waits for an input to turn on will essentially stop

the program. In some cases it will also prevent the I/O scan from happening, so it’s impossible for that input to change state again. The machine will freeze forever.

Ladder Diagram doesn’t give you the option of shooting yourself in the foot like this, but Structured Text does. Stay away from infinite loops.

Mixing Ladder Diagram and Structured Text I’ve shown you how you can write programs, functions, and function blocks in Structured Text, but sometimes it’s nice to add a little Structured Text in the middle of your Ladder Diagram program. It turns out that a program can include something called an Action(which is like a mini local sub-program that you can call from your program) and theAction can be written in a different implementation language than the parent program. To add an Action, right click on an existing (Ladder Diagram) program and choose Add->Action+ from the context menu. All you have to enter is a Name and choose anImplementation Language. Choose Structured Text. The new Action will show up in theSolution Explorer under your program. The Action has access to all the declarations (inputs, outputs, and local variables) of the parent program or POU. You can call the Action like any other program: just add a block and enter the Action name.

String Functions String functions can be used in both Ladder Diagram and Structured Text, but when you start to do complicated string manipulation then I suggest moving into Structured Text because it can be easier to understand.

Here are your typical string functions and what they do:



LEN(s) – returns the number of characters in string s



LEFT(s, n) – returns the n left-most characters from string s, or returns s if n > LEN(s)



RIGHT(s, n) – returns the n right-most characters from string s, or returns s if n > LEN(s)



MID(s, n, p) – returns n characters from string s, starting at position p (first character number is 1, not 0)



CONCAT(s1, s2) – returns strings s1 and s2 joined (concatenated) together



INSERT(s1, s2, p) – returns a new string formed by inserting s2 into s1 at position p



DELETE(s, n, p) – the opposite of MID, returns string s with the n characters starting at position p removed



REPLACE(s1, s2, n, p) – combines DELETE and INSERT – removes n characters from s1 starting at position p, and replaces them with s2



FIND(s1, s2) – returns the position of string s2 in string s1, or 0 if not found, and is case sensitive

You can, of course, create your own string functions. For instance, it might be useful to have a different replace function that takes a 3 strings: a string to search, a string to find, a string to replace all instances of the found string with:

Notice how the variables are declared as T_MaxString instead of STRINGR

STRING Limitations Since variables are statically allocated in TwinCAT 3, when you define a STRINGvariable you have to declare the length. Implicitly this is 80 characters, and it uses up 81 bytes of memory (80 for the data and one byte for a null terminator). Strings are limited to a length of 255 characters. There is a specific type called T_MaxString which is an alias for STRING(255). Be careful because TwinCAT 3 will silently truncate a string to the maximum defined length of the destination string when you do an assignment.

When you make your own string functions, you should use T_MaxString as the variable type to make sure they work with any string passed to them. If you don’t, the input and output variables will be silently truncated to the length you specify.

Conclusions Structured Text is a powerful tool. In some PLCs, like the Allen-Bradley ControlLogix line, you have to pay extra for the Structured Text editor, but with TwinCAT 3 you get it for free. (Actually, you get the Ladder Diagram editor for free tooR)

With great power comes great responsibility. Use your new powers wisely and sparingly. When programming PLCs, the first priority is correctness and the second priority is readability. Nobody gets points for writing fewer lines of code. Remember that.

10 - TwinCAT 3 Tutorial: Building an HMI in .NET Beckhoff offers a paid HMI add-on for TwinCAT 3. That add-on is reasonably priced and offers basic functionality such as buttons, indicators, alarms, and more. There are also 3rd party HMI solutions that are compatible with TwinCAT 3, and there’s an OPC-UA server available for TwinCAT 3, so any HMI, SCADA or historian system that supports OPC-UA should be compatible with TwinCAT 3.

I’m going to describe an alternative. TwinCAT 3 comes with a free DLL that allows you to communicate from a Windows application directly to the PLC over Beckhoff’s ADS protocol. Since you can download a free version of Visual Studio from Microsoft (called the Express Edition, or more recently Community Edition), this is an interesting way to add an HMI to your system without paying additional software costs. Additionally, unlike packaged HMI solutions with their pre-defined set of features, .NET’s functionality is comparatively unlimited. An application written in .NET can do anything an HMI package can do, and more.

I can’t teach you VB.NET or C# in this short tutorial. There are so many excellent resources available online for learning VB.NET or C# that adding anything here would be superfluous, so I’m going to assume that you already know some .NET programming. If so, you probably already understand why using .NET to build your HMI would be a better alternative than paying for a 3rd party solution (in your case, at least).

After you’ve installed TwinCAT 3, the ADS DLLs are located inC:\TwinCAT\AdsApi. The .NET DLLs are located in the .NET sub-folder. Pick the version corresponding to the .NET version that you’re using. If you’ve downloaded Visual Studio Express or Community edition version 2010 or better, then you’ll want the DLL for version 4.

Simple Example: a Button In your TwinCAT 3 PLC project, create a Global Variable List called MyGVL, and inside that create a BOOL variable called MyBoolVariable. Go ahead and do that now, then activate the configuration and go online so you can see the value of that variable. Assuming you have Visual Studio 2010 Express or better installed, open it and use the new project wizard to create a Windows Forms project. I find the Windows Forms graphical editor to be more “HMI-like”. Of course you could also develop your HMI in WPF, but the concepts will be similar.

In the Solution Explorer window, under Solution->HmiTest->References, right click onReferences and choose Add Reference+ from

the

context

menu.

Choose

the Browsetab,

and

navigate

to

the C:\TwinCAT\AdsApi\.NET\v4.0.30319 folder, and select the TwinCAT.Ads.dll file. Click OK. The new project wizard should have created a new form for you called Form1. Open that form in the forms editor, and drag a button from the Toolbox onto the form (button1). Double-click on the button to create a click event handler (button1_Click). Put this in the click event handler:

private void button1_Click(object sender, EventArgs e) { using (var client = new TwinCAT.Ads.TcAdsClient()) { client.Connect(851); client.WriteSymbol("MyGVL.MyBoolVariable", true, reloadSymbolInfo: true); Thread.Sleep(1000); client.WriteSymbol("MyGVL.MyBoolVariable", false, reloadSymbolInfo: true); } } This isn’t a very practical example, but it’s a simple introduction to the ADS DLL. When you click the button, it creates a new ADS client. By default, this client will connect to the TwinCAT 3 (or TwinCAT 2) runtime on the local machine (but there’s an optional parameter to Connect which will make it connect to a remote node). You have to specify a port number. The default port number of the first PLC instance is 851 for TwinCAT 3 (it was 801 for TwinCAT 2). If you’re running multiple virtual PLCs in your system, the second would be 852 and so on. The WriteSymbol method looks up a variable by name, and then writes the given value to that variable. In this case, we writetrue, wait 1000 ms and then write false. You should be able to run your new Windows Forms program and click the button. If you have the TwinCAT 3 solution open in the background and you’re online with the Global Variable List, you’ll see the value of the variable change to TRUE for 1 second, and then change back to FALSE.

This example is simple, but it has several problems. First, creating a new client object every time is simple but inefficient. Second, the port shouldn’t be specified in each button click handler. Third, the variable name is entered twice and is entered in the client event handler, when really it should

be a property of the button. Fourth, just writing true, pausing for 1 second, and writing false actually freezes the HMI during that second, and is just a bad way to do it. We should be using the MouseDown and MouseUp events instead (to write a true and a false value, respectively). In order to handle cases where the PLC runtime is stopped, we really want to block reads and writes because it can really slow down the HMI waiting for timeouts to expire. We also want to fail gracefully if the variable doesn’t exist (either because of a typo or because the variable name changed on the PLC side and someone forgot to update the HMI).

A Better Architecture The requirement to use a single ADS client, and the requirement to block communications when the PLC isn’t responding, and to report on bad variable names implies the need for a centralized communication manager object, or at least one object for each virtual PLC.

The requirement for a variable name to be associated with each button or indicator implies the variable name should be a property of the button or indicator control.

I suggest creating custom UserControls for a standard momentary button and a standard indicator, each with custom properties to define the variable name and the indicator colors in the case of the indicator. At the beginning of your program, you instantiate a new communication manager object, and then register all of the buttons and indicators with the communication manager. This can be done fairly painlessly with a few lines of code. Then, adding a new button or indicator is as simple as dragging and dropping one of your custom user controls from the Toolbox onto your form and setting the variable name in the property page.

Example Communication Manager You can create a custom MomentaryButton by creating a new class and inheriting from Button:

using System.Windows.Forms; using System.ComponentModel;

namespace HmiTest { class MomentaryButton : Button { [Description("The BOOL Variable in the PLC to write to"), Category("PLC")] public string VariableName { get; set; }

} } Here I’ve taken the regular windows forms Button control and added a custom property called VariableName. When you drag a MomentaryButton out of your Toolbox onto your form, you’ll be able to edit the new VariableName property in the property page. Note that you’ll have to build your project to get it to show up in theToolbox. You can create an Indicator by inheriting from Label:

using System.Windows.Forms; using System.ComponentModel; using System.Drawing;

namespace HmiTest { [ToolboxItem(true)] class Indicator : Label { public Indicator() { this.AutoSize = false; this.Width = 100; this.Height = 25; this.TextAlign = ContentAlignment.MiddleCenter; this.BorderStyle = BorderStyle.FixedSingle; this.OnColor = Color.Green; this.OffColor = Color.DarkGray; }

[Description("The BOOL Variable in the PLC to read from"), Category("PLC")] public string VariableName { get; set; }

[Description("Color when the Variable is TRUE"), Category("PLC")] public Color OnColor { get; set; }

[Description("Color when the Variable is FALSE"), Category("PLC")] public Color OffColor { get; set; } } } Here I’ve customized a bit more by adding a default height and width, aligning to center, setting a border and turning off the auto-size feature. In addition to a VariableNameproperty, I’ve also added OnColor and OffColor properties, which will control the background color of the indicator when the value of the variable is TRUE or FALSE, respectively. Most of the functionality is in the new CommunicationManager class:

using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows.Forms; using TwinCAT.Ads;

namespace HmiTest { class CommunicationManager : IDisposable { private readonly int port; private readonly TcAdsClient client = new TcAdsClient(); private readonly List pollActions = new List(); private readonly Dictionary readWriteErrors = new Dictionary(); private bool connected; private DateTime? lastErrorTime = null;

public CommunicationManager(int port) { this.port = port; }

public void Poll() { foreach (var action in this.pollActions) { action(); } }

public bool IsConnected { get { return this.connected; } }

public ReadOnlyCollection GetReadWriteErrors() { var result = this.readWriteErrors.Keys .OrderBy(x => x) .ToList(); return result.AsReadOnly(); }

public void Register(Control control) { if (control == null) return; if (control is MomentaryButton) { this.register(control as MomentaryButton); } else if (control is Indicator) { this.register(control as Indicator); } }

private void register(MomentaryButton momentaryButton) { momentaryButton.MouseDown += (s, e) => { this.doWithClient(c => { c.WriteSymbol(momentaryButton.VariableName, true, reloadSymbolInfo: true); }, momentaryButton.VariableName); }; momentaryButton.MouseUp += (s, e) => { this.doWithClient(c => { c.WriteSymbol(momentaryButton.VariableName, false, reloadSymbolInfo: true); }, momentaryButton.VariableName); }; }

private void register(Indicator indicator) { this.pollActions.Add(() => { this.doWithClient(c => { if (string.IsNullOrWhiteSpace( indicator.VariableName)) { return; }

bool value = (bool)c.ReadSymbol( indicator.VariableName, typeof(bool), reloadSymbolInfo: true); indicator.BackColor = value ? indicator.OnColor : indicator.OffColor; }, indicator.VariableName); }); }

private void doWithClient( Action action, string variableName) { this.tryConnect(); if (this.connected) { try { action(this.client); this.readWriteSuccess(variableName); } catch (AdsException) { readWriteError(variableName); } } }

private void tryConnect() { if (!this.connected) { if (this.lastErrorTime.HasValue)

{ // wait a bit before re-establishing connection var elapsed = DateTime.Now .Subtract(this.lastErrorTime.Value); if (elapsed.TotalMilliseconds < 3000) { return; } } try { this.client.Connect(this.port); this.connected = this.client.IsConnected; } catch (AdsException) { connectError(); } } }

private void connectError() { this.connected = false; this.lastErrorTime = DateTime.Now; }

private void readWriteSuccess(string variableName) { if (this.readWriteErrors.ContainsKey(variableName)) { this.readWriteErrors.Remove(variableName); } }

private void readWriteError(string variableName) { if (this.readWriteErrors.ContainsKey(variableName)) { this.readWriteErrors[variableName] = DateTime.Now; } else { this.readWriteErrors.Add(variableName, DateTime.Now); } }

public void Dispose() { this.client.Dispose(); } } } Yes, it’s a little beefy for a class, but it does quite a bit, including managing the connectivity to the PLC and recording read/write errors for missing or misspelled variable names. Here’s how you use the CommunicationManager in your form: First, add a System.Windows.Forms.Timer to your form (in the forms designer) and rename it to tmrPoll. I left the Interval at 100 ms, which is the default. Then add a readonly instance of CommunicationManager as a form member variable, and add the following in the form’s constructor:

using System.Windows.Forms;

namespace HmiTest { public partial class Form1 : Form { private readonly CommunicationManager communicationManager = new CommunicationManager(851);

public Form1() { InitializeComponent();

foreach (var control in this.Controls) { this.communicationManager .Register(control as Control); } this.tmrPoll.Tick += (s, e) => { this.communicationManager.Poll(); }; this.tmrPoll.Start(); } } } Once you’ve done that, then you can drag and drop your new MomentaryButtons and Indicators on your form, set the VariableName, Text, OnColor andOffColor properties, and you’re done. There are also some troubleshooting features. If you want to display whether the connection to the PLC is “alive”, then use a timer to query theconnectionManager.IsConnected property and update something on the screen to indicate it’s connected or not. You could also log the disconnection to a file, etc. You can also get a list of read/write errors callingconnectionManager.GetReadWriteErrors(). Here’s an example of a button click event handler that will call the function, take the result, and display it in a message box:by

private void button1_Click(object sender, EventArgs e) { var readWriteErrorVariableNames = this.communicationManager.GetReadWriteErrors(); var readWriteErrors = string.Join( Environment.NewLine, readWriteErrorVariableNames); if (string.IsNullOrWhiteSpace(readWriteErrors)) { MessageBox.Show("None"); } else { MessageBox.Show(readWriteErrors); } } That’s a little crude, but it illustrates the idea.

More Ways to Read and Write Data For more .NET examples on reading and writing PLC variables, see the following Beckhoff information page: https://infosys.beckhoff.com/english.php?content=../content/1033/tcsample_labview/html/tcsample_labview_overv iew.htm&id= These sections are particularly useful:



Reading Arrays



Writing Structures



Reading and Writing Strings



Reading and Writing Time and Date Variables

Some Things to Note Here are some lessons I’ve learned:



If the HMI is running on the same PC as the runtime, ADS is very fast.



Try to keep the number of reads per second to less than 200 if on the same PC.



The amount of data you get in a single read doesn’t seem to affect performance much, so reading an array or a large structure is very efficient.



If an indicator can’t be seen, there’s no reason to poll the value or update it. If you build your HMI as a bunch of tab pages, before you go to read an indicator’s value, check to see if the parent tab page is visible. If not, skip it.



The CommunicationManager above isn’t threadsafe.



I have reason to believe the TwinCAT.Ads.dll is not threadsafe. If you need a multi-threaded HMI, write a threadsafe wrapper for the client, and serialize all calls with locks.

Further Thoughts This chapter only scratches the surface of what you can do with .NET and TwinCAT 3 working together. Obviously .NET has strong capabilities for connecting to databases, accessing files and network resources, and communicating with many other peripherals. TwinCAT 3 is a fast real-time system with a large library of industrial hardware to choose from. The ADS DLL joins the performance of TwinCAT 3 with the versatility of .NET programming.

As you can see, adding more custom controls is fairly simple. You can easily add gauges, bar graphs, and other graphical indicators to display data from the PLC. You can use array and structure reads to implement event logging, alarms, and historians, and store that data in a local database, or a database on a server in your data center. You can report on that data with the Microsoft Chart control, or the Microsoft Report Viewer. You can have the HMI send emails when critical events happen.

Customers are calling for more and more connectivity between the automation system and the ERP and MES systems. Data and connectivity are big topics. TwinCAT 3’s seamless integration with .NET is a primary advantage over other automation solutions.

11 - TwinCAT 3 Tutorial: Introduction to Motion Control Motion Control is a big topic. Motion Control refers to the use of servo (and stepper) motors in your system. A servo requires a motor and a position feedback device such as a resolver or an encoder, and it controls the position of the motor using a feedback control system. Motion Control systems are not new, but they have a parallel lineage to PLCs. Since around 2000 we’ve seen more and more convergence of Motion Control and PLC systems, but they are still separately packaged modules within the system. In TwinCAT 3 they are separately licensed modules. The PLC system is licensed as TwinCAT PLC, and the motion control system is called TwinCAT NC. The latter is licensed depending on how many “axes” (i.e. servo motors) your system needs. The base package is 10 axes, and you can add more axes for more money.

The “NC” in TwinCAT NC means “numerical control”. It’s the same NC as “CNC machines”, which can mill (or cut) away material from a block of steel with accuracies better than a thousandth of an inch. In fact Beckhoff originally got its start in motion control systems, so TwinCAT 3 includes one of the better motion controller systems available today.

The TwinCAT NC module is just a basic motion controller that gives you “point to point” motion. That is, each axis is controlled independently. If you need your axes to work in a co-ordinated way, such as in a 3-axis CNC mill where you need to follow a complex path in 3 dimensions, then you need “interpolated motion”. TwinCAT 3 offers this as an add-on to TwinCAT NC, called “TwinCAT NC I”. This add-on actually includes a built-in G-code interpreter. G-code is the language that CNC mills (and FDM-type 3D printers) use to instruct the motion controller on the path to follow to create a finished part.

Industrial robots, such as those from ABB, Fanuc, Motoman, etc., are actually controlled by interpolated motion controllers internally. It’s no co-incidence that Fanuc is both a leading supplier of industrial robots and CNC mill controllers. The technology is similar. In fact TwinCAT 3 does support kinematics packages for industrial robots, and you can even purchase a Codian Delta Robot through Beckhoff and use TwinCAT NC I with a delta robot kinematics package to control the robot directly from TwinCAT 3. Since motion controllers have evolved from their origins in CNC milling and industrial robots, the sequential nature of their programming (in the form of G-code or proprietary robot programming languages) has never fit well with ladder logic programming, and gives rise to what I call the Ladder Logic/Motion Controller Impedance Mismatch.

In PLC programming it’s typical to think in terms of discrete outputs. Turning on an output can do many things, such as turning on a pneumatic valve to apply air pressure to a cylinder, turning on a motor to run a saw or a pump, or turning on a heater to control the temperature of a fluid. In all of these cases you can think of a discrete output meaning “try to do X.” For instance, “try to extend this cylinder,” or “try to start motor,” or “try to increase temperature.” We normally have feedback mechanism like a proximity switch, or a level or temperature sensor to tell us that our action succeeded. This try/confirm idea naturally gives rise to ladder logic programming patterns like the Five Rung Logic Blockand the Step Pattern. Implicit in the Five Rung logic is the idea of “when is it safe to perform (and continue performing) this action?” Motion controllers, on the other hand, are designed to follow a strict set of sequential instructions, with little consideration of how to recover when things go wrong. Consider the idea of a weld robot. It waits for a part to be loaded, follows a path (series of points) to move the weld tool into position, clamps the welding tool, fires the welder, unclamps, and follows another path back to the home position. The linear nature of the sequence makes this particularly easy to program, but what if someone opens the gate and removes the part while the robot is moving in towards the part? The robot can’t continue the program because the part is no longer there. However, the check for part presence would have been done at the beginning of the program. That means you need to insert a second part presence check right before you clamp the welder, and if it’s not present then skip over the weld and jump to the move-out sequence. Unfortunately this means the robot recovers by continuing in towards the missing part. The “right” thing would be to move back out immediately, but programming a way for the robot to find its way back away from the part without crashing into an obstacle can be quite difficult and complicated. That’s why many industrial robots are tended to by operators who frequently have to enter the cell with a teach pendant and recover by manually “jogging” the robot safely around obstacles back to a known home position. If the motion was done by a PLC controlling a sequence of air cylinders, the recovery logic would be comparatively simple: just retract them in the right sequence. Typically your “safety” rung in your Five Rung logic block already contains the logic about when it’s “safe” to retract those cylinders without crashing, so if you give them all the trigger signal to retract, they’ll automatically retract in the right order.

When a new control system programmer comes out of school, they always want to program the industrial robots because they look so cool. However, I always tell them, “the robots are stupid but PLCs make them smart.” For a PLC to make a robot smart, it needs to know the current robot (or motion controller) position. If you don’t know where the robot is, how can you program a smart recovery path? Unfortunately, getting the current robot position out of the robot and into the PLC can be difficult at best, and nearly impossible with some older robot controllers. Thankfully, with TwinCAT 3, the motion controller and the PLC are operating in the same runtime, so the PLC automatically has access to all motion controller information, which makes your job a lot easier.

Creating a Motion (NC) Task To add a motion controller to your TwinCAT 3 project, go into the Solution Explorer and find the MOTION node directly below the SYSTEM node. Right click on the MOTIONnode and choose Add New Item+ from the context menu. That will pop up the Insert Motion Configuration dialog box. Choose NC/PTP NCI Configuration and click Ok. You can only insert one motion controller. The motion controller automatically defines a task and that task can be scheduled under the SYSTEM->Real-Time node just like your PLC tasks. Typically this should be a high priority task (higher than your PLC tasks) and should run often, such as once every one or two milliseconds, even if your PLC only runs once every 10 milliseconds. This task handles all communication with the servo drives (even for drives from manufacturers other than Beckhoff) and provides a generalized “axis” interface for your PLC to deal with. It can also close the position feedback loop for some less expensive drives that don’t offer a position loop, synchronize multiple axes in an interpolated system, and perform kinematic transformations in the case of industrial robots. It even has a G-code interpreter.

Adding A Servo Drive and Axis Beckhoff offers a really good line of EtherCAT servo drives in their AX5000 range. It’s also advantageous to purchase a Beckhoff servo motor so you know they’re compatible (and it makes the configuration simpler). In order to size your servo motor you need to start with the performance requirements of your mechanical system (such as load inertia, required rates acceleration, deceleration, speed, and expected duty cycle. I suggest taking this information to your local Beckhoff sales representative and they can help you to size the motors and drives accordingly. When sizing the motor, optimal performance is typically achieved when the load inertia and motor inertia are approximately equal, though in practice this is sometimes impossible. Other mechanical considerations will also factor in: for instance, a belt drive can sometimes give you higher top speeds, but at the expense of lower acceleration and deceleration (due to the belt flexing), so adding more torque won’t help if your mechanical system can’t handle it.

Beckhoff also offers an interesting line of lower cost servo drives that can connect right into the terminal strip of an EK1100 head module. These are limited to around 48V and 4A max, and the position loop is closed in the motion control task rather than in the drive itself, but for lower performance smaller applications these can offer significant cost savings. There is also a line of similar terminal slices for stepper motor control, which offers an even lower cost alternative, if your application doesn’t need servo-class performance.

In Solution Explorer, under the I/O node, go to the point in your I/O tree where you’re going to attach the drive (or you can just do a bus scan too). When you add the drive (manually or with a scan), it will say, “EtherCAT drive(s) added. Append

linked axis to NC-Configuration“? If you choose Yes, it will add an axis object under the NC-Task in theMOTION node, and it will link the drive I/O under that axis to the new drive under the I/Onode. Typically you would answer Yes when programming a system for the first time. Here’s what it looks like after I’ve added an AX5118 drive (single axis 18 amp):

I highlighted the new drive and the new axis. Note that you can (and should) rename the drive and axis to match the real name of your axis in your electrical drawings.

Note that if you expand the Axis->Drive node you’ll see that it’s automatically linked some I/O to the physical I/O under the actual drive:

You never have to touch these mappings yourself, even if you’re connecting the drive to the axis manually. Double-click on Axis 1 under the Axes node, and go to the Settingstab. You’ll see where the drive I/O is linked to the axis:

If you click the Link To I/O+ button, it’ll give you a list of drives available for linking.

Configuring the Drive Double-click on the drive under the I/O. All of the configuration is done under the Drive Manager tab. The first thing you want to do is setup the motor and feedback. If you’ve purchased a Beckhoff motor, this is simple. In the tree, go to Channel A->Configuration->Motor and Feedback. Click the Select motor button on the right:

That will open the Select a Motor dialog box. You will need the motor part # from the motor nameplate, or from the motor ordering information. TwinCAT 3 includes a datasheet for each motor, so it knows all the motor characteristics and feedback characteristics. Select your motor and click OK. TwinCAT 3 will then display a dialog box titled, Power supply and extra settings for:

This is a “gotcha”. Most people would think this means you should specify what voltage we’re supplying to the drive, which in the United States would be 480V, 3 phase, 60 Hz. However, since I specified a Beckhoff motor and it was designed for Europe, I actually must keep the voltage set to 400V or I get an error (the voltage has to match the motor voltage). This was several TwinCAT 3 versions ago, so I don’t know if anything has changed since then, so it might be a good idea to check with your Beckhoff representative about this. Of course, these are still power supply settings (relating to the incoming power) so if you set the voltage to 400V with 10% tolerance, and connect it to 480V, the drive will fault because the input voltage is too high. Since the drive (and apparently the motor) is actually 480V tolerant, the “correct” way to handle this situation is to select Other settings, and then manually edit the U+rng setting to 30.0% (this is the maximum value allowed). This will allow your voltage to go up to 520V which is an 8% over-voltage allowance based on 480V. This means the drive will actually work from 360V up to 520V. Here are the settings:

Note: in my latest project (spring of 2016) I was able to specify 480V for all the servo motors, so it looks like Beckhoff has updated the software to fix this problem since I wrote it. Click OK. Note that if you’re in Canada, the typical industrial voltage is 600V. Unfortunately this means you’ll have to use a stepdown transformer to use these drives. Since there are also many other devices that can run off 480V, it’s typical to stepdown to just 480V instead of 400V, and use the settings shown above.

After setting the power supply settings, it will ask if you want to set the scaling and parameters now. If this is your first time adding a motor to this drive, say OK, but if you’ve already modified these parameters and you’re just selecting the motor again (or selecting a new motor), you’ll probably want to say Cancel. If you click OK, then it will display the Scaling and NC parameters node in the drive manager tree:

Make sure you set the Feed constant (highlighted). Assuming you’re driving a linear axis of some kind (rail or ball screw), this is the distance that the linear axis moves in one revolution of the motor. As soon as you set it, click the Set NC Parameters button. This will set the defaultNC parameters (below). You need to set reasonable motion limits for the axis (this is done in the Axis node underAxes, on the Parameters tab). Make sure you do this before trying to move your axis. This will depend on your application. For instance, it sets Maximum Velocity to 100% of the maximum motor speed. However, the mechanical limitations of your machine may not be able to tolerate that speed. You have to calculate that maximum linear speed your system can handle, and set this parameter accordingly. Similarly, set reasonable limits for Manual Velocity (a.k.a. “jogging”). The automatically calculated values are typically much higher than you would want during system testing and start-up. Calibration Velocity is used during the homing routines. Finally, setup the digital I/O on the drive. Not all drives have Digital I/O, but most at least offer inputs for limit switches. These are important, so let’s take a short detour on the topic of axis limits. An axis can have up to 3 sets of limits:

1.

Software Limits

2.

Hardware Limit Switches

3.

Hard Stops

Software Limits are software-only limits on the axis travel. They are practically free, but they’re also the least useful. First of all, software limits are only useful after the axis has been calibrated (homed), so they won’t protect you during system startup, or during the homing operation. Secondly, software limits depend on the motor’s encoder value, so it won’t catch

certain classes of failures such as a shaft coupler break, a belt break or a tooth skip on a belt. You set your software limits in the Axis node under Axes, on theParameters tab:

Hardware Limit Switches are physical limit switches that sense the axis has moved beyond the expected range of motion. Ideally these should be mounted to sense the carriage position (after the belt, if you have one, or after the shaft coupler or anything else likely to break). Typically these are normally closed switches, proximity sensors, or through-beam type sensors. The state of the sensor should be “on” when the axis is in the “ok” range, and “off” if it exceeds the limit. This protects you against a cut wire or blocked sensor. They need to be positioned far enough away from the end of travel to allow the axis to decelerate from maximum speed to a stop (or almost to a stop) before hitting the Hard Stop.

The Hard Stop is a physical limit on the axis. Ideally this needs to be able to take the impact of the axis hitting it at full speed, though this is rarely possible on heavier loads. Note that shock absorbers can be installed as well. If you’re wondering why the Hard Stop needs to be able to take an impact at full speed, consider the worst possible scenario: a power outage, or some kind of motor cabling fault while the axis is moving at top speed and near the end of the axis travel. The drive won’t be able to decelerate the axis, even when it trips the hardware limit switch, so the only option left is a crash. Thankfully this is rare, but it should be designed against if possible.

Your specific choice of axis limits is application-dependent. However, on a typical ball-screw or belt-drive type axis I suggest the default should be normally closed Hardware Limit Switches, placed at a reasonable deceleration distance from a physical Hard Stop capable of stopping the axis in the event of a full speed impact. Given that scenario, I think Software Limits are unnecessary. As always, work with your mechanical designer(s) to find a reasonable solution for your application.

An AX5000 series drive has optional I/O that you can configure for your application, including positive and negative overtravel limit switches. In the drive’s Device Managertab, under the Device->Digital I/O node in the tree, see the Digital I/O settings box. In the AX5000 series, find parameter P-0-0401 and expand it:

Expand Positive limit switch and set the following 3 parameters:



Configuration



Limit switch reaction



Input number

Configuration and Input number are self-explanatory. For the Limit switch reaction, note that Beckhoff drives categorize events into 3 categories: Class 1 Diagnostic (fault), Class 2 Diagnostic (warning), and Class 3 Diagnostic (status), a.k.a. C1D, C2D, and C3D. Simply, C1D is a “drive shut down” error, kicks the drive out of OP mode into ErrSafe-OP mode, and requires you to execute a drive reset command to clear the error (from the PLC, this is accomplished with an FB_SoEReset). This seems rather harsh for an overtravel limit fault, which really isn’t an internal drive, motor, or feedback error. I lean towards using a C2D warning, with an E-Stop. Ultimately it’s up to you to choose a reasonable configuration. Does a Hardware Limit Switch fault in your application indicate something internal has malfunctioned? For selecting E-Stop vs. Axis halt, this refers to programmable error reactions. In theDrive Manager tab, go to the Channel A->Configuration->Error reaction / drive Halt node in the tree:

Here you can set your deceleration rates in the event of an E-Stop or Drive Halt error reaction.

Online Mode and Jogging If you happen to have your I/O and drive (with motor) connected right now, you can actually activate the configuration and jog the motor back and forth with the built-in manual controls.

Assuming you’ve activated the configuration, in Solution Explorer, go to MOTION->NC Task 1 SAF->Axes and doubleclick on the axis node. Now go to the Online tab:

First you have to enable the axis. This means turning on the feedback loops within the drive, and turning on torque (i.e. current) to the motor. Note that when you do this, you will be causing motion, so make absolutely sure that no dangerous motion can result before you do this. To enable the axis, click the Set button inside the Enablinggroup box. That will display the Set Enabling dialog box:

Click the All button. That’s a shortcut to enable the controller, the forward direction, the backward direction, and set the speed override to 100%. If successful, the drive will be enabled and the Online tab will look like this:

Now to test the axis, you can use the manual slow buttons (F2 and F3) at the bottom to “jog” the axis. When you do that, the motor should move, and the position number at the top of the screen should change to indicate the actual position. If you have the limit switches installed, this is a good time to test them out. Jog towards a limit and make sure the axis responds appropriately when it reaches the sensor.

If your axis is hooked up to the actual load, now is the time to do some initial “tuning” of the axis. Most tuning is simply choosing appropriate maximum values for acceleration, deceleration, and jerk on the axis Parameter tab. It’s sometimes helpful to setup a repeating back-and-forth motion while you try changing the parameters. Using the axis Functions tab, you can create a Reversing Sequence:

Remember that your axis hasn’t been “homed” (a.k.a. “referenced”) yet, so the positions are based on where the axis was when it started up. Make sure that you can safety move to the desired positions without hitting an end stop before you start the reversing sequence. Start with a shorter move and move it up from there.

You want to modify the acceleration, deceleration, and jerk parameters so that you get a fast response without unacceptable overshoot. Remember that at no time should you exceed the maximum velocity and torque capabilities of your mechanical system.

TwinCAT 3 comes with a really good oscilloscope-like tool called the TwinCAT 3 Scope. You create one by adding a TwinCAT 3 Measurement project under the solution in theSolution Explorer. I won’t go into the Scope now, but it’s a useful tool when trying to optimize a motion axis. If you want to play around with the axis Online tab but you don’t have the physical I/O or drive connected, don’t worry. If your axis isn’t linked to the drive, or if you disable the drive, then TwinCAT 3 treats it as a simulated axis. You can do everything you would do with a normal drive and motor, and TwinCAT 3 will simulate an actual position that’s equal to the commanded position. This is really useful for simulating your system offline before you start it up in the field.

Manually Releasing a Brake Some servo motors come with an optional motor brake. Some systems may have an external brake. In both cases the drive itself controls the brake output directly, energizing it to release the brake mechanism. The drive makes sure that torque is applied to the servo motor before releasing the brake, so the axis doesn’t fall or move unexpectedly.

In some cases you want to be able to manually release the brake for maintenance tasks. Before explaining this procedure, you must understand that this is situation-specific. If there’s any residual force on the axis, or if the axis carries a vertical load and will fall under gravity if you release the brake, then uncontrolled motion will occur when you release the brake. It is your responsibility to make the system safe before you release the brake. This might including blocking a vertical axis mechanically so it can’t fall, or releasing stored potential energy such as air before unlocking the brake. The manual brake release function is in the Drive Manager tab of the physical drive node in the I/O list. In the tree, go to Channel A->Service Functions->Manual brake control. There you have the options for Automatic, Force lock and Force unlock.

Linking the Axis to the PLC We’ve already seen how the NC task’s axis node links to the physical drive. Now we need to link the axis to the PLC. The first thing you have to do in your PLC is add a reference to the Tc2_MC2 library. In Solution Explorer, go to PLC->PLC1>PLC1 Project and right click on the References node. (That assumes your PLC project is called PLC1.) ClickAdd library+ from the context menu to open the Add Library dialog. You can find theTc2_MC2 library under Motion->PTP in the tree.

Next you need to define a variable of type AXIS_REF in your PLC project (typically in a global variable list):

Now build your PLC project (right click on the PLC1 Project node in Solution Explorer and choose Build from the context menu). Now the Axis1 variable will be available for linking to the axis in the NC task. Double-click on the axis under the Axes node in the NC task and go to the Settings tab:

Click the Link To PLC+ button to open the Select Axis PLC Reference dialog:

Select the line that says MyGVL.Axis1 and click OK. Now you’ve linked the axis to your PLC. This actually creates I/O linking between the PLC task and the axis in the NC task:

Make sure you activate this configuration before continuing.

Enabling the Axis You use the MC_Power function block to enable an axis from the PLC:

In this example, set EnableAxis1 to TRUE and if the function block is successful, theAxis1Enabled coil will be set to TRUE. If it won’t enable, the MC_Power function block has diagnostic outputs: Error and ErrorID. If you do get an error, you can find more information about the error codes by searching the internet for TwinCAT NC Error Codes.

Jogging the Axis Use the MC_Jog function block to jog the axis from the PLC:

Make sure you choose sensible values for Velocity, Acceleration,Deceleration and Jerk. You would normally connect JogForwardPB and JogBackwardsPB to physical button inputs or momentary buttons on your HMI. The axis will jog as long as you hold down on the button. Note that in almost all cases you should condition theJogForward and JogBackwards inputs so that they only work if you have the machine in manual mode. Also note that this block can also perform incremental jog operations (where you move a specific amount with each button push). To implement incremental jog, change the Modeinput to E_JogMode.MC_JOGMODE_INCHING and set the Position input to the amount you’d like to move with each button press. If you want to implement both modes and give the operator the ability to switch between modes, make sure you only use one MC_Jog block and just change the Mode input based on the operator’s selection.

Homing the Axis Use the MC_Home function block to home (a.k.a. “reference”) the axis to a known position:

Similar to jogging, you should condition the Execute input with your manual mode signal, so the axis can only be homed if the machine is in manual mode. When you execute the home function by pressing the HomePB momentary button, the axis will start moving towards the home sensor. When the sensor turns on, it will stop and move off the sensor slowly to record the exact encoder raw value when the input turns off. Once it turns off, the axis position will change to the position given in thePosition input of the block. In order to make this work, it’s best to implement the home sensor as a normally open switch positioned near the end of the travel. It needs to be built mechanically so that the sensor state is “on” if you’re on one side of it and “off” if you’re on the other side. The logic of MC_Home uses the initial sensor state to determine which direction to start moving to look for the sensor “edge”. You can change the direction and speeds of the homing sequence in the Parameter tab of the axis.

I

suggest setting the speed towards the sensor reasonably high, and the speed off the sensor to be slow, as this will allow it to find the sensor quickly, but still obtain good accuracy when finding the sensor edge.

Reading the Axis Status If you want to know whether the axis has been homed, or if you want any kind of information about the axis status, use the MC_ReadStatus function block:

This block can be called constantly (once per scan) and the resulting variableAxisStatus contains a lot of interesting information about the state of the axis right now. Also, once you’ve called this function, it also updates theMyGVL.Axis1.Status variable with the same value, which you can use anywhere in your logic.

Moving the Axis to an Absolute Position Now that the axis is referenced, you can command it to move to an absolute position with the MC_MoveAbsolute function block:

Make sure that MoveCommand is conditioned with theMyGVL.Axis1.Status.Homed status signal. You don’t want to try to move to an absolute position if you haven’t homed the axis yet. When the block sees a rising edge on the Execute input, it will initiate a move to the given Position using the given motion parameters (Velocity,Acceleration, Deceleration, and Jerk). Note that turning off the Executeinput does not stop the motion. You must use MC_Halt or MC_Stop to do that. The reason is that when the rising edge happens on Execute, it pre-computes a desired motion profile for the axis and the NC task starts feeding this commanded position to the drive over the time period of the motion profile. The only way to change this motion profile is by executing a different function block. For instance, you can execute another MC_MoveAbsolute block to “change your mind” on the fly and send the axis to another final position. The NC task will compute a blended motion profile to move the axis to the new position. You could also execute an MC_Stop function block which will re-compute the motion profile to bring the axis to a stop. What this means is that your PLC program must “remember” what you’ve commanded the axis to do so that you can take appropriate action by issuing new commands if the situation changes. For instance, if someone opens a guard door, you may be allowed a short amount of time where you have to decelerate to a stop under power before the safety system will drop out power to your drive. This is the responsibility of the PLC programmer to make sure this action happens. The only action the safety system can take is removing power, it can’t enforce a deceleration.

Conclusion Obviously motion control is a big topic, and I’ve only introduced the subject and how it relates to TwinCAT 3 here. I hope this will be enough to get your axes moving and head you in the right direction. Please understand that motion control is complicated and requires a lot of experience. If you’re stuck on some technical detail, don’t be afraid to contact Beckhoff’s technical support. In my experience they’re very knowledgeable and helpful.

12 - TwinCAT 3 Tutorial: Introduction TwinSAFE When we talk about “Safety” in industrial automation, we’re talking about specific functions of our machines, equipment and our processes related to the safeguarding of people. In most places there are legal requirements for machine safeguarding, and those regulations are generally based on national and international standards (e.g. CSA Z432-04 in Canada), but can vary between regions and nations. It is generally the role of a Professional Engineer to at least review and stamp the design of a safety system, though this can vary between jurisdictions.

Safety systems concern themselves with the control of energy. All sources of energy and all stored energy in a system must be accounted for and controlled. This includes electrical, pneumatic, and hydraulic sources, and includes potential energy stored in suspended loads, springs, compressed air, and kinetic energy of moving parts. Safety systems work by interrupting sources of energy and controlling potential or kinetic energy. Safety systems also involve proper mechanical guarding of a machine so that operators can’t come in contact with dangerous areas of the machine while sources of energy are connected or potential energy is uncontrolled.

A properly designed safety system consists of process, mechanical, electrical, and (increasingly) software elements that combine to form a safe working condition for machine operators and maintenance personnel. The demands on the reliability of the electrical and software elements are strong enough that ordinary electrical components and PLCs are unacceptable for use. In general (and this depends on specific cases) electrical and software components must be designed in such a way that any single component failure won’t lead to a loss of safety function, and the failure is detected and reported, and prevents further operation of the machine until it’s repaired.

Take, for example, an electrical relay. A typical relay is unsuitable for use in a safety system for at least 2 reasons:

1.

The relay can fail (typically by contact welding) in the “on” position resulting in the loss of its capability to interrupt a source of electrical energy.

2.

We typically detect failure of a relay by monitoring a normally closed contact. Depending on the construction of the relay, the failure may not be detected if the normally closed contact isn’t mechanically linked to the normally open contacts.

In order to reliably interrupt an electrical energy source in a way that a single component failure doesn’t cause a loss of safety function, and the fault is detected, you typically use two force guided contacts relays. The use of two solves the first problem of single point of failure, and the use of force guided contacts solves the second problem of reliably monitoring the relay’s state.

You can conveniently purchase a combination of two force guided contacts relays in this configuration, but it requires an external monitoring system to check for proper function. You can also purchase a “safety relay” with the two force guided contacts relays and the monitoring function all built-in.

Safety PLCs such as the Beckhoff EL6900 are capable of providing the monitoring function. Importantly, input devices such as e-stop buttons, gate switches, light curtains, and safety mats are also constructed with redundant components and must be monitored for correct operation, and the EL6900 can provide monitoring of these components as well. As you can imagine, there are special requirements placed on the design of a Safety PLC. Just like any other device in a safety system, the failure of any component in the Safety PLC mustn’t result in the loss of the safety function, and it must be detected. This includes both electrical and software components. The designs are then certified by 3rd party certifying bodies before they’re fit for use in a safety system.

The TwinSAFE module of TwinCAT 3 is the programming software for Beckhoff’s Safety PLC. The TwinSAFE program you write is executed by the EL6900 module and is separate from your traditional PLC program. Safety monitoring devices (i.e. “safety inputs”) are connected through EL1904 cards and safety output devices (i.e. “safety outputs”) are connected to EL2904 cards, both of which are certified safety devices. The communication between the EL6900 and EL1904/EL2904 cards is carried over the normal EtherCAT I/O network. This is possible because the communication uses a special Failsafe over EtherCAT (FSoE) protocol. The FSoE protocol is compatible with any EtherCAT master that supports slave-toslave messaging/mapping. Each FSoE device can monitor the status of the communication channel itself and can revert to a safe state (i.e. “off”) if the communication is lost. Aside: it’s worth noting that the FSoE protocol seems to work by the EtherCAT master routing data between the EL6900 card and the EL1904/EL2904 cards. This is happening inside the TwinCAT 3 real-time, which is typically running inside ring 0 of a Windows PC. The question arises: what could a malicious person who gained remote access to a TwinCAT 3 PC do to the safety system? The safety logic itself is supposedly password protected, but it’s not clear to me if the authentication is done inside the EL6900 or in the client, or if the authentication is programmed flawlessly (in most realworld situations the answer is usually “no”). In my opinion, a malicious person who gained remote access to the PC could likely (but not certainly) modify the safety logic in the EL6900, but could almost certainly override the EtherCAT master’s handling of the FSoE data and perform a man-in-the-middle attack by making each safety component think the communication link was still working but feed false data to one or more devices, such as telling the EL2904 cards to turn on an output when it shouldn’t. Therefore a robust safety system design needs to take this into account and take reasonable steps to prevent remote and/or unauthorized access to the TwinCAT 3 PC. In most cases you, as a control system integrator, are not an expert in IT systems and

certainly not an expert in IT security. Realizing this is step 1, and consulting a knowledgeable professional is step 2. While no system is 100% secure, there are industry best-practices that should be followed.

Adding the EL6900, EL1904, and EL2904 Cards The most basic TwinSAFE system needs the Safety PLC, an input card, and an output card. These are added to your I/O bus in the typical way. Here I’ve created a small test system with an EtherCAT master, an EK1100 head module, and the three safety cards:

Adding a TwinSAFE Project Right click on the SAFETY node in the tree in Solution Explorer and choose Add New Item+ from the context menu. That will pop up the Add New Item – TwinSAFE dialog:

Make sure TwinCAT Default Safety Project is selected and enter a name for your safety project in the Name box (highlighted). Click Add. Now you’ll see the TwinCAT 3 Safety Wizard:

Enter your name as the Author and a descriptive Internal Project Name. Click OK. After a moment the wizard will create your new safety project:

Linking the TwinSAFE Project to the Cards Expand the MySafety Project node so you can find the Target System node:

Double-click on the Target System node so you can view the EL6900 properties:

First you have to link it to the physical device (the one you added in the I/O tree). Click the link button (circled in red above). You will see the Choose Physical Terminal for Mapping dialog:

Select the EL6900 node at the bottom of the tree (highlighted) and click OK. Each safety device in a TwinSAFE system has a unique FSoE address, and this has to be set with dip switches on the physical card. This is set in binary. Refer to the card’s documentation for how the switches map to binary values (it’s not obvious by looking at the card, unfortunately). The FSoE-Address field in the EL6900 properties page (highlighted above) has to match the dip switch setting on the card. You connect the safety project to the EL1904 and EL2904 I/O cards by creating “Alias Devices” for them. In the Solution Explorer tree, below the Target System node, expand the TwinSafeGroup1 node and then expand the Alias Devices node inside the group. Right-click on the Alias Devices node and choose Import Alias-Device(s) from I/O-configuration from the context menu. That will open the Select from I/O tree dialog:

Click the Select All button and then click the OK button. It will try to read the dip switch (FSoE) addresses from each card when you import it. If you haven’t activated the configuration with the I/O in the tree, you will receive error messages telling you that you have to manually update the FSoE addresses for each alias. Now both cards are imported as alias devices:

Setting Input Card Properties Double-click on the Safety In 1 alias device node to edit the EL1904 properties:

Notice the FSoE Address box at the top. This must match the dip switch settings on the EL1904 card. Let’s assume that the input (EL1904) card has an FSoE address of 2, so set that value to 2. Now take a look at the 8001 FS Sensor Test and 8002 FS Logic of Input Pairs sections. Safety inputs are often wired in pairs, and there are different kinds of safety inputs. In all cases the system has to provide protection against short circuits and crossed channels. It does this by use of test pulses. The EL1904 can generate its own test pulses, but it can also be configured to accept test pulses generated by output signal switching devices (OSSD) such as light curtains or solid state gate switches. These parameters are used to configure what type of safety input the card should expect. There are two very common configurations. The first is the default configuration, as you can see in the diagram above. That is, you have generation and testing of test pulses turned on (8001:01 and 8001:02 set to True) and you have 8002:01 set to single logic channel 1/2. This configuration is appropriate for all dry contact circuits, such as E-Stop buttons, mechanical gate switches, and the normally closed feedback signals from a safety relay. The EL1904 card provides a terminal that sources power to the dry contact circuit, and it automatically inserts test pulses and expects to see those same test pulses on the input. Each test pulse source is offset in time, so it can detect if someone crosses the circuits, or if the circuit is shorted to 24V or 0V. The second common configuration is when you already have a device that outputs OSSD pulses on its outputs, so the EL1904 doesn’t need that feature. In that case, you turn off the test pulses, and configure the logic to expect OSSD pulses:

Note in the setting it refers to “asynchronous analysis”. Most OSSD devices should be outputting the test pulses out of phase with each other, so the pulses never overlap. If they do, it’s considered a fault, because that’s how the EL1904 detects crossed wires. However, there are a few OSSD devices that don’t force their pulses to be asynchronous. Ideally you should avoid these devices, but in some cases they still exist, so if you want to use them you’ll have to use the any pulse repetition OSSD, sensor test deactivated setting. This allows the test pulses to occur simultaneously. There is a fourth setting called short cut channel 1/2 is no module fault. The EL1904 documentation is rather vague on the use of this setting, but indicates it’s needed for “safety mats”. I’m not clear on what this setting does, and I suggest contacting Beckhoff before using it for anything other than a safety mat. In this example I’m going to describe an E-Stop function, so we can leave the default settings in the card.

Setting Output Card Properties Double-click on the Safety Out 1 alias device node to edit the EL2904 properties:

Note that I already set the DIP switch value (a.k.a. FSoE address) to 3.

The EL2904 output cards have 4 configuration parameters:

8000:01 Standard outputs active allows you to link a PLC output to a safety output, and the card will automatically “AND” the PLC output value with the safety logic output value. This allows the PLC to override an output by turning it off. In this

case, the safety logic is responsible for when it’s allowed to turn on, but the PLC controls the actual timing. In my opinion you should leave this set to False and not use this feature. First of all, it’s going to make the logic more obscure, and you can achieve the same effect with an AND block inside the safety logic itself. Secondly, if you want to perform monitoring of the output in your safety logic to make sure it switches on and off when you tell it to (via a feedback circuit) then you can’t do that if the safety logic doesn’t have access to the PLC signal. Therefore it’s generally better to do this all in the safety logic, not using this “standard outputs” feature. 8000:02 Current measurement active is for monitoring a broken circuit. With this feature enabled, when the output is on, the card expects to see between 20mA and 500mA of current, otherwise the card will fault. Obviously if the device you’re turning on doesn’t draw the right amount of current, you’ll have to turn this feature off. In my experience, the feature doesn’t work very well anyway and seems to trigger falsely. That could be because the brand of relay we were using isn’t compatible with the current measurement being used. I suggest trying it, but beware that it might not work. 8000:03 Testing of outputs active is for monitoring crossed circuits, and makes use of test pulses. The card creates a train of test pulses on the 4 card outputs and makes sure that (a) the pulses don’t overlap and (b) the pulses are all different lengths varying from 300 to 800 us. These should be short enough not to interfere with the operation of a mechanical relay. In general this feature should be activated, except in cases where you are interfacing with a device that can’t tolerate test pulses. (Note that setting 8000:02also activates test pulses, so you would have to set both settings to False.) 8000:04 Error acknowledge active controls how the card recovers from a detected output error. By default, you have to cycle power to the card to recover (typically by pulling the card out of the rack). If you change this setting to True, then you can reset it through the normal safety error acknowledgement signal configured in the TwinSAFE logic group. Ultimately I’m trying to explain how TwinSAFE works, but I can’t make your decisions for you. It’s your responsibility to understand the impacts of these settings and design your system to meet the safety requirements of your specific application.

The TwinSAFE Group When you created your TwinSAFE project, the wizard automatically created a TwinSAFE “group”. The groups are a way to logically separate your safety programs. For instance, let’s say you have two physically separate work-cells both controlled by the same TwinSAFE EL6900 safety PLC. Since they’re logically two distinct machines, it makes sense to separate the logic into two separate TwinSAFE groups.

The purpose of the groups is to manage error reactions. When a safety malfunction is detected, such as an output card fault, a communication fault with one of the cards, a discrepancy between the two channels of a two-channel input device, or a detected failure of a safety relay via a normally closed feedback circuit, then the entire group enters a faulted state,

all of the outputs revert to their “safe” state (i.e. “off”), and the fault must be acknowledged with the error acknowledgement signal. In the case where you have two physically separate machines controlled from the same safety PLC, it makes sense to separate them into two groups so that a safety device malfunction on one machine doesn’t cause the other machine to stop.

The TwinSAFE Group Properties When the wizard created your TwinSAFE group, it also created the first alias, calledErrorAcknowledgement. This is supposed to be linked to a PLC output or a standard input (such as a pushbutton) and used to acknowledge safety group errors. Linking standard variables is always a 2-step process: first you have to link from inside the logic to the alias, and then from the alias to the PLC or physical I/O. Start by opening the safety application called TwinSafeGroup1.sal by double-clicking on it in the Solution Explorer tree. That opens a blank TwinSAFE application:

Not very exciting, I admit. The trick is, you now have to right click on the background and choose Properties from the context menu. This opens the properties tool window:

You have to link the ErrAck signal (highlighted) to the ErrorAcknowledgement alias variable that the wizard created for you. Click on the spot shown by the red circle, above, and you should see a “R” button. Click on that button to open the Choose Alias Portdialog box:

Select the highlighted node (Channel 1 under ErrorAcknowledgement). Now the properties will show that you’ve linked the signal:

You should also link the ComErr (communication error) and FbErr (function block error) outputs. These are diagnostic outputs which tell you that there’s an error either in a connection (from the EL6900 to one of the EL1904/EL2904 cards) or in a function block (such as a two-channel discrepancy error), respectively. Start by creating two output aliases. Right click on the Alias Devices folder under the TwinSafeGroup1 folder in theSolution Explorer tree and select Add->New Item+ from the context menu. That will open the Add New Item dialog:

Select Standard from Installed Templates in the top left corner. Select 1 Digital Output (Standard) in the center box, and then enter a name (ComErr.sds) in the Name box. Finally, click the Add button. Now you’ll see the ComErr standard output alias device added:

Repeat the process to add an FbErr output alias device:

Now go back to the TwinSAFE Application Properties tool box and link the ComErr andFbErr outputs to your new aliases:

Now we have to link these standard input and output aliases to the PLC. I’ve created a GVL in my PLC project with the following 3 variables defined:

Right click on your PLC project and build it, so that you can link these variables. Standard input and output alias variables can only be linked from the alias itself. Double-click on the ErrorAcknowledgement.sds alias in the Solution Explorer tree:

Click on the link button, and link it to the MyGVL.ErrorAcknowledgement output from the PLC. Now do the same thing for the ComErr and FbErr aliases to connect them to your PLC. Whew! Now we’re ready to start writing some safety logic.

Writing Safety Logic Double-click on the TwinSafeGroup1.sal safety application in the Solution Explorer tree. What you see is a blank safety application:

The wizard automatically created the first “network” for you. The safety program is written in the function block diagram (FBD) language. The program is broken up into “networks” (think of them like rungs in a ladder logic program). Start by renaming Network1 toEStop. Click on the existing network name to select it and start typing the new name, and press enter when complete. Now you need to start adding function blocks to the network. Available safety function blocks are in your Toolbox window. Use View->Toolbox from the main menu:

Now click and drag the safeEstop function block (highlighted) and drop it in your network:

The safety function blocks are certified safety function blocks. The combination of the function blocks and the hardware platform (the EL6900) makes them suitable for use in a safety application.

This block can monitor up to 4 two-channel e-stop circuits. If any of the EStopInNsafety inputs turn off, then the EStopOut safety output will turn off immediately and theEStopDelOut safety output will turn off after a programmable delay (see the Delay Time (ms) box). To turn the EStopOut and EStopDelOut outputs back on, the function block needs to see all enabled EStopInN inputs on, and needs to see a 0->1->0 transition on the Restart input. The block has optional EDM1 and EDM2 “electronic device monitoring” inputs. EDM1corresponds to the EStopOut output and EDM2 corresponds to the EStopDelOutoutput. If EDM1 is enabled, then the EDM1 feedback input must be on prior to the 0->1->0 transition on the Restart input. Assuming the EStopOut output is connected through an EL2904 output card to turn on a pair of force-guided relays, then the normally-closed contacts of both relays should be connected in series to the EDM1 input of the function block. In the event that one of the force guided relays fails to switch off, this feature will prevent the safeEstop block from restarting, preventing the system from operating. Start by adding a two-channel e-stop circuit to inputs EStopIn1 and EStopIn2. Start by single-clicking on EStopIn1 to select it, then right click on it and selectProperties from the context menu:

Note that EStopIn1 and EStopIn2 are an “input pair” and can be configured to monitor both inputs together. That’s what we want in this case, so in the Channel Interface property, select the Two-Channel setting. Now under the Assigned Variable Name property, type in a descriptive variable name for this variable. I suggestEStopChannelA:

These changes are reflected on the function block as well:

As you can see, the EStopIn1 and EStopIn2 inputs now have lines under them indicating they are activated, and there’s also connecting lines between them inside the “AND” block with 100 indicating the block will monitor that the two inputs switch within 100 ms of each other. If they don’t, this is considered a function block error, so it will turn on the Error output, and it’s also a group error so all outputs in the group will turn off and you will need to send a 0->1->0 transition on the ErrAck group input to reset.

Now configure the EStopIn2 input with variable name EStopChannelB. Similarly, configure the Restart input with a variable

name

of EStopReset,

the EDM1 input

with

variable EStopFeedback,

the EStopOut output

with

variable EStopMCR(“MCR” meaning “master control relay”), and finally the Error output with variableEStopError:

Link the TwinSAFE Variables We declared the variables in our safety program, and now we have to link them to our Alias Devices. There should be a tab at the bottom of your screen called Variable Mapping. If you can’t find it, try going to View->Other Windows>Variable Mapping. This is where you link your safety program variables to aliases:

Clicking the “R” button in the right-most Alias Port column brings up the Choose Alias Port dialog box, just like when you linked the ErrAck, ComErr, and FbErr group I/O to the Alias Devices earlier. In this case you’re going to be linking the safety variables to the EL1904 and El2904 safety I/O cards. Go ahead and link all but the EStopErrorvariable to the Alias Devices:

The EStopError variable is a diagnostic signal, not a real safety output. We really just want to send that signal back to our PLC so we can display a fault message or show it on the HMI. First, create another standard output variable alias device calledEStopError.sds (the same as you did for ComErr.sds and FbErr.sds previously). Second, use the drop-down box in the Data Type column to change the data type fromsafeBool to Bool (a safeBool can only be linked with safety I/O and a Boolcan only be linked with standard variables). Third, link the EStopError variable to theEStopError.sds standard output alias:

Finally, you’ll need to create another input variable in your PLC (EStopError AT %I* : BOOL;), build your PLC project, double-click on the EStopError.sds alias, and use the link button to link it to the PLC I/O.

Downloading to the EL6900 Start by doing a safety “verification”. This is like a “build” or “compile” in that it checks for errors. In the case of TwinSAFE, it checks for things like inputs or outputs not connected to alias devices, or inputs on function blocks that are enabled and

not connected to variables. From the menu at the top, select TwinSAFE->Verify Safety Project (Project Level). If successful, it will display the message Verification Process succeeded in the status bar at the very bottom of the window. If you don’t have any hardware connected, then this is as far as you can go. Everything else requires you to have a working EtherCAT master with all your I/O connected and communicating. This requires a typical “activate configuration” as well.

If you do have the hardware connected, select TwinSAFE->Verify Complete Safety Project (Project and Hardware Level) from the main menu. This does a similar verification but it also checks that the DIP switches on the physical I/O cards match the settings in the alias devices, etc. If that verification is successful, then you can select TwinSAFE->Download Safety Projectfrom the main menu. That’s going to pop up a dialog box asking you for 3 things: 1.

Username

2.

Serial number

3.

Password

The default user name is Administrator. The serial number refers to the EL6900’s serial number. You can find it on the card itself, or you can also find it by double-clicking on theTarget System node under the safety project in the Solution Explorer tree. The default password is TwinSAFE (note that it’s case sensitive). Once you put the system into production, this password should be changed and kept in a safe place, typically with the safety audit documentation. Once you enter these fields and continue, it will then display a download screen where it downloads the function blocks to the EL6900. When the download is complete, you have to enter the password one more time to activate the download, which allows the EL6900 to run the safety program.

Going Online First, go online with your PLC project and look at the signals from the safety PLC. You should see that you have a communication error (or ComErr). To reset the communication error, it needs to see a 0->1->0 transition on theErrorAcknowledgement signal. Second, you can go online with the safety PLC. Double click on the safety group to display the safety program, and then select TwinSAFE->Show Online Data of Safety Project from the main menu. Now you’ll be able to monitor the online value of the variables, and see the state of the function blocks. There is also a small window or tab at the bottom of your screen called Safety Project Online View. Once you’re online, open this window and you can get more information about diagnostic signals from each connection and each function block.

Additional Function Blocks The EL6900 supports more than just the safeEstop function block. There is a document published by Beckhoff listing all the function blocks. The document is titled “Function Blocks for TwinSAFE Logic Terminals”, and is available from the TwinSAFE Product Documentation page.

Connections Between Function Blocks and Networks You can connect an output on one function block to an input on another function block by just clicking and dragging a connecting line between them. For example:

If you have a hard time getting your mouse to start the link or end the link, then try using the zoom feature to zoom in first, and that will make it easier to hover your mouse over the right link spot. This is even more important if you have a single output branching out to multiple inputs.

Let’s say you have a function block in one network and you want to connect an output from that block to an input on a function block in another network. You can do this by typing in a fully qualified address of the other point using the syntax:Network.FunctionBlockName.InputName. For example:

Monitoring in the PLC TwinSAFE provides the reliability you need to meet the safety requirements of your machine, but you still need to make the operator interface user-friendly. Typically this means bringing status signals into your PLC and using that to create faults and alarms. I’ve already described how to bring the error signal from a function block out to an alias device and link it to a PLC variable. You can also link PLC I/O directly to the safety I/O in the I/O tree in the Solution Explorer. For instance, here’s where you can link a PLC input to an EL1904 safety input:

Here’s how you link a PLC output to an EL2904 safety output:

Conclusion That’s it! Obviously safety is a big topic and TwinSAFE is necessarily complicated by its stringent requirements. Overall, the TwinSAFE system can be a cost-effective way to implement a safety system, and it provides excellent integration into the rest of the PLC control system which makes monitoring and diagnostics much simpler.

13 - TwinCAT 3 Tutorial: The Scope View In this section I’m going to introduce you to one of the most powerful tools in your debugging arsenal: the TwinCAT 3 Scope View. This is a software-based digital oscilloscope that comes free with TwinCAT 3.

Create a Test PLC Project First, create a PLC project for testing. I’m going to describe a simple scenario that generates a sine wave at a reasonably high frequency. Follow these instructions (assuming you’ve read the rest of the tutorials and you’re familiar with how to do this):

1.

Create a new TwinCAT 3 solution

2.

Use the wizard to create a standard PLC project (called PLC1)

3.

Setup the SYSTEM->Real-Time node (hint: “Read from Target“)

4.

Setup SYSTEM->Tasks->PlcTask to run at 1 ms

5.

Remove the structured text MAIN program and create a new ladder logic MAINprogram

6.

Create a global variable list called GVL

Here’s the contents of the GVL:

Here’s the MAIN program:

Now build and activate the configuration. Go online and make sure you can see it running.

Add a TwinCAT Measurement Project The TwinCAT 3 Scope View is built into the Visual Studio 2010 shell. You use it by first creating a new “TwinCAT Measurement Project.” In the Solution Explorer, go to the top level (Solution) node and right click on it. Select Add->New Project+ from the context menu. That will open the Add New Project dialog:

Make sure you have TwinCAT Measurement selected in the upper left under Installed Templates. Then select Empty Measurement Project in the middle panel, and give it a descriptive name in the Name field. Click the OK button to create the project. Now you’ll see the new project under your solution node in the Solution Explorer:

Add a Scope to your Measurement Project Now you have to add a Scope. There are two basic scope types: YT and XY. A YT scope plots some variable on the vertical Y axis and time along the horizontal (X) axis. An XY scope plots two variables against each other, one on the X and one on the Y axis. In our case we want a YT scope. In the Solution Explorer, right click on your new measurement project and select Add->New Item+ from the context menu. That will open the Add New Item dialog window:

With TwinCAT Measurement selected in the upper left under Installed Templates, selectScope YT Project in the middle panel, add a descriptive name for your scope in theName box, and click Add. Now you’ll see a new scope node under your measurement project in the Solution Explorer:

Adding Variables to your Axis Start by adding the GVL.SinOfAngle variable to the Axis that was created by the wizard. Right click on the Chart>Axis node under your scope in the Solution Explorerand choose Target Browser from the context menu. That will display the Target Browserwindow:

At first it will show the ROUTES node, and under it you should see your PC name (where the red box is in the figure above). Click on your PC name, expand Port_851, click on theGVL. node, and you will see SinOfAngle in the right pane. Double-click on SinOfAngleand it will add that variable to your Axis. Then close the Target Browser window. Now you’ll see SinOfAngle added to your Axis under the Solution Explorer:

Record Data Make sure you have the scope window visible by double-clicking on the scope node in the Solution Explorer. Now look for the TwinCAT Measurement buttons in your toolbar:

The button on the left is the Record button. The button in the middle is the Stop button. Click the Record button, wait a few seconds, and click the Stop button (and you’ll have to click an OK button on a dialog after clicking the Stop button). Now you’ll have some data to look at:

If you want to zoom in on the data, hover your mouse over the point where you’d like to zoom, and use the mouse wheel to zoom in (forward on the mouse wheel zooms in, backwards zooms out). If you don’t have a mouse wheel, look for the Zoom X button on the chart toolbar. Moving left/right is as simple as clicking, holding, and dragging the mouse left or right.

Measuring using Cursors You can take measurements from the scope using X and Y cursors. Let’s say I want to measure the sine wave period, which is the time between one positive (rising) zero crossing to he next positive zero crossing. First we need to create two X cursors. In theSolution Explorer, under the Chart node, find the Cursor node. Right click on the Cursornode and select New X Cursor. Do this twice. You’ll see the two cursors listed under theCursor node:

Notice that the C cursor is green and the C 01 cursor is blue (you can barely see it in the icon). You’ll also see new green and blue X cursor lines in the chart itself:

Now click and drag the green cursor to the first positive zero crossing of the sine wave, and click and drag the blue cursor to the second positive zero crossing:

Now right-click on the C node (the actual cursor under the Cursor node) in the Solution Explorer again and select Cursor Window from the context menu. That will display theCursor window:

You can see the relevant details of each cursor in the C and C 01 columns. Now click the Delta button (circled in red). That displays another column showing the difference between the two cursors:

Here we can see the time difference between the two cursors is about 360 ms, which makes sense since the input angle is changing by 1 degree every scan, and each scan is 1 ms long.

Adding More Axes The sine wave has a Y range of -1.0 to +1.0 in amplitude. Suppose we wanted to compare this to the input angle (in degrees) which ranges from 0.0 to about 360.0 degrees. Plotting them on the same graph would be difficult because the amplitudes are so dissimilar.

To scope variables with different amplitudes, simply add more axes. Right click on theChart node in the Solution Explorer and click New Axis from the context menu. That will add a new axis called Axis(1). You can rename these axes if you like:

Looking at your chart window, you’ll now see two Y-axes on the left side.

Now right click on the new Axis(1) node and select Target Browser from the context menu. This time use the Target Browser window to add the Port_851->MAIN.->Angle_deg variable to your new axis:

When you do this, it has to delete the captured data and it asks if you want to save the data to a file first. Just say “no”. Close the Target Browser window. Now use the Record and Stop buttons to record a few seconds of data again:

The chart looks a little more cluttered with two axes overlaid like this. Zooming in helps a little. However, I generally prefer using “stacked Y axes.” You can enable this feature in the chart properties. Right click on the Chart node in the Solution Explorer and chooseProperties from the context menu. Now scroll down to the bottom of the Propertieswindow and look for the Stacked Y-Axes property. Set this property to True. Now your axes will appear “stacked”:

I also find stacked axes useful when scoping boolean variables because I find it easier to see which ones are off and which ones are on at any given point in time.

Conclusion The Scope View is a powerful tool for troubleshooting irksome transient conditions or measuring high speed signals. Once you know the basics, the interface is fairly user-friendly and best-of-all the tool is included for free with TwinCAT 3.

14 - TwinCAT 3 Tutorial: Part Tracking Part tracking is a topic that’s applicable to all PLC makes and models, so I’m going to start by discussing the properties of a generalized discrete part tracking system, and then explain how that can be implemented in TwinCAT 3.

Properties of a Generalized Part Tracking System A good discrete part tracking system should:



Track the presence or absence of a part at each location



Work even with imperfect part sensing



Track the work done to each part



Works regardless of mode (even in manual mode)



Optionally track other metadata such as lot # or serial #



Be resilient to abnormal operation such as faults or loss of power



Allow the operator to recover from an abnormal operation, such as by removing a part from the system

Using a Structure to Track a Part in TwinCAT 3 A good way to implement part tracking in TwinCAT 3 is to use a Structure. Imagine a welding machine where the operator places a part into the machine, it welds the part, and then the part is automatically ejected either into the good part bin, or into a reject chute. A reasonable Part structure might look like this:

Part tracking data needs to survive the reboot of the controller PC, so we need to declare this as a persistent variable. Here I’ve created a new variable calledWelderPart in a persistent section of a global variable list:

Tracking Part Presence We presumably have a sensor or some other method to detect part presence. Perhaps an operator has to place a part in the fixture, we detect the part with a sensor, and then they have to push a button to initiate the cycle. When this happens we need to set thePresent bit and generate a new serial #. To generate a new serial #, we need a persistent LastSerialNumber variable:

Now create a new ladder logic Part Tracking program called TrackWelderPart:

The first rung creates a “pulse” bit, sometimes called a “one-shot”. This is a bit that’s only on for one scan. The reason this is only on for one scan is because in the second rung, we use a Set coil to set the WelderPart.Present bit, of which we have a normally closed contact in the first rung. The third and fourth rungs are responsible for incrementing the serial # and assigning it to the part tracking variable.

Notice so far that taking the part out and putting it back in (or a momentary failure of the part present sensor) won’t cause the part tracking to change. The part is still considered present, and the serial # is fixed.

Tracking Weld Status The weld status is tracked presumably by feedback from the welder. Most welders operate by the PLC sending a weld trigger signal, and the welder responding with aWeldOK or a WeldFault signal. If that’s the case, we can track weld status like this:

Again we’re creating a “pulse” bit that’s only on for one scan, called WeldedPulse. Notice also that the first three conditions on the first rung could be moved over to the program that fires the welder, and it could be used to create a coil called OkToWeld. You could then use OkToWeld here in place of those three contacts. This logic will prevent the welder from welding the same part twice, which is very important. It’s also important to realize that this logic works equally well in both automatic or manual mode. As long as the welder fires correctly with a part present then we’ll track the weld status. This is useful both for testing and to prevent mis-tracking the part.

Tracking Part Removal There are three ways this part can leave the welder:

1.

It can be ejected into the good part bin

2.

It can be ejected into the bad part chute

3.

An operator can remove it

I like to create a separate pulse for each event so that I can use that pulse bit for other tasks like part counters and logging events.

Let’s assume we have a Part Ejector cylinder and cylinder that toggles the chute between the good bin and rejected part chute position. Ejecting the part with the toggle in the good position sends it to the good part chute, and ejecting the part with the toggle in the bad position sends it to the bad part chute.

When removing the part from the part tracking memory, it needs to clear the entire structure. Thankfully there’s an easy way to do this. Declare a constant of type Partand use a MOVE instruction to copy the empty structure over to of the part tracking variable:

Somewhere else in your logic, you need to make sure that the ejector only extends if the chute position matches the part status, so the part tracking should have the Weldedand WeldOK bits set in order to eject in the good position. Don’t let it eject a bad part into the good part bin even in manual mode.

Recovering From Abnormal Conditions Let’s assume the operator removed the part by hand. You have some options. One way to handle it is to just accept that the part was removed. I like to use a timer just to “debounce” the sensor a little bit:

However, unless manual removal is part of the normal process, I don’t like to automatically accept a part removal. I prefer creating a cycle stop fault, and then having the operator press a button to manually remove the part tracking data:

This is a nice handshake with the operator. The machine is saying, “I know you removed the part, and now I know that you know that I know that you removed a part.” In any relationship, communication is key.

If you happen to have a situation where the part can literally fall out, like a vacuum gripper on a robot, you typically monitor the vacuum feedback to make sure the part is still attached. In that case, if the vacuum feedback turns off while you’re supposed to have a part, that’s a pretty serious fault (typically an immediate-stop fault) because it detects a dropped fault. In that case, you need to get the operator to confirm the removal of the part because they need to remove the part from the machine (wherever it dropped). It’s also possible there’s something wrong with the gripper.

Tracking a Part From Station to Station Machines often have automated transfer equipment such as conveyors, pick & place equipment or material handling robots to move parts around from station to station within the work cell. Imagine the case where a conveyor is moving the part from station 1 to station 2. Assume there’s a sensor at station 1 and station 2. We could use a persistent array to track the part at each station:

The logic to move a part from station 1 to station 2 might look like this:

Part Tracking in Rotary Tables Rotary tables present an interesting challenge. A rotary table is a table that can continuously rotate in either direction. It has some number of part nests, and it moves parts around a cell by indexing the entire table forward one index distance so all the parts are moved from the current station to the next station in one move.

If you use an indexing table (where you extend a cylinder or run a motor until a sensor turns on) then you can usually get away with using logic like the conveyor-type logic above, except that you index all the stations simultaneously. This is no problem, just remember to index the last station first and the first station last, so you don’t overwrite the next station.

However, rotary tables can be more complicated than this. What happens if the operator is in manual mode and indexes a 4 position rotary table 4 times? Shouldn’t the original parts be back in their original locations? Does that mean you index the last station into the first station too? Also, what about servo-type rotary tables that run in reverse or index to halfstation positions?

The problem is that we’ve modeled the situation wrong. The parts are always fixed in a certain nest, and the table position determines which station they’re at. It’s more correct to create a PartInNest tracking array:

(I deliberately added an index 0 at the beginning to make the part tracking logic simpler later, as you’ll see. Nest 0 is an invalid nest, meaning no nest at station.)

Then you need to use the table position to determine which nest number is at each station (or 0 if no nest). In some cases this is done with an array of binary proxes (one prox indicates you’re in some position, and a set of other proxes indicate which one you’re in, so you’d need 2 to indicate 4 stations, 3 to indicate up to 8 stations, etc.). In other cases you have a servo with an angle position, and you use the angle position to figure out which nest is at each station. For now just imagine a function that returns an integer (nest number) for a given station:

This is simplified logic. It assumes that if the proxes show off,off then nest 1 is at the OP10 station, and if it shows off,on then nest 2 is at the OP10 station, etc. You would have to customize it for your machine and for each station. This is just an example. Note that if no nest is in position it sets the nest number to 0.

Now there’s more than one way to do this. You could useNestInStation[NestNumberAtOP10].Present everywhere in your OP10 part tracking logic but the variable name starts to get really long, and it’s a bit complicated for an electrician to understand “indirect addressing”. Here’s another alternativeR create a program that takes a Part as a VAR_IN_OUT, and call it like this:

We’re doing something a little tricky here. We’re passing the actual nest part data by reference, meaning the OP10 part tracking program can read and write into that variable. Inside the program it looks like this:

The logic is straightforward, the variable name lengths are reasonable, and you don’t have to worry about which nest is at the OP10 station because that’s handled by the outer program. Note that you have to use the InAnyStationPRX input to determine if you’re actually in a station, or else you’re looking at index 0. By tracking the part in the nest, you don’t have to shift data around with MOVEinstructions when you index the table, and you have the option of displaying the part data on the HMI both by station and by nest. You can even display the part tracking by nest when the table isn’t in an actual station position.

Conclusion This has been a brief introductory tutorial to generalized part tracking in TwinCAT 3. In my experience you can build an arbitrarily large part tracking system based on these few simple concepts.

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF