After many frustrated nights trying to debug electronics projects blindly (the analog scope is wayyyy too much work to pull off the shelf and use), I decided it was time to spring for a digital storage oscilloscope. Since I had read many good things about them, I chose the Rigol DS1052E, which is a two channel 50 MHz scope that can be easily modded to work at 100 MHz. The scope is way smaller and lighter than I expected, and has a nice set of buttons that give it the feel of a quality instrument. It works perfectly well as a standalone device, however since it has a USB slave port that implements the usbtmc interface, it turned out to be pretty easy to control using Linux. After a night of coding (most of which involved re-learning Python), I was able to grab data from the device and plot it:
This is all well and good, and should be particularly useful for my engineering pursuits, however it has me thinking about the artistic possibilities of a high-speed data acquisition device. I’m not sure exactly what I will do with it yet, but I’m thinking scope+processing or scope+pd could lead to some interesting possibilities. Any suggestions?
Source code for the current, boring use after the break.
First, a rudimentary library for accessing the usbtmc driver. After I become more familiar with what I want from the device, I may turn this into an actual interface- for now, it just exposes the devices programming interface. Name it instrument.py:
import os class usbtmc: """Simple implementation of a USBTMC device driver, in the style of visa.h""" def __init__(self, device): self.device = device self.FILE = os.open(device, os.O_RDWR) # TODO: Test that the file opened def write(self, command): os.write(self.FILE, command); def read(self, length = 4000): return os.read(self.FILE, length) def getName(self): self.write("*IDN?") return self.read(300) def sendReset(self): self.write("*RST") class RigolScope: """Class to control a Rigol DS1000 series oscilloscope""" def __init__(self, device): self.meas = usbtmc(device) self.name = self.meas.getName() print self.name def write(self, command): """Send an arbitrary command directly to the scope""" self.meas.write(command) def read(self, command): """Read an arbitrary amount of data directly from the scope""" return self.meas.read(command) def reset(self): """Reset the instrument""" self.meas.sendReset()
And here is an example program that uses the library to grab the waveform from Channel 1 and graph it:
#!/usr/bin/python import numpy import matplotlib.pyplot as plot import instrument """ Example program to plot the Y-T data from Channel 1""" # Initialize our scope test = instrument.RigolScope("/dev/usbtmc0") # Stop data acquisition test.write(":STOP") # Grab the data from channel 1 test.write(":WAV:POIN:MODE NOR") test.write(":WAV:DATA? CHAN1") rawdata = test.read(9000) data = numpy.frombuffer(rawdata, 'B') # Get the voltage scale test.write(":CHAN1:SCAL?") voltscale = float(test.read(20)) # And the voltage offset test.write(":CHAN1:OFFS?") voltoffset = float(test.read(20)) # Walk through the data, and map it to actual voltages # First invert the data (ya rly) data = data * -1 + 255 # Now, we know from experimentation that the scope display range is actually # 30-229. So shift by 130 - the voltage offset in counts, then scale to # get the actual voltage. data = (data - 130.0 - voltoffset/voltscale*25) / 25 * voltscale # Get the timescale test.write(":TIM:SCAL?") timescale = float(test.read(20)) # Get the timescale offset test.write(":TIM:OFFS?") timeoffset = float(test.read(20)) # Now, generate a time axis. The scope display range is 0-600, with 300 being # time zero. time = numpy.arange(-300.0/50*timescale, 300.0/50*timescale, timescale/50.0) # If we generated too many points due to overflow, crop the length of time. if (time.size > data.size): time = time[0:600:1] # See if we should use a different time axis if (time < 1e-3): time = time * 1e6 tUnit = "uS" elif (time < 1): time = time * 1e3 tUnit = "mS" else: tUnit = "S" # Start data acquisition again, and put the scope back in local mode test.write(":RUN") test.write(":KEY:FORC") # Plot the data plot.plot(time, data) plot.title("Oscilloscope Channel 1") plot.ylabel("Voltage (V)") plot.xlabel("Time (" + tUnit + ")") plot.xlim(time, time) plot.show()