LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

How to use ANSI C/ C++ to control a USB-6216 BNC device

Solved!
Go to solution

Thank you so much for the insightful comments. Greatly appreciated!!! I took some time this weekend and revised my program accordingly. Also, I made additional adjustments so that the program will stop under any one of the following three conditions: 1) when nSweeps has been obtained via AI, 2) the user presses Enter key to exit, or 3) a fatal error occurs.

 

Again, because this is my first attempt trying to use C/C++ to control a DAQ device, I do want to make sure that I do not make silly mistakes and program is written correctly. If anyone could go over my revised program (posted below) and give me comments, I would GREATLY appreciate it.

 

Upon finalizing this program, there are a couple questions that I REALLY want to know the answers.

  1. Which clock is the AO task using now? And which clock is the AI task using now? I mean, in the revised program, the AO task is associated with the /Dev1/ai/StartTrigger. However, in the DAQmxCfgSampClkTiming() function, the sources of both the AI and AO tasks are associated with an empty string “”. What does this empty string mean? None? or their default clocks? I am very confused... Please help me out with this! Thanks!
  2. Is the buffer size of 10000 data points an overkill? I mean, all the NI example codes that I have learned so far, they all use a buffer size that is much smaller than 10000 data points. Will unpredictable errors occur during run time or when this program is run on a different computer/machine? OR is it that 10000 data points for a buffer size is simply okay? 

 

Thank you! 

 

#include <NIDAQmx.h>			// DAQmx 9.3
#include <iostream>			// std::cout, std::cin, std::endl

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

#define PI 3.141592653589793238

// function prototypes
int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData);
void StopTaskAOAI();
	
// initialize parameters at a global scope, so they are accessible inside any subroutine functions.
const unsigned int N = 10000;				// number of data points (This number refers to the number of data points per sweep, which include the stimulus duration and silent interval data points)
const unsigned int BUFFER_SIZE = N; 			// buffer size in data points, [default 1000]. 

// declare nSweeps parameters
const unsigned int nSweepsTarget = 10;			// number of sweeps to be obtained (i.e., the targeted number of sweeps to be obtained after the user has initiated the AI task)
unsigned int nSweepsRecorded = 0;			// number of sweeps that have been obtained via AI task

// declare and initialize an error code and error buffer. This will be used for both the AO and AI tasks. 
int32 error = 0;					// error code
char errBuff[2048] = {'\0'};				// error message

// create and initialize AO and AI task handles
TaskHandle  taskHandleAO = 0;
TaskHandle  taskHandleAI = 0;

int main(void)
{
	// prepare a sine wave that will be delivered via an AO channel
	double frequency = 100;					// frequency in Hz
	double amplitude = 1.0;					// baseline-to-peak amplitude of a sine wave (roughly in volts). Should be between +- 10 volts.
	double samplingRateWav = 20000.0;			// sampling rate of the sine wave
	float64 stimulus[N];					// stores N data points of float64
	for(int i = 0; i < N; i++)				// generate a sine wave with N data points
		stimulus[i] = amplitude * sin(2.0 * PI * frequency * (double)(i) / samplingRateWav);
	
	/*	--- Step 1 Create AO and AI tasks --- */
	DAQmxErrChk(DAQmxCreateTask("taskAO", &taskHandleAO));	
	DAQmxErrChk(DAQmxCreateTask("taskAI", &taskHandleAI));

	/*	--- Step 2 Create and configure an Analog Output Voltage channel and an Analog Input Voltage channel --- */
	DAQmxErrChk(DAQmxCreateAOVoltageChan(taskHandleAO, "Dev1/ao0", "", -10.0, 10.0, DAQmx_Val_Volts, NULL));	
	DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandleAI, "Dev1/ai0", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, NULL));

	/*	--- Step 3 Define sampling rate and related parameters for the AO task and the AI task ---*/
	DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleAO, "", 20000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, N)); 	
	DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleAI, "", 20000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, BUFFER_SIZE));	

	/*	--- Step 4 setup a Digital Edge, Start Trigger --- */
	DAQmxErrChk(DAQmxCfgDigEdgeStartTrig(taskHandleAO, "/Dev1/ai/StartTrigger", DAQmx_Val_Rising));

	/*	--- Step 5 setup parameters for AI buffer ---*/
	DAQmxErrChk(DAQmxRegisterEveryNSamplesEvent(taskHandleAI, DAQmx_Val_Acquired_Into_Buffer, BUFFER_SIZE, 0, EveryNCallback, NULL));

	/* ---- Step 6 write signal via AO ---- */
	DAQmxErrChk (DAQmxWriteAnalogF64(taskHandleAO, N, 0, 10.0, DAQmx_Val_GroupByChannel, stimulus, NULL, NULL));

	/* ---- Step 7 User press any key to start AO and AI tasks ----*/
	std::cout << "Press Enter to start AO and AI tasks";
	std::cin.get(); 
	DAQmxErrChk (DAQmxStartTask(taskHandleAO));
	std::cout<< "Generating voltage continuously..." << std::endl;
	DAQmxErrChk (DAQmxStartTask(taskHandleAI));

	/* --- Step 8 read data via AI ----*/
	// This step is executed through EveryNCallback() function --- 

	/*--- Step 9 User presses Enter to exit the program --- */
	std::cout <<  "Press Enter to stop AO and AI tasks " << std::endl;
	std::cin.get(); 
	
Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff, 2048);
	if( taskHandleAI!=0 ) 
	{
		StopTaskAOAI();
	}
	if( DAQmxFailed(error) )
		std::cout << "DAQmx Error: " << errBuff << std::endl;
	std::cout << "End of program, press Enter to quit" << std::endl;
	std::cin.get(); 
	return 0;
}

// A function to execute when every N samples have been obtained via AI
int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData)
{
	static int  totalRead = 0;
	int32       read = 0;
	float64     data[BUFFER_SIZE];		

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	DAQmxErrChk(DAQmxReadAnalogF64(taskHandleAI, BUFFER_SIZE, 10.0, DAQmx_Val_GroupByScanNumber, data, BUFFER_SIZE, &read, NULL));  // N, N
	if( read > 0 ) 
	{
		totalRead += read;				// increment totalRead
		nSweepsRecorded++;				// increment nSweepsRecorded
		std::cout << "sweep " << nSweepsRecorded << ". Acquired " << read << " samples. Total " << totalRead << " samples." << std::endl;

		if (nSweepsRecorded == nSweepsTarget)		// If nSweepsTarget sweeps have been obtained, stop AO and AI tasks.
		{
			StopTaskAOAI();							
			std::cout << nSweepsTarget << " sweeps have been obtained. Press Enter to exit." << std::endl;
		}
	}

Error:
	if( DAQmxFailed(error) ) 
	{
		DAQmxGetExtendedErrorInfo(errBuff, 2048);
		std::cout << "DAQmx Error: " << errBuff << std::endl;
		return error;				// This error code is returned to main() to signal that the program has executed in a way that was not intended. 
	}
	return 0;						
}

// A function to stop AO and AI tasks
void StopTaskAOAI()
{
	/* --- Step 10 stop AI and AO tasks --- */
	DAQmxStopTask(taskHandleAI);
	DAQmxStopTask(taskHandleAO);

	/* --- Step 11 clear AI and AO tasks --- */
	DAQmxClearTask(taskHandleAI);
	DAQmxClearTask(taskHandleAO);
}

 

0 Kudos
Message 11 of 21
(2,687 Views)

Hi Fuh,

 

In regards to the empty string " ", this in fact is the default setting, which also indicates that the clock being used is the onboard clock. Unless you need to specify a certain clock rate, using the onboard clock will keep other resources you may need elsewhere available.

 

Having a large buffer shouldn't cause an issue as far as I know. If you continue to troubleshoot and get errors related to over/underwriting, I'd be glad to help you with those going forward, as they may pertain to the buffer or clock rate issues.

 

I hope this helps,

 

FinchTrain

Message 12 of 21
(2,659 Views)

I second FinchTrain assertions. With reference to memory, a 10k samples buffer keeps ~80k in memory: with nowadays PCs often having 4GB+ RAM such a buffer should not have any impact on system resources.

 

In your last post I see some uncertainty in DAQmx concepts: do not confuse the start trigger and the clock source with each other! The start trigger marks the start of the acquisition / generation task: having the same trigger guarantees that both tasks are started in the same moment. The acquisition clock is a different item: it gives the rate of A-to-D or D-to-A conversions; even in this case both tasks should share the same clock to guarantee they are perfectly in sync; keeping the clock source blank states that the (unique) onboard click will be used so they will run with the same rate.

 

You should nevertheless give a second glance to error handling: even after your last modifications, if an error occurs EveryNCallback () the main function is not informed. I'm not an expert in C++ but I doubt that simply setting the error variable as global is enough since the system is sitting in step 9 waiting for user input and does not monitor that variable.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 13 of 21
(2,649 Views)

Thank you so much for the clarification! Greatly appreciated!!!

 

Speaking of the EveryNCallback() function and error handling, I am very confused on the pipeline of program execution. Please allow me to first explain what I have comprehended so far. My understanding is that, inside the main() function, Step 5, I run the DAQmxRegisterEveryNSamplesEvent() function to setup parameters and to designate EveryNCallback() function to be executed when every N samples are recorded from AI. I need to be aware that this is just a setup and to register; things won’t actually get executed until the AI task has been started in Step 7. Now, here comes the confusion of mine. After Step 7, the program pipeline goes to Step 8, i.e., the EveryNCallback() function, which is running in the background, I think. At the same time, the main() program is at Step 9 waiting for the user to press the Enter key. So, the EveryNCallback() function and the main() function are running simultaneously, one in the background and one at the front end. Is this correct?

 

If an error occurs inside the EveryNCallback() function, the DAQmxGetExtendedErrorInfo() function will put an error message into the errBuff variable. The problem is that, if I declare errBuff variable inside the main() function, this errBuff variable will not be accessible inside the EveryNCallback() function. On the other hand, if I declare errbuff inside the EveryNCallback() function, this variable won’t be available in the main() function. I understand that declaring errBuff at a global scope is not a good idea, but I simply don’t know how to transfer the errBuff variable back to main(). I understand that there are several ways to relay information from a subroutine back to main() in C/C++. However, in this case, the NI built-in functions, such as the EveryNCallback() function has already had a fixed set of input and output parameters. It is not that I am allowed to modify the structure of the EveryNCallback() function. All I have from the DAQmx library are the function prototypes and the NIDAQmx.lib file. Can you advise me on how to transfer errBuff back to main() through EveryNCallback()?

 

Additionally, an error can occur inside EveryNCallback() function at any time. What shall I do to ensure that the main() function is aware of and will react to an error that may occur inside EveryNCallback() function at any time. Can you please help me solve this? Or give me a direction on what I need to do?

 

Thank you so much in advance for your time and help!

 

0 Kudos
Message 14 of 21
(2,594 Views)

You are right in that after starting the task there are 2 concurrent processes: the main thread that handles the user interface and the read task where EveryNSamplesCallback is executed. You must keep this in mind when designing the rest of the application: EveryNSamplesEvent.prj example can help you in this matter.

Basically, you'll see that the callback that starts the acquisition task teminates immediately after DAQmxStartTask is called: the main thread now sits in RunUserInterface, waiting for user events on UI controls, while the acquisition process runs in the backgroung periodically calling EveryNSamplesCallback.

At this moment several events can happen: the user operates on a control on the panel (e.g.the stop button), or some error is found in reading, or the program is closed: in every single event of these, the acquisition task is stopped and a message is issued in case of errors. No need in this scenario to transfer the error info from one thread to the other, while the user interface is free to handle user actions.

 

Having said this, this paradigm is not mandatory. As an alternative, you can use callbackData parameter of EveryNSamples callback to pass informations back to the main thread: pass the pointer to a variable declared in the main task and sit in a loop continuously checking this value together with the keyboard: if EveryNSamplesCallback finds an error it can simply fill that variable with a flag or an error code and let the main thread terminate the process and inform the user of the problem; the same applies if the user terminates the process by itself.

 

Hope all this makes sense to you: feel free to ask in case I was not clear enough at some place.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 15 of 21
(2,588 Views)

Wow! You are a genius! It all makes sense now. I will try those (i.e., passing errBuff through the callbackData parameter in EveryNSamples() function and to create a loop in the main() function) sometime this week and let you know how it goes.

 

Eventually, I will need to transfer this simple program to GUI, so it's more user friendly. During the past a couple weeks, I have started integrating this program into a GUI program using Visual C++ and have encountered an issue. As you have now mentioned the example program EveryNSamplesEvent.prj, which sounds like a name of a GUI project, I searched immediately on the C drive of my computer, but was unable to find this example code. Can you advise me on where I can find that example code?

 

0 Kudos
Message 16 of 21
(2,580 Views)

That particular example is located in <PublicDocumentFolder>\National Instruments\CVI\samples\DAQmx\Events\Every N Samples\Cont Acq-Int Clk-Every N Samples Event folder.

 

CVI Examples can easily locate with the Example finder (Help >> Find examples... menu function): DAQmx examples can be found under Hardware Input And Output >> DAQmx folder in the search tree.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 17 of 21
(2,573 Views)

Thank you so much for the clarification and assistance! Greatly appreciated! This is the reason that I love using NI instruments so much. The support provided through the Discussion Forum is simply awesome!!!

 

There is one more improvement that I would really want to make for this small project. This improvement is called an odd-ball paradigm. In all the previous attempts of my DAQmx programming codes, there is only one stimulus and that stimulus gets presented repeatedly. In an odd-ball paradigm, some of the stimulus presentations will be using a different stimulus (i.e., an odd-ball stimulus). For example, we can use a 100 Hz tone as the primary stimulus and use a 50 Hz tone as an odd-ball stimulus. And for example, through the 10 stimulus presentations, the 100 Hz tone will be used for the 1, 2, 4, 5, 6, 8, 9, and 10th stimulus presentations, whereas the 50 Hz tone will be used for the 3rd and 7th stimulus presentations.

 

In order to implement this odd-ball paradigm in this program, I spent one whole day today and wrote a program (please see below) to the best of my ability. The program compiled, but it gave me an error code -200547. I think I may have made some horrible mistakes.

 

 

If anyone could give me some suggestions or directions to go, I would greatly appreciate it.

 

Thank you so much in advance for your time and kindness to help me out!

 

 

#include <stdio.h>
#include <NIDAQmx.h>			// DAQmx 9.3
#include <math.h>			
#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else #define PI 3.141592653589793238 // function prototypes int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData); void StopTaskAOAI(); // initialize parameters at a global scope, so they are accessible inside any subroutine functions. const unsigned int N = 10000; // number of data points (This number refers to the number of data points per sweep, which include the stimulus duration and silent interval data points) const unsigned int BUFFER_SIZE = N; // buffer size in data points, [default 1000]. // declare nSweeps parameters const unsigned int N_SWEEPS_TARGET = 10; // number of sweeps to be obtained (i.e., the targeted number of sweeps to be obtained after the user has initiated the AI task) unsigned int nSweepsRecorded = 0; // number of sweeps that have been obtained via AI task // declare and initialize an error code and error buffer. This will be used for both the AO and AI tasks. int32 error = 0; // error code char errBuff[2048] = {'\0'}; // error message // create and initialize AO and AI task handles TaskHandle taskHandleAO = 0; TaskHandle taskHandleAI = 0; // create two stimuli float64 stimulus1[N]; // stimulus 1 (e.g., a 100 Hz tone) float64 stimulus2[N]; // stimulus 2 (e.g., a 50 Hz tone) // arbitrarily define a presentation sequence int sequence[N_SWEEPS_TARGET] = {1, 1, 2, 1, 1, 1, 2, 1, 1, 1}; // 1 for stimulus 1, and 2 for stimulus 2 int main(void) { // prepare two sine waves that will be delivered via an AO channel double frequency1 = 100; // frequency in Hz, for stimulus 1 double frequency2 = 100; // frequency in Hz, for stimulus 2 double amplitude = 1.0; // baseline-to-peak amplitude of a sine wave (roughly in volts). Should be between +- 10 volts. double samplingRateWav = 20000.0; // sampling rate of the sine wave for (int i = 0; i < N; i++) // generate two sine waves (i.e., stimulus 1 and stimulus 2) { stimulus1[i] = amplitude * sin(2.0 * PI * frequency1 * (double)(i) / samplingRateWav); stimulus2[i] = amplitude * sin(2.0 * PI * frequency2 * (double)(i) / samplingRateWav); } /* --- Step 1 Create AO and AI tasks --- */ DAQmxErrChk(DAQmxCreateTask("taskAO", &taskHandleAO)); DAQmxErrChk(DAQmxCreateTask("taskAI", &taskHandleAI)); /* --- Step 2 Create and configure an Analog Output Voltage channel and an Analog Input Voltage channel --- */ DAQmxErrChk(DAQmxCreateAOVoltageChan(taskHandleAO, "Dev1/ao0", "", -10.0, 10.0, DAQmx_Val_Volts, NULL)); DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandleAI, "Dev1/ai0", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, NULL)); /* --- Step 3 Define sampling rate and related parameters for the AO task and the AI task ---*/ DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleAO, "", 20000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, N)); DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleAI, "", 20000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, BUFFER_SIZE)); /* --- Step 4 setup a Digital Edge, Start Trigger --- */ DAQmxErrChk(DAQmxCfgDigEdgeStartTrig(taskHandleAO, "/Dev1/ai/StartTrigger", DAQmx_Val_Rising)); /* --- Step 5 setup parameters for AI buffer ---*/ DAQmxErrChk(DAQmxRegisterEveryNSamplesEvent(taskHandleAI, DAQmx_Val_Acquired_Into_Buffer, BUFFER_SIZE, 0, EveryNCallback, NULL)); /* ---- Step 6 User presses Enter to start AO and AI tasks ----*/ printf("Press Enter to start AO and AI tasks\n"); getchar(); for (unsigned int sweep = 0; sweep < N_SWEEPS_TARGET; sweep++) { /* ---- Step 7 write signal via AO ---- */ switch (sequence[nSweepsRecorded]) { case 1: DAQmxErrChk(DAQmxWriteAnalogF64(taskHandleAO, N, 0, 10.0, DAQmx_Val_GroupByChannel, stimulus1, NULL, NULL)); break; case 2: DAQmxErrChk(DAQmxWriteAnalogF64(taskHandleAO, N, 0, 10.0, DAQmx_Val_GroupByChannel, stimulus2, NULL, NULL)); break; default: printf("Unrecognizable sequence number"); /* ---- Step 8 start AO and AI tasks --- */ DAQmxErrChk(DAQmxStartTask(taskHandleAO)); DAQmxErrChk(DAQmxStartTask(taskHandleAI)); /* --- Step 9 read data via AI ----*/ // This step is executed through EveryNCallback() function --- /* ---- Step 10 stop AO and AI tasks --- */ DAQmxStopTask(taskHandleAO); DAQmxStopTask(taskHandleAI); } } /*--- Step 10 User presses Enter to exit the program --- */ printf("Press Enter to stop AO and AI tasks\n"); getchar(); Error: if( DAQmxFailed(error) ) DAQmxGetExtendedErrorInfo(errBuff, 2048); if( taskHandleAI!=0 ) { StopTaskAOAI(); } if( DAQmxFailed(error) ) printf("DAQmx Error: %s\n", errBuff); printf("End of program, press Enter to quit\n"); getchar(); return 0; } // A function to execute when every N samples have been obtained via AI int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData) { static int totalRead = 0; int32 read = 0; float64 data[BUFFER_SIZE]; /*********************************************/ // DAQmx Read Code /*********************************************/ DAQmxErrChk(DAQmxReadAnalogF64(taskHandleAI, BUFFER_SIZE, 10.0, DAQmx_Val_GroupByScanNumber, data, BUFFER_SIZE, &read, NULL)); // N, N if( read > 0 ) { totalRead += read; // increment totalRead nSweepsRecorded++; // increment nSweepsRecorded printf("Sweep %d: Acquired %d samples, total %d samples.\n", nSweepsRecorded, read, totalRead); if (nSweepsRecorded == N_SWEEPS_TARGET) // If N_SWEEPS_TARGET has been obtained, stop AO and AI tasks. { StopTaskAOAI(); printf("%d sweeps have been obtained. Press Enter to exit.\n", N_SWEEPS_TARGET); } } Error: if( DAQmxFailed(error) ) { DAQmxGetExtendedErrorInfo(errBuff, 2048); StopTaskAOAI(); printf("DAQmx Error: %s\n", errBuff); /* NOTE: If an error occurs here, the user will see the error message and will respond accordingly*/ } return 0; } // A function to stop AO and AI tasks void StopTaskAOAI() { /* --- Step 11 stop AI and AO tasks --- */ DAQmxStopTask(taskHandleAO); DAQmxStopTask(taskHandleAI); /* --- Step 12 clear AI and AO tasks --- */ DAQmxClearTask(taskHandleAO); DAQmxClearTask(taskHandleAI); /* --- Step 13 reset AI and AO task handles --- */ taskHandleAO = 0; taskHandleAI = 0; }
0 Kudos
Message 18 of 21
(2,428 Views)

Hi Fuh,

 

Error -200547 indicates that the write buffer has overflowed.

 

 

Your buffer size is only set for 10,000 data points in this case for a 20kHz sampling rate.  It is possible that you are not handling this data fast enough. Increase your buffer size to a minimum of 40,000 and see if the issue persists. This may also lead to further steps to maximize your code's performance, but this will be a great first step to isolate the issue. Also check to make sure you do not have any other tasks running in the background of your computer (other code/non crucial applications). This may be cluttering your memory, or creating buffer issues from other program's references/functionality.

 

Thanks,

FinchTrain

 

Message 19 of 21
(2,398 Views)

I would like to know where and how the error arise: the exact line and if on the first sweep or in a subsequent one.

Additionally, I do not understant how the program can wait in 9 that the process is done: as far as I can understand the tasks will be stopped immediately after they are started.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 20 of 21
(2,357 Views)