Overview
This document show one software design architecture for machine control. They overall software design architecture is documented in an Example program in the Developer Zone article Machine Control Software Design Examples
This architecture can be used to build control applications on LabVIEW Real-Time controllers including CompactRIO, Compact FieldPoint, and PXI. The same basic architecture could also work on other platforms such as Windows based controllers. LabVIEW Real-Time is a full programming language providing developers numerous options of how to construct a controller and enabling them to create very flexible and complex systems. LabVIEW Real-Time controllers are being used in applications ranging from control of nuclear power plant rods, to hardware in the loop testing for engine ECUs, to adaptive control for oil well drilling, to high speed vibration monitoring for predictive maintenance. However, the flexibility of LabVIEW Real-Time can make starting a new application daunting, especially for programmers new to LabVIEW or new to programming real-time systems. This document does not attempt to provide an architecture for every application and is not intended as a replacement for LabVIEW Real-Time training. It is written to help provide a framework for engineers designing industrial control applications, especially engineers who are familiar with the use of PLCs. This document is designed to explain how to easily construct LabVIEW Real-Time applications that also provide the flexibility to handle non-traditional applications such as high speed buffered I/O, data logging, or machine vision.
Table of Contents
Terminology
Although there are many ways to build a control application in LabVIEW Real-Time, there are fundamental concepts of real-time programming and control applications that need to be understood.
Responsiveness: A control application will need the ability to take action to an event such as an I/O change, HMI input, or internal state change. The time required to take action after an event is known as responsiveness and different control applications will have different tolerances for responsiveness varying from uS to minutes. Most industrial applications have responsiveness requirements in the mS to seconds range. An important design criterion for a control application is the required responsiveness as this will determine the control loop rates and will affect decisions on I/O, processor, and software.
Determinism and jitter: Determinism is the repeatability of the timing of a control loop. Jitter is how you measure determinism and is the error in timing. For example, if a loop is set to run once every 50mS but it sometimes runs at 50.5 mS then the jitter is 0.5mS. Increased determinism and reliability are the primary advantages of a real-time control system and good determinism is critical for stable control applications. Low determinism will lead to poor analog control and can make a system not responsive.
Priority: Most controllers use a single processor to handle all control, monitoring, and communication tasks. Since there is a single resource (processor) with multiple parallel demands, the programmer needs a way to manage what demands are most important. By setting critical control loops to a high priority you can have a full featured controller that still exhibits good determinism and responsiveness. For instance in an application with a temperature control loop and embedded logging functionality, the control loop can be set to a high priority to preempt the logging operation and provide deterministic temperature control. This assures that lower priority tasks such as logging, a web server, HMI, etc do not negatively affect analog controls or digital logic.
Basic Controller Architecture Background
Most programmers who are new to LabVIEW start with a very basic application and then expand their program to add more functions. One benefit of LabVIEW is that it is very easy to build and run a simple program. For instance here is the logic for a simple PID loop running on a Compact FieldPoint system.
Unfortunately building more complex systems in LabVIEW requires a more sophisticated architecture that allows better code reuse, scalability, and management of execution. This section will describe and build a basic architecture for control applications and will perform the same PID loop using this architecture.
The most basic controller has three main sequences or routines;
- Initialization Routines
- Control Routines
- Shutdown Routines
These routines deal with the state of the controller and are not dependant on the user logic. Because National Instruments real-time controllers allow you to define the full capabilities of your system you will need to build the sequences in LabVIEW.
The initialization routine:
Before executing the main control loop the program needs to perform an initialization routine. The initialization routine will prepare the controller for execution. This is not the place for logic related to the machine such as logic for machine startup or initialization; that logic should go in the main control loop. This initialization routine will:
- Set all internal variables, including output variables in the I/O memory table, to default states
- Create all necessary queues, RT FIFOs, VI Refnums and download any FPGA bit files.
- Perform any additional user defined logic to prepare the controller for operation such as preparing log files.
The control routine:
This is the heart of the controller and where all the user logic executes. It executes a scanning architecture with multiple parallel loops. Although you can diagram it as one loop performing the I/O Scan Task, high priority tasks, and then low priority tasks in reality it is multiple synchronized loops.

The first loop will write and read physical IO to a memory table and will execute first. Then subsequent tasks will execute user logic from highest priority to lowest priority ending with housekeeping and communication tasks that will operate at the lowest priority. You must verify that you have programmed your controller with enough idle processor time to service housekeeping and communication tasks.
The shutdown routine:
When the controller is commanded to stop running or experiences a fault condition, it will stop running the main control loop and will run a shutdown routine. The shutdown routine is for shutting down the controller and putting it in a safe state. It is only used for controller shutdown, and is not the place for machine shutdown routines which should go in the main control loop. The shutdown routine will:
- Set all outputs to safe states
- Stop any parallel loops that are running
- Perform any additional logic such as notifying operator of a controller fault or logging state information
The Control Routine Details
Most LabVIEW programmers are familiar with direct I/O access where subroutines directly send and receive inputs and outputs from the hardware. This method is ideal for waveform acquisition and signal processing and for smaller single point applications. However, control applications normally use single point reads and writes and can become very large with multiple states all of which need access to the I/O. (Note that this architecture can also support waveform measurements and waveforms are covered in a later section.) Accessing I/O introduces overhead on the system and can be relatively slow, additionally managing multiple I/O accesses throughout all levels of a program makes it very difficult to change I/O and implement features such as simulation or forcing. To avoid these problems, the control routine uses a scanning I/O architecture. In a scanning I/O architecture the physical hardware is only accessed once per loop iteration. Input and output values are stored in memory in a construct known as an I/O memory table and the control code accesses the memory space instead of the direct hardware. This architecture provides numerous benefits:
- I/O abstraction so subVIs and functions can be reused (no hard coding of IO)
- Low overhead
- Deterministic operation
- Easy transition between HW platforms
- Support for simulation
- Support for interlocks and “forcing” (covered later)
- Elimination risk of I/O changes during logic execution
I/O Memory Table:
The I/O memory table is intended to pass data between the I/O scan loop and other parallel control or measurement loops or tasks. There are numerous methods for inter-loop communication, but one of the simplest is to use a global variable. To make updating the program simple we will use a global with multiple clusters. Each cluster must only have one “writer” to avoid race conditions where multiple data sources overwrite data. In this case each task that writes data to the global has a dedicated cluster.
I/O Scan:
The main control loop will be the highest priority loop so it will run first each cycle. It will have two steps occurring repeatedly at the desired control loop rate.
- Write to outputs - The I/O memory table is read and output modules are updated.
- Read from inputs - Input modules are read and the values are written to the I/O memory table.
Note: Writing to the outputs first helps assure deterministic operation and provides a cleaner program for performing error handling and transitioning to a shutdown state.
User Logic Tasks
The user logic is the machine specific logic that defines the control application. In many cases it is based on a state machine to handle complex machine control with multiple states. A later section will explore how to use state machines to design the user logic. To execute in the control architecture, user logic must:
- Execute in less time than timed loop rate
- Access I/O through the I/O memory table instead of through direct I/O reads and writes
- Not use “while loops” except to retain state information in shift registers
- Not use “for loops” except in algorithms
- Not use “waits”, instead use timer functions or “Tick Count” for timing logic
- Not perform waveform, logging, or non-deterministic operations (the use of parallel loops for these operations is explained later)
The user logic can:
- Include single point operations such as PID or point-by-point analysis
- Use a state machine to structure the code
Each logic task will be structured using a timed loop. The priority of the timed loop must be lower than the I/O scan loop and the timing should be an integer multiple of the I/O scan loop rate. The logic task will have three steps:
- Copy I/O memory table to local cluster - The I/O memory table is read and a local cluster (strict type def) is written. A local cluster is used to provide scalability for multiple tasks and to enable the use of the LabVIEW StateChart module.
- Execute logic – I/O is read and written to local clusters for inputs and outputs
- Copy local cluster to I/O memory table outputs – The local output cluster (strict type def) is read and written to the I/O memory table. A local cluster is used to provide scalability for multiple tasks and to enable use of the LabVIEW StateChart module.
Basic Controller Architecture Example in LabVIEW
To demonstrate this control architecture, we will build a basic PID control application. This simple application controls a temperature chamber to maintain 350 degrees. It has one analog input from a thermocouple, one analog output (0-10V) that is connected to the input on a heater controller, and will use a PID algorithm for control. This application is overly simplistic and is used here to explain the architecture components without adding the complexity of an intricate control example. More detailed control examples are explored later which demonstrate using this architecture for more complex control applications.
I/O Memory Table:
To create the I/O memory table:
-
Create a global variable called IO Table Global.vi with two clusters, one for the data from the IO task and one for data from the Task 1. Create two controls, Input_TC and Output_Heater.
I/O Scan:
- To create the I/O Scan:
- Open a top level vi.
- Drop a timed loop on the diagram, configure the rate to match the loop time required by your application.
- On the diagram create two subVIs: Write Outputs and Read Inputs.

- Open the Write Outputs. On the block diagram drop the I/O Table Global and logic to write to the physical outputs. This example is programmed for FieldPoint so it uses an FP Write VI. The same architecture could easily be used to program CompactRIO, PXI, or DAQ. For error handling this example included an error input and output on the front panel with connections on the VI connector pane.
- Open the Read Inputs. On the block diagram drop the I/O Table Global and logic to read the physical inputs. Also add error handling.

- Save and close the two subVIs. On the top level vi place a case structure around the Write Outputs and Read Inputs subVIs. In the false case make a new subVI called Shutdown Outputs. You will enter this case if the controller needs to shutdown.
- Open the Shutdown Outputs.vi and add logic to write safe values to the outputs on shutdown and save the subvi. Instead of writing the output values from the I/O memory table you will write safe output values. In this case the heater should go off if we exit the program so the Output_Heater value is replaced with a constant of zero.

- On the top level vi you now need to add logic to decide when to shutdown. Remember that your logic can shutdown the machine. This shutdown stops the controller. In most cases this should only happen if there is an error or if the controller needs to be reprogrammed. For this example we will shutdown if there is an error. We created a functional global to collect and trap errors from multiple parallel tasks.
- Note: Shut down behavior is defined by the programmer. You may decide to ignore certain errors or to retry operations before shutting down.
- In the top level vi use the functional global to detect errors and control the case structure to shutdown on an error. Note that we for each task we need to assign a Task #. We will use 0 for the initialization routine so for the I/O Scan Task we will use Task # one.

Initialization and Shutdown Routines:
- Now we need to add the initialization routine and a shutdown routine. The initialization routine needs to configure the controller so it is ready to run any logic and the shutdown routine needs to perform any actions based on a shutdown.
- The initialization routine will set the default values for the outputs and perform error trapping. Remember the main control loop starts by writing outputs to the hardware so we need to load startup values for the outputs into the I/O memory table.
- Create a subVI called Default Outputs.vi and write code to set the I/O memory table to the default values. We want the default behavior of the heater output to be off so we will create a 0 constant and wire it to the Output_Heater shared variable.

- Add error trapping with the functional global and use Task # 0.
- On the top level vi add a sequence structure to the left of the I/O Scan Task. Wire the error cluster from the Default Outputs.vi to the border of the timed loop to assure execution order.

- For the shutdown routine add a sequence structure to the right of the I/O Scan Task and wire the error cluster to the border of the sequence structure. Add any shutdown logic into the structure. In this example we have no shutdown routines.

[+] Enlarge Image - You now have a complete initialization, I/O Scan, and Shutdown routine. Now you need to add your logic tasks.
User Logic Tasks:
Each logic task will be structured using a timed loop with a priority lower than the I/O scan loop and the timing an integer multiple of the I/O scan loop rate. To make your application scale and to allow you to run loops different rates than the I/O Scan task you will create a local version of the I/O. To do this you will copy I/O memory table to two local clusters (input and output) that are strict type def.A strict type def will update all instances throughout the program when you change the control. This makes it easier to modify your inputs and outputs if your program grows.
-
First a strict type def custom control is created called Input Cluster.ctl. In the custom control we add a cluster and create one controls called Input_TC.
- Next a strict type def custom control is created called Output Cluster.ctl. In the custom control we add a cluster and create one controls called Output_Heater. Tip: If you are going to use the StateChart Module it will create a strict type def Input Cluster.ctl and Output Cluster.ctl automatically.
- On the diagram create two subVIs, one called Read Inputs Local and one called Write Outputs Local.

- Open the Read Inputs Local. On the block diagram drop the I/O Table Global and logic to read the inputs and write them to the cluster. Add error handling and modify the connector to write out the cluster and error and input an error cluster.
- Open the Write Outputs Local. On the block diagram drop the I/O Table Global and logic to read the inputs and write them to the cluster. Add error handling and modify the connector to write out the cluster and error and input an error cluster.

- Save and close the two subVIs. On the top level vi place a case structure and error handling around the subVIs, note that for the Fault Merge.vi this is Task # two.

- Now create a subVI called Logic.vi. In this vi you will wire in and out the error clusters and place your logic. Remember logic must:
- Execute in less time than timed loop rate
- Not directly access hardware for I/O reads and writes
- Not use “while loops” except to retain state information in shift registers
- Not use “for loops” except in algorithms
- Not use “waits”, instead use timer functions or “Tick Count” for timing logic
- Not perform waveform, logging, or non-deterministic operations, use of parallel loops for these operations is explained later
- Normally we would create a subVI for encapsulation to enable reuse, however since this example is so simple PID is already a reusable subVI this step is already done for us. Simply drop a PID VI on the block diagram and wire constants so the output range is [10, 0] the PID gains are [10, 0.1, 0] and the setpoint is 350. The PID gains and setpoint could have also been wired to elements from the I/O memory table and published to an operator terminal if they needed to be changed later. Wire the Input_TC value to the “Process Variable” terminal and wire the Output_Heater value to the “Output” terminal. Add the appropriate error handling components.

Tip: StateCharts can be used for sequencing, batch, or machine control applications - On your top level vi wire the error cluster from the initialization routine to the border of the timed loop and save and run the application.
We can now run the temperature control program. It has start-up and shutdown procedures, a scanning architecture, reusable subVIs that are not hard coded to I/O, and error handling. This is the fundamental architecture for machine control used throughout this document.

We will now look at a more complicated example which highlights the benefits of this type of architecture.
State Machines
Most real control applications are far more complex than the simple PID application built in the previous example. While PID may be used to control a temperature setpoint, this will only be a small component in the application. A slightly more realistic example would be a chemical reacting vessel. In this imaginary application the controller needs to:
- Wait for an operator start command from a push button
- Meter two chemical flows into a tank based on output from a flow totalizer (two parallel processes one for each chemical flow)
- After filling the tank turn on a stirrer and raise the temperature in the tank
- Once the temperature has reached 200 degrees the system will turn off the stirrers and hold the temperature constant for 10 seconds
- Pump the contents to a holding tank
- Go back to wait state
Note that for simplicity in this application the chemical flow rates have been hard coded to 850, the temperature to 200, and the time to 10 seconds. In a real application these values could be loaded from a recipe or entered by an operator.
State Machine Overview
The best way to represent this type of control system is to use a state machine. The state machine design pattern is one of the most widely recognized and highly useful design patterns for LabVIEW. You can use the state machine design pattern to implement any algorithm that can be explicitly described by a state diagram or flow chart. A state machine usually illustrates a moderately complex decision making algorithm, such as a diagnostic routine or a process monitor.
A state machine, which is more precisely defined as a finite state machine, consists of a set of states and a transition function that maps to the next state. A full state machine should be designed to execute actions on entry, while it exists in the state, or on exit. Because we are using the state machine inside our larger machine control architecture it cannot use wait statements and cannot use loops except to retain state or perform algorithms such as a for loop used for array manipulation.
Use state machines in applications where distinguishable states exist. Each state can lead to one or multiple states, or end the process flow. A state machine relies on user input or in-state calculation to determine which state to go to next. Many applications require an initialization state, followed by a default state, where many different actions can be performed. The actions performed can depend on previous and current inputs as well as states. A shutdown state is commonly used to perform clean up actions.
State Machine Example in LabVIEW
To build this application the first step is to map out the logic and I/O points. Because this application involves a sequence of steps a flow chart is a good tool for planning the application. Below is a flow chart for this application and a list of I/O signals.

Each state in a state machine performs a unique action and calls other states. State transitions depends on whether some condition or sequence occurs. Translating the state transition diagram into a LabVIEW block diagram requires the following infrastructure components:
- Case structure—Contains a case for each state, and the code to execute for each state
- Shift register—Contains state transition information
- State functionality code—Implements the function of the state
- Transition code—Determines the next state in the sequence
For this example we will use simulated I/O instead of physical I/O so we can test our logic. To do this we will use a global variable instead of hardware read/write VIs. This is a convenient way to test our logic with interactive controls and indicators before deploying to actual hardware. The ability to easily simulate I/O is one benefits of this architecture.

Because one state in our machine has parallel processes we will build a second state machine to represent the parallel logic and call these in that state. We will only exit the state when both parallel processes have completed.

LabVIEW StateChart Module:
The previous example was built using basic LabVIEW structures of loops, sequences, and strict type def enums. Starting in LabVIEW 8.5 there is a new module, the LabVIEW StateChart Module. This module is a new editor for LabVIEW that allows you to quickly build full state based machine logic. It is hierarchical so that multiple state charts and LabVIEW VIs can be used together in a complex application. It also runs on Windows, real-time targets, and FPGA targets. The StateChart Module is especially useful if you need to have multiple possible transitions from a state or if you want to have specific events trigger a transition from multiple states. For instance based on an operator command you may want to abort production from any state. The StateChart Module allows you to easily program the system to transition from any state and to give priorities to transitions so that the abort command executes even if other transitions are triggered at the same time. I will not go into a tutorial on the StateChart Module and did not use it in this example so that engineers who do not have the module can still open and run the examples. All of the features of the StateChart Module can be programmed in LabVIEW, however the StateChart module dramatically improves development time and is much easier to read and debug compared to traditional LabVIEW state machines. I strongly recommend it for anyone building machine control applications.
Quick Start – Modifying an Example
Overview
The easiest way to get started with this design is to modify an existing example. In this section we will walk through modifying the previous Chemical Mixing example that used the state chart to build our own application. There are 4 main steps
- Modify the I/O Scan Task to read and write to the physical I/O for your application.
- Modify the Initialization Routine to write the default values for your physical outputs.
- Modify the Task 1 to map the I/O. You will change the StateChart, Read Inputs Local.vi, and Write Inputs Local.vi.
- Modify /rewrite the StateChart to fit your application.
Modify the I/O Scan Task
Open the Chemical Mixing.lvproj. Since your application will use different I/O you will need to modify the I/O Scan Task to read and write to your I/O. Remember that I/O can be physical I/O, networked I/O, or simulated I/O.
-
Open the IO Table Global. This defines all the I/O for your system.
-
Change the controls in the clusters to those needed for your application. Save the IO table Global.vi with these changes.
- Open the Write Outputs.vi and the Read Inputs.vi.

- Modify the VIs so they write and read to your physical I/O instead of the simulated I/O. If your hardware target requires initialization to start the IO (for instance CompactRIO requires calling the Open FPGA Reference.VI) you should use the initialization routine. In your write and read you would then access the IO via the Read/Write control.
- Open the Shutdown Outputs.vi. Modify the VI so it writes to your physical I/O instead of the simulated I/O
Modify the Initialization Routine
Since your application will use different I/O you will need to modify the Initialization Routine to set the default values for your outputs.
- Open the Default Outputs.vi and modify it to set the default values for your outputs.
Modify the Task 1 to Map the I/O
You now have a complete I/O Scan Task and Initialization and Shutdown Routines. Now you need to modify your logic. Since each logic task creates a local copy of I/O for its execution you need to remap the I/O
- Under the StateChart folder open the Outputs.ctl and Inputs.ctl. Modify these to match the I/O for your application.

- Update the Write Outputs Local.vi and Read Outputs Local.vi to read and write your I/O.
Modify /rewrite the StateChart
Now you need to enter your logic. You can do this by modifying/rewriting the StateChart to perform your application specific logic.
Reader Comments | Submit a comment »
Legal
This tutorial (this "tutorial") was developed by National Instruments ("NI"). Although technical support of this tutorial may be made available by National Instruments, the content in this tutorial may not be completely tested and verified, and NI does not guarantee its quality in any way or that NI will continue to support this content with each new revision of related products and drivers. THIS TUTORIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND AND SUBJECT TO CERTAIN RESTRICTIONS AS MORE SPECIFICALLY SET FORTH IN NI.COM'S TERMS OF USE (http://ni.com/legal/termsofuse/unitedstates/us/).


