
ADC_AutomationControls_PM01 (AutomationDirect) asked a question.
Control systems, particularly larger installations, tend to include large numbers of identical sensors, output devices, or operations. Rather than manually writing dozens (or more!) of copies of the same code, try combining two powerful Productivity features, User-Defined Structures and Called Tasks, to save hours of programming time and facilitate easy system-wide changes down the road!
The building blocks: a quick refresher
Called Tasks have been part of Productivity since the beginning, but the feature isn’t intuitive for many. The ability to run a block of code a programmable number of times comes with both power and pitfalls. Blocks of code can be disabled as desired in the program, making troubleshooting easier or optimizing scan time for code that doesn’t need to run in production. On the other hand, OUT coils in a called task won’t be updated if the task is disabled, which may cause confusion and unexpected behavior. Alternatively, a task can run multiple times in a scan. But why would you want to do that?
User-Defined Structures are a more recent Productivity feature, released in 2020. With the ability to nest structures up to four levels deep, complex structures can be built and combined to effectively describe machine components and processes, all in the tag name, making programming and troubleshooting much easier. Structures for operation zones can be created that contain structures for individual sensors. This can help create uniformity in naming conventions and assist overall organization.
A sum larger than its parts
How can these be combined to save time and improve reliability? Let’s look at an example.
A brewery has a cellar with 15 fermenters. Each fermenter has a lower and upper temperature probe, as well as a pressure sensor and a valve that controls cooling glycol to the tank jackets. Each tank also has a temperature setpoint, a dynamic high and low alarm point, and a user-configurable offset between the temperature setpoint and alarm points. A single tank has 10 tags a user may want to see, notwithstanding all the supporting tags that may be required to scale sensors, or any mode controls that may be required. Multiply all that by the 15 fermenters, and there’s a ton of code that needs to be duplicated, along with many opportunities for mistakes or typos. And heaven forbid any code needs to change down the line…
At first glance, some low-hanging fruit for organization is obvious: the analog probes. Each of these might have an identifier, plus the engineering units, raw analog data, and a scaling instruction. Add to that some error checking to ensure the input module and I/O rack it’s tied to are functioning as expected, and the benefits become clear:
All the above tags can be contained inside a Temperature_Sensor structure. Do the same with a pressure sensor, and most of the work is done. All that’s left is to build a Tank structure, create instances of it, and write the code.
Build it right, build it once
Identical tasks like tank temperature control are ideal for a mix of user-defined structures, arrays, and called tasks. Below is an example of how a task might look for managing a tank like this:
Thanks to the use of a user-defined structure, all necessary tags are fairly easy to identify without much effort. All that’s needed is to repeat this code another 14 times, and the logic is all done!
Of course, that’s not an efficient use of time. Instead, consider creating an array of the Tank structure and running the same code inside a FOR loop, with the loop count as the array index:
All it takes is two extra rungs, and the code can be repeated as many times as desired.
That’s all well and good, but where do called tasks come in? With the mindset of writing good code as few times as possible, let’s look at how the temperature probes are handled:
The above code copies the data from a physical input to the SCL .Input tag, calculates the .InMin and .InMax values based on the module resolution, and outputs the probe temperature in Fahrenheit. It also converts to Celsius for good measure. The last rung checks for conditions that would render the probe value untrustworthy and sets a .Valid boolean accordingly.
Note the structure’s root tag name: CALL Temperature. This structure is distinct from the Tank structure, and is used multiple times in a scan, for every tank temperature sensor. The above ladder resides in a called task that is referenced from another FOR loop:
The above ladder loops through all 15 tanks and copies the sensor structures to the CALL tags that match the structure type. It then calls the task that contains the CALL tags, and copies the structures back to the original tank structure. This process can be repeated as many times as necessary.
Higher quality code with less effort
Combining the use of structures, arrays, and called tasks, a tedious project that takes 180 rungs to control 15 tanks can be optimized down to 13 rungs. The amount of time that could be saved on a simple project like this is significant, and if these techniques are applied to much larger projects, the efficiency gains can be massive. Programming this way allows code changes to be applied system-wide very quickly, nearly eliminates the risk of typos during code propagation, and allows more time for important tasks like commenting. Since called tasks are editable during runtime, service is made easy, to boot!
Productivity PROtips: User-Defined Structures and Code Reuse
I have tried this before back when user defined structures was first released. At that time timmers within the for loop or in called task within a for loop broke this functionality. I believe this had to do with there is stuff on the back end for timers to work and that it is not copied with the copy data instruction. Has that been corrected and we can use timers within for loops.
Question: Has that been corrected and we can use timers within for loops.
Answer: At this time no. I checked and as with the Productivity series the Click and Do-More provide a similar warning per the below note on compile if a time based instruction is placed in a FOR loop. Per help topic P170:
Note: Use caution when implementing time-based instructions (such as TMR, STMR, DRM) within FOR-NEXT loops as they may not function as expected. The warning "Asynchronous instruction found within FOR loop…" will be displayed at time of project compile.
Ping: AOI - equivalent ... please.😊
Yeah this is an attempt to mimic an AOI or POU but doesn't come close. I do it all the time in Productivity, but it requires a substantial amount of copy paste and find/replace and is very inflexible requiring a lot of work-arounds. In some situations it works, but it is very limited as a number of functions will not work inside a loop.
I put everything into a User-Defined Structure as an Array, but then write the ladder logic as normal but iterate through the loop and reference the current loop as the index of the array. So, for a Chiller:
If CH(i).Hand, and NOT CH(i).EstopAlarm, then CH(i).PumpRun
Each iteration of the FOR NEXT LOOP increments the Index (i).
Is there an advantage to doing the Call Task version? It seems like extra code to rifle through, rather than using the FOR NEXT LOOP in logic and just writing it normally but with the Array reference instead of tags directly.