Implementing a function generator using Measurement Computing’s High speed analog output products Part 1


Streaming analog outputs are used in various applications, such as the controlling XY tables, modulating signals, audio, testing stimulus/response, and testing systems and circuits.

 

Stand- alone function generators have one or more channels, have a range of waveforms they can generate, and have adjustable amplitude, offset, duty cycle and frequency.

 

Measurement Computing makes many products with analog outputs, such as the USB-3100 series. However they cannot uniformly stream analog data out.  If one was to output an array of values as fast as possible using one of these, the data would be output, but, it would not come out at a uniform rate because every so often, the Windows® operating system takes over to service higher priorities.  Another reason to stream the data out is to output the data at rates higher than you can one value at a time from an array.  By letting the device and driver take over the streaming output in a background operation, you can still use the analog inputs, digital IO, and counters to monitor or control the rest of your test.

 

In this series of articles we will develop a full-featured, multichannel function generator that can output periodic waveforms, arbitrary waveforms, and DC levels. We will also explore:

                        

·         How to change waveforms while streaming data out.

·         How to adjust waveform parameters such as amplitude, frequency and duty cycle.

·         How to do all the above on multiple channels.

 

Measurement Computing’s product offerings include multifunction A/D boards with high-speed analog outputs.  Most have at least two channels, and some can update at a maximum rate of 1 million samples per second.

At the time of this writing, the list of supported products:

1 million updates per second

USB-2627/37,

USB-2527/37,

 USB-1616HS-2, -4 and –BNC,

USB-1602/4/8HS-2AO,

 USB-1208HS-2AO and -4AO,

 PCI-2515/17.

500K updates per second

USB-1608GX-2AO

100K updates per second

USB-3101FS

50K updates per second

USB-1208FS-Plus,

USB-1408FS-Plus

5K to 10K updates per second

USB-1208FS,

USB-1408FS

5K updates per second

USB-2416-4AO, USB-2408-2AO

 

High-speed analog output streaming is accomplished using the Universal Library function AOutScan().  Any program using AOutScan() for streaming will also include the following library functions:

WinBufAllocEx(), WinArrayToBuf(), GetStatus(), StopBackground() and depending on the device you use, FromEngUnits().

 

Functions are explained as they are used, but since this is a focused application note on analog output scanning/streaming, it will not explain all features or proper syntax.  The Universal Library help file (ulhelp.chm) includes information on these functions and their parameters.  You get to ULHELP.CHM by clicking on Start >> All Programs >> Measurement Computing >> Universal Library >> UL Help.

 

At the end of each article is the fully-functional application source code.  It is not recommended that you copy and paste any of the code within these discussions as this is more pseudo code than proper syntax.

 

This application focuses on the 1 million updates per second products, particularly the USB-1208HS-4AO.

 

The application is coded in Visual Basic.NET for 2008 and newer.

 

The order of operations for creating a one channel-fixed function generator is as follows:

1.        Create the waveform array

2.       Create a handle to the buffer for the array

3.       Copy the array to the output buffer (handle)

4.       Set up the output scan parameters such as channel number, rate, amplitude, etc.

5.       Start the output scan

6.       Monitor the output scan

7.       Stop the scan

 

Create the waveform array

The desired waveform can be anything.  You can output a periodic waveform such as a sine, ramp, triangle or square wave, or a pseudo random waveform such as noise. For this example, we will use a sine wave.

 

To create a sine wave, you use the trig function SinΘ where Θ is the angle of a circle in Pi Radians.  The number of Pi radians in a circle equates to 2 pi radians or 6.28.

Next, pick an array size for the sine wave; since there are 360 points in a circle (or 1 period of a sine wave), we will use 360.

As this is not a trig lesson, let it suffice to say the equation we need is this:

 

Array(iteration) =  Amplitude * Sin(iteration* 6.28 / 360)

 Where:

·         Array(iteration) is 1 of the 360 elements we want to plot.

·         Amplitude is the maximum amplitude of the sine wave

·         Iteration is the index of our loop (yet to be seen)

 

So in the end it will look like this:

 

For iteration = 0 To PointsPerWaveForm - 1

                DataArray (iteration) = MaxDacValue * Math.Sin(iteration * 6.28 / PointsPerWaveForm)

Next

 

Where:

·         DataArray is the array where we will store the 360 elements we want to plot.

·         MaxDacValue is the maximum amplitude of the sine wave

·         Iteration is the index of our loop

·         PointsPerWaveForm in our application is 360

 

When outputting a repeating periodic signal, it is important to make sure your waveform begins and ends the same place or at 0 V.  The above is one way of accomplishing this.

 

We have already spent too much time discussing what we want to output, and no time in setting the USB-1208HS-2AO to output a waveform.  You can study the attached example at the end of this article for more details.

 

At this point, we have a 360-element data array of voltage values we want to send to the analog output.

 

Use the Universal Library function FromEngUnits()to convert the data in our array from floating point numbers to short integers because that is what the driver expects.

 

Create a separate array of short integers –the same length as the one created for the floating point values –which is also 360 elements.

 

Dim DAData(359) as short

 

Then implement .FromEngUnits() like this:

 

For MyIndex = 0 To 359

                ULStat = Daqboard.FromEngUnits(Bip10Volts, DADataV(MyIndex), DataArray(MyIndex))

 Next

 

Where:

·         MyIndex is the loop index variable used to point to a particular array element.

·         Bip10Volts is the Universal Library range code for the USB-1208HS-4AO.

·         DataArray is the array of sine wave voltage values we want to output.

·         DAData is the array of converted to short Integer values the driver uses.

 

Create a handle to the buffer for the array

 

A handle is nothing more than a reference to a location in PC memory.  The amount of memory is the size of the array.  We create a handle and copy the array using a UL function to that handle.  Nothing could be easier!

 

Here is the code to create the handle:

Handle = MccDaq.MccService.WinBufAllocEx(BufferSize )

 

Where:

·         Handle is our reference to a location in PC memory where the copied array starts.

·         BufferSize is the amount of memory we need to hold our data.  It is determined by the number of elements in the array.  As you recall from above, it is 360.

 

When the function is called that starts outputting the array, the handle tells it where we put the information to stream out.  That parameter is called Handle.

 

Copy the array to the output buffer (handle)

 

Next, copy the array of values to the buffer, using the Handle as the reference point (the beginning location in memory of the array).

 

ULStat = MccDaq.MccService.WinArrayToBuf(DAData, Handle, 0, 360)

 

There are four parameters to this function call, the name of the array (DAData), the Handle, the first element in the buffer to start putting the array as shown here, “0”, and the number of elements to write, 360.

The function copies the array called DAData to the data buffer, starting at buffer location 0 and filling in 360 elements.

 

Set up the output scan parameters

 

To start the scan, call the function AOutScan().

ULStat = Daqboard.AOutScan(0, 0, 360, Rate, BIP10Volts, Handle, Options)

 

Where:

·         LowChannel  and HighChannel Since we are only using one channel, the LowChannel  and HighChannel  values will be the same (0). 

·         Count is the number of points in the buffer (360). 

·         Rate is how often you want the D/A to output a new value.  This parameter MUST be a variable.  A more in depth discussion to follow.

·         Range is output range of the D/A channel.  Most boards have only one range, but if there is more than one it can be set to any of those supported by the board.

·         Handle is the integer pointer we created before using WinBufAllocEx().

·         Options are board specific, relating to features such as continuous scan, external clock, etc.  We will look at this more later. Our Options settings will be Background and Continuous.

 

Rate requires some discussion.  As noted above, our sine wave consists of 360 points.  The maximum update rate of the USB-1208HS is one million updates per second.  With regard to the sine wave, if we stream the waveform data out as fast as possible, the maximum frequency is:

1,000,000 / 360 or 2777.77 Hz.

 

To calculate a particular frequency you use the equation:

 

Points per Period * Frequency = Rate

 

To output a 1 KHz sine wave, using the equation above:

 

360 * 1000 = 360,000

 

Here is the .AOutScan function again with the syntax for Rate and Options:

 

Rate = 360 * 1000

Options = MccDaq.ScanOptions.Background + MccDaq.ScanOptions.Continuous

ULStat = Daqboard.AOutScan(0, 0, 360, Rate, BIP10Volts, Handle, Options)

 

The Rate parameter has one more function.  As stated above, Rate must be a variable because the function call returns the rate at which the data is flowing.  More often than not, the value you request is the rate that the function call returns.  However, due to how the device and driver work, and depending on the rate you request, you may not get exactly what you asked for.  You may get a rate that the device can provide as close possible to your requested rate.

 

Monitor the output scan

 

There are two things to do to make sure the scan is running.  The first is to check the return code of the function call, which is the variable to the left of the equal sign.  Here we use a variable named ULStat.  It is declared as type MccDaq.ErrorInfo.  Once declared, examine the value of this variable after each function call.  If the function call had all correct parameters and executed correctly, then ULStat is 0 or NoErrors.  If ULStat is not 0, then the function did not execute, and you need to resolve the problem. Again, refer to the Error Codes section in ulhelp.chm in to help you resolve the issue.

 

The other way to monitor the scan is to call the library function GetStatus.  It has four parameters –three of which return information.

 

Here is the prototype:

 

Public Function GetStatus(ByRef status As Short, ByRef curCount As Integer, ByRef curIndex As Integer, ByVal functionType As MccDaq.FunctionType) As MccDaq.ErrorInfo

 

 Where:

·         Status – Returns the status of the operation:

0 – A background process is not currently executing.
1 – A background process is currently executing.

 ·         curCount – The current number of samples output.

 ·         curIndex – The current sample index.

 

When the program is running, first check if Status = 1 (running).  If it is, then check to see where the output scan is in the buffer and how many samples have been output since the start of the scan.

 

To output a large fixed array of data once, use curCount to let you know where the output scan is in the buffer, and when it is done.

 

To output an array of data repeatedly like we are doing here, you can use curIndex to find out where the output scan is in the buffer.  Don’t forget the program is looping through the buffer.

 

The last parameter is the type of scan to run.  This variable is required since the USB-1208HS-2AO has more than one type of streaming capability – it also can run an analog input scan. This parameter allows the program to tell them apart. For now, set this parameter to AoFunction.

 

This function call is placed within a loop or timer event like this:

Private Sub tmrOutScan_Tick()

                ULStat = Daqboard.GetStatus(Status, curCount, curIndex, AoFunction)

                lblCurIndex.Text = CurIndex.ToString()

End Sub

 

All we need do now is send the value of curIndex to a label on the form.

 

Stop the scan

 

To stop the scan, call the library function StopBackground(), which has one parameter – the type of scan we are running.  This is the same as the last parameter in GetStatus() –AoFunction.

 

I find it best to create a global Boolean variable with an appropriate name such as “MyScanStatus.”

I set this variable to True when I want the output scan to run, and False when I want to stop the scan.

This variable can be called anywhere in the program you need it to be.  For example, set the variable to False at the end of a test after it completes, or from a button press event.

 

The timer event is also where we put the syntax to stop the scan.

 

Private Sub tmrOutScan_Tick()

                ULStat = Daqboard.GetStatus(Status, CurCount, CurIndex, AoFunction)

                lblCurIndex.Text = CurIndex.ToString()

                lblCurIndex.Refresh()

 

                If MyScanStatus = False or Then

                                tmrOutScan.Enabled = False

                                ULStat = Daqboard.StopBackground(MccDaq.FunctionType.AoFunction)

                 End If

End Sub

 

Again, check ULStat value to make sure the scan really did stop.  Its value should be 0 (NoErrors).

At this point the scan has stopped.

 

To run the scan again with the same array, you only need to call the .AOutScan() line of syntax again as is.

 

If you will not be running the scan again, then you need to do some clean up to release the memory handle like this:

 

ULStat = MccDaq.MccService.WinBufFreeEx(Handle)

 

Here is the sine wave as it appears on an oscilloscope from AOut0 of the USB-1208HS-4AO

 

Some more notes:

 

As stated above, if we use a 360 point waveform, at 1 MHz output; the fastest we can output our periodic sine wave is 2.77 KHz. 

To output our sine wave at a higher rate, reduce the number of points that create one period.  By cutting the number of points and the number of elements in the array in half to 180, the sine wave is output at a frequency up to 1,000,000 / 180 = 5.55 KHz which is twice as fast as before.  You can vary the number of points per period less or more as you see fit.

 

There are two main issues here.

1.        The waveform generated by the device may not have enough steps to look very nice.

2.       The device driver may require more data points than are being created.

 

For example, to output a sine wave at 60 KHz, then the number of points per period is only 16.  If won’t look much like a sine wave on an oscilloscope, but more like a rough stair case with eight steps or levels on the positive half of the sine wave, and eight steps on the negative half.

 

But even if the 16 points per period is enough, the other problem is the driver may throw an error stating 16 points is not a big enough buffer of data.  This can be resolved by creating a larger array and also buffer by an integer multiple, and then copying those 16 points over and over into the array/buffer.  Using the above example created of a sine wave of 360 points and therefore an array of 360 elements.   We know 360 worked.  If we apply our 16-point waveform to this array, we can copy it in 22.5 times.  We want the waveform to begin and end at the same voltage of 0V,so make the buffer a bit smaller or a bit larger than 360 and adjust all the related parameters discussed above to this new array and buffer size.  With a  22-point waveform, the  array and buffer would be 352 points and the array could be populated 22 copies of our 16 point waveform.  After that, it’s all the same; create a handle of 352 points, copy the array to the buffer, start the scan, at some point stop the scan.

 

To make things easier a DLL is included in this posting that contains a family of functions and objects, including a Function Generator.  So in the interest in moving this forward, use this DLL.

 

When converting the array of values from single point to short integers, there is an added AOScan option called SCALEDATA.  Use this option to create and pass an array of type doubles instead of creating an array of type singles, and then convert that to a different array of short integers.  When using SCALEDATA, you must use:

·         ScaledWinBufAllocEx() to create an integer pointer Handle,

·         ScaledWinArrayToBuf() to copy the array to the buffer

As the SCALEDATA option is not available to all devices, see hardware-specific information to determine if the device supports scaled data.


Posted 1/31/2014 11:19:16 AM by Jeff
https://kb.mccdaq.com/KnowledgebaseArticle50503.aspx