What makes a piece of test equipment truly useful is the ability to automate measurements. Instead of interactively twiddling knobs and pushing buttons (even if they are on-screen), the ability for the instrument to be controlled through a program or script can be a great time-saver, reducing the chances for error and allowing for measurement regimes that would not otherwise be humanly possible (or time-efficient). To this end, the ADP3450 has the ability to be automated, although instead of using the older and more archaic SCPI standard commands, it has its own SDK and API which supports devices that work with the WaveForms software.
WaveForms SDK
The WaveForms SDK is installed with the main WaveForms package. The SDK consists of the WaveForms Runtime which depends on the DWF Dynamic Library and a number of other configuration files. The SDK supports C, C#, Python and Visual Basic. In the context of the Analog Discovery Pro ADP3450, the SDK works as shown in the block diagram provided in WaveForms Help tab:
The WaveForms SDK is documented online in the WaveForms SDK Reference Manual, however, the latest version is a PDF file which is contained within the SDK install folder. The SDK also comes with a number of example files which make it easier to get started with basic tasks. The example files can be found at the following paths depending on your operating system:
- Windows 32-bit: C:\Program Files\Digilent\WaveFormsSDK\samples
- Windows 64-bit: C:\Program Files (x86)\Digilent\WaveFormsSDK\samples
- Linux: /usr/share/digilent/waveforms/samples
- OS X: /Applications/WaveForms.app/Contents/Resources/SDK/
A list of the included sample files are as follows –
+---c | analogin_acquisition.cpp | analogin_record.cpp | analogin_sample.cpp | analogin_trigger.cpp | analogio_analogdiscovery2_power.cpp | analogio_analogdiscovery2_systemmonitor.cpp | analogio_analogdiscovery_power.cpp | analogio_analogdiscovery_systemmonitor.cpp | analogoutin.cpp | analogout_custom.cpp | analogout_custom_am.cpp | analogout_sine.cpp | analogout_sweep.cpp | analogout_sync.cpp | device_enumeration.cpp | digitalin_acquisition.cpp | digitalin_record.cpp | digitalio.cpp | digitalout_binarycounter.cpp | digitalout_phase.cpp | digitalout_pins.cpp | digitalout_walking1.cpp | readme.txt | sample.h | +---cs | dwf.cs | +---dwfcmd | analogin.txt | analoginout.txt | analoginoutcustom.txt | analoginoutsync.txt | analogintrigger.txt | analogplay.txt | analogplay2.txt | analogplayrecord.txt | analogrecord.txt | analogrecordoutsine.txt | dwfcmd.cpp | dwfcmd.exe | +---py | AnalogImpedance_Analyzer.py | AnalogImpedance_Compensation.py | AnalogImpedance_Input.py | AnalogImpedance_Measure.py | AnalogImpedance_Meter.py | AnalogImpedance_OffsetSweep.py | AnalogImpedance_OffsetSweepCp.py | AnalogImpedance_ThinkSpeak.py | AnalogInDigitalIn_Acquisition.py | AnalogIn_Acquisition.py | AnalogIn_Logger.py | AnalogIn_Record.py | AnalogIn_Record1.py | AnalogIn_Record_int16.py | AnalogIn_Record_Trigger.py | AnalogIn_Record_Trigger_int16.py | AnalogIn_Record_Wave_Mono.py | AnalogIn_Sample.py | AnalogIn_ShiftScreen.py | AnalogIn_Trigger.py | AnalogIO_ADP5250_DMM.py | AnalogIO_AnalogDiscovery2_Power.py | AnalogIO_AnalogDiscovery2_SystemMonitor.py | AnalogIO_AnalogDiscovery_Power.py | AnalogIO_AnalogDiscovery_SystemMonitor.py | AnalogIO_DigitalDiscovery.py | AnalogIO_ElectronicsExplorer.py | AnalogNetwork_Analyzer.py | AnalogOutIn.py | AnalogOutIn_PlayRecord.py | AnalogOutIn_PlayRecordStereo.py | AnalogOutIn_Synchronization.py | AnalogOut_Custom.py | AnalogOut_Pattern.py | AnalogOut_Phase.py | AnalogOut_Play.py | AnalogOut_Pulse.py | AnalogOut_Sine.py | AnalogOut_Sweep.py | AnalogOut_Sync.py | Analog_Transistor.py | Device_Enumeration.py | Device_Info.py | Device_InfoEx.py | Device_Speed.py | Device_Synchronization.py | DigitalDiscovery_Play.py | DigitalDiscovery_PlayRecord.py | DigitalDiscovery_RecordToFile.py | DigitalDiscovery_RecordToFile16.py | DigitalIn_Acquisition.py | DigitalIn_Acquisition_8x100k.py | DigitalIn_Acquisition_8x256M.py | DigitalIn_PulseTrigger.py | DigitalIn_Record.py | DigitalIn_Record_Compress.py | DigitalIn_Spi_Spy.py | DigitalIn_Sync.py | DigitalIn_Trigger.py | DigitalIO.py | DigitalOut_BinrayCounter.py | DigitalOut_Clock.py | DigitalOut_Custom.py | DigitalOut_CustomBus.py | DigitalOut_Duty.py | DigitalOut_Phase.py | DigitalOut_Pins.py | DigitalOut_Pulse.py | DigitalOut_ResetPattern.py | DigitalOut_ROM_Mux.py | DigitalOut_SPI.py | Digital_Can.py | Digital_I2c.py | Digital_I2c_PmodAcl.py | Digital_I2c_PmodGyro.py | Digital_I2c_Spy.py | Digital_Spi.py | Digital_Spi_Dual.py | Digital_Spi_Quad.py | Digital_Spi_Siso.py | Digital_Spi_Spy.py | Digital_Spi_Spy_DD.py | Digital_Uart.py | Digital_Uart_RX.py | dwfconstants.py | Enumerate.py | \---vb dwf.vb
WaveForms SDK for Python
As Python is amongst my favourite programming languages and seeing the sheer number of Python examples, I felt it most appropriate that I use the SDK to automate the testing that would be required for the following applications and instrument performance chapters.
My Experience with the SDK
Getting started with a new SDK can be a very daunting experience as it’s like trying to learn how to walk all over again, especially if you already know SCPI as this does not follow such conventions. I feel that the use of the SDK rather than SCPI is likely to be related to the hardware itself and can have some benefits in terms of performance. To learn to use the SDK effectively requires quite a bit of time commitment, or furious reference to examples and the reference manual punctuated by repeated head-scratching moments, as I had myself experienced. However, I also feel it to be a missed opportunity to have users learn a useful programming language of SCPI that would help them work with many other instruments from “big-box” vendors and is a standard of its own. Working with this SDK limits you to WaveForms-compatible devices from Digilent, but perhaps they see this as a way to maintain a form of competitive advantage in the form of vendor lock-in.
Some of the head-scratching I experienced had to do with the accuracy of the documentation – for example:
It seems that the authors may have confused dwf with dfw and thus introduce an inconsistency in the table. In other cases, while functions are listed in the document along with their arguments, the expected values for the arguments or valid values that can be supplied to these arguments are missing, thus causing some confusion. In some cases, one must use a specific read/enumerate function first to understand what the hardware is capable of and then write the appropriate value, but this is not something that is demonstrated by much of the example code. Some of the example code is also hardware-specific, thus will not run on the ADP3450.
One confusion I had was simply how to configure the digital bus voltage (which is the only “power supply” the ADP3450 has). I looked up the reference and found that the node and channel definitions for the ADP3450 were not included in the online documentation.
I ended up guessing the value, however, it was only later that I realised that the online version of the reference manual was not the most up-to-date. Instead, a PDF document is provided with the SDK that had this information included. The inconsistency in documentation across sources was confusing, especially as the reference manual and tutorials for the ADP3450 were available only from the website and not as downloadable PDFs.
Some of the examples also had subtle errors, for example in the AnalogIO Pattern and Custom examples, line 30 in this case seems to be missing a print function declaration – I suspect they meant print(“Opening first device…”) as the current code doesn’t seem to be valid. While looking through the examples, I did find some variations in the code which initialises the device as some perform more diagnostic printing than others, but the examples could have been better documented to aid understanding. This is probably best illustrated by looking at a typical code example from the SDK.
Examining an Example: AnalogIn_Record.py
Below is the code for one of the examples, AnalogIn_Record.py as provided by Digilent.
""" DWF Python Example Author: Digilent, Inc. Revision: 2018-07-19 Requires: Python 2.7, 3 """ from ctypes import * from dwfconstants import * import math import time import matplotlib.pyplot as plt import sys import numpy if sys.platform.startswith("win"): dwf = cdll.dwf elif sys.platform.startswith("darwin"): dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf") else: dwf = cdll.LoadLibrary("libdwf.so") #declare ctype variables hdwf = c_int() sts = c_byte() hzAcq = c_double(100000) nSamples = 200000 rgdSamples = (c_double*nSamples)() cAvailable = c_int() cLost = c_int() cCorrupted = c_int() fLost = 0 fCorrupted = 0 #print(DWF version version = create_string_buffer(16) dwf.FDwfGetVersion(version) print("DWF Version: "+str(version.value)) #open device print("Opening first device") dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf)) if hdwf.value == hdwfNone.value: szerr = create_string_buffer(512) dwf.FDwfGetLastErrorMsg(szerr) print(str(szerr.value)) print("failed to open device") quit() print("Generating sine wave...") dwf.FDwfAnalogOutNodeEnableSet(hdwf, c_int(0), AnalogOutNodeCarrier, c_bool(True)) dwf.FDwfAnalogOutNodeFunctionSet(hdwf, c_int(0), AnalogOutNodeCarrier, funcSine) dwf.FDwfAnalogOutNodeFrequencySet(hdwf, c_int(0), AnalogOutNodeCarrier, c_double(1)) dwf.FDwfAnalogOutNodeAmplitudeSet(hdwf, c_int(0), AnalogOutNodeCarrier, c_double(2)) dwf.FDwfAnalogOutConfigure(hdwf, c_int(0), c_bool(True)) #set up acquisition dwf.FDwfAnalogInChannelEnableSet(hdwf, c_int(0), c_bool(True)) dwf.FDwfAnalogInChannelRangeSet(hdwf, c_int(0), c_double(5)) dwf.FDwfAnalogInAcquisitionModeSet(hdwf, acqmodeRecord) dwf.FDwfAnalogInFrequencySet(hdwf, hzAcq) dwf.FDwfAnalogInRecordLengthSet(hdwf, c_double(nSamples/hzAcq.value)) # -1 infinite record length #wait at least 2 seconds for the offset to stabilize time.sleep(2) print("Starting oscilloscope") dwf.FDwfAnalogInConfigure(hdwf, c_int(0), c_int(1)) cSamples = 0 while cSamples < nSamples: dwf.FDwfAnalogInStatus(hdwf, c_int(1), byref(sts)) if cSamples == 0 and (sts == DwfStateConfig or sts == DwfStatePrefill or sts == DwfStateArmed) : # Acquisition not yet started. continue dwf.FDwfAnalogInStatusRecord(hdwf, byref(cAvailable), byref(cLost), byref(cCorrupted)) cSamples += cLost.value if cLost.value : fLost = 1 if cCorrupted.value : fCorrupted = 1 if cAvailable.value==0 : continue if cSamples+cAvailable.value > nSamples : cAvailable = c_int(nSamples-cSamples) dwf.FDwfAnalogInStatusData(hdwf, c_int(0), byref(rgdSamples, sizeof(c_double)*cSamples), cAvailable) # get channel 1 data #dwf.FDwfAnalogInStatusData(hdwf, c_int(1), byref(rgdSamples, sizeof(c_double)*cSamples), cAvailable) # get channel 2 data cSamples += cAvailable.value dwf.FDwfAnalogOutReset(hdwf, c_int(0)) dwf.FDwfDeviceCloseAll() print("Recording done") if fLost: print("Samples were lost! Reduce frequency") if fCorrupted: print("Samples could be corrupted! Reduce frequency") f = open("record.csv", "w") for v in rgdSamples: f.write("%s\n" % v) f.close() plt.plot(numpy.fromiter(rgdSamples, dtype = numpy.float)) plt.show()
As a rather typical example of the code provided, I felt that the examples had limited amounts of commenting and used a number of magic number values which makes the code less easily understood. Looking up references for the FDwf functions is almost mandatory if the user is to understand what each argument value actually means, and even then, some of these values do not seem to be well documented in the reference. The code seems to require the use of ctypes, thus values are all wrapped with their corresponding C type declarations, thus making things a little more cluttered and unpythonic. Some examples also require a number of libraries, some hefty, to be installed for the full experience – this one, for example, expects matplotlib and numpy. There are occasional formatting or comment typographical errors as well suggesting that these examples may have been quickly compiled, although the sheer number of examples is definitely appreciated. Not all examples will run on the ADP3450 because of the hardware-specific limitations and also because of differences in the hardware values used in certain functions (e.g. AnalogIO Node/Channel Numbers).
Conclusion
The WaveForms SDK makes the ADP3450 a much more useful and versatile instrument. By enabling the possibility to automate the instrument through programs interfacing with the WaveForms SDK, it becomes possible to automate processes which would otherwise take lots of time to manually achieve. The SDK is automatically installed with every installation of WaveForms and supports C, C#, Python and Visual Basic.
It should be noted that their SDK is quite different compared to traditional SCPI commands and VISA automation, being a set of functions which are hardware-specific to WaveForms-compatible hardware. This may have advantages in terms of performance and ease of implementation, but I also feel it to be a missed opportunity to teach users the “standardised” SCPI as used by big-box instruments and may represent an element of vendor lock-in.
My experience with the SDK was aided greatly by the number of Python-based examples which covered many use cases, thus allowing more rapid implementation of programs that combine both WaveForms SDK equipment and SCPI-standard equipment in later experiments. Considering that I have never explored the WaveForms SDK, the learning curve was not too bad. Unfortunately, the documentation did have some minor inconsistencies with the online version of the SDK Reference Manual missing data relevant to the ADP3450 which was present only in the PDF document version of the reference. The sample code did occasionally contain errors, some files are hardware-specific, but all had limited comments and magic numbers scattered in the code, requiring furious reference to the documentation to understand the specific configuration value which is used. At times, these values are not as well documented in the reference as desired, leading to some experimentation being necessary. I also found that the SDK features heavy use of ctypes variables due to the interfacing with a (presumably) C-based library, which makes for unpythonic code clutter. Some examples also require the installation of some hefty libraries such as matplotlib and numpy to run.
However, it is still undoubtedly a very powerful API to have as it allows for the same code to automate the ADP3450, whether run “on-device” in Linux Mode, on host connected via USB, Ethernet or Wi-Fi without any changes. I found the performance of the API to be stable for the simple experiments I have conducted.
---
This post is a part of the Digilent Analog Discovery Pro ADP3450 USB/Ethernet Mixed Signal Oscilloscope RoadTest Review.