Rigol-DSO & PyVisa

DS1074Z und PyVisa

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.

Klasse für Rigol DS1074Z

Download Code

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

Screenshot-GUI

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()

Steuerungs-GUI

Das Python-Skript zum Speichern von Screenshots wurde mittlerweile erweitert, sodass damit nun auch die wichtigsten Funktionen ferngesteuert werden können.

Back