Moderne digitale Speicheroszilloskope besitzen in den aller meisten Fälle eine USB-Schnittstelle (manche zusätzlich auch LAN) zur Fernsteuerung über VISA. Diese Schnittstelle lässt sich mittels Python und pyVisa leicht in eigener Software nutzen. Für mein Rigol DS1074Z habe ich mir dafür, eine auf pyVisa aufbauende, Python-Klasse geschrieben. Als Beispiel findet sich weiter unten auf dieser Seite eine kleine GUI.
Die Klasse ist noch nicht vollständig!
# -*- coding: utf-8 -*-
"""
Created on Wed Jul 30 19:41:17 2014
@author: Sven Riester
"""
import numpy
import matplotlib.pyplot as plot
import visa
import time
# Konstanten
AUTO = 'AUTO'
NORMAL = 'NORM'
AVERAGES = 'AVER'
PEAK = 'PEAK'
HIGH_RESOLUTION = 'HRES'
BW_20M = '20M'
OFF = 'OFF'
ON = 'ON'
AC = 'AC'
DC = 'DC'
GND = 'GND'
CHANNEL_1 = 'CHAN1'
CHANNEL_2 = 'CHAN2'
CHANNEL_3 = 'CHAN3'
CHANNEL_4 = 'CHAN4'
# FFT-Fensterfunktionen
RECTANGLE = 'RECT'
BLACKMAN = 'BLAC'
HANNING = 'HANN'
HAMMING = 'HAMM'
FLATTOP = 'FLAT'
TRIANGLE = 'TRI'
dB = 'DB'
Vrms = 'VRMS'
# Mathe-Funktionen
ADD = 'ADD'
SUBT = 'SUBT'
MULT = 'MULT'
DIV = 'DIV'
AND = 'AND'
OR = 'OR'
XOR = 'XOR'
NOT = 'NOT'
FFT = 'FFT'
INTG = 'INTG'
DIFF = 'DIFF'
SQRT = 'SQRT'
LOG = 'LOG'
LN = 'LN'
EXP = 'EXP'
ABS = 'ABS'
def get_device_list():
'''Gibt eine Liste mit den angeschlossenen Instrumenten zurück'''
device_list = visa.get_instruments_list()
return device_list
class DS1074Z(object):
def __init__(self, device_id):
self.scope = visa.instrument(device_id, timeout=100)
return
def autoscale(self):
self.scope.write(':AUT')
return
def run(self):
self.scope.write(':RUN')
return
def stop(self):
self.scope.write(':STOP')
return
def single(self):
'''Set the oscilloscope to the single trigger mode.'''
self.scope.write(':SING')
return
def force_trigger(self):
'''Generate a trigger signal forcefully.'''
self.scope.write(':TFOR')
return
def get_timescale(self):
'''Gibt die Einstellung der Zeitbasis zurück'''
return self.scope.ask_for_values(":TIM:SCAL?")[0]
def set_timescale(self, timescale):
'''Ãndert die Einstellung der Zeitbasis'''
self.scope.write(":TIM:SCAL "+str(timescale))
return
def get_averages_nr(self):
'''Query the number of averages under the average acquisition mode.'''
return self.scope.ask_for_values(':ACQ:AVER?')[0]
def set_averages_nr(self, value):
'''Set the number of averages under the average acquisition mode.
value = 2^n with n=1...10'''
self.scope.write(':ACQ:AVER '+str(value))
return
def get_mem_depth(self):
'''Query the number of averages under the average acquisition mode.'''
return self.scope.ask_for_values(':ACQ:MDEP?')[0]
def set_mem_depth(self, value):
'''Set the number of averages under the average acquisition mode.
value:
When a single channel is on: {AUTO|12000|120000|1200000|12000000|24000000}
When dual channels are on: {AUTO|6000|60000|600000|6000000|12000000}
When four channels are on: {AUTO|3000|30000|300000|3000000|6000000}
Wherein, 24000000, 12000000 and 6000000 are options.'''
self.scope.write(':ACQ:MDEP '+str(value))
return
def get_acquisition_mode(self):
'''Query the acquisition mode when the oscilloscope samples.'''
return self.scope.ask_for_values(':ACQ:TYPE?')[0]
def set_acquisition_mode(self, value):
'''Set the acquisition mode when the oscilloscope samples.'''
self.scope.write(':ACQ:TYPE '+value)
return
def get_samplerate(self):
'''Gibt die Abtastrate zurück'''
return self.scope.ask_for_values(':ACQ:SRAT?')[0]
def set_samplerate(self, value):
'''Set the acquisition mode when the oscilloscope samples.'''
self.scope.write(':ACQ:SRAT '+value)
return
# Seite 22 wurde vorerst ausgelassen
def get_bandwidth_limit(self, channel):
'''Gibt die Eingangsbandbreite zurück'''
return self.scope.ask_for_values(':'+str(channel)+':BWL?')[0]
def set_bandwidth_limit(self, channel, value):
'''Ãnder die Eingangsbandbreite: value = OFF oder 20M'''
self.scope.write(':'+str(channel)+':BWL '+value)[0]
return
def get_coupling(self, channel):
'''Gibt die Eingangskopplung zurück'''
return self.scope.ask_for_values(':'+str(channel)+':COUP?')[0]
def set_coupling(self, channel, value):
'''Ãndert die Eingangskopplung: value = AC, DC oder GND'''
self.scope.write(':'+str(channel)+':COUP '+value)
return
def get_channel_state(self, channel):
'''Gibt den Zustand des Kanal zurück: ON oder OFF'''
return self.scope.ask_for_values(':'+str(channel)+':DISP?')[0]
def set_channel_state(self, channel, value):
'''Ãndert den Zustand des Kanals'''
self.scope.write(':'+str(channel)+':DISP '+value)
return
def get_channel_inv(self, channel):
'''Gibt zurück ob das Signal invertiert wird oder nicht'''
return self.scope.ask_for_values(':'+str(channel)+':INV?')[0]
def set_channel_inv(self, channel, value):
'''Aktiviert/deaktiviert die Invertierung für den Kanal'''
self.scope.write(':'+str(channel)+':INV '+value)
return
# Seite 25
def get_offset(self, channel):
'''Kanal-Offset abfragen'''
return self.scope.ask_for_values(':'+str(channel)+':OFFS?')[0]
def set_offset(self, channel, value):
'''Ãndert den Offset des Kanals'''
self.scope.write(':'+str(channel)+':OFFS '+value)
return
def get_voltscale(self, channel):
'''Gibt die Einstellung der Vertikalablenkung zurück'''
return self.scope.ask_for_values(':'+str(channel)+':SCAL?')[0]
def set_voltscale(self, channel, voltscale):
'''Ãndert die Einstellung der Vertikalablenkung'''
self.scope.write(':'+str(channel)+':SCAL '+str(voltscale))
return
def get_probe_div(self, channel):
'''Teilerfaktor fuer Tastkopf abfragen'''
return self.scope.ask_for_values(':'+str(channel)+':PROB?')[0]
def set_probe_div(self, channel, value):
'''Teilerfaktor fuer Tastkopf aendern'''
self.scope.write(':'+str(channel)+':PROB '+value)
return
# Mathe
def set_Math(self, state):
'''Mathefunktion ein- bzw. ausschalten'''
self.scope.write(':MATH:DISP '+state)
return
def set_Math_Operator(self, Operator):
'''Anzuwendende Mathefunktion'''
self.scope.write(':MATH:OPER '+Operator)
return
def set_Math_Source1(self, Source):
'''Anzuwendende Mathefunktion'''
self.scope.write(':MATH:SOUR1 '+Source)
return
def set_Math_Source2(self, Source):
'''Anzuwendende Mathefunktion'''
self.scope.write(':MATH:SOUR2 '+Source)
return
def get_Math_Scale(self):
'''Gibt zurück ob das Signal invertiert wird oder nicht'''
return self.scope.ask_for_values(':MATH:SCAL?')[0]
def set_Math_Scale(self, value):
'''Aktiviert/deaktiviert die Invertierung für den Kanal'''
self.scope.write(':MATH:SCAL '+value)
return
def get_Math_Offset(self):
'''Gibt zurück ob das Signal invertiert wird oder nicht'''
return self.scope.ask_for_values(':MATH:OFFS?')[0]
def set_Math_Offset(self, value):
'''Aktiviert/deaktiviert die Invertierung für den Kanal'''
self.scope.write(':MATH:OFFS '+value)
return
def invert_Math(self, value):
'''Aktiviert/deaktiviert die Invertierung für den Kanal'''
self.scope.write(':MATH:INV '+value)
return
def Reset_Math(self):
'''Aktiviert/deaktiviert die Invertierung für den Kanal'''
self.scope.write(':MATH:RES')
return
def FFT_Window(self, window):
'''FFT-Fensterung'''
self.scope.write(':MATH:FFT:WIND '+window)
return
def FFT_Splitscreen(self, state = ON):
'''Schaltet für FFT auf Splitscreen'''
self.scope.write(':MATH:FFT:SPL '+state)
return
def FFT_Unit(self, Unit = dB):
'''FFT-Einheit: dB oder Vrms'''
self.scope.write(':MATH:FFT:UNIT '+Unit)
return
# Seite 55
# Zusätzliche Funktionen für mehr Komfort
def screenshot(self, path, name, suffix = 'png'):
'''Speichert den aktuellen Bildschirm als Grafik-Datei unter dem
angegebenen Pfad.'''
self.scope.write(":DISP:DATA?")
wave_data = self.scope.read_raw()[2+9:]
with open(path+name+'.'+suffix, "wb") as f:
f.write( wave_data )
f.close()
return
def get_data(self, channel):
'''Holt Daten von Oszilloskop ab und gibt sie in einem Dictonary
als Float-Array zusammen mit der Samplerate zurück.'''
self.scope.write(":WAV:FORM ASC")
self.scope.write(":WAV:SOUR CHAN1")
self.scope.write(":WAV:MODE RAW")
self.scope.write(":WAV:DATA?")
data = self.scope.read_raw()[1:].split(',')
for x in range(len(data)):
data[x]=float(data[x])
return {'Samplerate': self.get_samplerate(), 'Data': data}
def movingaverage(values,window):
weigths = numpy.repeat(1.0, window)/window
smas = numpy.convolve(values, weigths, 'valid')
return smas
Als kleines Beispiel wurde eine GUI mit wx geschrieben, die es ermöglicht via USB Screenshots vom DSO zu machen und auf dem PC zuspeichern.
# -*- coding: utf-8 -*-
"""
Created on Sat Nov 08 14:57:35 2014
Kleine GUI zum Abspeichern eines Screenshots vom DSO-Bildschirm ueber USB.
@author: Sven
"""
from DS1074Z import *
import wx
import shutil
class MainFrame(wx.Frame):
'''Bedienoberfläche'''
def __init__(self):
'''Konstruktor: GUI einrichten und nach Oszis suchen'''
wx.Frame.__init__(self, None, -1, u'Rigol DS1074Z Screenshot',
style=wx.MINIMIZE_BOX | wx.SYSTEM_MENU |
wx.CAPTION | wx.CLOSE_BOX) # Fenser erzeugen
self.panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition,
wx.DefaultSize, wx.TAB_TRAVERSAL )
x_size = 950
y_size = 540
self.SetSize((x_size,y_size))
self.DSO = 0
self.menubar = wx.MenuBar()
self.DeviceMenu = wx.Menu()
dev_list = get_device_list()
for i in dev_list:
item = self.DeviceMenu.AppendItem( wx.MenuItem(self.DeviceMenu, -1, i,
kind=wx.ITEM_RADIO) )
self.Bind(wx.EVT_MENU, self.connect, item)
self.menubar.Append(self.DeviceMenu, 'Devices')
self.SetMenuBar(self.menubar)
png = wx.Image('temp.png', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.image = wx.StaticBitmap(self.panel, -1, png, (10, 5), (png.GetWidth(), png.GetHeight()))
self.Save = wx.Button(self.panel, -1, 'Save as', pos=(840, 450))
self.Shot = wx.Button(self.panel, -1, 'Screenshot', pos=(840, 410))
self.Bind(wx.EVT_BUTTON, self.save, self.Save)
self.Bind(wx.EVT_BUTTON, self.shot, self.Shot)
def connect(self, event):
'''Stellt bei Auswahl eines Menu-Items die Verbindung her'''
item = self.GetMenuBar().FindItemById(event.GetId())
text = item.GetText()
self.DSO = DS1074Z(text)
event.Skip()
def shot(self, event):
'''Macht einen Screenshot'''
self.DSO.screenshot('','temp')
png = wx.Image('temp.png', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.image = wx.StaticBitmap(self.panel, -1, png, (10, 5), (png.GetWidth(), png.GetHeight()))
self.panel.Update()
self.panel.Refresh()
event.Skip()
def save(self, event):
'''Speichert Screenshot'''
saveFileDialog = wx.FileDialog(self, "Save As", "", "",
"PNG (*.png)|*.png",
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
if saveFileDialog.ShowModal()==wx.ID_OK:
shutil.copy2('temp.png', saveFileDialog.GetPath())
saveFileDialog.Destroy()
event.Skip()
#########################
# Fenster erzeugen #
#########################
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = MainFrame()
app.frame.Show()
app.MainLoop()
Das Python-Skript zum Speichern von Screenshots wurde mittlerweile erweitert, sodass damit nun auch die wichtigsten Funktionen ferngesteuert werden können.