01-04-2009 02:58 PM
My team was hoping to take advantage of both LabVIEW and C++ this year in the form of a mixed mode. We used the tutorial here:
http://zone.ni.com/devzone/cda/tut/p/id/5694
and mastered developing shared libraries and writing wrapper functions for our C++ code.
We wanted to program our user mode in LabVIEW and use the assistance of C++ for our autonomous. We planned for our autonomous, to let LabVIEW clean up the signals from our sensors and handle the vision processing, then give these values to a call library function node containing some object oriented autonomous code we had planned for our bot. Because the call library function node is a one call per loop sort of operation, we struggled to come up with an idea for how to keep our non-primitive data types (c++ objects used in the code to represent the field and other robots on it) in memory after the call library function node finished.
At first we had the idea to let the C++ code run continuously and have it communicate through pointers. We had already had great success experimenting with using pointers and the call library function node in the past. We planned on putting the call library function node outside the LabVIEW autonomous loop with pointers to variables as the parameters. The c library function would store these pointers and start an infinite loop of reading the data LabVIEW is giving us from these pointers, executing the c++ code we wrote, and writing values for motors to other pointers provided to us as parameters of the initial call library function call. When we attempted this though, we ran into some difficulties. Calling a c function that is an infinite loop seemed to make the LabVIEW code freeze. Upon execution we would receive a message saying that the connection to the CRIO has been lost. We reconfigured the call library function node and got it working, however, there was no data coming back to our front panel or any other indication that the C++ test code was working (multiplying two pointers and storing them in another pointer. This code did work when there was no infinite loop.) and pressing the stop button in LabVIEW wouldn't shutdown the program.
Does anyone know any way we can make this approach work or any way of keeping our objects used in the c++ code in memory after the call library function node is finished? Is there a way we can instantiate our objects inside the shared library so that the objects are already there and will stay there even after the function called has used them?
01-05-2009 03:22 PM
Dear Don,
I recommend that you add a Wait to your infinite loop. This may solve the problem of LabVIEW freezing up on your and losing connection to the cRIO.
If I run into any other information that my be of use to you in this, I'll post back.
Best Regards,
~Nate
01-05-2009 06:51 PM
Let me first give a bit of background to explain some of the behaviours you are seeing. Then I'll make a few recommendations that may allow what your are attempting. I also have a few warnings about how what you are doing is prone to difficult debugging and spectacular failures.
"Calling a c function that is an infinite loop seemed to make the LabVIEW code freeze."
The call library function node, or CLF node for short, will delay execution of all diagram elements that depend on results from the CLF. So, if a wire from the CLF goes to other diagram elements, those elements will wait until the CLF returns. If the CLF has no outputs, then the diagram it is on can finish, but cannot be restarted until the CLF finishes. Additionally, the CLF configuration node has settings to the right about threading. The default is to execute the CLF in what is called the UI thread. This is the default and helps to simplify writing the C/C++ code since LV will guarantee only one call to the function at a time. The option to Run in any thread will allow simultaneous calls -- and if critical sections or mutexes are needed, the C code will have to provide them. The other affect of this setting is that the UI thread is responsible for handling the RT debugging protocol and some other communications tasks. If the UI thread is stuck in your code, then these services will no longer work. LV is hung until your code gives the thread back to do work for LV. So, with this explanation, one thing that will improve the situation is to change your CLF configuration to be called in the execution thread, not the UI thread.
"We reconfigured the call library function node and got it working, however ..."
I suspect that the Thread settings is the change made here. And what are now observing is that some of LV is still alive, but the diagram dependencies are keeping other LV code from running. Also, if your code has no sleep calls, it will heavily load the CPU and all other code will be sluggish. This approach can work. It is a way of giving your code a thread and that leaves LV with plenty of threads to continue working, but you do need to manage the parallelism on the diagram as well as managing the CPU resources. The stop button in this situation will sort of work. LV code will stop running, but it scheduler in LV doesn't kill threads, like the one stuck in your C code. If your diagram implements a watchdog that your C code can see, your C code can then return when the watchdog indicates the diagram is done, and this will allow the stop button to start working again.
"Because the call library function node is a one call per loop sort of operation, we struggled to come up with an idea for how to keep our non-primitive data types (c++ objects used in the code to represent the field and other robots on it) in memory after the call library function node finished. "
I don't have the setup to test this for certain, but I'd expect static variables to solve your problem. I believe the statically constructed objects to be built when the code runs the first time. It is possible that VxWorks is funny here and that the statics, especially the ones of global scope to be built when the library is loaded. For dynamic numbers of objects, you can new() the objects and store the reference in a static.
Recommendations:
An alternative hybrid approach is to coroutine. Instead of giving a thread to both LV and the CLF code and dealing with the protection issues, you can bounce from diagram to C, to diagram to C, etc. In other words, you write to C code to intentionally remember state, do some work, and return. It is a bit different to write it this way, but you have fewer corruptions due to unprotected access of data. This approach also allows you to send more information into and out of the CLF, getting rid of the pointers and doing things by value.
Warnings:
1. If you don't know what I mean by protected memory access or thread synchronization, then you seriously need to get an OS textbook and read up on race conditions, mutexes, critical sections, semaphores, etc.. You need to get really good at using the debugging tools, and you need lots of experience and time to spend staring at the wall trying to imaging how a particular corruption could have happened.
2. The LV execution system may not work the way you expect. In particular, a CLF may be called by any number of threads over its lifetime. LV by default has one UI thread, and you've already seen what happens when you prevent it from its duties, it has around four threads per priority per execution system. These are lazily allocated. As a VI executes, it goes onto a queue for the execution system at a given priority. Since there are four threads servicing the queue, a for loop executing 1000 times may midway through its execution suspend momentarily and resume to be run by a different thread. This makes thread local storage difficult or dangerous to use.
3. Beware that C/C++ and LV have different protection mechanisms for accessing the FPGA registers. Updating I/O from both could corrupt registers leading to very unpredictable behavior.
Summary:
This is technically feasible. NI does it internally, and system integrators do it. Especially with the short development timeline you have, I'd enourage you to consider doing all your development in one tool, either one. The environments are very comparable in features, and I can't really see a good reason to do a hybrid architecture as you describe.
Greg McKaskle
01-14-2009 07:31 AM
Ok, I finnaly got it all up and running. I had to declare the objects that i wanted to stay in memory after each call outside the rest of the program. Even when the call function library node is complete, I think the crio keeps the kernel module loaded, so thats why these stay. Included is the code to my example, "wrapper.cpp":
#include "wrapper.h"
accumulator test_object;
extern "C"
{
double wrapper(double value)
{
retrun test_function(value);
}
}
void accumulator::add(double value)
{
accumulator::total+=value;
}
double test_function(double value)
{
test_object.add(value);
return test_object.total;
}