├── .gitignore ├── LICENSE ├── README ├── capture_channel_1.py ├── instrument.py └── realtime_chart.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Matt Mets 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | PyUSBtmc is a simple library for interacting with test equipment (primarily Rigol oscilloscopes). 2 | 3 | It was conceived of by Matt Mets in 2010, and is distributed under the MIT License. See LICENSE for a copy of this license. 4 | -------------------------------------------------------------------------------- /capture_channel_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import numpy 3 | import matplotlib.pyplot as plot 4 | 5 | import instrument 6 | 7 | """ Example program to plot the Y-T data from Channel 1""" 8 | 9 | # Initialize our scope 10 | test = instrument.RigolScope("/dev/usbtmc0") 11 | 12 | # Stop data acquisition 13 | test.write(":STOP") 14 | 15 | # Grab the data from channel 1 16 | test.write(":WAV:POIN:MODE NOR") 17 | 18 | test.write(":WAV:DATA? CHAN1") 19 | rawdata = test.read(9000) 20 | data = numpy.frombuffer(rawdata, 'B') 21 | 22 | # Get the voltage scale 23 | test.write(":CHAN1:SCAL?") 24 | voltscale = float(test.read(20)) 25 | 26 | # And the voltage offset 27 | test.write(":CHAN1:OFFS?") 28 | voltoffset = float(test.read(20)) 29 | 30 | # Walk through the data, and map it to actual voltages 31 | # First invert the data (ya rly) 32 | data = data * -1 + 255 33 | 34 | # Now, we know from experimentation that the scope display range is actually 35 | # 30-229. So shift by 130 - the voltage offset in counts, then scale to 36 | # get the actual voltage. 37 | data = (data - 130.0 - voltoffset/voltscale*25) / 25 * voltscale 38 | 39 | # Get the timescale 40 | test.write(":TIM:SCAL?") 41 | timescale = float(test.read(20)) 42 | 43 | # Get the timescale offset 44 | test.write(":TIM:OFFS?") 45 | timeoffset = float(test.read(20)) 46 | 47 | # Now, generate a time axis. The scope display range is 0-600, with 300 being 48 | # time zero. 49 | time = numpy.arange(-300.0/50*timescale, 300.0/50*timescale, timescale/50.0) 50 | 51 | # If we generated too many points due to overflow, crop the length of time. 52 | if (time.size > data.size): 53 | time = time[0:600:1] 54 | 55 | # See if we should use a different time axis 56 | if (time[599] < 1e-3): 57 | time = time * 1e6 58 | tUnit = "uS" 59 | elif (time[599] < 1): 60 | time = time * 1e3 61 | tUnit = "mS" 62 | else: 63 | tUnit = "S" 64 | 65 | # Start data acquisition again, and put the scope back in local mode 66 | test.write(":RUN") 67 | test.write(":KEY:FORC") 68 | 69 | # Plot the data 70 | plot.plot(time, data) 71 | plot.title("Oscilloscope Channel 1") 72 | plot.ylabel("Voltage (V)") 73 | plot.xlabel("Time (" + tUnit + ")") 74 | plot.xlim(time[0], time[599]) 75 | plot.show() 76 | -------------------------------------------------------------------------------- /instrument.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class usbtmc: 4 | """Simple implementation of a USBTMC device driver, in the style of visa.h""" 5 | 6 | def __init__(self, device): 7 | self.device = device 8 | self.FILE = os.open(device, os.O_RDWR) 9 | 10 | # TODO: Test that the file opened 11 | 12 | def write(self, command): 13 | os.write(self.FILE, command); 14 | 15 | def read(self, length = 4000): 16 | return os.read(self.FILE, length) 17 | 18 | def getName(self): 19 | self.write("*IDN?") 20 | return self.read(300) 21 | 22 | def sendReset(self): 23 | self.write("*RST") 24 | 25 | 26 | class RigolScope: 27 | """Class to control a Rigol DS1000 series oscilloscope""" 28 | def __init__(self, device): 29 | self.meas = usbtmc(device) 30 | 31 | self.name = self.meas.getName() 32 | 33 | print self.name 34 | 35 | def write(self, command): 36 | """Send an arbitrary command directly to the scope""" 37 | self.meas.write(command) 38 | 39 | def read(self, command): 40 | """Read an arbitrary amount of data directly from the scope""" 41 | return self.meas.read(command) 42 | 43 | def reset(self): 44 | """Reset the instrument""" 45 | self.meas.sendReset() 46 | 47 | -------------------------------------------------------------------------------- /realtime_chart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import numpy 3 | import matplotlib.pyplot as plot 4 | 5 | import instrument 6 | 7 | 8 | """ Example program to plot the Y-T data from Channel 1""" 9 | 10 | def getChannelData(channel): 11 | if (channel==1): 12 | channelName = "CHAN1" 13 | elif (channel==2): 14 | channelName = "CHAN2" 15 | else: 16 | print "Invalid channel!" 17 | return 18 | 19 | # Grab the data from channel 1 20 | test.write(":WAV:POIN:MODE NOR") 21 | 22 | test.write(":WAV:DATA? " + channelName) 23 | rawdata = test.read(9000) 24 | data = numpy.frombuffer(rawdata, 'B') 25 | 26 | # Get the voltage scale 27 | test.write(":" + channelName + ":SCAL?") 28 | voltscale = float(test.read(20)) 29 | 30 | # And the voltage offset 31 | test.write(":" + channelName + ":OFFS?") 32 | voltoffset = float(test.read(20)) 33 | 34 | # Walk through the data, and map it to actual voltages 35 | # First invert the data (ya rly) 36 | data = data * -1 + 255 37 | 38 | # Now, we know from experimentation that the scope display range is actually 39 | # 30-229. So shift by 130 - the voltage offset in counts, then scale to 40 | # get the actual voltage. 41 | data = (data - 130.0 - voltoffset/voltscale*25) / 25 * voltscale 42 | 43 | # Get the timescale 44 | test.write(":TIM:SCAL?") 45 | timescale = float(test.read(20)) 46 | 47 | # Get the timescale offset 48 | test.write(":TIM:OFFS?") 49 | timeoffset = float(test.read(20)) 50 | 51 | # Now, generate a time axis. The scope display range is 0-600, with 300 being 52 | # time zero. 53 | time = numpy.arange(-300.0/50*timescale, 300.0/50*timescale, timescale/50.0) 54 | 55 | # If we generated too many points due to overflow, crop the length of time. 56 | if (time.size > data.size): 57 | time = time[0:600:1] 58 | 59 | # See if we should use a different time axis 60 | # if (time[599] < 1e-3): 61 | # time = time * 1e6 62 | # tUnit = "uS" 63 | # elif (time[599] < 1): 64 | # time = time * 1e3 65 | # tUnit = "mS" 66 | # else: 67 | # tUnit = "S" 68 | return [time, data] 69 | 70 | 71 | # Initialize our scope 72 | test = instrument.RigolScope("/dev/usbtmc0") 73 | 74 | plot.ion() 75 | 76 | while 1: 77 | t1, d1 = getChannelData(1) 78 | t2, d2 = getChannelData(2) 79 | 80 | # Start data acquisition again, and put the scope back in local mode 81 | test.write(":KEY:FORC") 82 | 83 | # Plot the data 84 | plot.clf() 85 | plot.plot(t1, d1) 86 | plot.plot(t2, d2) 87 | plot.title("Oscilloscope data") 88 | plot.ylabel("Voltage (V)") 89 | plot.xlabel("Time (s)") 90 | plot.xlim(t1[0], t1[599]) 91 | plot.draw() 92 | --------------------------------------------------------------------------------