diff --git a/docs/changes/newsfragments/7837.improved_driver b/docs/changes/newsfragments/7837.improved_driver new file mode 100644 index 00000000000..02fbe24d6a3 --- /dev/null +++ b/docs/changes/newsfragments/7837.improved_driver @@ -0,0 +1 @@ +Add hold parameter, force_jump parameter, and set_event_jump method to the Tektronix AWG70000A driver for improved sequence control. diff --git a/docs/changes/newsfragments/7838.improved_driver b/docs/changes/newsfragments/7838.improved_driver new file mode 100644 index 00000000000..28e2e756c33 --- /dev/null +++ b/docs/changes/newsfragments/7838.improved_driver @@ -0,0 +1 @@ +Add TektronixDPOAcquisition, TektronixDPOCursor, and TektronixDPOMeasurementImmediate modules to the Tektronix DPO7200xx driver. Enhanced TektronixDPOTrigger with ready, state, and level parameters. Added coupling parameter to TektronixDPOChannel. diff --git a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py index c321940bfdf..28bcff15c0d 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET import zipfile as zf from functools import partial -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal, Self import numpy as np import numpy.typing as npt @@ -191,7 +191,7 @@ def __init__( if channel not in list(range(1, num_channels + 1)): raise ValueError("Illegal channel value.") - self.state: Parameter = self.add_parameter( + self.state: Parameter[int, Self] = self.add_parameter( "state", label=f"Channel {channel} state", get_cmd=f"OUTPut{channel}:STATe?", @@ -199,7 +199,18 @@ def __init__( vals=vals.Ints(0, 1), get_parser=int, ) - """Parameter state""" + """Channel State: (OFF: 0, ON: 1)""" + + self.hold: Parameter[Literal["FIRST", "ZERO"], Self] = self.add_parameter( + "hold", + label=f"Channel {channel} hold value", + get_cmd=f"OUTPut{channel}:WVALUE:ANALOG:STATE?", + set_cmd=f"OUTPut{channel}:WVALUE:ANALOG:STATE {{}}", + vals=vals.Enum("FIRST", "ZERO"), + ) + """ the output condition of a waveform of the specified + channel to hold while the instrument is in the waiting-for-trigger state. + ZERO = 0V, FIRST = first value of next sequence""" ################################################## # FGEN PARAMETERS @@ -587,6 +598,14 @@ def __init__( ) """Parameter all_output_off""" + self.force_jump: Parameter[int, Self] = self.add_parameter( + "force_jump", + label="Force Jump", + set_cmd="SOURCE1:JUMP:FORCE {}", + vals=vals.Ints(1, 16383), + ) + """Parameter force_jump""" + add_channel_list = self.num_channels > 2 # We deem 2 channels too few for a channel list if add_channel_list: @@ -619,6 +638,23 @@ def __init__( self.connect_message() + def set_event_jump( + self, sequence_name: str, current_step: int, next_step: int + ) -> None: + """ + Set event jump for a given step in the sequence + + Args: + sequence_name: The name of the sequence + current_step: The step number in the sequence (1-indexed) + next_step: The step number to jump to (1-indexed) + + """ + + self.write( + f"SLISt:SEQuence:STEP{current_step}:EJUMp {sequence_name}, {next_step}" + ) + def force_triggerA(self) -> None: """ Force a trigger A event diff --git a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py index f179ff5b71e..fd4c162c3e8 100644 --- a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py +++ b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py @@ -7,7 +7,7 @@ import textwrap import time from functools import partial -from typing import TYPE_CHECKING, Any, ClassVar, Generic +from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, Self import numpy as np import numpy.typing as npt @@ -27,7 +27,7 @@ create_on_off_val_mapping, ) from qcodes.parameters.parameter_base import ParameterDataTypeVar -from qcodes.validators import Arrays, Enum +from qcodes.validators import Arrays, Enum, Numbers if TYPE_CHECKING: from collections.abc import Callable @@ -96,7 +96,19 @@ def __init__( TektronixDPOTrigger(self, "delayed_trigger", delayed_trigger=True), ) """Instrument module delayed_trigger""" - + self.acquisition: TektronixDPOAcquisition = self.add_submodule( + "acquisition", TektronixDPOAcquisition(self, "acquisition") + ) + """Instrument module acquisition""" + self.cursor: TektronixDPOCursor = self.add_submodule( + "cursor", TektronixDPOCursor(self, "cursor") + ) + """Instrument module cursor""" + self.measure_immediate: TektronixDPOMeasurementImmediate = self.add_submodule( + "measure_immediate", + TektronixDPOMeasurementImmediate(self, "measure_immediate"), + ) + """Instrument module measure immediate""" measurement_list = ChannelList(self, "measurement", TektronixDPOMeasurement) for measurement_number in range(1, self.number_of_measurements): measurement_name = f"measurement{measurement_number}" @@ -458,6 +470,17 @@ def __init__( ) """Instrument module waveform""" + self.coupling: Parameter[Literal["AC", "DC", "DCREJECT", "GND"], Self] = ( + self.add_parameter( + "coupling", + get_cmd=f"{self._identifier}:COUPling?", + set_cmd=f"{self._identifier}:COUPling {{}}", + vals=Enum("AC", "DC", "DCREJECT", "GND"), + get_parser=str, + ) + ) + """Parameter coupling: 'AC', 'DC', 'DCREJECT', 'GND'""" + self.scale: Parameter = self.add_parameter( "scale", get_cmd=f"{self._identifier}:SCA?", @@ -465,7 +488,7 @@ def __init__( get_parser=float, unit="V/div", ) - """Parameter scale""" + """Parameter scale V/div""" self.offset: Parameter = self.add_parameter( "offset", @@ -474,16 +497,17 @@ def __init__( get_parser=float, unit="V", ) - """Parameter offset""" + """Parameter offset voltage""" self.position: Parameter = self.add_parameter( "position", get_cmd=f"{self._identifier}:POS?", set_cmd=f"{self._identifier}:POS {{}}", get_parser=float, - unit="V", + vals=Numbers(-8, 8), + unit="div", ) - """Parameter position""" + """Parameter position [-8, 8] divisions""" self.termination: Parameter = self.add_parameter( "termination", @@ -677,6 +701,90 @@ def _set_scale(self, value: float) -> None: self.write(f"HORizontal:MODE:SCAle {value}") +class TektronixDPOAcquisition(InstrumentChannel): + """ + This submodule controls the acquisition mode of the + oscilloscope. It is used to set the acquisition mode + and the number of acquisitions. + """ + + def __init__( + self, + parent: Instrument, + name: str, + **kwargs: "Unpack[InstrumentBaseKWArgs]", + ) -> None: + super().__init__(parent, name, **kwargs) + + self.mode: Parameter[ + Literal["sample", "peakdetect", "average", "high_res", "wfmdb", "envelope"], + Self, + ] = self.add_parameter( + "mode", + get_cmd="ACQuire:MODe?", + set_cmd="ACQuire:MODe {}", + vals=Enum( + "sample", + "peakdetect", + "average", + "high_res", + "wfmdb", + "envelope", + ), + get_parser=str.lower, + ) + """Sample mode. This command sets or queries the acquisition mode. The acquisition mode + determines how the instrument acquires and processes data. The available acquisition modes are: + - Sample: The instrument acquires data at the specified sample rate and record length. + This is the default acquisition mode. + - Peak Detect: The instrument captures the maximum and minimum values for each sample interval. This mode is useful + for capturing narrow pulses or glitches that may be missed in sample mode. + - Average: The instrument acquires multiple waveforms and averages them together to reduce noise. + The number of waveforms to average can be set with the 'averages' parameter. + - High Res: The instrument acquires data at a higher resolution by using oversampling and digital filtering. + This mode is useful for capturing small signal details. + - WfmDB: Statistical database aquisition mode. The instrument acquires data and stores it in a statistical + database for later analysis. + - Envelope: The instrument captures the maximum and minimum values for each sample interval over multiple acquisitions + """ + + self.state: Parameter[Literal["ON", "OFF", "RUN", "STOP"], Self] = ( + self.add_parameter( + "state", + get_cmd="ACQuire:STATE?", + set_cmd="ACQuire:STATE {}", + vals=Enum( + "ON", + "OFF", + "RUN", + "STOP", + ), + get_parser=str.upper, + ) + ) + """ + This command starts or stops acquisitions. When state is set to ON or RUN, a + new acquisition will be started. If the last acquisition was a single acquisition + sequence, a new single sequence acquisition will be started. If the last acquisition + was continuous, a new continuous acquisition will be started. + + State can be 'ON', 'OFF', 'RUN', or 'STOP'. + + """ + + self.stop_after: Parameter = self.add_parameter( + "stop_after", + get_cmd="ACQuire:STOPAfter?", + set_cmd="ACQuire:STOPAfter {}", + vals=Enum("SEQUENCE", "RUNSTOP"), + get_parser=str.upper, + ) + """This command sets or queries whether the instrument continually acquires + acquisitions or acquires a single sequence. Pressing SINGLE on the front + panel button is equivalent to sending these commands: ACQUIRE:STOPAFTER + SEQUENCE and ACQUIRE:STATE 1.""" + + class TektronixDPOTrigger(InstrumentChannel): """ Submodule for trigger setup. @@ -712,6 +820,42 @@ def __init__( ["video", "i2c", "can", "spi", "communication", "serial", "rs232"] ) + self.ready: Parameter = self.add_parameter( + "ready", + get_cmd=f"TRIGger:{self._identifier}:READY?", + get_parser=str.lower, + ) + """Indicates whether the trigger system is ready to accept a trigger. + A value of 1 indicates that the trigger system is ready to accept a trigger. + A value of 0 indicates that the trigger system is not ready to accept a trigger. + """ + + self.state: Parameter = self.add_parameter( + "state", + get_cmd="TRIGger:STATe?", + get_parser=str.lower, + ) + """Gets the current Trigger state: + + ARMED indicates that the instrument is acquiring pretrigger information. + + AUTO indicates that the instrument is in the automatic mode and acquires data + even in the absence of a trigger. + + DPO indicates that the instrument is in DPO mode. + + PARTIAL indicates that the A trigger has occurred and the instrument is waiting + for the B trigger to occur. + + READY indicates that all pretrigger information is acquired and that the instrument + is ready to accept a trigger. + + SAVE indicates that the instrument is in save mode and is not acquiring data. + + TRIGGER indicates that the instrument triggered and is acquiring the post trigger + information. + """ + self.type: Parameter = self.add_parameter( "type", get_cmd=f"TRIGger:{self._identifier}:TYPE?", @@ -719,7 +863,7 @@ def __init__( vals=Enum(*trigger_types), get_parser=str.lower, ) - """Parameter type""" + """Trigger type""" edge_couplings = ["ac", "dc", "hfrej", "lfrej", "noiserej"] if self._identifier == "B": @@ -732,16 +876,19 @@ def __init__( vals=Enum(*edge_couplings), get_parser=str.lower, ) - """Parameter edge_coupling""" + """Trigger edge coupling for A and B triggers: + Trigger A: 'ac', 'dc', 'hfrej', 'lfrej', 'noiserej' + Trigger B: 'ac', 'dc', 'hfrej', 'lfrej', 'noiserej', 'atrigger' + """ self.edge_slope: Parameter = self.add_parameter( "edge_slope", get_cmd=f"TRIGger:{self._identifier}:EDGE:SLOpe?", set_cmd=f"TRIGger:{self._identifier}:EDGE:SLOpe {{}}", - vals=Enum("rise", "fall", "either"), + vals=Enum("RISE", "rise", "FALL", "fall", "EITHER", "either"), get_parser=str.lower, ) - """Parameter edge_slope""" + """Trigger edge slope: 'rise', 'fall', or 'either'""" trigger_sources = [ f"CH{i}" for i in range(1, TektronixDPO7000xx.number_of_channels) @@ -752,16 +899,27 @@ def __init__( if self._identifier == "A": trigger_sources.append("line") + trigger_sources.append("AUX") + self.source: Parameter = self.add_parameter( "source", get_cmd=f"TRIGger:{self._identifier}:EDGE:SOUrce?", set_cmd=f"TRIGger:{self._identifier}:EDGE:SOUrce {{}}", vals=Enum(*trigger_sources), ) - """Parameter source""" + """Trigger source: 'CH1', 'CH2', ..., 'CH4', 'D0', 'D1', ..., 'D15', 'AUX', 'LINE'""" + + self.level: Parameter = self.add_parameter( + "level", + get_cmd=f"TRIGger:{self._identifier}:LEVel?", + set_cmd=f"TRIGger:{self._identifier}:LEVel {{}}", + get_parser=float, + unit="V", + ) + """Trigger level: The voltage level at which the trigger condition is met.""" def _trigger_type(self, value: str) -> None: - if value != "edge": + if value.lower() != "edge": raise NotImplementedError( "We currently only support the 'edge' trigger type" ) @@ -1002,3 +1160,225 @@ def __init__( def reset(self) -> None: self.write("MEASUrement:STATIstics:COUNt RESEt") + + +class TektronixDPOMeasurementImmediate(InstrumentChannel): + """ + The measurement commands let you specify an additional measurement, IMMed. The immediate measurement + has no front panel equivalent. Immediate measurements are never displayed. + Because they are computed only when needed, immediate measurements slow the + waveform update rate less than displayed measurements. + + """ + + def __init__( + self, + parent: Instrument, + name: str, + **kwargs: "Unpack[InstrumentBaseKWArgs]", + ) -> None: + super().__init__(parent, name, **kwargs) + + self.gating: Parameter = self.add_parameter( + "gating", + get_cmd="MEASUrement:GATing?", + set_cmd="MEASUrement:GATing {}", + vals=Enum("ON", "OFF", "ZOOM1", "ZOOM2", "ZOOM3", "ZOOM4", "CURSOR"), + ) + """Gating for the immediate measurement. Gating allows you to specify a subset of the waveform + to be measured. When gating is on, the measurement is performed only on the portion of the waveform + defined by the gate. The gate can be defined by zooming in on a portion of + the waveform and selecting one of the zoom gates (ZOOM1, ZOOM2, ZOOM3, ZOOM4), or by using the + cursor gate (CURSOR), which uses the horizontal positions of the cursors to define the gate. + """ + + self.source1: Parameter = self.add_parameter( + "source1", + get_cmd="MEASUrement:IMMed:SOUrce1?", + set_cmd="MEASUrement:IMMed:SOUrce1 {}", + vals=Enum(*TektronixDPOWaveform.valid_identifiers), + ) + """Source 1 for the immediate measurement: + CH1, CH2, CH3, CH4, MATH1, MATH2, MATH3, MATH4, + REF1, REF2, REF3, REF4, HISTogram""" + + self.source2: Parameter = self.add_parameter( + "source2", + get_cmd="MEASUrement:IMMed:SOUrce2?", + set_cmd="MEASUrement:IMMed:SOUrce2 {}", + vals=Enum(*TektronixDPOWaveform.valid_identifiers), + ) + """Source 2 for the immediate measurement. Source2 measurements only apply + to phase and delay measurement types, which require both a target (Source1) + and reference (Source2) source. + CH1, CH2, CH3, CH4, MATH1, MATH2, MATH3, MATH4, + REF1, REF2, REF3, REF4 + """ + + self.type: Parameter = self.add_parameter( + "type", + get_cmd="MEASUrement:IMMed:TYPE?", + set_cmd="MEASUrement:IMMed:TYPE {}", + vals=Enum( + "ACRMS", + "AMPlitude", + "AREa", + "BURst", + "CARea", + "CMEan", + "CRMs", + "DELay", + "DISTDUty", + "EXTINCTDB", + "EXTINCTPCT", + "EXTINCTRATIO", + "EYEHeight", + "EYEWIdth", + "FALL", + "FREQuency", + "HIGH", + "HITs", + "LOW", + "MAXimum", + "MEAN", + "MEDian", + "MINImum", + "NCROss", + "NDUty", + "NOVershoot", + "NWIdth", + "PBASe", + "PCROss", + "PCTCROss", + "PDUty", + "PEAKHits", + "PERIod", + "PHAse", + "PK2Pk", + "PKPKJitter", + "PKPKNoise", + "POVershoot", + "PTOP", + "PWIdth", + "QFACtor", + "RISe", + "RMS", + "RMSJitter", + "RMSNoise", + "SIGMA1", + "SIGMA2", + "SIGMA3", + "SIXSigmajit", + "SNRatio", + "STDdev", + "UNDEFINED", + "WAVEFORMS", + ), + get_parser=str.lower, + ) + """ + Immediate measurement type + Please see page 2-547 of the programmers manual for a detailed description of these arguments. + https://download.tek.com/manual/MSO-DPO5000-B-DPO7000-C-DPO70000-B-C-D-DX-DSA70000-B-C-D-and-MSO70000-C-DX-_2.pdf + """ + + self.units: Parameter = self.add_parameter( + "units", + get_cmd="MEASUrement:IMMed:UNITS?", + get_parser=strip_quotes, + ) + """Units of the immediate measurement""" + + self.value: Parameter = self.add_parameter( + "value", + get_cmd="MEASUrement:IMMed:VALue?", + get_parser=float, + ) + """The value of the immediate measurement""" + + +class TektronixDPOCursor(InstrumentChannel): + """ + The cursor submodule allows you to set and retrieve + information regarding the cursor type, state, and + positions. The cursor can be used to measure + voltage and time differences between two points on + the waveform display. + + Methods: + - function: Set or get the cursor type (e.g., horizontal bars, vertical bars, etc.) + - state: Set or get the cursor state (ON or OFF) + - x1: Set or get the x1 position of the cursor (in seconds) + - x2: Set or get the x2 position of the cursor (in seconds) + - y1: Set or get the y1 position of the cursor (in Volts) + - y2: Set or get the y2 position of the cursor (in Volts) + + """ + + def __init__( + self, + parent: Instrument, + name: str, + **kwargs: "Unpack[InstrumentBaseKWArgs]", + ) -> None: + super().__init__(parent, name, **kwargs) + + self.function: Parameter = self.add_parameter( + "function", + get_cmd="CURSOR:FUNCtion?", + set_cmd="CURSOR:FUNCtion {}", + vals=Enum( + "OFF", + "HBARS", + "VBARS", + "SCREEN", + "WAVEFORM", + ), + get_parser=str.lower, + ) + """Cursor Type [OFF, HBARS, VBARS, SCREEN, WAVEFORM]""" + + self.state: Parameter = self.add_parameter( + "state", + get_cmd="CURSOR:STATE?", + set_cmd="CURSOR:STATE {}", + vals=Enum("ON", "OFF"), + get_parser=str.lower, + ) + """Cursor state [ON, OFF]""" + + self.x1: Parameter = self.add_parameter( + "x1", + get_cmd="CURSOR:VBARS:POSITION1?", + set_cmd="CURSOR:VBARS:POSITION1 {}", + get_parser=float, + unit="s", + ) + """Cursor x1 position in seconds""" + + self.x2: Parameter = self.add_parameter( + "x2", + get_cmd="CURSOR:VBARS:POSITION2?", + set_cmd="CURSOR:VBARS:POSITION2 {}", + get_parser=float, + unit="s", + ) + """Cursor x2 position in seconds""" + + self.y1: Parameter = self.add_parameter( + "y1", + get_cmd="CURSOR:HBARS:POSITION1?", + set_cmd="CURSOR:HBARS:POSITION1 {}", + get_parser=float, + unit="V", + ) + """Cursor y1 position in Volts""" + + self.y2: Parameter = self.add_parameter( + "y2", + get_cmd="CURSOR:HBARS:POSITION2?", + set_cmd="CURSOR:HBARS:POSITION2 {}", + get_parser=float, + unit="V", + ) + """Cursor y2 position in Volts"""