LabVIEW Real-Time Application
A LabVIEW real-time application example
This blog gives a full LabVIEW real-time application example. This includes:
- Using the correct loop rates for the correct purpose
- Using a combination of the cRIO embedded real-time (Linux) system and the cRIO FPGA
- An example implementation of a real time FFT
- Some example code for encoder quadrature decoding
The example is based on a motor controller development, but the techniques shown here could also be applied to many other industrial control problems. The front panel shown below gives you an idea of where we are heading. The code discussed in this blog was developed and tested under LabVIEW 2018, but all the functionality shown should also work under LabVIEW 2017.
LabVIEW cRIO real-time project overview
Having programmed in LabVIEW real-time for quite a few years, we generally recommend to keep ALL of the project real-time. In other words, a a typical real-time project does not generally require any non-RT (host computer) VI components. Everything you need for a real-time project (including file writing and graphics) can be done on the cRIO real-time target. We feel it is cleanest to run all VIs on the cRIO RT target.
The cRIO target has two key components: A (Linux) real-time OS and an FPGA. Broadly speaking, anything that requires ~1ms loop time can be implemented on the Linux RT OS, and anything under ~1ms should be implemented on the FPGA. The RT OS makes use of the LabVIEW Scan Engine, which gives very convenient access to the NI c-Series modules – more on this below. You won’t have to write a single line of HDL code to operate standard code on the cRIO FPGA, so that’s great news if you are new to FPGAs. However, FPGA compile times are MUCH longer than compiling code for the cRIO RT OS. This means that each project requirements needs to be balanced in terms of what is really required in terms of execution time vs. development effort.
It is straightforward to implement communication between the FPGA and the cRIO RT OS, so it is best to let each part of the system get on with the tasks most suited to the particular environment, and implement communication between the two levels (cRIO FPGA and RT OS) as required. We include some example communication techniques in this blog.
Our project view for the finished project looks like this
We will explain some of the details later on, so let’s focus on the three main levels for now. First, the “My Computer” level is the host (or development) computer. This will generally be a Windows PC, as LabVIEW real-time is not well supported on MacOS or Linux (as of 2018/2019). For the vast majority of our real-time applications, we have nothing running at the “My Computer” level – why should we – we are developing a real-time application after all, and Windows host PC is not real-time. NI support staff have sometimes argued that the host PC could (or even should) be used for some of the front panel graphics, primarily to reduce load on the RT OS target. We disagree with that view. With a little care in the design process, all of the graphics required for a standard project can easily be handled on the cRIO RT OS system.
The second level is the “Chassis (cRIO-9038)”. The cRIO-9038 chassis used for this example project is towards the high end price segment, and generally a good choice if you are not quite sure about your project requirements yet. If budget is tight, an excellent lower-cost alternative is the cRIO-9036, with a very similar spec to the 9038 but lower cost. At the “Chassis (cRIO-9038)” level you can see the “force_ctrl_demo_v1.vi”. This is our top-level application VI, which runs a 1ms feedback-control loop, some FFT processing, and also the graphics. We could also include file writing (to a USB drive or the internal cRIO memory) here, but we have excluded file writing in this example to keep things simple.
One level below the Chassis is the FPGA Target. Most of the cRIO FPGA clocks run at 40MHz, so you can implement functionality that requires ~1us (and less) here.
When first setting up a cRIO project, you will be asked whether you want the project to operate in “Scan Engine” or “LabVIEW FPGA interface” mode. We find this choice to be somewhat confusing, as it seems to imply that the two modes are mutually exclusive, i.e. you either run everything in Scan Engine mode or everything in FPGA interface mode. In practice, this is not actually the case. You can run any given number of C Series modules on the FPGA or via the Chassis Scan Engine. For EACH module, you can only pick one mode, but across the Chassis you can mix and match. If ANY of your modules are directly accessed via the FPGA, then you MUST chose the “LabVIEW FPGA interface” mode, which is the case for our example here.
NI refers to the mixed use of C Series modules as the “Hybrid mode”, which is described here. In years of working with Hybrid mode we haven’t found a single drawback compared to working with “pure” Scan Engine mode. As a little background info, NI uses the FPGA to run the Scan Engine functionality that let’s us access the C Series modules so easily via the RT Chassis. So to NI it technically makes a difference whether you are in “pure” Scan Engine mode or also use the FPGA interface directly, but to the common user there is no noticeable difference (or drawback) between just using the Scan Engine or operating in Hybrid mode.
FPGA example code for motor control
For our motor control application example, the main feedback control loop runs at 1ms and can be implemented on the cRIO RT OS level. There are two example tasks we implemented on the FPGA target.
First, our motor controller (using a Linmot BP1100 power amplifier) provides a simulated encoder signal using standard quadrature. The minimum edge separation between the two quadrature lines (Phase A and B) is 10us, which is way too fast for the 1ms RT system. We hence implement the quadrature logic on the FPGA as shown below.
Using the Loop Timer, we runs this FPGA loop at 1us, which is sufficiently fast to capture all edges of the quadrature lines. The index line is unused in our example, and the Phase A and Phase B lines enter some standard logic that takes care of both the motion direction and counting of pulses. The motor position information that is required in the feedback loop is written to the user defined variable “EncoderTickCount”. We have defined two of these variables, “EncoderTickCountInstantaneous” and “EncoderTickCount1msMean”. Let’s keep in mind that the cRIO RT OS level only accesses these variables every 1ms. If we have a little bit of flicker on the encoder count, then the mean over the last 1ms interval may actually be more useful.
There are several methods to communicate between the cRIO FPGA and the cRIO RT OS, and we demonstrate two of these method here. The first and perhaps simplest method is to put an indicator (at the FPGA level) on each variable you want to access. For example, we have put indicators on “Encoder Phase A” and “Encoder Phase B”. In our cRIO RT OS VI, these variables can then be accessed by the FPGA read/write function from the FPGA interface pallet:
This method is useful, but does require wiring an FPGA reference to each of the variables you want to access. We generally use this FPGA interface method for accessing FPGA diagnostic information, such as monitoring the FPGA loop counter.
The second and often more convenient method is the use of User Defined Variables. These variables are defined at the Chassis level as shown below
User Defined Variables can be of the usual types (fixed point, integer, bool etc) and either have a direction “FPGA to Host” or “Host to FPGA”. In our case, the variable “EncoderTickCount1msMean” is written to on the FPGA, so the direction is “FPGA to Host”.
A second example functionality implemented on the FPGA is the reading of forces for our force-control (or force-feedback) motor control. We use the NI 9237 C Series module as a load cell bridge amplifier. The NI 9237 module can deliver 50kS/s per channel, so if we were to read at 1ms (at the RT OS level) we would not be making use of the full sample rate. Instead, we implement an FPGA loop that reads the forces every 20us (50kHz), and takes the mean over 50 samples.
Taking a 50 sample mean with a 20us loop rate is equivalent to taking a 1ms mean. Any high frequency noise on the load cells is hence eliminated, and a clean (1ms average) signal is provided to the RT OS level, again using the User Defined Variable approach. We could of course implement something more sophisticated than a simple 50 sample mean here, such as a low pass filter at say 1kHz.
Note also that many of the NI C Series modules have a “Start/Stop” functionality. When using the C Series modules in a cDAQ Chassis or via the Shared Variable Engine in the cRIO Chassis, the “Start/Stop” requirements are automatically taken care off. However, when using C Series modules at the FPGA level, we must explicitly handle the “Start/Stop” functionality.
Once you have implemented your FPGA code, you need to compile it. This will take 15-20 min even for the simplest code. When we started using the cRIO, we used to download the FPGA compile tools, which are quite “heavy” and clutter up even the most robust development machines. There has also been an issue for a few years now in terms of FPGA compiler compatibility with Windows 10. Based on our experience, we strongly recommend getting a NI software subscription that includes the FPGA compile cloud service. Using the compile cloud, you only need to install the FPGA module, but not the FPGA compile tools (simply ignore the LabVIEW installation warning that the FPGA module requires the compile tools – the FPGA modules generally works just fine without the FPGA compile tools installed). Uploading to the FPGA compile cloud and starting a new compilation takes only a few minutes, and saves you the headaches of installing heavy and bulky FPGA compilation tools.
cRIO Chassis real-time system timing settings
We mentioned the LabVIEW Scan Engine several times above. The Scan Engine makes sure that all your C Series module channels are read at the appropriate rate. When first starting a cRIO project, the default timing setting for the Scan Engine is 10ms. To change this, go to the controller properties
and navigate to the Scan Engine settings
If you do not change the timing here, you could end up running a 1ms real-time loop, but with input signals that are only updated every 10ms (no LabVIEW warning or error is generated in this case). For general control applications, we recommend changing the Scan Engine period to 1ms. If selecting a number under 1ms, LabVIEW issues a warning. In theory, the cRIO system can run real-time loops and the Scan Engine under 1ms (say 500us), but this generally pushes the RT system beyond what it is designed for. We recommend against settings of <1ms. With a 1ms Scan Engine period, we have found the cRIO RT OS to be extremely reliable and robust. Also remember, if you need parts of your code to execute faster, implement these parts on the FPGA as outlined above.
Real-time (and non real-time) loops on the cRIO Chassis OS
When designing a control system, think about what is “hard real time” and what is not. We generally run 5-15 different loops on our cRIO-9038 system, ranging from loop rates of ~1ms to ~100ms. Some of these loops are timed loops and some are while loops (with a loop timer). Any direct control functionality is implemented in the timed loop structure, while non-deterministic actions such as file access or graphics are located in while loops. For a common real-time loop design pitfall, have a look at this blog.
For our motor control application, we use three loops. The main control (feedback) loop runs at 1ms
This loop handles digital inputs and outputs (e.g. enable/disable lines, motor homing sequence), and also computes the drive signal to the motor. If we were to run into CPU limitations on the cRIO RT OS, we could increase the loop priority (currently 100) to a higher number. In the top left corner, we wired an indicator to the “Finished Late [i-1]” loop property. If this signal becomes true (i.e. our LED indicator starts flashing occasionally) then this is a sign of having “overloaded” the loop, such that it cannot complete within the 1ms time constraint.
We included a second example timed-loop to compute a real-time FFT of two of our signals, one of the force readings, and the motor velocity.
The timing is important for this loop, as it defines (along with the FFT length) the spacing in the frequency domain. In this example, we run the FFT loop every 10ms, and with an FFT length of 1000. The code in the sequence on the left calculates an evenly spaced vector, which is used as the basis of the FFT frequency axis.
Finally, our third example loop is used to update some of the charts.
In our experience, simple graphics indicators cause no issues in 1ms timed RT loops, but anything else (such as charts, or access of object property nodes) is best done in a non real-time while loop. In our example, the graphics while loop runs at 20ms. As a while loop (not timed loop), this is only an approximate indicator for the actual loop timing.
For a real-world project, there are many more techniques such at RT FIFOs that can be used to communicate between loops. We also recommend to make use of sub-VIs wherever possible, to keep the top level VI manageable. For this example project, we implemented most of the code within the top level VI for simplicity.
We hope this blog is a useful introduction to structuring a hybrid FPGA and cRIO RT OS project. Contact us if you need any help or have any suggestions for future blogs.
The code for this project can be downloaded here. No warranty of any kind is provided for the code functionality.