Measurement Studio for VC++

cancel
Showing results for 
Search instead for 
Did you mean: 

Create a continuous data stream from C++, and read it in LabView

Solved!
Go to solution

Hello all.

 

I'm working on a project which involves connecting to a motion tracker and reading position and orientation data from it in realtime. The code to get the data is in c++, so I decided that the best way to do this would be to create a c++ DLL file which contains all the necessary functions to first connect to the device and read the data from it, and use the Call Library Function node to feed this data into Labview. 

I'm having trouble though, since ideally I would like a continuous stream of data from the c++ code into Labview, and I'm not sure how to achieve this. Putting the call library function node in a while loop seems like an obvious solution, but if I do it this way I would have to reconnect to the device every time I get the data, which is quite a bit too slow. 

So my question is, if I created c++ function which created a data stream, could I read this into Labview without having to continually call a function? I'd prefer to only have to call a function once, and then read the data stream until a stop command is given.

I'm using Labview 2010, version 10.0.

Apologies if the question is poorly phrased, many thanks for your help.

Dave

 

0 Kudos
Message 1 of 13
(11,702 Views)

There's a few ways to do this depending on how you connect to the device. You can probably either do it completly in c++ or mostly in LabVIEW. Can you post the c++ function declarations you use to open the device and read from it? Although, the full API to the device would probably help even more.

0 Kudos
Message 2 of 13
(11,701 Views)

The device sends its data to a Device Manager on a PC, which we can interact with using the development kit which shipped with the device. I could post the function declarations if it would be useful, but I believe it may cloud the issue on this occasion. Opening / connecting to the device and reading from it is done using classes and their methods, which is handy because it makes it fairly trivial to write a program which gets the data. Below is a function similar to the one I will most likely be using to get the data from the device. 


#include <vhandtk/vhtBase.h> //contains functions and objects used for accessing the device

 

int main( )

{

vhtIOConn *trackerDict; \\ This is a class for handling the connection to the device manager
vhtTrackerEmulator *tracker; \\ This is a class for holding all the data for the Tracker

 

tracker = new vhtTrackerEmulator(); \\ I'm using a Tracker emulator at the moment, it behaves the same as the device in every way

 

while( true ) {

 

tracker->update(); \\ gets new values for the position and orientation of the tracker

double x = tracker->getRawData(0); \ \gets x co-ordinate
double y = tracker->getRawData(1); \ \gets y co-ordinate
double z = tracker->getRawData(2); \ \gets z co-ordinate

cout << x << " " << y << " " << z << "\n"; \\prints the co-ords to the screen

 

// waits for 100ms
Sleep(100);

}

return 0;
}

 

Once it has done the initial connection stuff, it can update the values more than quick enough. Once I have the data I can do any C++ stuff with it, like print it to the screen or write it too a file etc, but I'm not sure how I would go about creating a data stream which could be read by Labview. 

Ideally, the Labview VI and the program to get the tracker data would run simultaneously, with the VI running while the tracker data program just produces a constant stream of data.

Many thanks for your help.

0 Kudos
Message 3 of 13
(11,696 Views)

I see, I thought that if all you need when opening the device is a handle like xx_open(HANDLE *device) then you could open it from LabVIEW directly with a dll call and then subsequently pass the handle to dll calls in read operations. In your case I'd definitely stick to mostly C++ code.

 

What I would do; with a dll call create a object of say, class device, call method init of that object and pass the pointer to the object back to LabVIEW. This object in the init call would open the device, create a thread using CreateThread and this thread would do read from the device and save the data to a queue, a large buffer or whatever you think is suitable in that object. Then, have a method in that object, say called read which lets you retrieve the data from that buffer. From LabVIEW, I'd make another dll call, pass the pointer back to the dll and call the read function on that pointer and return the data back to LabVIEW.

 

What kind of timing do you need when calling update(). Because you might not even need to create the thread if calling update() when you call the read function would be enough.

 

I have to run now, but let me know if you have questions about this method, since I can post a small example of it.

0 Kudos
Message 4 of 13
(11,692 Views)

This method sounds like an excellent suggestion, but I do have a few questions where I dont think I've understood fully.

From what I understand the basic premise is to use one call library function node to access a DLL which creates an instance of the device object, and passes a pointer too it into labview. Then a seperate call library function node would pass this pointer to another DLL which could access the device object, update it and read the data. This part could be in a while loop and carry on reading the data until a stop command is given.

My main concern is that the object may go out of memory or be deallocated, since it wouldnt be held in any namespace or anything. 

I also have a more general programming question about the purpose of the buffer. Would the buffer basically be a big table of position values, which are stored until they can be read into the rest of the VI?  

Many thanks for your very helpful advice.

Dave

0 Kudos
Message 5 of 13
(11,684 Views)
Solution
Accepted by dr8086

@dr8086 wrote:

This method sounds like an excellent suggestion, but I do have a few questions where I dont think I've understood fully.

From what I understand the basic premise is to use one call library function node to access a DLL which creates an instance of the device object, and passes a pointer too it into labview. Then a seperate call library function node would pass this pointer to another DLL which could access the device object, update it and read the data. This part could be in a while loop and carry on reading the data until a stop command is given.


That's it. I'm including some skeleton code as an example. I'm also including the code because I don't know how much you have experience with multi threading, so I'm showing how you'd have to use critical sections to guard the interactions between threads so that they don't lead to issues.

// exported function to access the devices
extern "C"	__declspec(dllexport) int __stdcall init(uintptr_t *ptrOut)
{
	*ptrOut= (uintptr_t)new CDevice();
	return 0;
}

extern "C"	__declspec(dllexport) int __stdcall get_data(uintptr_t ptr, double vals[], int size)
{
	return ((CDevice*)ptr)->get_data(vals, size);
}

extern "C"	__declspec(dllexport) int __stdcall close(uintptr_t ptr, double last_vals[], int size)
{
	int r= ((CDevice*)ptr)->close();
	((CDevice*)ptr)->get_data(last_vals, size);
	delete (CDevice*)ptr;
	return r;
}



// h file
// Represents a device
class CDevice
{
public:
	virtual ~CDevice();
	int init();
	int get_data(double vals[], int size);
	int close();

	// only called by new thread
	int ThreadProc();

private:
	CRITICAL_SECTION	rBufferSafe;	// Needed for thread saftey
	vhtTrackerEmulator *tracker;
	HANDLE				hThread;
	double				buffer[500];
	int					buffer_used;
	bool				done;		// this HAS to be protected by critical section since 2 threads access it. Use a get/set method with critical sections inside
}






//cpp file

DWORD WINAPI DeviceProc(LPVOID lpParam)
{
    ((CDevice*)lpParam)->ThreadProc();      // Call the function to do the work
    return 0;
}


CDevice::~CDevice()
{
	DeleteCriticalSection(&rBufferSafe);
}

int CDevice::init()
{
	tracker = new vhtTrackerEmulator();
	InitializeCriticalSection(&rBufferSafe);
	buffer_used= 0;
	done= false;
	hThread = CreateThread(NULL, 0, DeviceProc, this, 0, NULL);	// this thread will now be saving data to an internal buffer
	return 0;
}

int CDevice::get_data(double vals[], int size)
{
	EnterCriticalSection(&rBufferSafe);
	if (vals)	// provides a way to get the current used buffer size
	{
		memcpy(vals, buffer, min(size, buffer_used));
		int len= min(size, buffer_used);
		buffer_used= 0;					// Whatever wasn't read is erased
	} else	// just return the buffer size
		int len= buffer_used;
	LeaveCriticalSection(&rBufferSafe);
	return len;
}

int CDevice::close()
{
	done= true;
	WaitForSingleObject(hThread, INFINITE);	// handle timeouts etc.
	delete tracker;
	tracker= NULL;
	return 0;
}

int CDevice::ThreadProc()
{
	while (!bdone)
	{
		tracker->update();
		EnterCriticalSection(&rBufferSafe);
		if (buffer_used<500)
			buffer[buffer_used++]= tracker->getRawData(0);
		LeaveCriticalSection(&rBufferSafe);
		Sleep(100);
	}
	return 0;
}

 


@dr8086 wrote:

My main concern is that the object may go out of memory or be deallocated, since it wouldnt be held in any namespace or anything.


Since you create the object with new, the object won't expire until either the dll is unloaded or the process (LabVIEW) closes. So the object will stay valid between dll calls provided LabVIEW didn't unload the dll (which it does if the VIs are closed). When that happens, I'm not exactly sure what happens to live objects (i.e. if you forgot to call close), I imagine the system reclaims the memory but the device might still be open.

 

What I do to make sure that everything gets closed when the dll unloads before I could call close and delete the object is to everytime I create a new object in the dll I add it to a list, when the dll unloads, if the object is still on the list I delete it.

 


@dr8086 wrote:

I also have a more general programming question about the purpose of the buffer. Would the buffer basically be a big table of position values, which are stored until they can be read into the rest of the VI? 


Yes, see the example code.

 

However, depending on the frequency with which you need to collect data from the device you might not need this buffer at all. I.e. if you collect a sample about every 100ms then you could remove all threading and buffer related functions and instead read the data from the read function itself like this:

 

double CDevice::get_data()
{
    tracker->update();
    return tracker->getRawData(0);
}

 Because you'd only need a buffer and a seperate thread if you collect data at a high frequency and you cannot lose any data.

 

Matt

Message 6 of 13
(11,680 Views)

Hi, many thanks for your detailed, comprehensive and very helpful answer.

Im fairly new to programming so it took me a while to get to grips with the code, but now I believe I understand it. I only have a few more points on which I'm not quite clear.

The code you posted uses class methods/member functions to connect to, get data from, and close the device. Can the Call Library Function node use class member functions just the same as ordinary functions? I thought it only had access to things in the global namespace, whereas a class is effectively its own little namespace (this is most likely a fundamental misunderstanding on my part). 

Also, would the exported function declarations in lines 1-20 go in the .h file with the class definition? 

I agree that due to the relatively slow data capture rate required (10-20 Hz is plenty) I can probably go without the buffer, but it may become necessary if the data processing on the other end starts to get a bit slow.

Many thanks for your advice it's been invaluable.

Dave

0 Kudos
Message 7 of 13
(11,660 Views)

@dr8086 wrote:

The code you posted uses class methods/member functions to connect to, get data from, and close the device. Can the Call Library Function node use class member functions just the same as ordinary functions? I thought it only had access to things in the global namespace, whereas a class is effectively its own little namespace (this is most likely a fundamental misunderstanding on my part).


You're right, the CLFN cannot access any of the c++ class functions. That's why I provided the top three functions. Those functions can be accessed by the CLFN. Those functions return and take a pointer to the class, so that you can call the functions in the class on that pointer. In otherwords, the exported functions bridge the CLFN to the class functions which it cannot access directly.

Maybe I didn't understand what you're asking?

 


@dr8086 wrote:

Also, would the exported function declarations in lines 1-20 go in the .h file with the class definition?


Well, normally these functions go in the cpp file and then you declare them in a header file. However you don't "need" to declare them in the header file since they'll be exported anyway. You'd only need to declare them if you're planning on using the LabVIEW import wizard because that requires a h file to import from.

 


@dr8086 wrote:

I agree that due to the relatively slow data capture rate required (10-20 Hz is plenty) I can probably go without the buffer, but it may become necessary if the data processing on the other end starts to get a bit slow.


In that case you can remove the thread and buffer functions. I.e. you can delete the ThreadProc and DeviceProc functions with all the variables that they needed.

 

If you're worried that processing might be too slow you don't "have" to go to c++ to create the second acquisition thread; if you do two loops in a block diagram LabVIEW will create two threads for you. So you if you do acquisition and processing in two different loops LabVIEW will have two threads doing the work. You can strategically place wait vis in the loops to allow scheduling time for the other thread so that one loop doesn't eat all the CPU time. If you're unsure, I can look over your c++ code if you post it.

 

Matt

0 Kudos
Message 8 of 13
(11,654 Views)

Many thanks, I think I now have all the information I need to proceed with the project.

Thanks for all the help.

 

Best regards

Dave

0 Kudos
Message 9 of 13
(11,648 Views)

Well done.You have done a great job.Accept my best wishes.

Plumbing Clifton
Plumbing Greenwich
Plumbing Addison IL
Plumbing Teaneck NJ

[url=http://www.harrisonplumbing.net/]plumber Harrison[/url]
[url=http://www.paramusplumbing.com/]Paramus Plumbing[/url]
[url=http://www.somersetplumbing.net/]Plumbing Somerset NJ[/url]
[url=http://www.englewoodplumbing.net/]Englewood Plumbing[/url]
0 Kudos
Message 10 of 13
(11,598 Views)