# All manual references are to R&S RTO Digital Oscilloscope User Manual
# for firmware 3.65, 2017
import logging
import time
import warnings
from typing import Any, Optional
import numpy as np
from packaging import version
import qcodes.validators as vals
from qcodes.instrument import Instrument, InstrumentChannel, VisaInstrument
from qcodes.parameters import ArrayParameter, create_on_off_val_mapping
log = logging.getLogger(__name__)
class ScopeTrace(ArrayParameter):
def __init__(
self, name: str, instrument: InstrumentChannel, channum: int, **kwargs: Any
) -> None:
"""
The ScopeTrace parameter is attached to a channel of the oscilloscope.
For now, we only support reading out the entire trace.
"""
super().__init__(
name=name,
shape=(1,),
label="Voltage", # TODO: Is this sometimes dbm?
unit="V",
setpoint_names=("Time",),
setpoint_labels=("Time",),
setpoint_units=("s",),
docstring="Holds scope trace",
snapshot_value=False,
instrument=instrument,
**kwargs,
)
self.channel = instrument
self.channum = channum
self._trace_ready = False
def prepare_trace(self) -> None:
"""
Prepare the scope for returning data, calculate the setpoints
"""
assert self.root_instrument is not None
# We always use 16 bit integers for the data format
self.root_instrument.dataformat("INT,16")
# ensure little-endianess
self.root_instrument.write("FORMat:BORder LSBFirst")
# only export y-values
self.root_instrument.write("EXPort:WAVeform:INCXvalues OFF")
# only export one channel
self.root_instrument.write("EXPort:WAVeform:MULTichannel OFF")
# now get setpoints
hdr = self.root_instrument.ask(f"CHANnel{self.channum}:" "DATA:HEADER?")
hdr_vals = list(map(float, hdr.split(",")))
t_start = hdr_vals[0]
t_stop = hdr_vals[1]
no_samples = int(hdr_vals[2])
values_per_sample = hdr_vals[3]
# NOTE (WilliamHPNielsen):
# If samples are multi-valued, we need a `MultiParameter`
# instead of an `ArrayParameter`.
if values_per_sample > 1:
raise NotImplementedError('There are several values per sample '
'in this trace (are you using envelope'
' or peak detect?). We currently do '
'not support saving such a trace.')
self.shape = (no_samples,)
self.setpoints = (tuple(np.linspace(t_start, t_stop, no_samples)),)
self._trace_ready = True
# we must ensure that all this took effect before proceeding
self.root_instrument.ask("*OPC?")
def get_raw(self) -> np.ndarray:
"""
Returns a trace
"""
instr = self.root_instrument
assert instr is not None
if not self._trace_ready:
raise ValueError('Trace not ready! Please call '
'prepare_trace().')
if instr.run_mode() == 'RUN Nx SINGLE':
total_acquisitions = instr.num_acquisitions()
completed_acquisitions = instr.completed_acquisitions()
log.info(f'Acquiring {total_acquisitions} traces.')
while completed_acquisitions < total_acquisitions:
log.info(f'Acquired {completed_acquisitions}:'
f'{total_acquisitions}')
time.sleep(0.25)
completed_acquisitions = instr.completed_acquisitions()
log.info('Acquisition completed. Polling trace from instrument.')
vh = instr.visa_handle
vh.write(f'CHANnel{self.channum}:DATA?')
raw_vals = vh.read_raw()
num_length = int(raw_vals[1:2])
no_points = int(raw_vals[2:2+num_length])
# cut of the header and the trailing '\n'
raw_vals = raw_vals[2+num_length:-1]
dataformat = instr.dataformat.get_latest()
if dataformat == 'INT,8':
int_vals = np.frombuffer(raw_vals, dtype=np.int8, count=no_points)
else:
int_vals = np.frombuffer(raw_vals, dtype=np.int16, count=no_points // 2)
# now the integer values must be converted to physical
# values
scale = self.channel.scale()
no_divs = 10 # TODO: Is this ever NOT 10?
# we always export as 16 bit integers
quant_levels = 253*256
conv_factor = scale*no_divs/quant_levels
output = conv_factor*int_vals + self.channel.offset()
return output
[docs]class RohdeSchwarzRTO1000ScopeMeasurement(InstrumentChannel):
"""
Class to hold a measurement of the scope.
"""
def __init__(self, parent: Instrument, name: str, meas_nr: int) -> None:
"""
Args:
parent: The instrument to which the channel is attached
name: The name of the measurement
meas_nr: The number of the measurement in question. Must match the
actual number as used by the instrument (1..8)
"""
if meas_nr not in range(1, 9):
raise ValueError('Invalid measurement number; Min: 1, max 8')
self.meas_nr = meas_nr
super().__init__(parent, name)
self.sources = vals.Enum('C1W1', 'C1W2', 'C1W3',
'C2W1', 'C2W2', 'C2W3',
'C3W1', 'C3W2', 'C3W3',
'C4W1', 'C4W2', 'C4W3',
'M1', 'M2', 'M3', 'M4',
'R1', 'R2', 'R3', 'R4',
'SBUS1', 'SBUS2', 'SBUS3', 'SBUS4',
'D0', 'D1', 'D2', 'D3',
'D4', 'D5', 'D6', 'D7',
'D8', 'D9', 'D10', 'D11',
'D12', 'D13', 'D14', 'D15',
'TRK1', 'TRK2', 'TRK3', 'TRK4',
'TRK5', 'TRK6', 'TRK7', 'TRK8',
'SG1TL1', 'SG1TL2',
'SG2TL1', 'SG2TL2',
'SG3TL1', 'SG3TL2',
'SG4TL1', 'SG4TL2',
'Z1V1', 'Z1V2', 'Z1V3', 'Z1V4',
'Z1I1', 'Z1I2', 'Z1I3', 'Z1I4',
'Z2V1', 'Z2V2', 'Z2V3', 'Z2V4',
'Z2I1', 'Z2I2', 'Z2I3', 'Z2I4')
self.categories = vals.Enum('AMPTime', 'JITTer', 'EYEJitter',
'SPECtrum', 'HISTogram', 'PROTocol')
self.meas_type = vals.Enum(
# Amplitude/time measurements
'HIGH', 'LOW', 'AMPLitude',
'MAXimum', 'MINimum', 'PDELta',
'MEAN', 'RMS', 'STDDev',
'POVershoot', 'NOVershoot', 'AREA',
'RTIMe', 'FTIMe', 'PPULse',
'NPULse', 'PERiod', 'FREQuency',
'PDCYcle', 'NDCYcle', 'CYCarea',
'CYCMean', 'CYCRms', 'CYCStddev',
'PULCnt', 'DELay', 'PHASe',
'BWIDth', 'PSWitching', 'NSWitching',
'PULSetrain', 'EDGecount', 'SHT',
'SHR', 'DTOTrigger', 'PROBemeter',
'SLERising', 'SLEFalling',
# Jitter measurements
'CCJitter', 'NCJitter', 'CCWidth',
'CCDutycycle', 'TIE', 'UINTerval',
'DRATe', 'SKWDelay', 'SKWPhase',
# Eye diagram measurements
'ERPercent', 'ERDB', 'EHEight',
'EWIDth', 'ETOP', 'EBASe',
'QFACtor', 'RMSNoise', 'SNRatio',
'DCDistortion', 'ERTime', 'EFTime',
'EBRate', 'EAMPlitude', 'PPJitter',
'STDJitter', 'RMSJitter',
# Spectrum measurements
'CPOWer', 'OBWidth', 'SBWidth',
'THD', 'THDPCT', 'THDA',
'THDU', 'THDR', 'HAR',
'PLISt',
# Histogram measurements
'WCOunt', 'WSAMples', 'HSAMples',
'HPEak', 'PEAK', 'UPEakvalue',
'LPEakvalue', 'HMAXimum', 'HMINimum',
'MEDian', 'MAXMin', 'HMEan',
'HSTDdev', 'M1STddev', 'M2STddev',
'M3STddev', 'MKPositive', 'MKNegative'
)
self.add_parameter('enable',
label=f'Measurement {meas_nr} enable',
set_cmd=f'MEASurement{meas_nr}:ENABle {{}}',
vals=vals.Enum('ON', 'OFF'),
docstring='Switches the measurement on or off.')
self.add_parameter('source',
label=f'Measurement {meas_nr} source',
set_cmd=f'MEASurement{meas_nr}:SOURce {{}}',
vals=self.sources,
docstring='Set the source of a measurement if the '
'measurement only needs one source.')
self.add_parameter('source_first',
label=f'Measurement {meas_nr} first source',
set_cmd=f'MEASurement{meas_nr}:FSRC {{}}',
vals=self.sources,
docstring='Set the first source of a measurement'
' if the measurement only needs multiple'
' sources.')
self.add_parameter('source_second',
label=f'Measurement {meas_nr} second source',
set_cmd=f'MEASurement{meas_nr}:SSRC {{}}',
vals=self.sources,
docstring='Set the second source of a measurement'
' if the measurement only needs multiple'
' sources.')
self.add_parameter('category',
label=f'Measurement {meas_nr} category',
set_cmd=f'MEASurement{meas_nr}:CATegory {{}}',
vals=self.categories,
docstring='Set the category of a measurement.')
self.add_parameter('main',
label=f'Measurement {meas_nr} main',
set_cmd=f'MEASurement{meas_nr}:MAIN {{}}',
vals=self.meas_type,
docstring='Set the main of a measurement.')
self.add_parameter('statistics_enable',
label=f'Measurement {meas_nr} enable statistics',
set_cmd=f'MEASurement{meas_nr}:STATistics:ENABle'
f' {{}}',
vals=vals.Enum('ON', 'OFF'),
docstring='Switches the measurement on or off.')
self.add_parameter('clear',
label=f'Measurement {meas_nr} clear statistics',
set_cmd=f'MEASurement{meas_nr}:CLEar',
docstring='Clears/reset measurement.')
self.add_parameter('event_count',
label=f'Measurement {meas_nr} number of events',
get_cmd=f'MEASurement{meas_nr}:RESult:EVTCount?',
get_parser=int,
docstring='Number of measurement results in the'
' long-term measurement.')
self.add_parameter('result_avg',
label=f'Measurement {meas_nr} averages',
get_cmd=f'MEASurement{meas_nr}:RESult:AVG?',
get_parser=float,
docstring='Average of the long-term measurement'
' results.')
ScopeMeasurement = RohdeSchwarzRTO1000ScopeMeasurement
[docs]class RohdeSchwarzRTO1000ScopeChannel(InstrumentChannel):
"""
Class to hold an input channel of the scope.
Exposes: state, coupling, ground, scale, range, position, offset,
invert, bandwidth, impedance, overload.
"""
def __init__(self, parent: Instrument, name: str, channum: int) -> None:
"""
Args:
parent: The instrument to which the channel is attached
name: The name of the channel
channum: The number of the channel in question. Must match the
actual number as used by the instrument (1..4)
"""
if channum not in [1, 2, 3, 4]:
raise ValueError('Invalid channel number! Must be 1, 2, 3, or 4.')
self.channum = channum
super().__init__(parent, name)
self.add_parameter('state',
label=f'Channel {channum} state',
get_cmd=f'CHANnel{channum}:STATe?',
set_cmd=f'CHANnel{channum}:STATE {{}}',
vals=vals.Enum('ON', 'OFF'),
docstring='Switches the channel on or off')
self.add_parameter('coupling',
label=f'Channel {channum} coupling',
get_cmd=f'CHANnel{channum}:COUPling?',
set_cmd=f'CHANnel{channum}:COUPling {{}}',
vals=vals.Enum('DC', 'DCLimit', 'AC'),
docstring=('Selects the connection of the channel'
'signal. DC: 50 Ohm, DCLimit 1 MOhm, '
'AC: Con. through DC capacitor'))
self.add_parameter('ground',
label=f'Channel {channum} ground',
get_cmd=f'CHANnel{channum}:GND?',
set_cmd=f'CHANnel{channum}:GND {{}}',
vals=vals.Enum('ON', 'OFF'),
docstring=('Connects/disconnects the signal to/from'
'the ground.'))
# NB (WilliamHPNielsen): This parameter depends on other parameters and
# should be dynamically updated accordingly. Cf. p 1178 of the manual
self.add_parameter('scale',
label=f'Channel {channum} Y scale',
unit='V/div',
get_cmd=f'CHANnel{channum}:SCALe?',
set_cmd=self._set_scale,
get_parser=float,
)
self.add_parameter('range',
label=f'Channel {channum} Y range',
unit='V',
get_cmd=f'CHANnel{channum}:RANGe?',
set_cmd=self._set_range,
get_parser=float
)
# TODO (WilliamHPNielsen): would it be better to recast this in terms
# of Volts?
self.add_parameter('position',
label=f'Channel {channum} vert. pos.',
unit='div',
get_cmd=f'CHANnel{channum}:POSition?',
set_cmd=f'CHANnel{channum}:POSition {{}}',
get_parser=float,
vals=vals.Numbers(-5, 5),
docstring=('Positive values move the waveform up,'
' negative values move it down.'))
self.add_parameter('offset',
label=f'Channel {channum} offset',
unit='V',
get_cmd=f'CHANnel{channum}:OFFSet?',
set_cmd=f'CHANnel{channum}:OFFSet {{}}',
get_parser=float,
)
self.add_parameter('invert',
label=f'Channel {channum} inverted',
get_cmd=f'CHANnel{channum}:INVert?',
set_cmd=f'CHANnel{channum}:INVert {{}}',
vals=vals.Enum('ON', 'OFF'))
# TODO (WilliamHPNielsen): This parameter should be dynamically
# validated since 800 MHz BW is only available for 50 Ohm coupling
self.add_parameter('bandwidth',
label=f'Channel {channum} bandwidth',
get_cmd=f'CHANnel{channum}:BANDwidth?',
set_cmd=f'CHANnel{channum}:BANDwidth {{}}',
vals=vals.Enum('FULL', 'B800', 'B200', 'B20')
)
self.add_parameter('impedance',
label=f'Channel {channum} impedance',
unit='Ohm',
get_cmd=f'CHANnel{channum}:IMPedance?',
set_cmd=f'CHANnel{channum}:IMPedance {{}}',
vals=vals.Ints(1, 100000),
docstring=('Sets the impedance of the channel '
'for power calculations and '
'measurements.'))
self.add_parameter('overload',
label=f'Channel {channum} overload',
get_cmd=f'CHANnel{channum}:OVERload?')
self.add_parameter('arithmetics',
label=f'Channel {channum} arithmetics',
set_cmd=f'CHANnel{channum}:ARIThmetics {{}}',
get_cmd=f'CHANnel{channum}:ARIThmetics?',
val_mapping={'AVERAGE': 'AVER',
'OFF': 'OFF',
'ENVELOPE': 'ENV'}
)
self.add_parameter('trace',
channum=self.channum,
parameter_class=ScopeTrace)
self._trace_ready = False
# Specialised/interlinked set/getters
def _set_range(self, value: float) -> None:
self.scale.cache.set(value/10)
self._parent.write(f'CHANnel{self.channum}:RANGe {value}')
def _set_scale(self, value: float) -> None:
self.range.cache.set(value*10)
self._parent.write(f'CHANnel{self.channum}:SCALe {value}')
ScopeChannel = RohdeSchwarzRTO1000ScopeChannel
[docs]class RohdeSchwarzRTO1000(VisaInstrument):
"""
QCoDeS Instrument driver for the
Rohde-Schwarz RTO1000 series oscilloscopes.
"""
def __init__(self, name: str, address: str,
model: Optional[str] = None, timeout: float = 5.,
HD: bool = True,
terminator: str = '\n',
**kwargs: Any) -> None:
"""
Args:
name: name of the instrument
address: VISA resource address
model: The instrument model. For newer firmware versions,
this can be auto-detected
timeout: The VISA query timeout
HD: Does the unit have the High Definition Option (allowing
16 bit vertical resolution)
terminator: Command termination character to strip from VISA
commands.
"""
super().__init__(name=name, address=address, timeout=timeout,
terminator=terminator, **kwargs)
# With firmware versions earlier than 3.65, it seems that the
# model number can NOT be queried from the instrument
# (at least fails with RTO1024, fw 2.52.1.1), so in that case
# the user must provide the model manually.
firmware_version_str = self.get_idn()["firmware"]
if firmware_version_str is None:
raise RuntimeError("Could not determine firmware version of RTO1000.")
firmware_version = version.parse(firmware_version_str)
if firmware_version < version.parse("3"):
log.warning(
"Old firmware version detected. This driver may "
"not be compatible. Please upgrade your firmware."
)
if firmware_version >= version.parse("3.65"):
# strip just in case there is a newline character at the end
self.model = self.ask('DIAGnostic:SERVice:WFAModel?').strip()
if model is not None and model != self.model:
warnings.warn("The model number provided by the user "
"does not match the instrument's response."
" I am going to assume that this oscilloscope "
f"is a model {self.model}")
else:
if model is None:
raise ValueError('No model number provided. Please provide '
'a model number (eg. "RTO1024").')
else:
self.model = model
self.HD = HD
# Now assign model-specific values
self.num_chans = int(self.model[-1])
self.num_meas = 8
self._horisontal_divs = int(self.ask('TIMebase:DIVisions?'))
self.add_parameter('display',
label='Display state',
set_cmd='SYSTem:DISPlay:UPDate {}',
val_mapping={'remote': 0,
'view': 1})
# Triggering
self.add_parameter('trigger_display',
label='Trigger display state',
set_cmd='DISPlay:TRIGger:LINes {}',
get_cmd='DISPlay:TRIGger:LINes?',
val_mapping={'ON': 1, 'OFF': 0})
# TODO: (WilliamHPNielsen) There are more available trigger
# settings than implemented here. See p. 1261 of the manual
# here we just use trigger1, which is the A-trigger
self.add_parameter('trigger_source',
label='Trigger source',
set_cmd='TRIGger1:SOURce {}',
get_cmd='TRIGger1:SOURce?',
val_mapping={'CH1': 'CHAN1',
'CH2': 'CHAN2',
'CH3': 'CHAN3',
'CH4': 'CHAN4',
'EXT': 'EXT'})
self.add_parameter('trigger_mode',
label='Trigger mode',
set_cmd='TRIGger:MODE {}',
get_cmd='TRIGger1:SOURce?',
vals=vals.Enum('AUTO', 'NORMAL', 'FREERUN'),
docstring='Sets the trigger mode which determines'
' the behaviour of the instrument if no'
' trigger occurs.\n'
'Options: AUTO, NORMAL, FREERUN.',
unit='none')
self.add_parameter('trigger_type',
label='Trigger type',
set_cmd='TRIGger1:TYPE {}',
get_cmd='TRIGger1:TYPE?',
val_mapping={'EDGE': 'EDGE',
'GLITCH': 'GLIT',
'WIDTH': 'WIDT',
'RUNT': 'RUNT',
'WINDOW': 'WIND',
'TIMEOUT': 'TIM',
'INTERVAL': 'INT',
'SLEWRATE': 'SLEW',
'DATATOCLOCK': 'DAT',
'STATE': 'STAT',
'PATTERN': 'PATT',
'ANEDGE': 'ANED',
'SERPATTERN': 'SERP',
'NFC': 'NFC',
'TV': 'TV',
'CDR': 'CDR'}
)
# See manual p. 1262 for an explanation of trigger types
self.add_parameter('trigger_level',
label='Trigger level',
set_cmd=self._set_trigger_level,
get_cmd=self._get_trigger_level)
self.add_parameter('trigger_edge_slope',
label='Edge trigger slope',
set_cmd='TRIGger1:EDGE:SLOPe {}',
get_cmd='TRIGger1:EDGE:SLOPe?',
vals=vals.Enum('POS', 'NEG', 'EITH'))
# Horizontal settings
self.add_parameter('timebase_scale',
label='Timebase scale',
set_cmd=self._set_timebase_scale,
get_cmd='TIMebase:SCALe?',
unit='s/div',
get_parser=float,
vals=vals.Numbers(25e-12, 10000))
self.add_parameter('timebase_range',
label='Timebase range',
set_cmd=self._set_timebase_range,
get_cmd='TIMebase:RANGe?',
unit='s',
get_parser=float,
vals=vals.Numbers(250e-12, 100e3))
self.add_parameter('timebase_position',
label='Horizontal position',
set_cmd=self._set_timebase_position,
get_cmd='TIMEbase:HORizontal:POSition?',
get_parser=float,
unit='s',
vals=vals.Numbers(-100e24, 100e24))
# Acquisition
# I couldn't find a way to query the run mode, so we manually keep
# track of it. It is very important when getting the trace to make
# sense of completed_acquisitions.
self.add_parameter('run_mode',
label='Run/acquisition mode of the scope',
get_cmd=None,
set_cmd=None)
self.run_mode('RUN CONT')
self.add_parameter('num_acquisitions',
label='Number of single acquisitions to perform',
get_cmd='ACQuire:COUNt?',
set_cmd='ACQuire:COUNt {}',
vals=vals.Ints(1, 16777215),
get_parser=int)
self.add_parameter('completed_acquisitions',
label='Number of completed acquisitions',
get_cmd='ACQuire:CURRent?',
get_parser=int)
self.add_parameter('sampling_rate',
label='Sample rate',
docstring='Number of averages for measuring '
'trace.',
unit='Sa/s',
get_cmd='ACQuire:POINts:ARATe' + '?',
get_parser=int)
self.add_parameter('acquisition_sample_rate',
label='Acquisition sample rate',
unit='Sa/s',
docstring='recorded waveform samples per second',
get_cmd='ACQuire:SRATe'+'?',
set_cmd='ACQuire:SRATe ' + ' {:.2f}',
vals=vals.Numbers(2, 20e12),
get_parser=float)
# Data
self.add_parameter('dataformat',
label='Export data format',
set_cmd='FORMat:DATA {}',
get_cmd='FORMat:DATA?',
vals=vals.Enum('ASC,0', 'REAL,32',
'INT,8', 'INT,16'))
# High definition mode (might not be available on all instruments)
if HD:
self.add_parameter('high_definition_state',
label='High definition (16 bit) state',
set_cmd=self._set_hd_mode,
get_cmd='HDEFinition:STAte?',
val_mapping=create_on_off_val_mapping(on_val=1,
off_val=0),
docstring='Sets the filter bandwidth for the'
' high definition mode.\n'
'ON: high definition mode, up to 16'
' bit digital resolution\n'
'Options: ON, OFF\n\n'
'Warning/Bug: By opening the HD '
'acquisition menu on the scope, '
'this value will be set to "ON".')
self.add_parameter('high_definition_bandwidth',
label='High definition mode bandwidth',
set_cmd='HDEFinition:BWIDth {}',
get_cmd='HDEFinition:BWIDth?',
unit='Hz',
get_parser=float,
vals=vals.Numbers(1e4, 1e9))
self.add_parameter('error_count',
label='Number of errors in the error stack',
get_cmd='SYSTem:ERRor:COUNt?',
unit='#',
get_parser=int)
self.add_parameter('error_next',
label='Next error from the error stack',
get_cmd='SYSTem:ERRor:NEXT?',
get_parser=str)
# Add the channels to the instrument
for ch in range(1, self.num_chans + 1):
chan = RohdeSchwarzRTO1000ScopeChannel(self, f"channel{ch}", ch)
self.add_submodule(f"ch{ch}", chan)
for measId in range(1, self.num_meas + 1):
measCh = RohdeSchwarzRTO1000ScopeMeasurement(
self, f"measurement{measId}", measId
)
self.add_submodule(f"meas{measId}", measCh)
self.add_function('stop', call_cmd='STOP')
self.add_function('reset', call_cmd='*RST')
self.add_parameter('opc', get_cmd='*OPC?')
self.add_parameter('stop_opc', get_cmd='STOP;*OPC?')
self.add_parameter('status_operation',
get_cmd='STATus:OPERation:CONDition?',
get_parser=int)
self.add_function('run_continues', call_cmd='RUNContinous')
# starts the shutdown of the system
self.add_function('system_shutdown', call_cmd='SYSTem:EXIT')
self.connect_message()
[docs] def run_cont(self) -> None:
"""
Set the instrument in 'RUN CONT' mode
"""
self.write('RUN')
self.run_mode.set('RUN CONT')
[docs] def run_single(self) -> None:
"""
Set the instrument in 'RUN Nx SINGLE' mode
"""
self.write('SINGLE')
self.run_mode.set('RUN Nx SINGLE')
[docs] def is_triggered(self) -> bool:
wait_trigger_mask = 0b01000
return bool(self.status_operation() & wait_trigger_mask) is False
[docs] def is_running(self) -> bool:
measuring_mask = 0b10000
return bool(self.status_operation() & measuring_mask)
[docs] def is_acquiring(self) -> bool:
return self.is_triggered() & self.is_running()
# Specialised set/get functions
def _set_hd_mode(self, value: int) -> None:
"""
Set/unset the high def mode
"""
self._make_traces_not_ready()
self.write(f'HDEFinition:STAte {value}')
def _set_timebase_range(self, value: float) -> None:
"""
Set the full range of the timebase
"""
self._make_traces_not_ready()
self.timebase_scale.cache.set(value/self._horisontal_divs)
self.write(f'TIMebase:RANGe {value}')
def _set_timebase_scale(self, value: float) -> None:
"""
Set the length of one horizontal division.
"""
self._make_traces_not_ready()
self.timebase_range.cache.set(value*self._horisontal_divs)
self.write(f'TIMebase:SCALe {value}')
def _set_timebase_position(self, value: float) -> None:
"""
Set the horizontal position.
"""
self._make_traces_not_ready()
self.write(f'TIMEbase:HORizontal:POSition {value}')
def _make_traces_not_ready(self) -> None:
"""
Make the scope traces be not ready.
"""
self.ch1.trace._trace_ready = False
self.ch2.trace._trace_ready = False
self.ch3.trace._trace_ready = False
self.ch4.trace._trace_ready = False
def _set_trigger_level(self, value: float) -> None:
"""
Set the trigger level on the currently used trigger source
channel.
"""
trans = {'CH1': 1, 'CH2': 2, 'CH3': 3, 'CH4': 4, 'EXT': 5}
# We use get and not get_latest because we don't trust users to
# not touch the front panel of an oscilloscope.
source = trans[self.trigger_source.get()]
if source != 5:
submodule = self.submodules[f'ch{source}']
assert isinstance(submodule, InstrumentChannel)
v_range = submodule.range()
offset = submodule.offset()
if (value < -v_range/2 + offset) or (value > v_range/2 + offset):
raise ValueError('Trigger level outside channel range.')
self.write(f'TRIGger1:LEVel{source} {value}')
def _get_trigger_level(self) -> float:
"""
Get the trigger level from the currently used trigger source
"""
trans = {'CH1': 1, 'CH2': 2, 'CH3': 3, 'CH4': 4, 'EXT': 5}
# we use get and not get_latest because we don't trust users to
# not touch the front panel of an oscilloscope
source = trans[self.trigger_source.get()]
val = self.ask(f'TRIGger1:LEVel{source}?')
return float(val.strip())
class RTO1000(RohdeSchwarzRTO1000):
"""
Backwards compatibility alias for RohdeSchwarzRTO1000
"""
pass