From f1445e48e61aadb03badb928d3ea338dcdf435ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20L=C3=A5ngbacka?= Date: Fri, 3 Nov 2017 21:18:25 +0100 Subject: [PATCH 01/11] Separate drivers into base driver and model specific drivers. --- README.md | 1 + ivi/__init__.py | 1 + ivi/interface/pyvisa.py | 3 + ivi/keithley/test_script.py | 34 ++ ivi/rohdeschwarz/__init__.py | 33 ++ ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 603 ++++++++++++++++++++++ ivi/rohdeschwarz/rohdeschwarzRTB2002.py | 43 ++ ivi/rohdeschwarz/rohdeschwarzRTB2004.py | 46 ++ setup.py | 1 + 9 files changed, 765 insertions(+) create mode 100644 ivi/keithley/test_script.py create mode 100644 ivi/rohdeschwarz/__init__.py create mode 100644 ivi/rohdeschwarz/rohdeschwarzBaseScope.py create mode 100644 ivi/rohdeschwarz/rohdeschwarzRTB2002.py create mode 100644 ivi/rohdeschwarz/rohdeschwarzRTB2004.py diff --git a/README.md b/README.md index 36c2180e..abf56de4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Instrument standard from the [IVI foundation](http://www.ivifoundation.org/). * Agilent InfiniiVision 7000A/B series * Agilent Infiniium 90000A/90000X series * LeCroy WaveRunner Xi-A series + * Rohde&Schwarz RTB2000 series * Tektronix MDO3000 series * Tektronix DPO4000 series * Tektronix MDO4000 series diff --git a/ivi/__init__.py b/ivi/__init__.py index 82efcc87..79ae0ac3 100644 --- a/ivi/__init__.py +++ b/ivi/__init__.py @@ -52,6 +52,7 @@ "jdsu", "lecroy", "rigol", + "rohdeschwarz", "tektronix", "testequity"] diff --git a/ivi/interface/pyvisa.py b/ivi/interface/pyvisa.py index 29f0712e..f6ffc3c9 100644 --- a/ivi/interface/pyvisa.py +++ b/ivi/interface/pyvisa.py @@ -37,6 +37,9 @@ except AttributeError: # Old style PyVISA visa_instrument_opener = visa.instrument + except OSError: + visa_rm = visa.ResourceManager('@py') + visa_instrument_opener = visa_rm.open_resource except ImportError: # PyVISA not installed, pass it up raise ImportError diff --git a/ivi/keithley/test_script.py b/ivi/keithley/test_script.py new file mode 100644 index 00000000..c0b4ca7c --- /dev/null +++ b/ivi/keithley/test_script.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Sep 27 17:37:04 2017 + +@author: jonas +""" + +from ivi.keithley import keithley2280S +import time +from matplotlib import pyplot + +PSU = keithley2280S('TCPIP::172.20.0.49::INSTR', prefer_pyvisa=True, query_instr_status=True) +PSU.outputs[0].voltage_level=2.0 +PSU.outputs[0].enabled = True +PSU.outputs[0].configure_measurement( + type='current', + sample_count=100, + measurement_range=0.1, + NPLC=0.02, + adc_autozero=False, + auto_clear_buffer=True) + +PSU.outputs[0].trigger_current_state = True +PSU.outputs[0].trigger_current_level = 1e-3 +PSU.outputs[0].trace_points = 1000 +PSU.outputs[0].trigger_sample_count = 20 +PSU.outputs[0].trigger_count = 5 + +PSU.outputs[0].enabled = True +PSU.trigger.initiate() + +V, t = PSU.outputs[0].fetch_measurement(measurement_type=('current', 'relative_time_seconds')) +pyplot.plot(V) \ No newline at end of file diff --git a/ivi/rohdeschwarz/__init__.py b/ivi/rohdeschwarz/__init__.py new file mode 100644 index 00000000..ab881571 --- /dev/null +++ b/ivi/rohdeschwarz/__init__.py @@ -0,0 +1,33 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Jonas Långbacka, Acconeer Ab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +# Oscilloscopes +# RTB2000 series +from .rohdeschwarzRTB2002 import rohdeschwarzRTB2002 +from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 + +# RTO2000 series +#from .rohdeschwarzRTO2044 import rohdeschwarzRTO2044 diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py new file mode 100644 index 00000000..c4f4161a --- /dev/null +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -0,0 +1,603 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Jonas Långbacka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .. import ivi +from .. import scope +from .. import scpi +from .. import extra + +AcquisitionTypeMapping = { + 'normal': ['samp', 'off'], + 'peak_detect': ['pdet', 'off'], + 'high_resolution': ['hres', 'off'], + 'average': ['samp','aver'], + 'envelope': ['samp', 'env'], + 'envelope_peak_detect': ['pdet', 'env']} +BandwidthMapping = { + 6e9: 'full', + 800e6: 'b800', + 200e6: 'b200', + 20e6: 'b20'} +VerticalCoupling = set(['ac', 'dc']) +TriggerTypeMapping = { + 'edge': 'edge', + 'width': 'glit', + 'glitch': 'glit', + 'tv': 'tv', + #'immediate': '', + 'ac_line': 'edge', + 'pattern': 'patt', + 'can': 'can', + 'duration': 'dur', + 'i2s': 'i2s', + 'iic': 'iic', + 'eburst': 'ebur', + 'lin': 'lin', + 'm1553': 'm1553', + 'sequence': 'seq', + 'spi': 'spi', + 'uart': 'uart', + 'usb': 'usb', + 'flexray': 'flex'} +TriggerCouplingMapping = { + 'ac': ('ac', 0, 0), + 'dc': ('dc', 0, 0), + 'hf_reject': ('dc', 0, 1), + 'lf_reject': ('lfr', 0, 0), + 'noise_reject': ('dc', 1, 0), + 'hf_reject_ac': ('ac', 0, 1), + 'noise_reject_ac': ('ac', 1, 0), + 'hf_noise_reject': ('dc', 1, 1), + 'hf_noise_reject_ac': ('ac', 1, 1), + 'lf_noise_reject': ('lfr', 1, 0)} +TVTriggerEventMapping = {'field1': 'fie1', + 'field2': 'fie2', + 'any_field': 'afi', + 'any_line': 'alin', + 'line_number': 'lfi1', + 'vertical': 'vert', + 'line_field1': 'lfi1', + 'line_field2': 'lfi2', + 'line': 'line', + 'line_alternate': 'lalt', + 'lvertical': 'lver'} +TVTriggerFormatMapping = {'generic': 'gen', + 'ntsc': 'ntsc', + 'pal': 'pal', + 'palm': 'palm', + 'secam': 'sec', + 'p480l60hz': 'p480', + 'p480': 'p480', + 'p720l60hz': 'p720', + 'p720': 'p720', + 'p1080l24hz': 'p1080', + 'p1080': 'p1080', + 'p1080l25hz': 'p1080l25hz', + 'p1080l50hz': 'p1080l50hz', + 'p1080l60hz': 'p1080l60hz', + 'i1080l50hz': 'i1080l50hz', + 'i1080': 'i1080l50hz', + 'i1080l60hz': 'i1080l60hz'} +PolarityMapping = {'positive': 'pos', + 'negative': 'neg'} +GlitchConditionMapping = {'less_than': 'less', + 'greater_than': 'gre'} +WidthConditionMapping = {'within': 'rang'} +SampleModeMapping = {'real_time': 'rtim', + 'equivalent_time': 'etim', + 'segmented': 'segm'} +SlopeMapping = { + 'positive': 'pos', + 'negative': 'neg', + 'either': 'eith', + 'alternating': 'alt'} +MeasurementFunctionMapping = { + 'rise_time': 'risetime', + 'fall_time': 'falltime', + 'frequency': 'frequency', + 'period': 'period', + 'voltage_rms': 'vrms display', + 'voltage_peak_to_peak': 'vpp', + 'voltage_max': 'vmax', + 'voltage_min': 'vmin', + 'voltage_high': 'vtop', + 'voltage_low': 'vbase', + 'voltage_average': 'vaverage display', + 'width_negative': 'nwidth', + 'width_positive': 'pwidth', + 'duty_cycle_positive': 'dutycycle', + 'amplitude': 'vamplitude', + 'voltage_cycle_rms': 'vrms cycle', + 'voltage_cycle_average': 'vaverage cycle', + 'overshoot': 'overshoot', + 'preshoot': 'preshoot', + 'ratio': 'vratio', + 'phase': 'phase', + 'delay': 'delay'} +MeasurementFunctionMappingDigital = { + 'rise_time': 'risetime', + 'fall_time': 'falltime', + 'frequency': 'frequency', + 'period': 'period', + 'width_negative': 'nwidth', + 'width_positive': 'pwidth', + 'duty_cycle_positive': 'dutycycle'} +ScreenshotImageFormatMapping = { + 'bmp': 'bmp', + 'bmp24': 'bmp', + 'bmp8': 'bmp8bit', + 'png': 'png', + 'png24': 'png'} +TimebaseModeMapping = { + 'main': 'main', + 'window': 'wind', + 'xy': 'xy', + 'roll': 'roll'} +TimebaseReferenceMapping = { + 'left': 8.33, + 'center': 50.0, + 'right': 91.67} +TriggerModifierMapping = {'none': 'normal', 'auto': 'auto'} + +class rohdeschwarzBaseScope(scope.Base, + ivi.Driver): +# scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi.common.Reset, +# scpi.common.SelfTest, scpi.common.Memory, +# scope.Base, scope.TVTrigger, +# scope.GlitchTrigger, scope.WidthTrigger, scope.AcLineTrigger, +# scope.WaveformMeasurement, scope.MinMaxWaveform, +# scope.ContinuousAcquisition, scope.AverageAcquisition, +# scope.SampleMode, scope.TriggerModifier, scope.AutoSetup, +# extra.common.SystemSetup, extra.common.Screenshot, +# ivi.Driver): + "Rohde&Schwarz generic IVI oscilloscope driver" + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', '') + self._analog_channel_name = list() + self._analog_channel_count = 4 + self._digital_channel_name = list() + self._digital_channel_count = 16 + self._channel_count = self._analog_channel_count + self._digital_channel_count + self._channel_label = list() + self._channel_probe_skew = list() + self._channel_scale = list() + self._channel_trigger_level = list() + self._channel_invert = list() + self._channel_probe_id = list() + self._channel_bw_limit = list() + + super(rohdeschwarzBaseScope, self).__init__(*args, **kwargs) + + self._self_test_delay = 40 + self._memory_size = 10 + + self._analog_channel_name = list() + self._analog_channel_count = 4 + self._digital_channel_name = list() + self._digital_channel_count = 16 + self._channel_count = self._analog_channel_count + self._digital_channel_count + self._bandwidth = 1e9 + + self._horizontal_divisions = 12 + self._vertical_divisions = 10 + + self._acquisition_segmented_count = 2 + self._acquisition_segmented_index = 1 + self._acquisition_number_of_points_minimum = 10e3 + self._acquisition_record_length = 20e3 + self._acquisition_record_length_automatic = True + + self._timebase_mode = 'main' + self._timebase_scale = 100e-6 + self._timebase_position = 0.0 + self._timebase_reference = 'center' + self._timebase_range = 1.2e-3 + self._timebase_window_position = 0.0 + self._timebase_window_range = 600e-6 + self._timebase_window_scale = 50e-6 + self._display_screenshot_image_format_mapping = ScreenshotImageFormatMapping + self._display_vectors = True + self._display_labels = True + + self._identity_description = "Rohde&Schwarz generic IVI oscilloscope driver" + self._identity_identifier = "" + self._identity_revision = "" + self._identity_vendor = "" + self._identity_instrument_manufacturer = "Rohde&Schwarz" + self._identity_instrument_model = "" + self._identity_instrument_firmware_revision = "" + self._identity_specification_major_version = 4 + self._identity_specification_minor_version = 1 + self._identity_supported_instrument_models = ['RTB2002', 'RTB2004', + 'RTO2002', 'RTO2004', 'RTO2012', 'RTO2014', 'RTO2022', 'RTO2024', 'RTO2032', 'RTO2034', + 'RTO2044', 'RTO2064'] + + self._add_property('timebase.scale', + self._get_timebase_scale, + self._set_timebase_scale, + None, + ivi.Doc(""" + Sets the horizontal scale or units per division for all channels and math waveforms. + """)) + + self._add_property('timebase.position', + self._get_timebase_position, + self._set_timebase_position, + None, + ivi.Doc(""" + Defines the trigger position, the time distance from the trigger point to the reference point (trigger offset). The trigger + point is the zero point of the diagram. Changing the horizontal position, you can move the trigger, even outside the screen. + """)) + + self._add_property('timebase.reference', + self._get_timebase_reference, + self._set_timebase_reference, + None, + ivi.Doc(""" + Defines the time reference point in the diagram. The reference point is the rescaling center of the time scale on the screen. + If you modify the time scale, the reference point remains fixed on the screen, and the scale is stretched or compressed to + both sides of the reference point. The reference point defines which part of the waveform is shown. By default, the reference + point is displayed in the center of the window, and you can move it to the left or right. + + Values: + * 'left' + * 'center' + * 'right' + """)) + + self._add_property('timebase.acquisition_time', + self._get_timebase_acquisition_time, + self._set_timebase_acquisition_time, + None, + ivi.Doc(""" + Defines the time of one acquisition, that is the time across the 12 divisions of the diagram: + timebase.acquisition_time = timebase.horizontal_divisions * timebase.scale + """)) + + self._add_property('timebase.range', + self._get_timebase_range, + self._set_timebase_range, + None, + ivi.Doc(""" + Sets the full-scale horizontal time in seconds for the main window. The + range is 12 times the current time-per-division setting. + """)) + + self._add_property('timebase.real_acquisition_time', + self._get_timebase_real_acquisition_time, + None, + ivi.Doc(""" + Queries the real acquisition time used in the hardware. If FFT analysis is performed, the value can differ from the adjusted + acquisition time (timebase.acquisition_time). + """)) + + self._add_property('timebase.divisions', + self._get_timebase_divisions, + None, + ivi.Doc(""" + Queries the number of horizontal divisions on the screen. + """)) + + self._add_property('acquisition.record_length_automatic', + self._get_acquisition_record_length_automatic, + self._set_acquisition_record_length_automatic, + None, + ivi.Doc(""" + Enables or disables the automatic record length. The instrument sets a value that fits to the selected timebase. + """)) + + + self._init_channels() + + def _initialize(self, resource = None, id_query = False, reset = False, **keywargs): + "Opens an I/O session to the instrument." + + self._channel_count = self._analog_channel_count + self._digital_channel_count + + super(rohdeschwarzBaseScope, self)._initialize(resource, id_query, reset, **keywargs) + + # interface clear + if not self._driver_operation_simulate: + self._clear() + + # check ID + if id_query and not self._driver_operation_simulate: + id = self.identity.instrument_model + id_check = self._instrument_id + id_short = id[:len(id_check)] + if id_short != id_check: + raise Exception("Instrument ID mismatch, expecting %s, got %s", id_check, id_short) + + # reset + if reset: + self.utility.reset() + + + def _utility_disable(self): + pass + + def _utility_lock_object(self): + pass + + def _utility_unlock_object(self): + pass + + def _init_channels(self): + try: + super(rohdeschwarzBaseScope, self)._init_channels() + except AttributeError: + pass + + self._channel_name = list() + self._channel_label = list() + self._channel_probe_skew = list() + self._channel_invert = list() + self._channel_probe_id = list() + self._channel_scale = list() + self._channel_trigger_level = list() + self._channel_bw_limit = list() + + self._analog_channel_name = list() + for i in range(self._analog_channel_count): + self._channel_name.append("channel%d" % (i+1)) + self._channel_label.append("%d" % (i+1)) + self._analog_channel_name.append("C%d" % (i+1)) + self._channel_probe_skew.append(0) + self._channel_scale.append(1.0) + self._channel_trigger_level.append(0.0) + self._channel_invert.append(False) + self._channel_probe_id.append("NONE") + self._channel_bw_limit.append(False) + + # digital channels + self._digital_channel_name = list() + if (self._digital_channel_count > 0): + for i in range(self._digital_channel_count): + self._channel_name.append("digital%d" % i) + self._channel_label.append("D%d" % i) + self._digital_channel_name.append("digital%d" % i) + + for i in range(self._analog_channel_count, self._channel_count): + self._channel_input_impedance[i] = 100000 + self._channel_input_frequency_max[i] = 1e9 + self._channel_probe_attenuation[i] = 1 + self._channel_coupling[i] = 'dc' + self._channel_offset[i] = 0 + self._channel_range[i] = 1 + + self._channel_count = self._analog_channel_count + self._digital_channel_count + self.channels._set_list(self._channel_name) + + + def _get_timebase_scale(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._timebase_scale = float(self._ask("timebase:scale?")) + self._timebase_range = self._timebase_scale * self._horizontal_divisions + self._set_cache_valid() + self._set_cache_valid(True, 'timebase_range') + return self._timebase_scale + + def _set_timebase_scale(self, value): + value = float(value) + if not self._driver_operation_simulate: + self._write("timebase:scale %e" % value) + self._timebase_scale = value + self._timebase_range = value * self._horizontal_divisions + self._set_cache_valid() + self._set_cache_valid(True, 'timebase_range') + self._set_cache_valid(False, 'timebase_window_scale') + self._set_cache_valid(False, 'timebase_window_range') + + def _get_timebase_position(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._timebase_position = float(self._ask("timebase:position?")) + self._set_cache_valid() + return self._timebase_position + + def _set_timebase_position(self, value): + value = float(value) + if not self._driver_operation_simulate: + self._write("timebase:position %e" % value) + self._timebase_position = value + self._set_cache_valid() + self._set_cache_valid(False, 'timebase_window_position') + + def _get_timebase_reference(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = float(self._ask("timebase:reference?")) + print(value) + self._timebase_reference = [k for k,v in TimebaseReferenceMapping.items() if v==value][0] # What to do for arbitrary reference values? + self._set_cache_valid() + return self._timebase_reference + + def _set_timebase_reference(self, value): + if value not in TimebaseReferenceMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("timebase:reference %s" % TimebaseReferenceMapping[value]) + self._timebase_reference = value + self._set_cache_valid() + + def _get_timebase_acquisition_time(self): + return self._get_timebase_range() + + def _set_timebase_acquisition_time(self, value): + value = float(value) + if not self._driver_operation_simulate: + self._write("timebase:acqtime %e" % value) + self._timebase_acquisition_time = value + self._timebase_range = value / self._horizontal_divisions + self._set_cache_valid() + self._set_cache_valid(True, 'timebase_range') + self._set_cache_valid(False, 'timebase_window_scale') + self._set_cache_valid(False, 'timebase_window_range') + + def _get_timebase_range(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._timebase_range = float(self._ask("timebase:range?")) + self._timebase_scale = self._timebase_range / self._horizontal_divisions + self._set_cache_valid() + self._set_cache_valid(True, 'timebase_scale') + return self._timebase_range + + def _set_timebase_range(self, value): + value = float(value) + if not self._driver_operation_simulate: + self._write("timebase:range %e" % value) + self._timebase_range = value + self._timebase_scale = value / self._horizontal_divisions + self._set_cache_valid() + self._set_cache_valid(True, 'timebase_scale') + self._set_cache_valid(False, 'timebase_window_scale') + self._set_cache_valid(False, 'timebase_window_range') + + def _get_timebase_real_acquisition_time(self): + if not self._driver_operation_simulate: + return float(self._ask("timebase:ratime?")) + return + + def _get_timebase_divisions(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._horizontal_divisions = int(float(self._ask("timebase:divisions?"))) + return self._horizontal_divisions + + def _get_acquisition_start_time(self): + return self._get_timebase_position() - self._get_acquisition_time_per_record() / 2 + + def _set_acquisition_start_time(self, value): + self._set_timebase_position(float(value) + self._get_acquisition_time_per_record() / 2) + + def _get_acquisition_type(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = [self._ask("channel:type?").lower()] + value.append(self._ask("channel:arithmetics?").lower()) + self._acquisition_type = [k for k,v in AcquisitionTypeMapping.items() if v==value] + self._set_cache_valid() + return self._acquisition_type + + def _set_acquisition_type(self, value): + if value not in AcquisitionTypeMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("channel:type %s" % AcquisitionTypeMapping[value][0]) + self._write("channel:arithmetics %s" % AcquisitionTypeMapping[value][1]) + self._acquisition_type = value + self._set_cache_valid() + + def _get_acquisition_number_of_points_minimum(self): + return self._get_acquisition_record_length() + + def _set_acquisition_number_of_points_minimum(self, value): + if value < 20e3: + value = 20e3 # Minimum number of points is 20 kSa + if not self._driver_operation_simulate: + self._acquisition_record_length = int(value) + self._acquisition_number_of_points_minimum = self._acquisition_record_length + self._acquisition_record_length_automatic = False + self._set_cache_valid() + self._write("acquire:points %d" % self._acquisition_record_length) + + def _get_acquisition_record_length_automatic(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._acquisition_record_length_automatic = (False, True)[int(self._ask("acquire:points:automatic?"))] + self._set_cache_valid() + return self._acquisition_record_length_automatic + + def _set_acquisition_record_length_automatic(self, value): + if not self._driver_operation_simulate: + self._write("acquire:points:automatic %d" % (0, 1)[value]) + self._get_acquisition_record_length() # Need to update record length after turning off automatic record length + self._acquisition_record_length_automatic = value + + def _get_acquisition_record_length(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._acquisition_record_length = int(self._ask("acquire:points?")) + self._set_cache_valid() + return self._acquisition_record_length + + def _get_acquisition_sample_rate(self): + return float(self._ask("acquire:srate?")) + #return self._get_acquisition_record_length() / self._get_timebase_real_acquisition_time() # Observe that the real acquisition time is longer than acquisition time + + def _get_acquisition_time_per_record(self): + return self._get_timebase_range() + + def _set_acquisition_time_per_record(self, value): + value = float(value) + self._set_timebase_range(value) + + + def _get_channel_label(self, index): + index = ivi.get_index(self._channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_label[index] = self._ask("%s:label?" % self._channel_name[index]).strip('"') + self._set_cache_valid(index=index) + return self._channel_label[index] + + def _set_channel_label(self, index, value): + value = str(value) + index = ivi.get_index(self._channel_name, index) + if not self._driver_operation_simulate: + self._write("%s:label '%s'" % (self._channel_name[index], value)) + self._channel_label[index] = value + self._set_cache_valid(index=index) + + def _get_channel_enabled(self, index): + index = ivi.get_index(self._channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_enabled[index] = bool(int(self._ask("%s:state?" % self._channel_name[index]))) + self._set_cache_valid(index=index) + return self._channel_enabled[index] + + def _set_channel_enabled(self, index, value): + value = bool(value) + index = ivi.get_index(self._channel_name, index) + if not self._driver_operation_simulate: + self._write("%s:state %d" % (self._channel_name[index], int(value))) + self._channel_enabled[index] = value + self._set_cache_valid(index=index) + + def _get_channel_input_frequency_max(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + bandwidth_limit = self._ask("%s:bandwidth?" % self._channel_name[index]).lower() + if bandwidth_limit == 'full': + self._channel_bw_limit[index] = self._bandwidth + elif bandwidth_limit[0] == 'b': + self._channel_bw_limit[index] = int(bandwidth_limit[1:]) + self._set_cache_valid(index=index) + return self._channel_bw_limit[index] + + def _set_channel_input_frequency_max(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate: + if value in BandwidthMapping.keys(): + self._write("%s:bwlimit %s" % (self._channel_name[index], bandwidth[str(1e-6*value)])) + else: + raise ivi.ValueNotSupportedException("Supported bandwidth limits are %d" % BandwidthMapping.keys() + self._channel_bw_limit[index] = value + self._set_cache_valid(index=index) \ No newline at end of file diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2002.py b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py new file mode 100644 index 00000000..88120fbb --- /dev/null +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py @@ -0,0 +1,43 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Jonas Långbacka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .rohdeschwarzBaseScope import * + +class rohdeschwarzRTB2002(rohdeschwarzBaseScope): + "Rohde&Schwarz RTB2002 IVI oscilloscope driver" + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', 'RTB2002') + + super(rohdeschwarzRTB2002, self).__init__(*args, **kwargs) + + self._analog_channel_count = 2 + self._digital_channel_count = 16 + self._channel_count = self._analog_channel_count + self._digital_channel_count + self._bandwidth = 70e6 + + self._init_channels() + diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py new file mode 100644 index 00000000..c13d75cb --- /dev/null +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py @@ -0,0 +1,46 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Jonas Långbacka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .rohdeschwarzBaseScope import * + +BandwidthMapping = { + 100e6: 'full', + 20e6: 'b20'} + +class rohdeschwarzRTB2004(rohdeschwarzBaseScope): + "Rohde&Schwarz RTB2004 IVI oscilloscope driver" + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', 'RTB2004') + + super(rohdeschwarzRTB2004, self).__init__(*args, **kwargs) + + self._analog_channel_count = 4 + self._digital_channel_count = 16 + self._channel_count = self._analog_channel_count + self._digital_channel_count + self._bandwidth = 70e6 + + self._init_channels() diff --git a/setup.py b/setup.py index e9484085..c831bee2 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ def run_tests(self): 'ivi.jdsu', 'ivi.lecroy', 'ivi.rigol', + 'ivi.rohdeschwarz', 'ivi.tektronix', 'ivi.testequity'], requires = ['numpy'], From 8288f2a0e9e1462c3d517729b0ce4b8479f6dd07 Mon Sep 17 00:00:00 2001 From: JonasLang Date: Sun, 14 Jan 2018 22:05:23 +0100 Subject: [PATCH 02/11] RTB2000: Add basic vertical setting functions such as channel state, scale, range, offset, polarity etc. Add trigger functions. --- ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 323 +++++++++++++++++++--- ivi/rohdeschwarz/rohdeschwarzRTB2004.py | 5 +- 2 files changed, 285 insertions(+), 43 deletions(-) diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py index c4f4161a..cb80a1cb 100644 --- a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017 Jonas Långbacka +Copyright (c) 2017-2018 Jonas Långbacka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -41,7 +41,10 @@ 800e6: 'b800', 200e6: 'b200', 20e6: 'b20'} -VerticalCoupling = set(['ac', 'dc']) +VerticalCouplingMapping = { + 'dc': 'dcl', + 'ac': 'acl', + 'gnd': 'gnd'} TriggerTypeMapping = { 'edge': 'edge', 'width': 'glit', @@ -62,17 +65,15 @@ 'uart': 'uart', 'usb': 'usb', 'flexray': 'flex'} +# TriggerCouplingMapping = 'coupling': ('coupling', 5 kHz LPF, 100 MHz LPF) TriggerCouplingMapping = { 'ac': ('ac', 0, 0), 'dc': ('dc', 0, 0), - 'hf_reject': ('dc', 0, 1), 'lf_reject': ('lfr', 0, 0), - 'noise_reject': ('dc', 1, 0), - 'hf_reject_ac': ('ac', 0, 1), - 'noise_reject_ac': ('ac', 1, 0), - 'hf_noise_reject': ('dc', 1, 1), - 'hf_noise_reject_ac': ('ac', 1, 1), - 'lf_noise_reject': ('lfr', 1, 0)} + 'hf_reject_dc': ('dc', 1, 0), + 'hf_reject_ac': ('ac', 1, 0), + 'noise_reject_dc': ('dc', 0, 1), + 'noise_reject_ac': ('ac', 0, 1)} TVTriggerEventMapping = {'field1': 'fie1', 'field2': 'fie2', 'any_field': 'afi', @@ -162,9 +163,9 @@ 'right': 91.67} TriggerModifierMapping = {'none': 'normal', 'auto': 'auto'} -class rohdeschwarzBaseScope(scope.Base, +class rohdeschwarzBaseScope(scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi.common.Reset, + scope.Base, ivi.Driver): -# scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi.common.Reset, # scpi.common.SelfTest, scpi.common.Memory, # scope.Base, scope.TVTrigger, # scope.GlitchTrigger, scope.WidthTrigger, scope.AcLineTrigger, @@ -183,27 +184,30 @@ def __init__(self, *args, **kwargs): self._digital_channel_count = 16 self._channel_count = self._analog_channel_count + self._digital_channel_count self._channel_label = list() - self._channel_probe_skew = list() - self._channel_scale = list() - self._channel_trigger_level = list() - self._channel_invert = list() - self._channel_probe_id = list() - self._channel_bw_limit = list() + + self._vertical_divisions = 10 + + # self._channel_probe_skew = list() + # self._channel_scale = list() + # self._channel_trigger_level = list() + # self._channel_invert = list()imp + # self._channel_bw_limit = list() super(rohdeschwarzBaseScope, self).__init__(*args, **kwargs) - self._self_test_delay = 40 self._memory_size = 10 - + self._analog_channel_name = list() self._analog_channel_count = 4 self._digital_channel_name = list() self._digital_channel_count = 16 self._channel_count = self._analog_channel_count + self._digital_channel_count + self._bandwidth = 1e9 + + self._trigger_holdoff_min_time = 51.2e-9 self._horizontal_divisions = 12 - self._vertical_divisions = 10 self._acquisition_segmented_count = 2 self._acquisition_segmented_index = 1 @@ -310,13 +314,50 @@ def __init__(self, *args, **kwargs): Enables or disables the automatic record length. The instrument sets a value that fits to the selected timebase. """)) + self._add_property('channels[].invert', + self._get_channel_invert, + self._set_channel_invert, + None, + ivi.Doc(""" + Selects whether or not to invert the channel. + """)) - self._init_channels() + self._add_property('channels[].label', + self._get_channel_label, + self._set_channel_label, + None, + ivi.Doc(""" + Sets the channel label. Setting a channel label also adds the label to + the nonvolatile label list. + """)) + + self._add_property('channels[].probe_skew', + self._get_channel_probe_skew, + self._set_channel_probe_skew, + None, + ivi.Doc(""" + Specifies the channel-to-channel skew factor for the channel. Each analog + channel can be adjusted + or - 100 ns for a total of 200 ns difference + between channels. This can be used to compensate for differences in cable + delay. Units are seconds. + """)) + + self._add_property('channels[].scale', + self._get_channel_scale, + self._set_channel_scale, + None, + ivi.Doc(""" + Specifies the vertical scale, or units per division, of the channel. Units + are volts. + """)) + + + self._init_channels() # Remove from base class? def _initialize(self, resource = None, id_query = False, reset = False, **keywargs): "Opens an I/O session to the instrument." - self._channel_count = self._analog_channel_count + self._digital_channel_count + #self._channel_count = self._analog_channel_count + self._digital_channel_count super(rohdeschwarzBaseScope, self)._initialize(resource, id_query, reset, **keywargs) @@ -336,7 +377,6 @@ def _initialize(self, resource = None, id_query = False, reset = False, **keywar if reset: self.utility.reset() - def _utility_disable(self): pass @@ -346,6 +386,9 @@ def _utility_lock_object(self): def _utility_unlock_object(self): pass + def _utility_self_test(self): + pass + def _init_channels(self): try: super(rohdeschwarzBaseScope, self)._init_channels() @@ -354,40 +397,46 @@ def _init_channels(self): self._channel_name = list() self._channel_label = list() + + # analog channels + #self._analog_channel_name = list() self._channel_probe_skew = list() self._channel_invert = list() - self._channel_probe_id = list() + self._channel_coupling = list() + self._channel_input_impedance = list() + self._channel_input_frequency_max = list() + self._channel_probe_attenuation = list() self._channel_scale = list() + self._channel_range = list() + self._channel_offset = list() self._channel_trigger_level = list() self._channel_bw_limit = list() - self._analog_channel_name = list() + #self._analog_channel_name = list() for i in range(self._analog_channel_count): self._channel_name.append("channel%d" % (i+1)) self._channel_label.append("%d" % (i+1)) self._analog_channel_name.append("C%d" % (i+1)) + self._channel_probe_skew.append(0) - self._channel_scale.append(1.0) - self._channel_trigger_level.append(0.0) self._channel_invert.append(False) - self._channel_probe_id.append("NONE") + self._channel_coupling.append('dc') + self._channel_input_impedance.append(1e9) + self._channel_input_frequency_max.append(1e9) + self._channel_probe_attenuation.append(1) + self._channel_scale.append(50e-3) + self._channel_range.append(self._vertical_divisions * self._channel_scale[i]) + self._channel_offset.append(0) + self._channel_trigger_level.append(0.0) self._channel_bw_limit.append(False) # digital channels - self._digital_channel_name = list() + #self._digital_channel_name = list() if (self._digital_channel_count > 0): for i in range(self._digital_channel_count): self._channel_name.append("digital%d" % i) self._channel_label.append("D%d" % i) self._digital_channel_name.append("digital%d" % i) - - for i in range(self._analog_channel_count, self._channel_count): - self._channel_input_impedance[i] = 100000 - self._channel_input_frequency_max[i] = 1e9 - self._channel_probe_attenuation[i] = 1 - self._channel_coupling[i] = 'dc' - self._channel_offset[i] = 0 - self._channel_range[i] = 1 self._channel_count = self._analog_channel_count + self._digital_channel_count self.channels._set_list(self._channel_name) @@ -429,7 +478,6 @@ def _set_timebase_position(self, value): def _get_timebase_reference(self): if not self._driver_operation_simulate and not self._get_cache_valid(): value = float(self._ask("timebase:reference?")) - print(value) self._timebase_reference = [k for k,v in TimebaseReferenceMapping.items() if v==value][0] # What to do for arbitrary reference values? self._set_cache_valid() return self._timebase_reference @@ -494,7 +542,7 @@ def _set_acquisition_start_time(self, value): def _get_acquisition_type(self): if not self._driver_operation_simulate and not self._get_cache_valid(): value = [self._ask("channel:type?").lower()] - value.append(self._ask("channel:arithmetics?").lower()) + value.append(self._ask("channel:arithmetics?")) self._acquisition_type = [k for k,v in AcquisitionTypeMapping.items() if v==value] self._set_cache_valid() return self._acquisition_type @@ -577,10 +625,18 @@ def _set_channel_enabled(self, index, value): value = bool(value) index = ivi.get_index(self._channel_name, index) if not self._driver_operation_simulate: + print(("%s:state %d" % (self._channel_name[index], int(value)))) self._write("%s:state %d" % (self._channel_name[index], int(value))) self._channel_enabled[index] = value self._set_cache_valid(index=index) + def _get_channel_input_impedance(self, index): + index = ivi.get_index(self._analog_channel_name, index) + return self._channel_input_impedance[index] + + def _set_channel_input_impedance(self, index, value): + raise ivi.ValueNotSupportedException('Input impedance of all BNC inputs is fixed to 1 MOhm') + def _get_channel_input_frequency_max(self, index): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): @@ -596,8 +652,191 @@ def _set_channel_input_frequency_max(self, index, value): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate: if value in BandwidthMapping.keys(): - self._write("%s:bwlimit %s" % (self._channel_name[index], bandwidth[str(1e-6*value)])) + self._write("%s:bandwidth %s" % (self._analog_channel_name[index], str(1e-6*value))) else: - raise ivi.ValueNotSupportedException("Supported bandwidth limits are %d" % BandwidthMapping.keys() + raise ivi.ValueNotSupportedException("Supported bandwidth limits are %d" % BandwidthMapping.keys()) self._channel_bw_limit[index] = value - self._set_cache_valid(index=index) \ No newline at end of file + self._set_cache_valid(index=index) + + def _get_channel_probe_attenuation(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_probe_attenuation[index] = float(self._ask("probe%s:setup:attenuation:manual?" % self._channel_name[index])) + self._set_cache_valid(index=index) + return self._channel_probe_attenuation[index] + + def _set_channel_probe_attenuation(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + value = float(value) + if 0.001 > value > 10000: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + print("probe%s:setup:attenuation:manual %f" % (self._channel_name[index], value)) + self._write("probe%s:setup:attenuation:manual %e" % (self._analog_channel_name[index], value)) + self._channel_probe_attenuation[index] = value + self._set_cache_valid(index=index) + self._set_cache_valid(False, 'channel_offset', index) + self._set_cache_valid(False, 'channel_scale', index) + self._set_cache_valid(False, 'channel_range', index) + self._set_cache_valid(False, 'channel_trigger_level', index) + self._set_cache_valid(False, 'trigger_level') + + def _get_channel_probe_skew(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_probe_skew[index] = float(self._ask("%s:skew?" % self._channel_name[index])) + self._set_cache_valid(index=index) + return self._channel_probe_skew[index] + + def _set_channel_probe_skew(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + value = float(value) + if not self._driver_operation_simulate: + self._write("%s:skew %e" % (self._channel_name[index], value)) + self._channel_probe_skew[index] = value + self._set_cache_valid(index=index) + + def _get_channel_invert(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + value = self._ask("%s:polarity?" % self._channel_name[index]).lower() + if value == 'norm': + self._channel_invert[index] = False + elif value == 'inv': + self._channel_invert[index] = True + self._set_cache_valid(index=index) + return self._channel_invert[index] + + def _set_channel_invert(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate: + self._write("%s:polarity %s" % (self._channel_name[index], ('norm', 'inv')[value])) + self._channel_invert[index] = value + + def _get_channel_coupling(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + value = self._ask("%s:coupling?" % self._channel_name[index]).lower() + self._channel_coupling[index] = [k for k,v in VerticalCouplingMapping.items() if v==value][0] + self._set_cache_valid(index=index) + return self._channel_coupling[index] + + def _set_channel_coupling(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + if value not in VerticalCouplingMapping.keys(): + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("%s:coupling %s" % (self._channel_name[index], VerticalCouplingMapping[value])) + self._channel_coupling[index] = value + self._set_cache_valid(index=index) + + def _get_channel_offset(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_offset[index] = float(self._ask("%s:offset?" % self._channel_name[index])) + self._set_cache_valid(index=index) + return self._channel_offset[index] + + def _set_channel_offset(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + value = float(value) + if abs(value) > 1.2: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("%s:offset %e" % (self._channel_name[index], value)) + self._channel_offset[index] = value + self._set_cache_valid(index=index) + + def _get_channel_range(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_range[index] = float(self._ask("%s:range?" % self._channel_name[index])) + self._channel_scale[index] = self._channel_range[index] / self._vertical_divisions + self._set_cache_valid(index=index) + self._set_cache_valid(True, "channel_scale", index) + return self._channel_range[index] + + def _set_channel_range(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + value = float(value) + # Instrument can handle 5 V/division with 1:1 probe compensation + if 1e-3 * self._channel_scale[index] * self._channel_probe_attenuation[index] > value > 5 * self._channel_scale[index] * self._channel_probe_attenuation[index]: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("%s:range %e" % (self._channel_name[index], value)) + self._channel_range[index] = value + self._channel_scale[index] = value / self._vertical_divisions + self._set_cache_valid(index=index) + self._set_cache_valid(True, "channel_scale", index) + self._set_cache_valid(False, "channel_offset", index) + + def _get_channel_scale(self, index): + index = ivi.get_index(self._analog_channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_scale[index] = float(self._ask("%s:scale?" % self._channel_name[index])) + self._channel_range[index] = self._channel_scale[index] * self._vertical_divisions + self._set_cache_valid(index=index) + self._set_cache_valid(True, "channel_range", index) + return self._channel_scale[index] + + def _set_channel_scale(self, index, value): + index = ivi.get_index(self._analog_channel_name, index) + value = float(value) + # Instrument can handle 5 V/division with 1:1 probe compensation + if 1e-3 * self._channel_probe_attenuation[index] > value > 5 * self._channel_probe_attenuation[index]: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("%s:scale %e" % (self._channel_name[index], value)) + self._channel_scale[index] = value + self._channel_range[index] = value * self._vertical_divisions + self._set_cache_valid(index=index) + self._set_cache_valid(True, "channel_range", index) + self._set_cache_valid(False, "channel_offset", index) + + + # Trigger functions + def _get_trigger_coupling(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + coupling = self._ask("trigger:a:edge:coupling?").lower() + hf_reject = int(self._ask("trigger:a:edge:filter:hfreject?")) + noise_reject = int(self._ask("trigger:a:edge:filter:nreject?")) + for k in TriggerCouplingMapping: + if (coupling, hf_reject, noise_reject) == TriggerCouplingMapping[k]: + self._trigger_coupling = k + break + return self._trigger_coupling + + def _set_trigger_coupling(self, value): + if value not in TriggerCouplingMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("trigger:a:edge:coupling %s" % TriggerCouplingMapping[value][0]) + self._write("trigger:a:edge:filter:hfreject %d" % TriggerCouplingMapping[value][1]) + self._write("trigger:a:edge:filter:nreject %d" % TriggerCouplingMapping[value][2]) + self._trigger_coupling = value + self._set_cache_valid() + + def _get_trigger_holdoff(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + # First check if trigger holdoff is enabled. One can set a value but it won't apply before enabled. + if self._ask("trigger:a:holdoff:mode?").lower() == 'off': + self._trigger_holdoff = 0.0 + print('hej1') + elif self._ask("trigger:a:holdoff:mode?").lower() == 'time': + self._trigger_holdoff = float(self._ask("trigger:a:holdoff:time?")) + print('hej2') + self._set_cache_valid() + return self._trigger_holdoff + + def _set_trigger_holdoff(self, value): + value = float(value) + if not self._driver_operation_simulate: + if float(value) == 0.0: + self._write("trigger:a:holdoff:mode off") + elif value < self._trigger_holdoff_min_time: + raise ivi.ValueNotSupportedException("Minimum trigger hold off time is %e" % self._trigger_holdoff_min_time) + else: + self._write("trigger:a:holdoff:time %e" % value) + self._write("trigger:a:holdoff:mode time") + self._trigger_holdoff = value + self._set_cache_valid() \ No newline at end of file diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py index c13d75cb..637071ec 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017 Jonas Långbacka +Copyright (c) 2017-2018 Jonas Långbacka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -42,5 +42,8 @@ def __init__(self, *args, **kwargs): self._digital_channel_count = 16 self._channel_count = self._analog_channel_count + self._digital_channel_count self._bandwidth = 70e6 + self._horizontal_divisions = 12 + self._vertical_divisions = 10 + self._trigger_holdoff_min_time = 51.2e-9 self._init_channels() From 515d82c6734ea3ec32133d4546f985a6b0f61038 Mon Sep 17 00:00:00 2001 From: JonasLang Date: Sun, 14 Jan 2018 22:10:50 +0100 Subject: [PATCH 03/11] Remove unrelated file --- ivi/keithley/test_script.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 ivi/keithley/test_script.py diff --git a/ivi/keithley/test_script.py b/ivi/keithley/test_script.py deleted file mode 100644 index c0b4ca7c..00000000 --- a/ivi/keithley/test_script.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Sep 27 17:37:04 2017 - -@author: jonas -""" - -from ivi.keithley import keithley2280S -import time -from matplotlib import pyplot - -PSU = keithley2280S('TCPIP::172.20.0.49::INSTR', prefer_pyvisa=True, query_instr_status=True) -PSU.outputs[0].voltage_level=2.0 -PSU.outputs[0].enabled = True -PSU.outputs[0].configure_measurement( - type='current', - sample_count=100, - measurement_range=0.1, - NPLC=0.02, - adc_autozero=False, - auto_clear_buffer=True) - -PSU.outputs[0].trigger_current_state = True -PSU.outputs[0].trigger_current_level = 1e-3 -PSU.outputs[0].trace_points = 1000 -PSU.outputs[0].trigger_sample_count = 20 -PSU.outputs[0].trigger_count = 5 - -PSU.outputs[0].enabled = True -PSU.trigger.initiate() - -V, t = PSU.outputs[0].fetch_measurement(measurement_type=('current', 'relative_time_seconds')) -pyplot.plot(V) \ No newline at end of file From 0b09c735cd01e719d419b73c7842b47bc2d482d2 Mon Sep 17 00:00:00 2001 From: JonasLang Date: Mon, 29 Jan 2018 15:34:48 +0100 Subject: [PATCH 04/11] Add more trigger functions. Change copyright information. --- ivi/rohdeschwarz/__init__.py | 7 +- ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 171 +++++++++++++++++++--- ivi/rohdeschwarz/rohdeschwarzRTB2002.py | 2 +- ivi/rohdeschwarz/rohdeschwarzRTB2004.py | 5 +- 4 files changed, 154 insertions(+), 31 deletions(-) diff --git a/ivi/rohdeschwarz/__init__.py b/ivi/rohdeschwarz/__init__.py index ab881571..10ac5760 100644 --- a/ivi/rohdeschwarz/__init__.py +++ b/ivi/rohdeschwarz/__init__.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017 Jonas Långbacka, Acconeer Ab +Copyright (c) 2017-2018 Acconeer AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,4 @@ # Oscilloscopes # RTB2000 series from .rohdeschwarzRTB2002 import rohdeschwarzRTB2002 -from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 - -# RTO2000 series -#from .rohdeschwarzRTO2044 import rohdeschwarzRTO2044 +from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 \ No newline at end of file diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py index cb80a1cb..b6a43ec3 100644 --- a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017-2018 Jonas Långbacka +Copyright (c) 2017-2018 Acconeer AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -42,9 +42,13 @@ 200e6: 'b200', 20e6: 'b20'} VerticalCouplingMapping = { - 'dc': 'dcl', - 'ac': 'acl', + 'dc': 'dcl', + 'ac': 'acl', 'gnd': 'gnd'} +TriggerModifierMapping = { + 'none': 'norm', # according to IVI standardization, oscilloscope normal triggde mode is called 'none' + 'normal': 'norm', + 'auto': 'auto'} TriggerTypeMapping = { 'edge': 'edge', 'width': 'glit', @@ -113,8 +117,7 @@ SlopeMapping = { 'positive': 'pos', 'negative': 'neg', - 'either': 'eith', - 'alternating': 'alt'} + 'either': 'eith'} MeasurementFunctionMapping = { 'rise_time': 'risetime', 'fall_time': 'falltime', @@ -161,17 +164,18 @@ 'left': 8.33, 'center': 50.0, 'right': 91.67} -TriggerModifierMapping = {'none': 'normal', 'auto': 'auto'} class rohdeschwarzBaseScope(scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi.common.Reset, scope.Base, + scope.ContinuousAcquisition, + scope.TriggerModifier, ivi.Driver): # scpi.common.SelfTest, scpi.common.Memory, -# scope.Base, scope.TVTrigger, +# scope.TVTrigger, # scope.GlitchTrigger, scope.WidthTrigger, scope.AcLineTrigger, # scope.WaveformMeasurement, scope.MinMaxWaveform, -# scope.ContinuousAcquisition, scope.AverageAcquisition, -# scope.SampleMode, scope.TriggerModifier, scope.AutoSetup, +# scope.AverageAcquisition, +# scope.SampleMode, scope.AutoSetup, # extra.common.SystemSetup, extra.common.Screenshot, # ivi.Driver): "Rohde&Schwarz generic IVI oscilloscope driver" @@ -197,15 +201,16 @@ def __init__(self, *args, **kwargs): self._memory_size = 10 - self._analog_channel_name = list() - self._analog_channel_count = 4 - self._digital_channel_name = list() - self._digital_channel_count = 16 - self._channel_count = self._analog_channel_count + self._digital_channel_count + # self._analog_channel_name = list() + # self._analog_channel_count = 4 + # self._digital_channel_name = list() + # self._digital_channel_count = 16 + # self._channel_count = self._analog_channel_count + self._digital_channel_count self._bandwidth = 1e9 - self._trigger_holdoff_min_time = 51.2e-9 + self._trigger_holdoff_min = 51.2e-9 + self._channel_offset_max = 1.2 self._horizontal_divisions = 12 @@ -223,6 +228,11 @@ def __init__(self, *args, **kwargs): self._timebase_window_position = 0.0 self._timebase_window_range = 600e-6 self._timebase_window_scale = 50e-6 + + self._trigger_mode = 'auto' + self._trigger_type = 'edge' + self._trigger_continuous = True + self._display_screenshot_image_format_mapping = ScreenshotImageFormatMapping self._display_vectors = True self._display_labels = True @@ -351,6 +361,13 @@ def __init__(self, *args, **kwargs): are volts. """)) + self._add_property('channels[].trigger_level', + self._get_channel_trigger_level, + self._set_channel_trigger_level, + None, + ivi.Doc(""" + Specifies the trigger level of the channel. Units are volts. + """)) self._init_channels() # Remove from base class? @@ -399,7 +416,7 @@ def _init_channels(self): self._channel_label = list() # analog channels - #self._analog_channel_name = list() + self._analog_channel_name = list() self._channel_probe_skew = list() self._channel_invert = list() self._channel_coupling = list() @@ -669,7 +686,7 @@ def _set_channel_probe_attenuation(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) if 0.001 > value > 10000: - raise ivi.ValueNotSupportedException() + raise ivi.OutOfRangeException if not self._driver_operation_simulate: print("probe%s:setup:attenuation:manual %f" % (self._channel_name[index], value)) self._write("probe%s:setup:attenuation:manual %e" % (self._analog_channel_name[index], value)) @@ -740,8 +757,8 @@ def _get_channel_offset(self, index): def _set_channel_offset(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) - if abs(value) > 1.2: - raise ivi.ValueNotSupportedException() + if abs(value) > self._channel_offset_max: + raise ivi.OutOfRangeException if not self._driver_operation_simulate: self._write("%s:offset %e" % (self._channel_name[index], value)) self._channel_offset[index] = value @@ -761,7 +778,7 @@ def _set_channel_range(self, index, value): value = float(value) # Instrument can handle 5 V/division with 1:1 probe compensation if 1e-3 * self._channel_scale[index] * self._channel_probe_attenuation[index] > value > 5 * self._channel_scale[index] * self._channel_probe_attenuation[index]: - raise ivi.ValueNotSupportedException() + raise ivi.OutOfRangeException if not self._driver_operation_simulate: self._write("%s:range %e" % (self._channel_name[index], value)) self._channel_range[index] = value @@ -784,7 +801,7 @@ def _set_channel_scale(self, index, value): value = float(value) # Instrument can handle 5 V/division with 1:1 probe compensation if 1e-3 * self._channel_probe_attenuation[index] > value > 5 * self._channel_probe_attenuation[index]: - raise ivi.ValueNotSupportedException() + raise ivi.OutOfRangeException if not self._driver_operation_simulate: self._write("%s:scale %e" % (self._channel_name[index], value)) self._channel_scale[index] = value @@ -833,10 +850,118 @@ def _set_trigger_holdoff(self, value): if not self._driver_operation_simulate: if float(value) == 0.0: self._write("trigger:a:holdoff:mode off") - elif value < self._trigger_holdoff_min_time: - raise ivi.ValueNotSupportedException("Minimum trigger hold off time is %e" % self._trigger_holdoff_min_time) + elif value < self._trigger_holdoff_min: + raise ivi.OutOfRangeException("Minimum trigger hold off time is %e" % self._trigger_holdoff_min) else: self._write("trigger:a:holdoff:time %e" % value) self._write("trigger:a:holdoff:mode time") self._trigger_holdoff = value + self._set_cache_valid() + + def _get_channel_trigger_level(self, index): + index = ivi.get_index(self._channel_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._channel_trigger_level[index] = float(self._ask("trigger:a:level%d?" % (index+1))) + self._set_cache_valid(index=index) + return self._channel_trigger_level[index] + + def _set_channel_trigger_level(self, index, value): + index = ivi.get_index(self._channel_name, index) + value = float(value) + if not self._driver_operation_simulate: + if value > self._channel_range[index]: + raise ivi.OutOfRangeException("Trigger level cannot be outside vertical range") + self._write("trigger:a:level%d %e" % (index+1, value)) + self._channel_trigger_level[index] = value + self._set_cache_valid(index=index) + self._set_cache_valid(False, "trigger_level") + + def _get_trigger_level(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + ch = self._get_trigger_source() + self._trigger_level = self._get_channel_trigger_level(ch) + self._set_cache_valid() + return self._trigger_level + + def _set_trigger_level(self, value): + value = float(value) + if not self._driver_operation_simulate: + ch = self._get_trigger_source() + self._set_channel_trigger_level(ch, value) + self._trigger_level = value + self._set_cache_valid() + + def _get_trigger_edge_slope(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = self._ask("trigger:a:edge:slope?").lower() + self._trigger_edge_slope = [k for k,v in SlopeMapping.items() if v==value][0] + self._set_cache_valid() + return self._trigger_edge_slope + + def _set_trigger_edge_slope(self, value): + if value not in SlopeMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("trigger:a:edge:slope %s" % SlopeMapping[value]) + self._trigger_edge_slope = value + self._set_cache_valid() + + def _get_trigger_source(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = self._ask("trigger:a:source?").lower() + if value[0:2] == 'ch': + self._trigger_source = 'channel' + value[-1] + elif value[0] == 'd': + self._trigger_source == 'digital' + value[-1] + else: + self._trigger_source = value + self._set_cache_valid() + return self._trigger_source + + def _set_trigger_source(self, value): + if hasattr(value, 'name'): + value = value.name + value = str(value) + if value not in self._channel_name + ['line']: + raise ivi.UnknownPhysicalNameException() + if not self._driver_operation_simulate: + if value[0:2] == 'ch': + scpi_string = 'ch' + value[-1] + elif value[0].lower() == 'd': + scpi_string = 'd' + value[-1] + else: + scpi_string = value + self._write("trigger:a:source %s" % scpi_string) + self._trigger_source = value + self._set_cache_valid() + + def _get_trigger_continuous(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._trigger_continuous = self._ask("acquire:state?").lower() == 'run' + self._set_cache_valid() + return self._trigger_continuous + + def _set_trigger_continuous(self, value): + value = bool(value) + if not self._driver_operation_simulate: + scpi_string = 'stop' + if value: + scpi_string = 'run' + self._write("acquire:state %s" % scpi_string) + self._trigger_continuous = value + self._set_cache_valid() + + def _get_trigger_modifier(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = self._ask("trigger:a:mode?").lower() + self._trigger_modifier = [k for k,v in TriggerModifierMapping.items() if v==value][0] + self._set_cache_valid() + return self._trigger_modifier + + def _set_trigger_modifier(self, value): + if value not in TriggerModifierMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("trigger:a:mode %s" % TriggerModifierMapping[value]) + self._trigger_modifier = value self._set_cache_valid() \ No newline at end of file diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2002.py b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py index 88120fbb..08efdfe9 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2002.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017 Jonas Långbacka +Copyright (c) 2017 Acconeer AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py index 637071ec..a17fcbd6 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017-2018 Jonas Långbacka +Copyright (c) 2017-2018 Acconeer AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,6 +44,7 @@ def __init__(self, *args, **kwargs): self._bandwidth = 70e6 self._horizontal_divisions = 12 self._vertical_divisions = 10 - self._trigger_holdoff_min_time = 51.2e-9 + self._trigger_holdoff_min = 51.2e-9 + self._channel_offset_max = 1.2 self._init_channels() From 39e57ac122fd35d5ac599399dc9f006f4f2e2b32 Mon Sep 17 00:00:00 2001 From: JonasLang Date: Mon, 29 Jan 2018 20:11:00 +0100 Subject: [PATCH 05/11] Add measurement abort, initiate and status functions --- ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 37 +++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py index b6a43ec3..ca86ecba 100644 --- a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -149,6 +149,11 @@ 'width_negative': 'nwidth', 'width_positive': 'pwidth', 'duty_cycle_positive': 'dutycycle'} +MeasurementStatusMapping = { + 'complete': 'comp', + 'in_progress': 'stop', + 'running': 'run', + 'break': 'bre'} ScreenshotImageFormatMapping = { 'bmp': 'bmp', 'bmp24': 'bmp', @@ -169,15 +174,8 @@ class rohdeschwarzBaseScope(scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi scope.Base, scope.ContinuousAcquisition, scope.TriggerModifier, + scope.WaveformMeasurement, ivi.Driver): -# scpi.common.SelfTest, scpi.common.Memory, -# scope.TVTrigger, -# scope.GlitchTrigger, scope.WidthTrigger, scope.AcLineTrigger, -# scope.WaveformMeasurement, scope.MinMaxWaveform, -# scope.AverageAcquisition, -# scope.SampleMode, scope.AutoSetup, -# extra.common.SystemSetup, extra.common.Screenshot, -# ivi.Driver): "Rohde&Schwarz generic IVI oscilloscope driver" def __init__(self, *args, **kwargs): @@ -964,4 +962,25 @@ def _set_trigger_modifier(self, value): if not self._driver_operation_simulate: self._write("trigger:a:mode %s" % TriggerModifierMapping[value]) self._trigger_modifier = value - self._set_cache_valid() \ No newline at end of file + self._set_cache_valid() + + def _measurement_abort(self): + if not self._driver_operation_simulate: + self._write("stop") + self._set_cache_valid(False, 'trigger_continuous') + + def _measurement_initiate(self): + if not self._driver_operation_simulate: + self._write("trigger:a:mode normal") + self._write("runsingle") + self._set_cache_valid(False, 'trigger_continuous') + + def _get_measurement_status(self): + if not self._driver_operation_simulate: + value = self._ask("acquire:state?").lower() + self._measurement_status = [k for k,v in MeasurementStatusMapping.items() if v==value][0] + if value == 'stop' or value == 'comp': + self._set_cache_valid(False, 'trigger_continuous') + return self._measurement_status + + # Measurement functions \ No newline at end of file From 4ec69a47ebc8121c88bc33f960b166121e24db8d Mon Sep 17 00:00:00 2001 From: JonasLang Date: Thu, 1 Feb 2018 14:36:19 +0100 Subject: [PATCH 06/11] Add functions for changing interpolation, setting reference levels for rise and fall time measurements, screenshot transfer. --- ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 155 ++++++++++++++++------ ivi/rohdeschwarz/rohdeschwarzRTB2004.py | 1 + 2 files changed, 114 insertions(+), 42 deletions(-) diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py index ca86ecba..2ef882d1 100644 --- a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -29,6 +29,10 @@ from .. import scpi from .. import extra +AcquisitionInterpolationMapping = { + 'linear': 'lin', + 'sinex': 'sinx', + 'sample_and_hold': 'smhd'} AcquisitionTypeMapping = { 'normal': ['samp', 'off'], 'peak_detect': ['pdet', 'off'], @@ -69,7 +73,6 @@ 'uart': 'uart', 'usb': 'usb', 'flexray': 'flex'} -# TriggerCouplingMapping = 'coupling': ('coupling', 5 kHz LPF, 100 MHz LPF) TriggerCouplingMapping = { 'ac': ('ac', 0, 0), 'dc': ('dc', 0, 0), @@ -78,34 +81,6 @@ 'hf_reject_ac': ('ac', 1, 0), 'noise_reject_dc': ('dc', 0, 1), 'noise_reject_ac': ('ac', 0, 1)} -TVTriggerEventMapping = {'field1': 'fie1', - 'field2': 'fie2', - 'any_field': 'afi', - 'any_line': 'alin', - 'line_number': 'lfi1', - 'vertical': 'vert', - 'line_field1': 'lfi1', - 'line_field2': 'lfi2', - 'line': 'line', - 'line_alternate': 'lalt', - 'lvertical': 'lver'} -TVTriggerFormatMapping = {'generic': 'gen', - 'ntsc': 'ntsc', - 'pal': 'pal', - 'palm': 'palm', - 'secam': 'sec', - 'p480l60hz': 'p480', - 'p480': 'p480', - 'p720l60hz': 'p720', - 'p720': 'p720', - 'p1080l24hz': 'p1080', - 'p1080': 'p1080', - 'p1080l25hz': 'p1080l25hz', - 'p1080l50hz': 'p1080l50hz', - 'p1080l60hz': 'p1080l60hz', - 'i1080l50hz': 'i1080l50hz', - 'i1080': 'i1080l50hz', - 'i1080l60hz': 'i1080l60hz'} PolarityMapping = {'positive': 'pos', 'negative': 'neg'} GlitchConditionMapping = {'less_than': 'less', @@ -156,10 +131,7 @@ 'break': 'bre'} ScreenshotImageFormatMapping = { 'bmp': 'bmp', - 'bmp24': 'bmp', - 'bmp8': 'bmp8bit', - 'png': 'png', - 'png24': 'png'} + 'png': 'png'} TimebaseModeMapping = { 'main': 'main', 'window': 'wind', @@ -171,10 +143,9 @@ 'right': 91.67} class rohdeschwarzBaseScope(scpi.common.IdnCommand, scpi.common.ErrorQuery, scpi.common.Reset, - scope.Base, - scope.ContinuousAcquisition, - scope.TriggerModifier, + scope.Base, scope.ContinuousAcquisition, scope.TriggerModifier, scope.Interpolation, scope.WaveformMeasurement, + extra.common.Screenshot, ivi.Driver): "Rohde&Schwarz generic IVI oscilloscope driver" @@ -217,6 +188,8 @@ def __init__(self, *args, **kwargs): self._acquisition_number_of_points_minimum = 10e3 self._acquisition_record_length = 20e3 self._acquisition_record_length_automatic = True + self._acquisition_type = 'normal' + self._acquisition_interpolation = 'sinex' self._timebase_mode = 'main' self._timebase_scale = 100e-6 @@ -456,7 +429,7 @@ def _init_channels(self): self._channel_count = self._analog_channel_count + self._digital_channel_count self.channels._set_list(self._channel_name) - + # scope.Base def _get_timebase_scale(self): if not self._driver_operation_simulate and not self._get_cache_valid(): self._timebase_scale = float(self._ask("timebase:scale?")) @@ -557,8 +530,8 @@ def _set_acquisition_start_time(self, value): def _get_acquisition_type(self): if not self._driver_operation_simulate and not self._get_cache_valid(): value = [self._ask("channel:type?").lower()] - value.append(self._ask("channel:arithmetics?")) - self._acquisition_type = [k for k,v in AcquisitionTypeMapping.items() if v==value] + value.append(self._ask("channel:arithmetics?").lower()) + self._acquisition_type = [k for k,v in AcquisitionTypeMapping.items() if v==value][0] self._set_cache_valid() return self._acquisition_type @@ -575,8 +548,8 @@ def _get_acquisition_number_of_points_minimum(self): return self._get_acquisition_record_length() def _set_acquisition_number_of_points_minimum(self, value): - if value < 20e3: - value = 20e3 # Minimum number of points is 20 kSa + if value < 10e3: + value = 10e3 # Minimum number of points is 10 kSa if not self._driver_operation_simulate: self._acquisition_record_length = int(value) self._acquisition_number_of_points_minimum = self._acquisition_record_length @@ -613,7 +586,21 @@ def _set_acquisition_time_per_record(self, value): value = float(value) self._set_timebase_range(value) + # scope.Interpolation + def _get_acquisition_interpolation(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = self._ask("acquire:interpolate?").lower() + self._acquisition_interpolation = [k for k,v in AcquisitionInterpolationMapping.items() if v==value][0] + return self._acquisition_interpolation + + def _set_acquisition_interpolation(self, value): + if value not in AcquisitionInterpolationMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("acquire:interpolate %s" % AcquisitionInterpolationMapping[value]) + self._acquisition_interpolation = value + # scope.Base, continued def _get_channel_label(self, index): index = ivi.get_index(self._channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): @@ -983,4 +970,88 @@ def _get_measurement_status(self): self._set_cache_valid(False, 'trigger_continuous') return self._measurement_status - # Measurement functions \ No newline at end of file + def _measurement_fetch_waveform(self, index): + index = ivi.get_index(self._channel_name, index) + + if self._driver_operation_simulate: + return list() + + data_header = self._ask("%s:data:header?" % self._channel_name[index]).split(',') # x0, xN, record length, values/sample interval + x0 = float(data_header[0]) + xN = float(data_header[1]) + N = int(data_header[2]) + dx = (xN - x0) / N + x = [(x0 + i * dx) for i in range(0, N)] + y = self._ask("%s:data?" % self._channel_name[index]).split(',') + y = [float(yi) for yi in y] + return list(zip(x, y)) + + def _measurement_read_waveform(self, index, maximum_time=None): + # Add functionaly according to Python-IVI scope specification + return self._measurement_fetch_waveform(index) + + # extra.common + def _display_fetch_screenshot(self, format='png', invert=False): + if self._driver_operation_simulate: + return b'' + + if format not in self._display_screenshot_image_format_mapping: + raise ivi.ValueNotSupportedException() + + self._write("hcopy:format %s" % format) + self._write("hcopy:color:scheme %s" % ('color' if not invert else 'inverted')) + + screenshot = self._ask_for_ieee_block("hcopy:data?") + self._read_raw() # flush buffer + + return screenshot + + # scope.WaveformMeasurement + def _get_reference_level_high(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._reference_level_high = float(self._ask("reflevel:relative:upper?")) + self._set_cache_valid() + return self._reference_level_high + + def _set_reference_level_high(self, value): + value = float(value) + if value < 0: value = 0 + if value > 100: value = 100 + if not self._driver_operation_simulate: + self._write("reflevel:relative:mode user") + self._write("reflevel:relative:upper %e" % value) + self._reference_level_high = value + self._set_cache_valid() + + def _get_reference_level_low(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._reference_level_low = float(self._ask(":measurement:reflevel:percent:low?")) + self._set_cache_valid() + return self._reference_level_low + + def _set_reference_level_low(self, value): + value = float(value) + if value < 0: value = 0 + if value > 100: value = 100 + if not self._driver_operation_simulate: + self._write("reflevel:relative:mode user") + self._write("reflevel:relative:lower %e" % value) + self._reference_level_low = value + self._set_cache_valid() + + def _get_reference_level_middle(self): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._reference_level_middle = float(self._ask("reflevel:relative:middle?")) + self._set_cache_valid() + return self._reference_level_middle + + def _set_reference_level_middle(self, value): + value = float(value) + if value < 0: value = 0 + if value > 100: value = 100 + if not self._driver_operation_simulate: + self._write("reflevel:relative:mode user") + self._write("reflevel:relative:middle %e" % value) + self._reference_level_middle = value + self._set_cache_valid() + diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py index a17fcbd6..1ec9335c 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py @@ -48,3 +48,4 @@ def __init__(self, *args, **kwargs): self._channel_offset_max = 1.2 self._init_channels() + From f86ef55a8e4187f075460bb3285e4f259d0dde1b Mon Sep 17 00:00:00 2001 From: Baijualex <35258271+Baijualex@users.noreply.github.com> Date: Thu, 8 Feb 2018 13:29:53 +0100 Subject: [PATCH 07/11] Add support for Rohde Schwarz RF signal generator SMC100A (#1) Add support for Rohde Schwarz RF signal generator SMC100A --- README.md | 1 + ivi/__init__.py | 1 + ivi/rohdeschwarz/__init__.py | 31 +++ ivi/rohdeschwarz/rohdeschwarzBaseRFSigGen.py | 198 +++++++++++++++++++ ivi/rohdeschwarz/rohdeschwarzSMC100A.py | 49 +++++ setup.py | 1 + 6 files changed, 281 insertions(+) create mode 100644 ivi/rohdeschwarz/__init__.py create mode 100644 ivi/rohdeschwarz/rohdeschwarzBaseRFSigGen.py create mode 100644 ivi/rohdeschwarz/rohdeschwarzSMC100A.py diff --git a/README.md b/README.md index 36c2180e..92c9bc59 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Instrument standard from the [IVI foundation](http://www.ivifoundation.org/). * Agilent 8340/1 A/B * Agilent 8642 A/B * Agilent ESG E4400B series + * Rohde and Schwarz SMC100A * Other * Agilent 8156A optical attenuator * Agilent 85644/5A tracking source diff --git a/ivi/__init__.py b/ivi/__init__.py index 82efcc87..79ae0ac3 100644 --- a/ivi/__init__.py +++ b/ivi/__init__.py @@ -52,6 +52,7 @@ "jdsu", "lecroy", "rigol", + "rohdeschwarz", "tektronix", "testequity"] diff --git a/ivi/rohdeschwarz/__init__.py b/ivi/rohdeschwarz/__init__.py new file mode 100644 index 00000000..3508b274 --- /dev/null +++ b/ivi/rohdeschwarz/__init__.py @@ -0,0 +1,31 @@ +# coding=utf-8 +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) Acconeer AB, 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +# RF Signal generators +# SMC 100A +from .rohdeschwarzSMC100A import rohdeschwarzSMC100A + diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseRFSigGen.py b/ivi/rohdeschwarz/rohdeschwarzBaseRFSigGen.py new file mode 100644 index 00000000..e86900a2 --- /dev/null +++ b/ivi/rohdeschwarz/rohdeschwarzBaseRFSigGen.py @@ -0,0 +1,198 @@ +# coding=utf-8 +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) Acconeer AB, 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .. import ivi +from .. import scpi +from .. import rfsiggen + +class rohdeschwarzBaseRFSigGen(scpi.common.IdnCommand, scpi.common.Reset, scpi.common.ErrorQuery, + ivi.Driver, rfsiggen.Base): + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', '') + + super(rohdeschwarzBaseRFSigGen, self).__init__(*args, **kwargs) + + self._rf_rms_voltage_level = 0.0 + + # frequency limit in Hertz + self._frequency_low = 9e3 + self._frequency_high = 1100e6 + # rf level limit in dBm + self._rf_level_low = -120.0 + self._rf_level_high = 19 + # rf rms voltage level limit in Volt + self._rf_rms_voltage_level_low = 223.61e-9 + self._rf_rms_voltage_level_high = 1.993 + + self._identity_description = "Rohde&Schwarz generic IVI RF signal generator driver" + self._identity_identifier = "" + self._identity_revision = "" + self._identity_vendor = "" + self._identity_instrument_manufacturer = "Rohde&Schwarz" + self._identity_instrument_model = "" + self._identity_instrument_firmware_revision = "" + self._identity_specification_major_version = 1 + self._identity_specification_minor_version = 0 + self._identity_supported_instrument_models = list(['SMC100A']) + + self._add_property('rf.rms_voltage_level', + self._get_rf_rms_voltage_level, + self._set_rf_rms_voltage_level, + None, + ivi.Doc(""" + Rms voltage level of the instrument. + """)) + + def _initialize(self, resource = None, id_query = False, reset = False, **keywargs): + "Opens an I/O session to the instrument." + + super(rohdeschwarzBaseRFSigGen, self)._initialize(resource, id_query, reset, **keywargs) + + # interface clear + if not self._driver_operation_simulate: + self._clear() + + # check ID + if id_query and not self._driver_operation_simulate: + id = self.identity.instrument_model + id_check = self._instrument_id + id_short = id[:len(id_check)] + if id_short != id_check: + raise Exception("Instrument ID mismatch, expecting %s, got %s", id_check, id_short) + + # reset + if reset: + self.utility.reset() + + def _get_rf_frequency(self): + "Reads the frequency of the generated RF output signal. The unit is Hertz" + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._rf_frequency = float(self._ask("FREQ?")) + self._set_cache_valid() + return self._rf_frequency + + def _set_rf_frequency(self, value): + "Specifies the frequency of the generated RF output signal. The unit is Hertz" + value = float(value) + if value < self._frequency_low or value > self._frequency_high: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("FREQ %e HZ" % value) + self._rf_frequency = value + self._set_cache_valid() + + def _get_rf_level(self): + """ + return set value in dBm of rf output level containing offset. + """ + return self._rf_level + + def _set_rf_level(self, value): + """ + Sets the level of the Level display, i.e. the level containing offset. The unit is dBm + """ + value = float(value) + if value < self._rf_level_low or value > self._rf_level_high: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("POW %e dBm" % value) + self._rf_level = value + self._rf_rms_voltage_level = self._dbm_to_rms(value) + self._set_cache_valid() + + def _get_rf_rms_voltage_level(self): + """ + return set value in volt of rf output level containing offset. + """ + return self._rf_rms_voltage_level + + def _set_rf_rms_voltage_level(self, value): + """ + Sets the rms voltage level of the Level display, i.e. the level containing offset. The unit is Volt + """ + value = float(value) + if value < self._rf_rms_voltage_level_low or value > self._rf_rms_voltage_level_high: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("POW %e V" % value) + self._rf_rms_voltage_level = value + self._rf_level = self._rms_to_dbm(value) + self._set_cache_valid() + + def _get_rf_output_enabled(self): + "Check if RF output is enabled, Returns True if enabled" + return self._rf_output_enabled + + def _set_rf_output_enabled(self, value): + """ + If value is non zero, the signal the RF signal generator produces appears at the output connector. + If it is zero, the signal the RF signal generator produces does not appear at the output connector + """ + value = bool(value) + if not self._driver_operation_simulate: + if value: + self._write("OUTP ON") + else: + self._write("OUTP OFF") + self._rf_output_enabled = value + self._set_cache_valid() + + def _rf_disable_all_modulation(self): + "Disables modulation, similar result as pressing MOD on/off." + if not self._driver_operation_simulate: + self._write("MOD OFF") + + def _rf_is_settled(self): + "Queries if the RF output signal is currently settled. Returns true if settled" + if not self._driver_operation_simulate: + return self._read_stb() & (1 << 7) == 0 + return True + + def _rf_wait_until_settled(self, maximum_time): + "This function waits maximumtime(milli seconds) until the state of the RF output signal has settled." + t = 0 + while not self._rf_is_settled() and t < maximum_time: + time.sleep(0.001) + t = t + 0.001 + + def _rms_to_dbm(self, v, r=50): + "This function converts rms volts v to dBm, r is resistance in ohms." + return (10 * ivi.np.log10(ivi.np.abs(v** 2.)/r) + 30) + + def _dbm_to_rms(self, p, r=50): + "This function converts dBm to rms volts, r is resistance in ohms." + return (ivi.np.sqrt(r*(10.**(p/10.))/1000)) + + def _utility_error_query(self): + error_code = 0 + error_message = "No error" + if not self._driver_operation_simulate: + error_msg_list = self._ask("system:error?").split(',') + error_code = int(error_msg_list[0]) + error_message = ''.join(error_msg_list[1:]).strip(' "') + return (error_code, error_message) diff --git a/ivi/rohdeschwarz/rohdeschwarzSMC100A.py b/ivi/rohdeschwarz/rohdeschwarzSMC100A.py new file mode 100644 index 00000000..24c27e9f --- /dev/null +++ b/ivi/rohdeschwarz/rohdeschwarzSMC100A.py @@ -0,0 +1,49 @@ +# coding=utf-8 +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) Acconeer AB, 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .rohdeschwarzBaseRFSigGen import * + + +class rohdeschwarzSMC100A(rohdeschwarzBaseRFSigGen): + "Rohde&Schwarz SMC 100A RF Signal Generator" + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', 'SMC100A') + + super(rohdeschwarzSMC100A, self).__init__(*args, **kwargs) + + # frequency limit in Hertz + self._frequency_low = 9e3 + self._frequency_high = 1100e6 + # rf level limit in dBm + self._rf_level_low = -120.0 + self._rf_level_high = 19 + # rf rms voltage level limit in Volt + self._rf_rms_voltage_level_low = 223.61e-9 + self._rf_rms_voltage_level_high = 1.993 + + self._initialize(*args, **kwargs) diff --git a/setup.py b/setup.py index e9484085..c831bee2 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ def run_tests(self): 'ivi.jdsu', 'ivi.lecroy', 'ivi.rigol', + 'ivi.rohdeschwarz', 'ivi.tektronix', 'ivi.testequity'], requires = ['numpy'], From ec88e59a45fa70c9b435d2f322d1acf2c867553b Mon Sep 17 00:00:00 2001 From: avicent Date: Mon, 16 Apr 2018 16:08:13 +0200 Subject: [PATCH 08/11] Keithley 2280S (#2) * Driver for Keithley 2280S power supply * Added more instrument functionality * Add ADC auto-zero functionality for faster sampling rates. * Add more measurement functionality. Make all functions support multichannel instruments. * Add more measurement functionality * Add more trigger functionality * Change _output_fetch_measurement() so that all data is fetched in a single instrument call --- README.md | 1 + ivi/__init__.py | 1 + ivi/interface/pyvisa.py | 3 + ivi/keithley/__init__.py | 29 ++ ivi/keithley/keithley2280S.py | 709 ++++++++++++++++++++++++++++++++++ setup.py | 1 + 6 files changed, 744 insertions(+) create mode 100644 ivi/keithley/__init__.py create mode 100644 ivi/keithley/keithley2280S.py diff --git a/README.md b/README.md index 92c9bc59..434b6762 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Instrument standard from the [IVI foundation](http://www.ivifoundation.org/). * Chroma 62000P series * Rigol DP800 series * Rigol DP1000 series + * Keithley (Tektronix) 2280S series * Tektronix PS2520G/PS2521G * RF Power Meters (pwrmeter): * Agilent 436A diff --git a/ivi/__init__.py b/ivi/__init__.py index 79ae0ac3..aaa2bf13 100644 --- a/ivi/__init__.py +++ b/ivi/__init__.py @@ -50,6 +50,7 @@ "colby", "ics", "jdsu", + "keithley", "lecroy", "rigol", "rohdeschwarz", diff --git a/ivi/interface/pyvisa.py b/ivi/interface/pyvisa.py index 29f0712e..23e6b36d 100644 --- a/ivi/interface/pyvisa.py +++ b/ivi/interface/pyvisa.py @@ -34,6 +34,9 @@ # New style PyVISA visa_rm = visa.ResourceManager() visa_instrument_opener = visa_rm.open_resource + except OSError: + visa_rm = visa.ResourceManager('@py') + visa_instrument_opener = visa_rm.open_resource except AttributeError: # Old style PyVISA visa_instrument_opener = visa.instrument diff --git a/ivi/keithley/__init__.py b/ivi/keithley/__init__.py new file mode 100644 index 00000000..a64334a0 --- /dev/null +++ b/ivi/keithley/__init__.py @@ -0,0 +1,29 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Jonas Långbacka, Acconeer Ab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +# DC Power Supplies +from .keithley2280S import keithley2280S +#from .keithley2280S import keithley2280S_60_3 \ No newline at end of file diff --git a/ivi/keithley/keithley2280S.py b/ivi/keithley/keithley2280S.py new file mode 100644 index 00000000..aaf0849b --- /dev/null +++ b/ivi/keithley/keithley2280S.py @@ -0,0 +1,709 @@ +""" + +Python Interchangeable Virtual Instrument Library + +Copyright (c) 2017 Acconeer Ab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" +from .. import ivi +from .. import scpi +from .. import dcpwr +from .. import extra + +TrackingType = set(['floating']) +TriggerSourceMapping = { + 'immediate': 'imm', + 'external': 'ext', + 'manual': 'man'} +RangeType = set(['voltage','current']) +MeasurementTypeMapping = { + 'voltage': "volt:dc", + 'current': "curr:dc", + 'concurrent': "conc:dc"} +BufferDataTypeMapping = { + 'current': ['read', float], + 'voltage': ['sour', float], + 'unit': ['unit', str], + 'mode': ['mode', str], + 'date': ['date', str], + 'time': ['time', str], + 'time_stamp': ['tstamp', str], + 'relative_time_seconds': ['rel', float], + 'relative_time': ['rstamp', str], + 'buffer_index': ['rnumber', int]} +TriggerDirection = set(['rise', 'fall']) +#MeasurementFunctionMapping = { +# 'minimum': 'min', +# 'maximum': 'max', +# 'average': 'mean', +# 'peak_to_peak': 'pkpk', +# 'standard_deviation': 'sdev'} + +class keithley2280S(scpi.dcpwr.Base, scpi.dcpwr.SoftwareTrigger, + dcpwr.Measurement, + extra.dcpwr.OCP): + "Keithley (Tektronix) 2280S series precision measurement DC supply driver" + + def __init__(self, *args, **kwargs): + self.__dict__.setdefault('_instrument_id', 'keithley2280S') + + super(keithley2280S, self).__init__(*args, **kwargs) + + self._output_count = 1 + + self._output_spec = [ + { + 'range': { + 'P32V': (32.0, 6.0) + }, + 'ovp_max': 33.0, + 'ocp_max': 6.1, + 'voltage_max': 32.0, + 'current_max': 6.0 + } + ] + + self._memory_size = 2500 + self._memory_offset = 0 + + self._output_trigger_delay = list() + + self._identity_description = "Keithley (Tektronix) 2280S series precision measurement DC supply driver" + self._identity_identifier = "" + self._identity_revision = "" + self._identity_vendor = "" + self._identity_instrument_manufacturer = "Keithley (Tektronix)" + self._identity_instrument_model = "" + self._identity_instrument_firmware_revision = "" + self._identity_specification_major_version = 3 + self._identity_specification_minor_version = 0 + self._identity_supported_instrument_models = ['2280S-32-6', '2280S-60-3'] + + self._add_property('outputs[].adc_auto_zero', + self._get_output_adc_autozero, + self._set_output_adc_autozero) + + self._add_property('outputs[].number_of_digits', + self._get_output_number_of_digits, + self._set_output_number_of_digits) + + self._add_property('outputs[].number_of_power_line_cycles', + self._get_output_number_of_power_line_cycles, + self._set_output_number_of_power_line_cycles) + + self._add_property('outputs[].trace_points', + self._get_output_trace_points, + self._set_output_trace_points) + + self._add_property('outputs[].trigger_count', + self._get_output_trigger_count, + self._set_output_trigger_count) + + self._add_property('outputs[].trigger_current_level', + self._get_output_trigger_current_level, + self._set_output_trigger_current_level) + + self._add_property('outputs[].trigger_voltage_level', + self._get_output_trigger_voltage_level, + self._set_output_trigger_voltage_level) + + self._add_property('outputs[].trigger_current_direction', + self._get_output_trigger_current_direction, + self._set_output_trigger_current_direction) + + self._add_property('outputs[].trigger_voltage_direction', + self._get_output_trigger_voltage_direction, + self._set_output_trigger_voltage_direction) + + self._add_property('outputs[].trigger_current_state', + self._get_output_trigger_current_state, + self._set_output_trigger_current_state) + + self._add_property('outputs[].trigger_voltage_state', + self._get_output_trigger_voltage_state, + self._set_output_trigger_voltage_state) + + self._add_property('outputs[].trigger_sample_count', + self._get_output_trigger_sample_count, + self._set_output_trigger_sample_count) + + self._add_property('outputs[].trigger_continuous', + self._get_output_trigger_continuous, + self._set_output_trigger_continuous) + + self._add_method('trigger.initiate', + self._trigger_initiate) + + self._add_method('trigger.abort', + self._trigger_abort) + + self._add_property('outputs[].trigger_source', + self._get_output_trigger_source, + self._set_output_trigger_source) + + self._add_property('outputs[].measurement_type', + self._get_output_measurement_type, + self._set_output_measurement_type) + + self._add_property('outputs[].measurement_range', + self._get_output_measurement_range, + self._set_output_measurement_range) + + self._add_method('outputs[].configure_measurement', + self._output_configure_measurement, + ivi.Doc(""" + Configure measurement parameters such as measurement type (e.g. source voltage, + current, time stamp, ...) number of samples, resolution, sampling interval in + terms of number of power line cycles (NPLC), measurement range, trigger options. + """)) + + self._add_method('outputs[].fetch_measurement', + self._output_fetch_measurement, + ivi.Doc(""" + Fetch measurement data from buffer memory. Either fetch all measurement data or + a specific interval. + """)) + + self._add_method('outputs[].clear_buffer', + self._output_clear_buffer) + + self._add_property('outputs[].auto_clear_buffer', + self._get_output_auto_clear_buffer, + self._set_output_auto_clear_buffer) + + self._add_method('system.query_power_line_frequeny', + self._system_query_power_line_frequency, + ivi.Doc(""" + Get power line frequency (either 50 Hz or 60 Hz). + """)) + + self._init_outputs() + + def _initialize(self, resource=None, id_query=False, reset=False, **keywargs): + "Opens an I/O session to the instrument." + + super(keithley2280S, self)._initialize(resource, id_query, reset, **keywargs) + + # interface clear + if not self._driver_operation_simulate: + self._clear() + self._system_query_power_line_frequency() + + # check ID + if id_query and not self._driver_operation_simulate: + id = self.identity.instrument_model + id_check = self._instrument_id + id_short = id[:len(id_check)] + if id_short != id_check: + raise Exception("Instrument ID mismatch, expecting %s, got %s", id_check, id_short) + + # reset + if reset: + self.utility_reset() + + def _utility_disable(self): + pass + + def _utility_lock_object(self): + pass + + def _utility_unlock_object(self): + pass + + def _init_outputs(self): + try: + super(keithley2280S, self)._init_outputs() + except AttributeError: + pass + + self._output_current_limit = list() + self._output_current_limit_behavior = list() + self._output_enabled = list() + self._output_ovp_enabled = list() + self._output_ovp_limit = list() + self._output_voltage_level = list() + self._output_trace_points = list() + self._output_trigger_source = list() + self._output_trigger_delay = list() + self._output_trigger_count = list() + self._output_trigger_continuous = list() + self._output_trigger_current_level = list() + self._output_trigger_current_direction = list() + self._output_trigger_current_state = list() + self._output_trigger_voltage_level = list() + self._output_trigger_voltage_direction = list() + self._output_trigger_voltage_state = list() + self._output_trigger_sample_count = list() + self._output_number_of_power_line_cycles = list() + self._output_number_of_digits = list() + self._output_measurement_type = list() + self._output_measurement_range = list() + self._output_adc_autozero = list() + self._output_auto_clear_buffer = list() + for i in range(self._output_count): + self._output_current_limit.append(0) + self._output_current_limit_behavior.append('regulate') + self._output_enabled.append(False) + self._output_ovp_enabled.append(True) + self._output_ovp_limit.append(0) + self._output_trace_points.append(100) + self._output_voltage_level.append(0) + self._output_trigger_source.append('bus') + self._output_trigger_delay.append(0) + self._output_trigger_count.append(1) + self._output_trigger_continuous.append(True) + self._output_trigger_current_level.append(0.0) + self._output_trigger_current_direction.append('rise') + self._output_trigger_current_state.append(False) + self._output_trigger_voltage_level.append(0.0) + self._output_trigger_voltage_direction.append('rise') + self._output_trigger_voltage_state.append(False) + self._output_trigger_sample_count.append(1) + self._output_number_of_power_line_cycles.append(1) + self._output_number_of_digits.append(6) + self._output_measurement_type.append('concurrent') + self._output_measurement_range.append(0.01) + self._output_adc_autozero.append(True) + self._output_auto_clear_buffer.append(True) + + def _get_output_adc_autozero(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_adc_autozero = (self._ask("system:azero%d:state?" % (index+1)) == '1') + self._set_cache_valid(index=index) + return self._output_auto_zero + + def _set_output_adc_autozero(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("system:azero%d:state %s" % (index+1,('off', 'on')[value])) + self._set_cache_valid(index=index) + self._output_adc_autozero = value + + def _get_output_number_of_power_line_cycles(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + # Need to write initialized measurement type for the operation to be processed + self._output_number_of_power_line_cycles[index] = self._ask(":sense%d:%s:nplcycles?" % (index+1, self._output_measurement_type[index])) + self._set_cache_valid(index=index) + return self._output_number_of_power_line_cycles + + def _set_output_number_of_power_line_cycles(self, index, value): + index = ivi.get_index(self._output_name, index) + if self._system_power_line_frequency == 50: + if value < 0.002 or value > 15: + raise ivi.OutOfRangeException('Number of power line cycles (NPLC) must be 0.002 < NPLC < 15 for 50 Hz power line frequency') + if self._system_power_line_frequency == 60: + if value < 0.002 or value > 12: + raise ivi.OutOfRangeException('Number of power line cycles (NPLC) must be 0.002 < NPLC < 12 for 60 Hz power line frequency') + if not self._driver_operation_simulate: + # Need to write initialized measurement type for the operation to be processed + self._write(":sense%d:%s:nplcycles %f" % (index+1, self._output_measurement_type[index], float(value))) + self._set_cache_valid(index=index) + self._output_number_of_power_line_cycles[index] = value + + def _get_output_number_of_digits(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + # Need to write initialized measurement type for the operation to be processed + self._output_number_of_digits[index] = self._ask(":sense%d:%s:digits?" % (index+1, self._output_measurement_type[index])) + self._set_cache_valid(index=index) + return self._output_number_of_digits + + def _set_output_number_of_digits(self, index, value): + index = ivi.get_index(self._output_name, index) + if (value < 4) or (value > 6): + raise ivi.OutOfRangeException('Number of measurement digits must be between 4 and 6') + if not self._driver_operation_simulate: + # Need to write initialized measurement type for the operation to be processed + self._write(":sense%d:%s:digits %d" % (index+1, self._output_measurement_type[index], value)) + self._set_cache_valid(index=index) + self._output_number_of_power_line_cycles[index] = value + + def _get_output_current_limit_behavior(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._output_current_limit_behavior[index] = 'regulate' + self._set_cache_valid(index=index) + return self._output_current_limit_behavior[index] + + def _set_output_current_limit_behavior(self, index, value): + raise ivi.ValueNotSupportedException() + + def _get_output_ovp_enabled(self, index): + # Alwayas enabled by default + return True + + def _set_output_ovp_enabled(self, index, value): + # Alwayas enabled by default + raise ivi.ValueNotSupportedException() + + def _get_output_ovp_limit(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._output_ovp_limit[index] = float(self._ask("source%d:voltage:protection:level?" % (index+1))) + self._set_cache_valid(index=index) + return self._output_ovp_limit[index] + + def _set_output_ovp_limit(self, index, value): + index = ivi.get_index(self._output_name, index) + value = float(value) + if abs(value) > self._output_spec[index]['ovp_max']: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("source%d:voltage:protection:level %f" % (index+1, float(value))) + self._output_ovp_limit[index] = value + self._set_cache_valid(index=index) + + def _get_output_ocp_limit(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(index=index): + self._output_ocp_limit[index] = float(self._ask("source%d:current:protection:level?" % (index+1))) + self._set_cache_valid(index=index) + return self._output_ocp_limit[index] + + def _set_output_ocp_limit(self, index, value): + index = ivi.get_index(self._output_name, index) + value = float(value) + if abs(value) > self._output_spec[index]['ocp_max']: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("source%d:current:protection:level %f" % (index+1,float(value))) + self._output_ocp_limit[index] = value + self._set_cache_valid(index=index) + + def _output_reset_output_protection(self): + if not self._driver_operation_simulate: + self._write("output:protection:clear") + + def _get_output_trace_points(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trace_points[index] = int(self._ask("trace%d:points?" % (index+1))) + self._get_cache_valid() + return self._output_trace_points[index] + + def _set_output_trace_points(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + if value > 2500: + raise ivi.OutOfRangeException() + self._write("trace%d:points %d" % (index+1, int(value))) + self._output_trace_points[index] = int(value) + self._set_cache_valid() + + def _get_output_trigger_current_direction(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_current_direction[index] = self._ask("trace%d:trigger:current:direction?" % (index+1)) + self._get_cache_valid() + return self._output_trigger_current_direction[index] + + def _set_output_trigger_current_direction(self, index, value): + index = ivi.get_index(self._output_name, index) + if value not in TriggerDirection: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("trace%d:trigger:current:direction %s" % (index+1, value)) + self._output_trigger_current_direction[index] = value.lower() + self._set_cache_valid() + + def _get_output_trigger_current_level(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_current_state[index] = float(self._ask("trace%d:trigger:current:level?" % (index+1))) + self._get_cache_valid() + return self._output_trigger_current_level[index] + + def _set_output_trigger_current_level(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:trigger:current:level %f" % (index+1, float(value))) + self._output_trigger_current_level[index] = float(value) + self._set_cache_valid() + + def _get_output_trigger_current_state(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_current_state[index] = bool(int(self._ask("trace%d:trigger:current:state?" % (index+1)))) + self._get_cache_valid() + return self._output_trigger_current_state[index] + + def _set_output_trigger_current_state(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:trigger:current:state %d" % (index+1, (0, 1)[value])) + self._output_trigger_current_state[index] = value + self._set_cache_valid() + + def _get_output_trigger_voltage_direction(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_voltage_direction[index] = self._ask("trace%d:trigger:voltage:direction?" % (index+1)) + self._get_cache_valid() + return self._output_trigger_voltage_direction[index] + + def _set_output_trigger_voltage_direction(self, index, value): + index = ivi.get_index(self._output_name, index) + if value not in TriggerDirection: + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("trace%d:trigger:voltage:direction %s" % (index+1, value)) + self._output_trigger_voltage_level[index] = value.lower() + self._set_cache_valid() + + def _get_output_trigger_voltage_state(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_voltage_state[index] = bool(int(self._ask("trace%d:trigger:voltage:state?" % (index+1)))) + self._get_cache_valid() + return self._output_trigger_voltage_state[index] + + def _set_output_trigger_voltage_state(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:trigger:voltage:state %d" % (index+1, (0, 1)[value])) + self._output_trigger_voltage_state[index] = value + self._set_cache_valid() + + def _get_output_trigger_voltage_level(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_voltage_state[index] = float(self._ask("trace%d:trigger:voltage:level?" % (index+1))) + self._get_cache_valid() + return self._output_trigger_voltage_level[index] + + def _set_output_trigger_voltage_level(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:trigger:voltage:level %f" % (index+1, float(value))) + self._output_trigger_voltage_level[index] = float(value) + self._set_cache_valid() + + def _get_output_trigger_source(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate and not self._get_cache_valid(): + value = self._ask("trigger:sequence%d:source?" % (index+1)).lower() + self._output_trigger_source[index] = [k for k,v in TriggerSourceMapping.items() if v==value][0] + return self._output_trigger_source[index] + + def _set_output_trigger_source(self, index, value): + index = ivi.get_index(self._output_name, index) + value = str(value) + if value not in TriggerSourceMapping: + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("trigger:sequence%d:source %s" % (index+1, TriggerSourceMapping[value])) + self._output_trigger_source = value + self._set_cache_valid() + + def _get_output_trigger_count(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_count[index] = int(self._ask("trigger:sequence%d:count?" % (index+1))) + self._set_cache_valid() + return self._output_trigger_count[index] + + def _set_output_trigger_count(self, index, value): + index = ivi.get_index(self._output_name, index) + if (value < 0) or (value > 2500): + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("trigger:sequence%d:count %d" % (index+1, value)) + self._output_trigger_count[index] = value + self._set_cache_valid(index=index) + + def _get_output_trigger_sample_count(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_sample_count[index] = int(self._ask("trigger:sequence%d:sample:count?" % (index+1))) + self._set_cache_valid(index=index) + return self._output_trigger_sample_count[index] + + def _set_output_trigger_sample_count(self, index, value): + index = ivi.get_index(self._output_name, index) + if (value < 0) or (value > 2500): + raise ivi.OutOfRangeException() + if not self._driver_operation_simulate: + self._write("trigger:sequence%d:sample:count %d" % (index+1, value)) + self._output_trigger_sample_count[index] = value + self._set_cache_valid(index=index) + + def _get_output_trigger_continuous(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_trigger_continuous[index] = self._ask("initiate%d:continuous?" % (index+1)) == '1' + self._set_cache_valid() + return self._output_trigger_continuous[index] + + def _set_output_trigger_continuous(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("initiate%d:continuous %s" % (index+1, ('off', 'on')[value])) + self._output_trigger_continuous[index] = value + self._set_cache_valid(index=index) + + def _trigger_abort(self): + if not self._driver_operation_simulate: + self._write("abort") # TODO: output dependent trigger abort + + def _trigger_initiate(self): + if not self._driver_operation_simulate: + self._write("initiate") #TODO: Output dependent trigger initiate + + def _get_output_measurement_type(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + value = self._ask("sense%d:function?" % (index+1)).lower()[1:-1] + self._output_measurement_type[index] = [k for k,v in MeasurementTypeMapping.items() if v==value][0] + self._set_cache_valid(index=index) + return self._output_measurement_type[index] + + def _set_output_measurement_type(self, index, type): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("sense%d:function \"%s\"" % (index+1, type)) + self._output_measurement_type[index] = type + self._set_cache_valid(index=index) + + def _get_output_measurement_range(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_measurement_range[index] = float(self._ask("sense%d:%s:range?" % (index+1, self._output_measurement_type[index]))) #TODO: add auto range + self._set_cache_valid(index=index) + return self._output_measurement_range[index] + + def _set_output_measurement_range(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("sense%d:%s:range %s" % (index+1, self._output_measurement_type[index], value)) #TODO: add auto range + self._output_measurement_range[index] = value + self._set_cache_valid(index=index) + + def _get_output_auto_clear_buffer(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._output_auto_clear_buffer[index] = (self._ask("trace%d:clear:auto?" % (index+1)) == '1') + self._set_cache_valid(index=index) + return self._output_auto_clear_buffer[index] + + def _set_output_auto_clear_buffer(self, index, value): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:clear:auto %s" % (index+1, ('off', 'on')[value])) + self._output_auto_clear_buffer[index] = value + self._set_cache_valid(index=index) + + def _output_clear_buffer(self, index): + index = ivi.get_index(self._output_name, index) + if not self._driver_operation_simulate: + self._write("trace%d:clear" % (index+1)) + + def _output_measure(self, index, type): + index = ivi.get_index(self._output_name, index) + if type not in set(['voltage', 'current']): + raise ivi.ValueNotSupportedException() + if not self._driver_operation_simulate: + self._write("form:elem \"read\"") + if type == 'voltage': + if not self._driver_operation_simulate: + return float(self._ask("measure:voltage?")) + if type == 'current': + return float(self._ask("measure:current?")) + return 0 + + def _output_configure_measurement(self, index, type, sample_count=None, trigger_continuous=None, NPLC=None, + measurement_digits=None, measurement_range=None, adc_autozero=None, auto_clear_buffer=None): + index = ivi.get_index(self._output_name, index) + if type not in MeasurementTypeMapping.keys(): + raise ivi.ValueNotSupportedException() + self._set_output_measurement_type(index, type) + + if sample_count is not None: + self._set_output_trigger_sample_count(index, sample_count) + if trigger_continuous is not None: + self._set_output_trigger_continuous(index, trigger_continuous) + if NPLC is not None: + self._set_output_number_of_power_line_cycles(index, NPLC) + if measurement_digits is not None: + self._set_output_number_of_digits(index, measurement_digits) + if measurement_range is not None: + self._set_output_measurement_range(index, measurement_range) + if adc_autozero is not None: + self._set_output_adc_autozero(index, adc_autozero) + if auto_clear_buffer is not None: + self._set_output_auto_clear_buffer(index, auto_clear_buffer) + + if not self._driver_operation_simulate: + # extend buffer memory size if sample_count exceeds configured buffer size + if sample_count is not None: + if int(self._ask(":trace:points?")) < sample_count: + self._set_output_trace_points(index, sample_count) + + def _output_fetch_measurement(self, index, measurement_type, buffer_range=None): + index = ivi.get_index(self._output_name, index) + # type can be multiple elements so need to check that all are valid + if type(measurement_type) in (tuple, list): + for t in measurement_type: + if t not in BufferDataTypeMapping: + raise ivi.ValueNotSupportedException() + elif type(measurement_type) is str: + if measurement_type not in BufferDataTypeMapping: + raise ivi.ValueNotSupportedException() + # make measurement_type a list so that we do not loop through the characters + measurement_type = [measurement_type] + else: + raise ivi.InvalidOptionValueException() + + # Check that buffer_range is a valid tuple or list + if buffer_range is not None: + if type(buffer_range) not in (tuple, list): + raise ivi.ValueNotSupportedException("buffer_range must be tuple or list of length 2") + if buffer_range[1] > self._memory_size: + raise ivi.ValueNotSupportedException("buffer_range buffer size is %d" % self._memory_size) + + scpi_string = '' + for m in measurement_type: + scpi_string += BufferDataTypeMapping[m][0] + ',' + if buffer_range is None: + scpi_string = "trace:data? \"%s\"" % scpi_string[:-1] # Add scpi command and remove last comma + else: + scpi_string = "trace:data:sel? %d, %d, \"%s\"" % (buffer_range[0], buffer_range[1], scpi_string[-1]) + + buffer_data = [] + + # Fetch data + buffer_raw_data = self._ask(scpi_string).split(',') + i = 0 + for m in measurement_type: + buffer_data_i = [BufferDataTypeMapping[m][1](value) for value in buffer_raw_data[i::len(measurement_type)]] + i += 1 + # If multiple data types where requested, return a list of measurement sequences + if len(measurement_type) > 1: + buffer_data.append(buffer_data_i) + else: + return buffer_data_i + return buffer_data + + def _system_query_power_line_frequency(self): + if not self._driver_operation_simulate: + self._system_power_line_frequency = int(self._ask("system:lfr?")) + return self._system_power_line_frequency \ No newline at end of file diff --git a/setup.py b/setup.py index c831bee2..2dab02b0 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ def run_tests(self): 'ivi.dicon', 'ivi.ics', 'ivi.jdsu', + 'ivi.keithley', 'ivi.lecroy', 'ivi.rigol', 'ivi.rohdeschwarz', From 776c75a922754cb0df0e706b8a283a3f42dcee3e Mon Sep 17 00:00:00 2001 From: JonasLang Date: Thu, 3 May 2018 11:55:22 +0200 Subject: [PATCH 09/11] Remove pyvisa-py backend exception from pyvisa.py. --- ivi/interface/pyvisa.py | 3 --- ivi/rohdeschwarz/__init__.py | 3 ++- ivi/rohdeschwarz/rohdeschwarzRTB2002.py | 11 +++++++---- ivi/rohdeschwarz/rohdeschwarzRTB2004.py | 1 - 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ivi/interface/pyvisa.py b/ivi/interface/pyvisa.py index f6ffc3c9..29f0712e 100644 --- a/ivi/interface/pyvisa.py +++ b/ivi/interface/pyvisa.py @@ -37,9 +37,6 @@ except AttributeError: # Old style PyVISA visa_instrument_opener = visa.instrument - except OSError: - visa_rm = visa.ResourceManager('@py') - visa_instrument_opener = visa_rm.open_resource except ImportError: # PyVISA not installed, pass it up raise ImportError diff --git a/ivi/rohdeschwarz/__init__.py b/ivi/rohdeschwarz/__init__.py index 10ac5760..589a195d 100644 --- a/ivi/rohdeschwarz/__init__.py +++ b/ivi/rohdeschwarz/__init__.py @@ -27,4 +27,5 @@ # Oscilloscopes # RTB2000 series from .rohdeschwarzRTB2002 import rohdeschwarzRTB2002 -from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 \ No newline at end of file +from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 + diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2002.py b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py index 08efdfe9..f5d31c9a 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2002.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2002.py @@ -2,7 +2,7 @@ Python Interchangeable Virtual Instrument Library -Copyright (c) 2017 Acconeer AB +Copyright (c) 2017-2018 Acconeer AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -33,11 +33,14 @@ def __init__(self, *args, **kwargs): self.__dict__.setdefault('_instrument_id', 'RTB2002') super(rohdeschwarzRTB2002, self).__init__(*args, **kwargs) - + self._analog_channel_count = 2 self._digital_channel_count = 16 self._channel_count = self._analog_channel_count + self._digital_channel_count self._bandwidth = 70e6 - - self._init_channels() + self._horizontal_divisions = 12 + self._vertical_divisions = 10 + self._trigger_holdoff_min = 51.2e-9 + self._channel_offset_max = 1.2 + self._init_channels() diff --git a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py index 1ec9335c..a17fcbd6 100644 --- a/ivi/rohdeschwarz/rohdeschwarzRTB2004.py +++ b/ivi/rohdeschwarz/rohdeschwarzRTB2004.py @@ -48,4 +48,3 @@ def __init__(self, *args, **kwargs): self._channel_offset_max = 1.2 self._init_channels() - From f239ddda764ba8f408f699eee108d8c47c5d46aa Mon Sep 17 00:00:00 2001 From: JonasLang Date: Thu, 31 May 2018 16:30:02 +0200 Subject: [PATCH 10/11] Add functionality to turn channel labels on and off. Remove obsolete code. --- ivi/rohdeschwarz/rohdeschwarzBaseScope.py | 102 ++++++++++------------ 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py index 2ef882d1..58a0ae12 100644 --- a/ivi/rohdeschwarz/rohdeschwarzBaseScope.py +++ b/ivi/rohdeschwarz/rohdeschwarzBaseScope.py @@ -53,26 +53,6 @@ 'none': 'norm', # according to IVI standardization, oscilloscope normal triggde mode is called 'none' 'normal': 'norm', 'auto': 'auto'} -TriggerTypeMapping = { - 'edge': 'edge', - 'width': 'glit', - 'glitch': 'glit', - 'tv': 'tv', - #'immediate': '', - 'ac_line': 'edge', - 'pattern': 'patt', - 'can': 'can', - 'duration': 'dur', - 'i2s': 'i2s', - 'iic': 'iic', - 'eburst': 'ebur', - 'lin': 'lin', - 'm1553': 'm1553', - 'sequence': 'seq', - 'spi': 'spi', - 'uart': 'uart', - 'usb': 'usb', - 'flexray': 'flex'} TriggerCouplingMapping = { 'ac': ('ac', 0, 0), 'dc': ('dc', 0, 0), @@ -160,27 +140,12 @@ def __init__(self, *args, **kwargs): self._vertical_divisions = 10 - # self._channel_probe_skew = list() - # self._channel_scale = list() - # self._channel_trigger_level = list() - # self._channel_invert = list()imp - # self._channel_bw_limit = list() - super(rohdeschwarzBaseScope, self).__init__(*args, **kwargs) self._memory_size = 10 - - # self._analog_channel_name = list() - # self._analog_channel_count = 4 - # self._digital_channel_name = list() - # self._digital_channel_count = 16 - # self._channel_count = self._analog_channel_count + self._digital_channel_count - self._bandwidth = 1e9 - self._trigger_holdoff_min = 51.2e-9 self._channel_offset_max = 1.2 - self._horizontal_divisions = 12 self._acquisition_segmented_count = 2 @@ -206,7 +171,6 @@ def __init__(self, *args, **kwargs): self._display_screenshot_image_format_mapping = ScreenshotImageFormatMapping self._display_vectors = True - self._display_labels = True self._identity_description = "Rohde&Schwarz generic IVI oscilloscope driver" self._identity_identifier = "" @@ -340,13 +304,19 @@ def __init__(self, *args, **kwargs): Specifies the trigger level of the channel. Units are volts. """)) + self._add_property('channels[].show_label', + self._get_channel_show_label, + self._set_channel_show_label, + None, + ivi.Doc(""" + Turns the analog and digital channel labels on and off. + """)) + self._init_channels() # Remove from base class? def _initialize(self, resource = None, id_query = False, reset = False, **keywargs): "Opens an I/O session to the instrument." - #self._channel_count = self._analog_channel_count + self._digital_channel_count - super(rohdeschwarzBaseScope, self)._initialize(resource, id_query, reset, **keywargs) # interface clear @@ -385,6 +355,7 @@ def _init_channels(self): self._channel_name = list() self._channel_label = list() + self._channel_show_label = list() # analog channels self._analog_channel_name = list() @@ -400,10 +371,10 @@ def _init_channels(self): self._channel_trigger_level = list() self._channel_bw_limit = list() - #self._analog_channel_name = list() for i in range(self._analog_channel_count): self._channel_name.append("channel%d" % (i+1)) - self._channel_label.append("%d" % (i+1)) + self._channel_label.append("C%d" % (i+1)) + self._channel_show_label.append(False) self._analog_channel_name.append("C%d" % (i+1)) self._channel_probe_skew.append(0) @@ -419,11 +390,11 @@ def _init_channels(self): self._channel_bw_limit.append(False) # digital channels - #self._digital_channel_name = list() if (self._digital_channel_count > 0): for i in range(self._digital_channel_count): self._channel_name.append("digital%d" % i) self._channel_label.append("D%d" % i) + self._channel_show_label.append(False) self._digital_channel_name.append("digital%d" % i) self._channel_count = self._analog_channel_count + self._digital_channel_count @@ -607,27 +578,42 @@ def _get_channel_label(self, index): self._channel_label[index] = self._ask("%s:label?" % self._channel_name[index]).strip('"') self._set_cache_valid(index=index) return self._channel_label[index] - + def _set_channel_label(self, index, value): value = str(value) index = ivi.get_index(self._channel_name, index) if not self._driver_operation_simulate: self._write("%s:label '%s'" % (self._channel_name[index], value)) + # If self._channel_show_label == True, then also display label on the screen + if self._channel_show_label[index] == True: + self._write("%s:label:state on" % self._channel_name[index]) self._channel_label[index] = value self._set_cache_valid(index=index) + + def _get_channel_show_label(self, index): + if not self._driver_operation_simulate and not self._get_cache_valid(): + self._channel_show_label[index] = bool(int(self._ask("%s:label:state?" % self._channel_name[index]))) + self._set_cache_valid() + return self._channel_show_label[index] + def _set_channel_show_label(self, index, value): + value = bool(value) + if not self._driver_operation_simulate: + self._write("%s:label:state %s" % (self._channel_name[index], ('off', 'on')[int(value)])) + self._channel_show_label[index] = value + self._set_cache_valid() + def _get_channel_enabled(self, index): index = ivi.get_index(self._channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): self._channel_enabled[index] = bool(int(self._ask("%s:state?" % self._channel_name[index]))) self._set_cache_valid(index=index) return self._channel_enabled[index] - + def _set_channel_enabled(self, index, value): value = bool(value) index = ivi.get_index(self._channel_name, index) if not self._driver_operation_simulate: - print(("%s:state %d" % (self._channel_name[index], int(value)))) self._write("%s:state %d" % (self._channel_name[index], int(value))) self._channel_enabled[index] = value self._set_cache_valid(index=index) @@ -635,7 +621,7 @@ def _set_channel_enabled(self, index, value): def _get_channel_input_impedance(self, index): index = ivi.get_index(self._analog_channel_name, index) return self._channel_input_impedance[index] - + def _set_channel_input_impedance(self, index, value): raise ivi.ValueNotSupportedException('Input impedance of all BNC inputs is fixed to 1 MOhm') @@ -666,7 +652,7 @@ def _get_channel_probe_attenuation(self, index): self._channel_probe_attenuation[index] = float(self._ask("probe%s:setup:attenuation:manual?" % self._channel_name[index])) self._set_cache_valid(index=index) return self._channel_probe_attenuation[index] - + def _set_channel_probe_attenuation(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) @@ -689,7 +675,7 @@ def _get_channel_probe_skew(self, index): self._channel_probe_skew[index] = float(self._ask("%s:skew?" % self._channel_name[index])) self._set_cache_valid(index=index) return self._channel_probe_skew[index] - + def _set_channel_probe_skew(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) @@ -708,7 +694,7 @@ def _get_channel_invert(self, index): self._channel_invert[index] = True self._set_cache_valid(index=index) return self._channel_invert[index] - + def _set_channel_invert(self, index, value): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate: @@ -722,7 +708,7 @@ def _get_channel_coupling(self, index): self._channel_coupling[index] = [k for k,v in VerticalCouplingMapping.items() if v==value][0] self._set_cache_valid(index=index) return self._channel_coupling[index] - + def _set_channel_coupling(self, index, value): index = ivi.get_index(self._analog_channel_name, index) if value not in VerticalCouplingMapping.keys(): @@ -731,14 +717,14 @@ def _set_channel_coupling(self, index, value): self._write("%s:coupling %s" % (self._channel_name[index], VerticalCouplingMapping[value])) self._channel_coupling[index] = value self._set_cache_valid(index=index) - + def _get_channel_offset(self, index): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): self._channel_offset[index] = float(self._ask("%s:offset?" % self._channel_name[index])) self._set_cache_valid(index=index) return self._channel_offset[index] - + def _set_channel_offset(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) @@ -748,7 +734,7 @@ def _set_channel_offset(self, index, value): self._write("%s:offset %e" % (self._channel_name[index], value)) self._channel_offset[index] = value self._set_cache_valid(index=index) - + def _get_channel_range(self, index): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): @@ -757,7 +743,7 @@ def _get_channel_range(self, index): self._set_cache_valid(index=index) self._set_cache_valid(True, "channel_scale", index) return self._channel_range[index] - + def _set_channel_range(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) @@ -771,7 +757,7 @@ def _set_channel_range(self, index, value): self._set_cache_valid(index=index) self._set_cache_valid(True, "channel_scale", index) self._set_cache_valid(False, "channel_offset", index) - + def _get_channel_scale(self, index): index = ivi.get_index(self._analog_channel_name, index) if not self._driver_operation_simulate and not self._get_cache_valid(index=index): @@ -780,7 +766,7 @@ def _get_channel_scale(self, index): self._set_cache_valid(index=index) self._set_cache_valid(True, "channel_range", index) return self._channel_scale[index] - + def _set_channel_scale(self, index, value): index = ivi.get_index(self._analog_channel_name, index) value = float(value) @@ -829,7 +815,7 @@ def _get_trigger_holdoff(self): print('hej2') self._set_cache_valid() return self._trigger_holdoff - + def _set_trigger_holdoff(self, value): value = float(value) if not self._driver_operation_simulate: @@ -902,7 +888,7 @@ def _get_trigger_source(self): self._trigger_source = value self._set_cache_valid() return self._trigger_source - + def _set_trigger_source(self, value): if hasattr(value, 'name'): value = value.name @@ -925,7 +911,7 @@ def _get_trigger_continuous(self): self._trigger_continuous = self._ask("acquire:state?").lower() == 'run' self._set_cache_valid() return self._trigger_continuous - + def _set_trigger_continuous(self, value): value = bool(value) if not self._driver_operation_simulate: From ad98bd429d5f292e2405d8e21f145393333bd30e Mon Sep 17 00:00:00 2001 From: Antonio Vicent Date: Tue, 5 Jun 2018 16:08:39 +0200 Subject: [PATCH 11/11] Add newline at end of ivi/rohdeschwarz/__init__.py --- ivi/rohdeschwarz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi/rohdeschwarz/__init__.py b/ivi/rohdeschwarz/__init__.py index 10ac5760..0b280f7e 100644 --- a/ivi/rohdeschwarz/__init__.py +++ b/ivi/rohdeschwarz/__init__.py @@ -27,4 +27,4 @@ # Oscilloscopes # RTB2000 series from .rohdeschwarzRTB2002 import rohdeschwarzRTB2002 -from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004 \ No newline at end of file +from .rohdeschwarzRTB2004 import rohdeschwarzRTB2004