We’ve been using a National Instruments (NI) system for the last year on a pretty complicated system for one of our clients. That system converts laser energy to voltage and takes 16 simultaneous 16-bit analog samples. Each of the 16 channels takes more than a thousand samples per millisecond, and reports the data back to a PC running a Microsoft Visual Studio application.
In our earliest endeavors we ran into problems with timing that stemmed from the National Instruments hardware drivers. It was no problem to take a single trigger initiated sample of all of our channels. But when we went to “re-trigger” the analog measurements 5-10 milliseconds would elapse before the sampling occurred. This would not work for our system. We needed analog data each and every millisecond, without fail.
The trick to getting this functionality relied on creating a retriggerable digital task, and coupling that with an analog callback event in Visual Studio. In short you create an analog task in NI’s DAQMX driver software that collects X number of samples and reports them back with the analog callback to your software. You also create a digital trigger event that generates X number of pulses whenever a rising edge is seen. The pulses generated by the digital task become the sample clock for the analog task. Since the digital task is retriggerable, and the analog sampling is also continuous (it just reports when it has a specific number of samples accumulated) this method allows you to collect data continuously.
In the scope capture above you can see the trigger signal (yellow) and the sample clocks (green) generated by the digital task. The analog signal (purple) is sampled with each rising edge transition of the sample clock. In this case the scope capture shows 1000 samples, each happening every 0.5uS.
For the test above we used NI’s USB-6366, a pretty powerful tool. It allows up to 8 channels analog of data to be read simultaneously, as well as providing a number of counters and digital i/o. Better yet, it can read 2 million samples per second per channel, and gets the data back to you over the USB port. With a little diligence you could create a nice 8 channel instrument under PC control using the USB-6366.
Below is a screen capture of a simple Visual Studio 2010 program that captures and displays a single channel. This is the same signal that’s shown in the screen capture of the oscilloscope.
The source code for this Visual Studio program is here. The main tasks associated with retriggerable analog measurements are located in the DAQ_Module.vb file. The task definitions are also shown below for reference.
You’ll need to download and install the DAQMX drivers and include the appropriate namespace in your Visual Studio program. This example measures voltages in the range of +/-10V and returns a signed INT16 value for each sample.
#Region "DAQ Tasks" Public Sub CreateTask_Analog() Try ' myTask_Trigger uses PFIO as an input and starts the analog sample clock for the my_Task analog. The clock is output on PFI12 which is the counter 0 default output. If runningTask_Trigger Is Nothing Then 'Create New Task myTask_Trigger = New NationalInstruments.DAQmx.Task() 'Pulse task outputs on PFI12 myTask_Trigger.COChannels.CreatePulseChannelTime("Dev1/ctr0", "", COPulseTimeUnits.Seconds, COPulseIdleState.Low, AnalogTriggerDelay, (AnalogSampleClock / 2), (AnalogSampleClock / 2)) 'Digital edge and retriggerable myTask_Trigger.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger("PFI0", DigitalEdgeStartTriggerEdge.Rising) myTask_Trigger.Triggers.StartTrigger.Retriggerable = True 'Finite pulse train of predetermined length myTask_Trigger.Timing.ConfigureImplicit(SampleQuantityMode.FiniteSamples, AnalogSamplesPerChannel) 'Configure Delay Property myTask_Trigger.COChannels.All.EnableInitialDelayOnRetrigger = True ''Set to false to allow on non-GUI thread 'myTask_Trigger.SynchronizeCallbacks = SynchronizeTasks runningTask_Trigger = myTask_Trigger myTask_Trigger.Start() End If ' myTask_Analog sets up the analog read a single channel channel which is returmed to the asynchronous callback at AnalogInCallback. If runningTask_Analog Is Nothing Then myTask_Analog = New NationalInstruments.DAQmx.Task() myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai0", "", CType(-1, AITerminalConfiguration), -10.0, 10.0, AIVoltageUnits.Volts) '' You can add more channels but you'll then need to redimension the first element of copyArray and analogsamplesArray 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai1", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai2", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai3", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai4", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai5", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai6", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai7", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts) 'Use counter: Continuous samples - Configure Timing Specs generates pulses as sample clock at PFI12 triggered by signal at PFI0. myTask_Analog.Timing.ConfigureSampleClock("PFI12", 2000000, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 720000) ' Verify the Task myTask_Analog.Control(TaskAction.Verify) ' Commit the Task myTask_Analog.Control(TaskAction.Commit) runningTask_Analog = myTask_Analog 'Access properties myAsyncCallback = New AsyncCallback(AddressOf AnalogInCallback) analogRawReader = New AnalogUnscaledReader(myTask_Analog.Stream) ' set to true to have the callback on the GUI thread analogRawReader.SynchronizeCallbacks = False analogRawReader.BeginReadInt16(AnalogSamplesPerChannel, myAsyncCallback, myTask_Analog) End If Catch exception As DaqException Exceptions += "CreateTask_Analog():" + exception.ToString + vbCrLf + vbCrLf UnloadTask_Analog() Catch ex As Exception Exceptions = Exceptions + "CreateTask_Analog():" + ex.ToString + vbCrLf + vbCrLf UnloadTask_Analog() End Try End Sub ' cancel the analog task Public Sub UnloadTask_Analog() Try If Not runningTask_Analog Is Nothing Then myTask_Analog.Dispose() runningTask_Analog = Nothing myAsyncCallback = Nothing End If If Not runningTask_Trigger Is Nothing Then myTask_Trigger.Dispose() runningTask_Trigger = Nothing End If Catch exception As DaqException Exceptions = Exceptions + "UnloadTask():" + exception.ToString + vbCrLf + vbCrLf End Try End Sub ' this callback copies the analog data to an array we can use elsewhere Public Sub AnalogInCallback(ByVal ar As IAsyncResult) Try copyArray = analogRawReader.EndReadInt16(ar) If analogsampleReady = False Then analogsamplesArray = copyArray analogsampleReady = True End If ThreadTracker.AnalogCallBack = Thread.CurrentThread.ManagedThreadId.ToString analogRawReader.BeginReadInt16(AnalogSamplesPerChannel, myAsyncCallback, myTask_Analog) Catch exception As DaqException Exceptions += "AnalogInCallback():" + exception.ToString + vbCrLf + vbCrLf UnloadTask_Analog() Catch ex As Exception Exceptions = Exceptions + "AnalogInCallback():" + ex.ToString + vbCrLf + vbCrLf UnloadTask_Analog() End Try End Sub #End Region
The physical connections to the USB-6366 terminal blocks are also pretty straightforward. PFI12 is shown, and may be probed to verify the sample clock generation, but it is routed internally to act as the DAQ’s sample clock, and no external connections are necessary.