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

Dear Sir or Madame,

 

I want to use Visual C/C++ (but not the .NET language) to contorl a USB-6216 BNC device, and to write a simple application for a small project in my lab. What I want to do is very simple. I just want to deliver an output signal through a AO channle and to record an input signal from an AI channel. I do not wish to LabVIEW, LabWindows/CVI, or Measurement Studio, so that this simple application can be stand-alone, and not dependent on LabVIEW, LabWindows/CVI, or Measurement Studio. Can someone advise me on what I need to do in order to accomplish that?

 

My naive thought is that I shall follow the following steps:

  1. install DAQmx drivers onto acomputer,
  2. connect the USB-6216 BNC device to this computer,
  3. make sure that this device is recognized by the computer, through Windows Device Manager
  4. write a Visual C/C++ program to include the NIDAQmx.h and to utilize the NIDAQmx.lib library. By the way,. I found the NIDAQmx.h and NIDAQmx.lib files in the C:\Program Files x86)\National Instruments\NI-DAQ\DAQmx ANSI C Dev\ folder that was created automatically for me when I installed LabVIEW on another computer in m lab a few years ago.

Please let me know if I have missed anything. Also, if there is any example code available for a simple task like this, it will be extremely helpful.

 

By the way, the computer that I am working on has Windows 7 and DAQmx 9.3 installed.

 

Thank you!

 

Fuh

 

0 Kudos
Message 1 of 21
(7,554 Views)

Heres two articles that detail steps to take including sample code.

 

Using NI-DAQmx in Text Based Programming Environments

http://www.ni.com/tutorial/5409/en/#toc3

 

Text Based NI-DAQmx Data Acquisition Examples

http://www.ni.com/example/6999/en/#ANSIC

 

Notice on the second article that there is a file path pointing you to numerous examples. This path should exist on your machine if you have installed the necessary drivers.

C:\Users\Public\Public Documents\National Instruments\NI-DAQ\Examples\DAQmx ANSI C

 

Ideally these resources should be able to get you going!

 

Cheers.

Message 2 of 21
(7,523 Views)

Thank you so much for pointing me to these wonderful resources! I will spend some time to study these resources and may come back later with additional questions...

 

Thanks again for your help! Greatly appreciated!!!

 

Fuh

 

0 Kudos
Message 3 of 21
(7,517 Views)

I have been studying on the NI example codes during the past a couple of weeks. And I have just written a script that combines two NI example codes: ContGen-IntClk.c and ContAcq-IntClk.c. My script runs through via my Visual C++ compiler and the console interface indicates that both the AO and AI data are delivered continuously. These are all good. However, there are a few things that I am uncertain.

 

1. How can I be sure that the AI and AO channels are in sync? I mean, I did not set anything in particular to synchronize the AI and AO channels. So I really cannot be sure if they are in sync.

2. How can I be sure that the AI and AO channels are using the internal clock of the DAQ device? I mean, in the DAQmxCfgSampClkTiming() function, the source was specified as "". And I am not sure if this means that the internal clock of the DAQ device is used or something else. I just want to make sure.

3. If I want the program to deliver the AO signal first, and then whenever the user is ready, the user will press a key on the keyboard to initiate data collection at the AI channel. In this scenario, what shall I do to accomplish that?

 

For your review and comments, I have attached my script on this message. Please note that this is my FIRST time trying to use C/C++ to control an NI DAQ device. So, there must be a lot of silly and/or serious mistakes. So, please feel free to point those out. So I can learn the correct way to implement this small project in my lab. Also, I have made extensive notes on my script. It is just a habit of mine. I tend to document on what I have learned, so I don’t forget in the future. Similarly, I also make notes on items that I don't understand. If any of you could help me with the questions I have, I would greatly appreciate it.

 

Thank you!

 

Fuh

0 Kudos
Message 4 of 21
(7,291 Views)

First of all, I'm not using C++ to develop my applications so my answer will cover only DAQmx concepts as I have learned them in CVI. For C** related questions you could perhaps try posting to the Measurement Studio for VC++ board.

 

 

1. To be sure both tasks are in sync you could for example share the same clock reference among them. You could for example set 

DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleAO, "/Dev1/ai/SampleClock", 50000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 1000));  	

supposing "dev1" is the name of your board, this instruction tells AO generation to use AI sample clock, so they will be in sync (for every sample read, a sample will be outputted on AO channel). For tasks to be really synced, you must start AO task first, and AI task last.

 

2. To have tasks use another clock, you should provide it explicitly, stating it in DAQmxCfgSampClkTiming and connecting a signal on some board pin

 

3. To achieve this, you must start the AO task and then have the program to wait for user input, next start the AI task. If in this scenario you still want tasks to be synced you must revert the concept I explained before: have the AI task use AO sample clock (i.e. use "/Dev1/ao/SampleClock" as the clock source for AI task).

 

 

With reference on your code, I see a little confusion on how to end the process. If you set tasks to be continuous, the done event will never be raised: this means that it is correct to wait for user input to stop the tasks but RegisterDoneEvent is unnecessary. On the other hand, is you set a finite number of samples, then you could rely on done event to clear board resources and tasks and cut off the getchar ().

 

Finally, your question I have no definitive response on your question about the start trigger: if you can simply manually start the AI/AO process, then the start trigger is unnecessary. You must use it instead if you want to start the process based on the state of an external signal, that is to be connected to a board PIN which is to be used as TriggerSource in DAQmxCfgDigEdgeStartTrig ().



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 5 of 21
(7,278 Views)

Wow! Those comments are very helpful. I have spent the past a few days trying to improve on my code. For your review and comments, I have attached my code with this message. Specifically, I have made a couple improvements. One improvement is that I have made an explicit statement in my code to ensure that the AO and AI channels are in sync. The second improvement is that I have modified the code so that the user has to manually press any key on the keyboard to start and stop and AO and AI tasks, separately. Specifically, the user will need to press any key to start the AO task, and then to press any key to start the AI task, and then to press any key to stop AI task, and then finally to press any key to stop the AO task.

 

However, for the small project I want to implement, the user shall not have to press any key to stop the AI task. Instead, after a certain number of data sweeps have been obtained, the AI task shall stop automatically. That is, for the small project I want to implement, after the user has started the AO task, the AO task will deliver a stimulus signal continuously. After the user has confirmed that the signal is coming out properly (via an output device that is connected to the AO channel), the user can then press any key on the keyboard to start the AI task. However, instead of having the user to manually stop the AI task, it is better that the program will simply stop the AI task, after a certain number of data sweeps have been obtained.

 

It might be confusing if I did not explain what a data sweep is. In the small project that I want to implement, it is my desire to present a stimulus signal that has approximately 12000 data points. Also, I want the AI task to be in sync with the AO channel, so that the data recorded from AI occur at the same time (i.e., are synchronized) with the AO task. And then, after the user has pressed any key to start the AI task, the AI task will begin. Specifically, the AI task shall start when the first data point of the stimulus signal is delivered. After that, each 12000 data points that have been obtained via AI is called a data sweep. And please note that the first data point obtained from AI shall corresponds to the same timing when the first data point of the stimulus signal is delivered via AO. Because the AO task is continuously delivering the stimulus signal, I shall be able to record say up to 10 data sweeps. Again, each data sweep recorded from AI shall be in sync with the stimulus signal that has been delivered via AO. And say after 10 data sweeps have been obtained, the AI task shall stop automatically. The AO task, on the other hand, will still be delivering the stimulus signal continuously, even though the AI task has stopped automatically. And this is totally fine, because the user can then stop the AO task at a later time. I have have tried different approaches, but am unable to have my program do what I have wanted.

 

Can someone advise me on what changes I shall make in order to make this happen?

 

Thank you!

 

0 Kudos
Message 6 of 21
(7,248 Views)

The easy part is to stop AI task automatically: you must simple switch from continuous acquisition to finite samples acquisition in task control. Stating the task to acquire e.g. 10 * SwepWidth samples you'll have at task end the data you want.  Again,  look in DAQmx examples for reference. 

The difficult task is to start AI task on sample 0 of AO: I have not a solution now,  let me consider it a while more



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 7 of 21
(7,242 Views)

Hello, could you tolerate a small gap between successive sweeps? If yes you could try a solution along this line (you'll have to program and test yourself as I don't have at present a daq board to test it with):

 

  1. Set the AO task so that it generates 1 sweep on a digital start trigger. Select one of available digital I/O as the start trigger on the rising slope
  2. Set a Done event callback that simply lowers and raises the trigger
  3. Start the AO task and setup a digital task to raise the trigger
  4. Issue the digital trigger to start the generation: at taks completion the done event callback will retrigger a new sweep: as said, you may have a small delay between sweeps due to software timing
  5. When the user wants to start the acquisition, setup the AI task to start with the same trigger, share the AO sample clock and acquire exactly one sweep;  start the AI task
  6. At the next done event, the trigger will start both AI and AO tasks
  7. If you want to acquire another sweep, simply repeat from step 5
  8. To stop the AO acquisition, remove the done callback (issue DAQmxRegisterDoneEvent again passing NULL in callback name parameter), set the digital trigger off and stop the digital task

In principles, items #1 to #4 and #8 can be programmed alone and tested to see system behaviour and performances. If the resulting output signal is good for you, then you can proceed adding the other steps.

I'm not be sure the whole stuff will work correctly but this is how I would move if I had to do this.



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 8 of 21
(7,222 Views)

I spent one whole day today working on the programming code. I got the “easy” part done. The challenging part, however, did seem a bit too much for me. Additionally, unpredictable gaps between successive sweeps are not desirable in the small project that I want to implement. So I probably shall change the way that the user interacts with the program. So, I will simply let the user press any key on the keyboard to initiate both the AO and AI tasks; the program will then deliver a stimulus signal via AO and record data via AI concurrently. When the user is satisfactory with the amount of data collected, the user can then press any key on the keyboard to stop the AI and AO tasks.

 

To implement this alternative approach, I have written another small program today. Please see my codes posted below.

 

In order to ensure that AI starts at sample 0 of AO, I created a Digital Edge Start Trigger and associated this trigger with AI task. Also, I make the buffer size the same length as the number of data points of the stimulus signal. So, both the AO and AI buffer sizes are 10000 data points now. Although this program ran through on my computer without showing any error message. I am unsure if everything has been setup correctly. Particularly, I am not sure if I have setup the Digital Edge Start Trigger properly. Also, I am not sure if the 10000 data points will be too much a burden for both the AO and AI buffers.

 

Because this is my first time trying to use C/C++ language to control a DAQ device, I don’t want to assume. Instead, I want to make sure that I did not make any silly mistakes and the program I wrote is legit. If anyone could go over the codes posted here and give me comments, I would greatly appreciate it.

 

Thank you!

 

#include <stdio.h>
#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

int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData);
	
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]. In this special case, I purposely make the BUFFER_SIAZE equals N so that a recording of one buffer is worth one sweep of data. The buffer size is kind of huge, though.

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 = 40000.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); // declare and initialize an error code and error buffer. This will be used for both the AO and AI tasks int32 error = 0; char errBuff[2048] = {'\0'}; // create and initialize AO and AI task handles TaskHandle taskHandleAO = 0; TaskHandle taskHandleAI = 0; /* --- 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, "", 40000.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 any key to start AO and AI tasks"; getchar(); DAQmxErrChk (DAQmxStartTask(taskHandleAO)); std::cout<< "Generating voltage continuously..." << std::endl; DAQmxErrChk (DAQmxStartTask(taskHandleAI)); /* --- Step 8 read data via AI ----*/ // This step is executed through the EveryNCallback() function --- /*--- Step 9 User press any key to stop the AO and AI tasks --- */ std::cout << "Press any key to stop AO and AI tasks " << std::endl; getchar(); DAQmxErrChk(DAQmxStopTask(taskHandleAO)); DAQmxErrChk(DAQmxStopTask(taskHandleAI)); /* --- Step 10 clear AI and AO tasks --- */ DAQmxErrChk(DAQmxClearTask(taskHandleAO)); DAQmxErrChk(DAQmxClearTask(taskHandleAI)); Error: if( DAQmxFailed(error) ) DAQmxGetExtendedErrorInfo(errBuff, 2048); if( taskHandleAI!=0 ) { /*********************************************/ // DAQmx Stop Code /*********************************************/ DAQmxStopTask(taskHandleAI); DAQmxClearTask(taskHandleAI); } if( DAQmxFailed(error) ) printf("DAQmx Error: %s\n", errBuff); std::cout << "End of program, press Enter key to quit" << std::endl; getchar(); return 0; } int32 CVICALLBACK EveryNCallback(TaskHandle taskHandleAI, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData) { int32 error = 0; char errBuff[2048] = {'\0'}; static int totalRead = 0; int32 read = 0; float64 data[BUFFER_SIZE]; // N /*********************************************/ // DAQmx Read Code /*********************************************/ DAQmxErrChk(DAQmxReadAnalogF64(taskHandleAI, BUFFER_SIZE, 10.0, DAQmx_Val_GroupByScanNumber, data, BUFFER_SIZE, &read, NULL)); // N, N if( read > 0 ) { printf("Acquired %d samples. Total %d\r", read, totalRead += read); fflush(stdout); } Error: if( DAQmxFailed(error) ) { DAQmxGetExtendedErrorInfo(errBuff, 2048); /*********************************************/ // DAQmx Stop Code /*********************************************/ DAQmxStopTask(taskHandleAI); DAQmxClearTask(taskHandleAI); printf("DAQmx Error: %s\n", errBuff); } return 0; }

 

 

 

 

 

 

0 Kudos
Message 9 of 21
(7,205 Views)

Revising your code, it only looks strange to me that AI and AO timing have different rate (20000 vs. 40000): I would assume they share the same clock and thus have the same rate.

 

Additionally, I would move step 10 past Error label: in case all works well, infact, stop and clear instructions are executed twice (before and after the error label).

 

Finally, you need to handle errors in EveryNSamples callback in a different way: at present you stop daq tasks inside that callback, but the main function is not informed and does not end unless user action (ok: the user is informed an error occurred so he's likely to do something, but I would prefer the callback to inform the main so that is can clear resources and terminate by itself).

 

 

I see no easy way to satisfy your complete requirements. As an alternative, I was thinking to use counters to generate both the sweep start signal and sampling frequency, so that the sweep start could also be used as a start trigger for AI task, but it's not easy for me to design a solution without hardware to test it with



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 10 of 21
(7,202 Views)