In this blog post, I put the Digilent Analog Discovery Pro ADP3450 into various applications.
Lissajous Waveform Generation
Having access to two waveform generator channels and two oscilloscope channels naturally leads to the temptation to generate Lissajous figures. For this, the ADP3450 certainly does work, but it seems that the Scope refresh rate is not as quick as I would have liked.
The X-Y mode does work a treat however. By default, the tabbed interface makes it difficult to experiment with changing the generator parameters, but changing the UI mode to windowed makes it possible to have the Wavegen settings visible at the same time as the Scope.
The figures can be made slightly more exciting by changing the waveforms, frequencies and perhaps even modulating or sweeping them.
The last time I saw these figures was on a CRO, so this detour really bought back some memories!
Filter Bode Plot
The Network module allows for the creation of Bode Plots. One of the most basic laboratory exercises in introductory analog electronics is to build a cascaded R-C filter and measure the transfer function. As a result, I decided to grab some 160Ω resistors and 100nF capacitors to build a three-stage R-C filter for measurement. From memory, the cut-off frequency should be about 10kHz. The reason for three stages? Because with four channels, the first is limited to measuring the input to the filter to avoid errors from the wavegen output reducing under load.
I remember doing this exercise painstakingly by hand in my undergraduate degree, but now, with a click of a button, it is done. I had to grab an extra X1 probe from elsewhere so I could hook up the wavegen, but the measurements seem sensible with a few wiggles to remind us about component non-idealities and noisy readings to remind us about the noise floor and dynamic range of the system. The filter seems to make a turn somewhere near 130kHz which could be because of measuring noise or inductive pick-up.
I-V Curve Tracing
Want to do some curve tracing? The Tracer module is for just that. I decided to put it to use by first tracing out an ordinary power diode. The user needs to supply a sense resistor that is used to measure the current flowing through the circuit as a voltage – for tracing other semiconductors, you may need two such resistors. The values should be in decade steps – 1, 10, 100, 1k, 10k, 100k or 1MΩ.
For now, I’ve just hooked up probes in X1 mode and set the unit to trace. It’s fairly quick at tracing but the trace is noisy and can be lumpy especially if a non-ideal resistance is used. The trace current available is limited by the wavegen outputs but also by the achievable voltage of the wavegen which is dropped across the semiconductor and sense resistor. In this sense, it’s not as ideal as using an SMU, but it is faster, more like an analog curve-tracer but without the control knobs.
A diode is easy, so how does it go with a higher-voltage device like an LED? Turns out, it does rather well. I think this makes it quite useful as an educational tool.
An even more useful application would be to trace transistors, thanks to having two wavegen outputs and four channels, this is possible. As I only had n-channel MOSFETs to hand, I traced three different types:
The axes can also be changed, but it’s clear that the MOSFETs have different characteristics. In fact, I found this tool useful enough that I may even use it into the future. Noting there is no transistor tester breakout as there was for the original Analog Discovery series, I designed a simple break-out just to save having to manually switch in resistors. Unfortunately, due to international shipping delays, it will not arrive before the end of the RoadTest period.
Impedance Measurement
I’ve not had an impedance analyser myself, but the thought of having one is quite interesting to me as I know real-life capacitors and inductors behave differently depending on frequency and this can be a problem depending on your application. It’s part of the reason why LCR meters have selectable test frequencies. Having the Impedance module allows for the characterisation of components as a function of frequency.
Starting off gently, I test a through-hole resistor which should be flat. For the most part, it is, but it seems to increase slightly above 1MHz in part because of stray capacitance it seems. At least it proves the system to be working, even though in this case, I probably didn’t pick the ideal value of current sensing resistor. Of course, not all resistors are purely resistive – a wire-wound has inductive properties at higher frequencies, in the case of this 6-ohm 25W unit, above 50kHz it starts to show some inductive properties.
Both of the curves above come from a 100nF capacitor, with the left one being ceramic and the right being polyester. It is hard to find a sense resistor that covers a full range without triggering the “resistor too low/high” warnings, but as long as the curves are not too noisy, it will do. It seems that the polyester capacitor is more stable at frequencies up to about 200kHz while the ceramic seems to be declining in capacitance steadily, but only starting to make a major turn by 300kHz.
Looking at the impedance graph for the polyester capacitor, it’s clear that the impedance follows a straight line but at 1MHz, an inflection point is reached. This is the capacitor’s self-resonance, a point where the capacitor stops being a capacitor essentially.
Electrolytic capacitors don't handle higher frequencies that well, as can be seen in this trace of a 470uF electrolytic.
Inductors, too, have a self-resonance frequency. The above is a 1.2mH power inductor and it is pretty much done by 850kHz with the curve seeming to indicate it is best used below about 500kHz with its value deviating somewhat by 200kHz. The frequency in part is determined by geometry and core material of the inductor.
A smaller 68uH power inductor has a higher self-resonant frequency about 5MHz.
A small bead filter inductor of 220uH rating also seems to have a self-resonant frequency about 5MHz but the inductance does first dip before increasing which probably reflects the difference in core material behaviour. This can be particularly important considerations especially when picking an inductor for miniaturised switching power converter circuits which could oscillate at 1.7-2.2MHz.
Audio Recording
For some signals, it’s better to hear them than just see them. To meet this need, it seems WaveForms is capable of playing back the buffer as audio. To test this, I used a Fiio X1 audio player with the output set at 50% into Channel 1 and Channel 2 at 1X attenuation.
Running at 50ms/div timebase resulted in about 60kHz sampling rate, with playback being slightly choppy. Shorter timebases resulted in more repetition of snippets due to the way the module replays the buffer as it is waiting for the next buffer. But backing off to 100ms/div results in a shift to screen mode and continuous playback, although the sampling rate of 30kHz is hardly transparent audio quality.
Using the Rec. menu allows us to record directly to a .wav file which produces better results. I selected a sampling rate of 50kHz (above 48kHz) for five minutes and the unit recorded a .wav file with no gaps. Playing it back, the audio sounded rather good without intrusive background noise – compared to other oscilloscopes, it seems that the noise level on this one is rather good, which is not surprising given the later tests which indicate about 9-bits noise-free.
The audio was played on loop so that the recorded .wav file had at least one repetition of the song. Having such capabilities integrated into the software is not something I’ve seen before, but it can be extremely helpful for educational situations where students may be building audio-frequency oscillators, modulators, filters and the like.
Host-less Data Logging – Mains Monitoring
In order to test the Linux Mode capabilities, I decided to monitor the mains power waveform continuously using the ADP3450. As it turns out, coupling a Micsig DP10013 High Voltage Differential Probe was particularly convenient because of the ADP3450’s rear USB ports which powered the active probe.
I took their example Python recording code, modified it to record at 192kHz sample rate and for one-hour periods at a time –
from ctypes import * from dwfconstants import * import math import time import sys import wave import datetime import os import array 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(192000) nSamples = 192000*3600 cAvailable = c_int() cLost = c_int() cCorrupted = c_int() #open device print("Opening first device") dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf)) if hdwf.value == hdwfNone.value: print("failed to open device") else : #set up acquisition dwf.FDwfAnalogInChannelEnableSet(hdwf, c_int(0), c_bool(True)) dwf.FDwfAnalogInChannelRangeSet(hdwf, c_int(0), c_double(1.0)) dwf.FDwfAnalogInChannelOffsetSet(hdwf, c_int(0), c_double(0)) dwf.FDwfAnalogInAcquisitionModeSet(hdwf, acqmodeRecord) dwf.FDwfAnalogInFrequencySet(hdwf, hzAcq) dwf.FDwfAnalogInRecordLengthSet(hdwf, c_double(-1)) # -1 infinite record length print("Starting oscilloscope") dwf.FDwfAnalogInConfigure(hdwf, c_int(0), c_int(1)) while (True) : cSamples = 0 #open WAV file starttime = datetime.datetime.now(); startfilename = "AD2_" + "{:04d}".format(starttime.year) + "{:02d}".format(starttime.month) + "{:02d}".format(starttime.day) + "_" + "{:02d}".format(starttime.hour) + "{:02d}".format(starttime.minute) + "{:02d}".format(starttime.second) + ".wav"; print("Writing WAV file '" + startfilename + "'"); waveWrite = wave.open(startfilename, "wb"); waveWrite.setnchannels(1); # 1 channels waveWrite.setsampwidth(2); # 16 bit / sample waveWrite.setframerate(hzAcq.value); waveWrite.setcomptype("NONE","No compression"); 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 > 0 : print("Samples Lost - "+str(cLost.value)) if cAvailable.value==0 : continue if cSamples+cAvailable.value > nSamples : cAvailable = c_int(nSamples-cSamples) rgSamples = (c_int16*cAvailable.value)() dwf.FDwfAnalogInStatusData16(hdwf, c_int(0), rgSamples, c_int(0), cAvailable) # get channel 1 data chunk cSamples += cAvailable.value waveWrite.writeframes(rgSamples) endtime = datetime.datetime.now(); print(" done") waveWrite.close(); endfilename = "AD2_" + "{:04d}".format(starttime.year) + "{:02d}".format(starttime.month) + "{:02d}".format(starttime.day) + "_" + "{:02d}".format(starttime.hour) + "{:02d}".format(starttime.minute) + "{:02d}".format(starttime.second) + "-" + "{:02d}".format(endtime.hour) + "{:02d}".format(endtime.minute) + "{:02d}".format(endtime.second) + ".wav"; print("Renaming file from '" + startfilename + "' to '" + endfilename + "'"); os.rename(startfilename, endfilename); dwf.FDwfDeviceCloseAll()
The recordings were made to an attached USB SSD and then the files were copied over the network for further analysis on a desktop computer. A small Python program took the audio file and then analysed it on a cycle by cycle basis, computing the RMS voltage, period, peak-to-peak value, crest factor and maximum deviation from an ideal sine wave of the same peak-to-peak value to identify anomalies. As this analysis requires a bit of computing power, I performed this step on a desktop computer.
import wave import struct import math import numpy as np import time import sys nomfreq = 50 #Hz fullscale = 500 #V print("Gough Mains Recording Analysis Program v1") print("-----------------------------------------") if len(sys.argv) < 2 : fni = input("Enter Filename to Analyse: ") else : fni = sys.argv[1:] for fn in fni : stime = time.time() try: wf = wave.open(fn,"rb") except: print("Could not open file. Wrong filename or type?") exit() nc = wf.getnchannels() sw = wf.getsampwidth() fr = wf.getframerate() nf = wf.getnframes() fp = 0 if nc != 1 or sw != 2 : print("Incorrect File Format! Expecting Mono/16-bit/Uncompressed WAV file") exit() print("Analysing File " + str(fn) +" ...") f = open(fn.split(".")[0]+".csv","a") global wfmbuf bufferlen = int(1.1*1/nomfreq*fr) if bufferlen%2 : bufferlen = bufferlen+1 wfmbuf = wf.readframes(bufferlen) # Prime Buffer # Look for a positive transition cval = struct.unpack("<h",wfmbuf[0:2])[0] lval = cval while (True) : if lval < 0 and cval >=0 : break; else : wfmbuf = wfmbuf[2:] wfmbuf = wfmbuf + wf.readframes(1) fp = fp +1 lval = cval cval = struct.unpack("<h",wfmbuf[0:2])[0] while len(wfmbuf) == bufferlen*2 : # While there are still samples left # Find End of Cycle eptr = int(bufferlen*2*0.8) if eptr%2 : eptr = eptr+1 cval = struct.unpack("<h",wfmbuf[eptr:eptr+2])[0] lval = cval while (True) : if (lval < 0 and cval >=0) or eptr > bufferlen*2: break; else : eptr = eptr+2 lval = cval cval = struct.unpack("<h",wfmbuf[eptr:eptr+2])[0] # Compute iFreq - Naive time between ZCs ifreq = 1/(eptr/2/fr) # Convert to Real Values cyclevalues = [struct.unpack("<h",wfmbuf[0:2])[0]] for z in range(2,eptr,2) : cyclevalues.append(struct.unpack("<h",wfmbuf[z:z+2])[0]) for z in range(0,len(cyclevalues)) : cyclevalues[z] = cyclevalues[z] / 32768.0 * fullscale # Compute RMS irms = np.sqrt(np.mean(np.asarray(cyclevalues)**2)) # Compute Pk-Pk ipk = np.ptp(cyclevalues) # Compute Crest Factor icf = ipk/2/irms # Compute MaxDev iwave = [0] for z in range (1,len(cyclevalues)) : iwave.append(ipk/2*math.sin(z/len(cyclevalues)*2*math.pi)) imaxdev = 0 for z in range (0,len(cyclevalues)) : if abs(iwave[z]-cyclevalues[z]) > z : imaxdev = abs(iwave[z]-cyclevalues[z]) # Write Output f.write(str(fp)+","+str(ifreq)+","+str(irms)+","+str(icf)+","+str(imaxdev)+"\n") # Throw Away Cycle, Read New Data wfmbuf = wfmbuf[eptr:] wfmbuf = wfmbuf + wf.readframes(int(eptr/2)) fp = fp + int(eptr/2) print(str(int(fp/nf*100.0))+"% complete\r",end="") print ("\nfinished.") wf.close() f.flush() f.close() etime = time.time() print ("Time taken = "+str(etime-stime)+"s")
This experiment was left to run over several days and the ADP3450 successfully captured almost 100GB of data. Unfortunately, despite there being storms in this period, the mains power to the house was quite rock solid. A few anomalies were identified by the captures, but nothing significant – these included …
… ripple injection signalling which was over the specified strength, a mains sag event …
… and step changes in voltage likely from an automatic tap changer regulating the voltage on the distribution network. I suppose perhaps I am lucky where I am that the power quality is good, but the marketing “fear” of surges and spikes perhaps is a little overblown.
Digital Bus Sniffing
I originally planned to do some more digital bus work, however, time constraints got in the way. I previously demonstrated UART decoding, but I also had the chance to test I2C decoding with a project of mine using an infrared time-of-flight sensor.
The above is the decoded data from a poll for the next ranging value, followed by the raw ranging data, then arming the sensor for another ranging attempt. I won’t go into details about it, but I did notice that running the I2C sniffing for longer periods would result in lost sample warnings.
I found the use of standard header pins on the ADP3450 to be a nice design choice as it makes using different wires easier. The standard break-out that ends in Dupont wire pins is not ideal for sniffing purposes as it is intended to connect to a pin. Instead, when sniffing, usually grabbers are the tool of choice but it was not part of the inclusions.
Static I/O
I decided that the StaticIO module also deserved some love, especially the seven-segment display capability, so I wired up an Arduino Uno running a seven-segment counter to the 5V-tolerant digital inputs.
The result is the above – it seems to faithfully display as a seven-segment counter would, however, I found the contrast a little low, making the digits less obvious than they would otherwise be. Unfortunately, as eight lines are needed for each seven segment digit, only two digits can be emulated with the 16 available digital channels, but this is still something new and potentially useful in educational contexts.
Conclusion
I had the chance to apply the ADP3450 across multiple different application scenarios to learn how it performs. I started by replicating classic undergraduate laboratory experiments including the generation of Lissajous patterns which went relatively well. By choosing to have the instruments as free-floating windows, it was possible to configure the Wavegen outputs while watching the Scope. Unfortunately, the limited buffer and re-arm time means that the frame rate of the moving patterns was somewhat limited.
Running a Bode plot on a simple cascade of R-C elements went as well as one may expect, although did show the limitations of the noise-floor and dynamic range of the input. Curve tracing also went well with ordinary diodes, LEDs and MOSFETs, although was limited to lower voltages and currents. I found it useful as it could resolve the small-signal behaviour of MOSFETs and be potentially used to measure the small-signal gain of BJTs although I did not test the latter. The connections were somewhat laborious so I designed a breakout PCB for this, but unfortunately, it is still in the delivery pipeline. The Impedance module also proved very useful in measuring the self-resonant frequency for inductors and capacitors, which is something that is especially useful when trying to match the right components for modern switching converters.
Some features which would be especially useful in educational contexts include the audio capability which allows you to “hear” the input and record the input to an audio file. This functionality worked although required some careful choice of timebase if unbroken audio is expected with on-screen plotting. Another is the StaticIO module’s capability of emulating a seven-segment display which worked well although had slightly low contrast.
Host-less capabilities in Linux Mode were tested with a mains power cycle-by-cycle monitoring project which unfortunately did not capture any significant waveform anomalies during the monitoring campaign, but did illustrate the power of the WaveForms SDK automation examples in needing minimal modification to record into hour-long .wav files which could then be analysed offline on a desktop computer later. The availability of USB ports on the rear also increased the convenience of using an active high-voltage differential probe as it provided a convenient source of power.
Unfortunately, due to time limitations, I didn’t have as much of a chance to test the digital bus decoding features all that thoroughly. I tested I2C decoding which appeared to work fine, although on longer captures would complain of missing samples. I feel that the limited bandwidth and buffer size of the hardware again becomes a limiting factor, and continuous break-free digital bus decoding is probably better handled by dedicated instruments.
---
This post is a part of the Digilent Analog Discovery Pro ADP3450 USB/Ethernet Mixed Signal Oscilloscope RoadTest Review.