User-defined function best practices
When designing the UDF, there are some questions that need to be considered.
Modern automation controllers continue to expand with options and opportunities for the programmer. One of those improvements is the ability to develop User-defined functions (UDF), or add-on instructions in the Rockwell Automation ecosystem. These improve portability, consistency, and code security, all of which add to the kit of the automation programmer. Like all tools they can be used properly or abused; who hasn’t pried the lid off of a can of paint with a ‘screwdriver’? I’d like to take a moment and discuss some of the options and opportunities that these constructs offer and some of the Best Practices that should be employed.
These constructs are often confused with a Sub-Routine. Quite often an introduction will begin that way. It is important to keep in mind that they are not, “quite”. A better analogy would come from Object Oriented (OO) Programming, they are an Object or Class. These are elements that are expected to be completely self-contained, working with information from outside but not totally dependent upon it.
When designing the UDF consider the desired functionality in the broader sense, there are some questions that need to be considered…
- What happens if the preceding rung logic or enable is False?
- What happens on pre-scan (that execution period just before the controller goes to Run)?
- How are internal variables initialized?
- How are internal Timers or other time sensitive elements handled?
These questions don’t exist or have the same implications for a subroutine. In a subroutine the data required inside the routine must come from the outside and everything inside the routine is considered “fair game” for modification. The UDF doesn’t follow the same rules.
Let’s begin by looking at some of the concepts associated with UDF’s and see what they require and how some of these can and should be employed.
Data into and out of the UDF is handled in two ways, pass by value (ByVal) and pass by reference (ByRef). These are broken down as follows (I’ll use Rockwell nomenclature for the moment
- Pass By Value: Input and Output
- Pass By Reference: InOut
The ByRef parameter exchange is exactly as expected, the pointer to the data specified is applied to a local name and all of that data is exposed to the UDF. The benefit here is that the UDF can modify the data in the supplied data, the bad news is - the UDF can modify the data. Planning for this must be considered.
One of the biggest mistakes that’s made with ByRef data is to create a huge User-Defined Type (UDT) and put everything into that; this is typical of the subroutine method. Including things such as one-shot bits, internal timers, counters, and working registers/arrays. Nothing stops the programmer from doing this but the real question is “Does this data need to be exposed?” Temporary and working variables have no value outside of the routine, therefore, as good form, don’t put them there. These types of elements are best suited to internal / local variables that actually have a name that means something, versus the obscure names that always seem to creep up “WorkingBits”.
The ByVal data parameters are exactly as stated, a value is copied from outside of the UDF to internal / local variables, and vice-versa, for use. The simpler of the two parameter types is the Output. This parameter is designed to be calculated inside the UDF and made available for use outside of the UDF. Every time the block is scanned the output will be recalculated and made available. If the programmer wants to use the result(s) then they do so as needed.
The other parameter type, Input, the converse of the Output but with some interesting twists. The Input parameters are copied from the variables or registers indicated and available for use in the block. This is the straight-forward use of these parameters. The twist that exist with Input parameters is that they do not have to have an outside variable assigned to them. These variables can exist as exposed, internal registers.
This opens Input parameters to be employed in other ways. The parameters can themselves be used directly as specific instance variables. Input parameters have the property of a Default value which is available outside the UDF as well as inside. In one stroke it is possible to create ‘Configuration’ parameters for the instance of the UDF; combining UDT type qualities with the UDF. This means that if the timer Setpoint is 30 seconds, that can be defined as an Input and visible to the UDF as well as the “outside world”. This is behavior is just like an object property in OO programming.
The final items that need to be discussed are those elements that were excluded from the “Bucket” structure: Internal Variables. These variables, of whatever type is required, need to be declared as part of the UDF itself. It is at this point that the UDF diverges from the sub-routine model in automation controllers. Each time a UDF is used in code memory space is reserved for the internal bits & registers, the distinction is that they are not visible for use; only parameters are exposed.
The last thing that has to be considered when dealing with UDF’s and their internal data elements is initialization and what happens when the block is NOT being scanned. Typically, there are extra states available for those. It is the requirement of the programmer to set these up themselves. Assuming that the block will always be scanned is not practical and ‘sloppy’. It’s kinda like going to your mothers for dinner and then not helping to clean the table and do the dishes afterwards. Be courteous to those that follow after you.
It should be apparent that the ideas and concepts raised here just scratch the surface of the capability of the User Defined Function capabilities. The most important point to all of this is that these are very powerful tools for the programmer’s kit, they just need to be thought out ahead of time. These can add to the safety and security of your code base, they can also help to spaghettify your code.
This post was written by Jeff Monforton. Jeff is a senior engineer at MAVERICK Technologies, a leading automation solutions provider offering industrial automation, strategic manufacturing, and enterprise integration services for the process industries. MAVERICK delivers expertise and consulting in a wide variety of areas including industrial automation controls, distributed control systems, manufacturing execution systems, operational strategy, business process optimization and more. MAVERICK Technologies is a CFE Media content partner.
MAVERICK Technologies is a CSIA member as of 3/20/2015