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 atrick 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.