Friday, March 20, 2015

Linux data acquisition with an Agilent Infiniium MSO9404A oscilloscope

I need to capture some analog data.  My coworker has a National Instruments USB data acquisition module, but as far as I can tell, the drivers are all proprietary and focused on using Labview.

So instead I used a fancy 9000-series Agilent Infiniium scope.  I plugged my laptop's ethernet cable into the scope, then used the Windows control panel on the scope to set its IPv4 address to  I set my laptop to, and found that I could ping the scope.  So far so good.

Back in the day, test equipment was controllable over a serial GPIB bus.  Today's fancy gear uses the same conventions over TCP/IP.  There's a confusing mess of acronyms like VXI-11 and VISA, and a bunch of half-baked libraries and crappy-looking system drivers that appear to be required to use them.  pyvisa looks nice, but wants a proprietary National Instruments driver distributed as a .iso(!).  Not my cup of tea.

Then I ran across this MATLAB example that's basically just chatting with the device over TCP/IP. That led me to Agilent's Programmer's Reference guide for the 9000-series scopes.

After fighting with the 1100-page manual for a few hours, I came up with the following settings that let me get samples at a specific rate like I would from an ADC.

Note that you can also interactively talk to the scope using nc or telnet to port 5025 while you're experimenting.

 # Using ethernet to talk to an Agilent Infiniium MSO9404A  
 # See also the "Infiniium 9000A Programmer's Reference"  
 import socket  
 import sys  
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 sock.connect(("", 5025))  
 def cmd(s):  
  sock.send(s + "\n")  
 def resp():  
  r = sock.recv(1048576)  
  while not r.endswith("\n"):  
   r += sock.recv(1048576)  
  return r  
 print "Querying scope"  
 print "Scope identifies as: ", resp(),  
 # This doesn't seem to affect the samples we receive  
 cmd(":timebase:range 1E-6")  
 cmd(":timebase:delay 0")  
 cmd(":channel1:range 5")  
 cmd(":channel1:offset 2.5")  
 cmd(":channel1:input dc")  
 cmd(":trigger:mode edge")  
 cmd(":trigger:slope positive")  
 cmd(":trigger:level chan1,2.5")  
 cmd(":system:header off")  
 cmd(":acquire:mode rtime") # Realtime mode; don't average multiple passes  
 cmd(":acquire:complete 100")  
 cmd(":waveform:source channel1")  
 cmd(":waveform:format ascii")  
 cmd(":acquire:count 1")  
 cmd(":acquire:srate:auto off")  
 cmd(":acquire:srate 4000")  
 cmd(":acquire:points:auto off")  
 cmd(":acquire:points 16")  
 # This was on by default, and took me a long time to figure out why  
 # I was getting ~16x the number of samples I requested.  
 cmd(":acquire:interpolate off")  
 cmd(":digitize channel1")
# This should block until the capture is done, since we used :digitize
 sample_string = resp().rstrip().rstrip(",")  
 ascii_samples = sample_string.split(",")  
 samples = []  
 for f in ascii_samples:  
   print "Couldn't convert:", f  
 print "Got ", len(samples), " samples. Values 1-10:", samples[:10]  

No comments: