Retriggerable Analog Measurements with National Instruments’ DAQMX

usb6366 retriggerable scope

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.

usb6366 vs2010 form1

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.

 

usb6366 connections

Speak Your Mind

*