From a1a3f5991f8aa47cc980f4a78cf4e7c95e799192 Mon Sep 17 00:00:00 2001 From: TNO-Helios Date: Thu, 5 Dec 2024 11:43:29 +0100 Subject: [PATCH] Add Rigol and Keithley driver classes --- .../drivers/Rigol/Rigol_DP932.py | 75 ++++++++ .../drivers/Tektronix/Keithley_2401.py | 173 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py create mode 100644 src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py diff --git a/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py b/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py new file mode 100644 index 000000000..06f18999e --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py @@ -0,0 +1,75 @@ +from qcodes import VisaInstrument +from qcodes.utils.validators import Numbers + +class RigolDP932E(VisaInstrument): + """ + QCoDeS driver for the Rigol DP932E Programmable DC Power Supply. + """ + + def __init__(self, name: str, address: str, **kwargs): + super().__init__(name, address, **kwargs) + + # Add parameters for each channel + for channel in range(1, 4): + self.add_parameter(f'voltage_{channel}', + label=f'Channel {channel} Voltage', + unit='V', + get_cmd=f':MEASure:VOLTage? CH{channel}', + set_cmd=f':APPLy CH{channel},{{}}', + get_parser=float, + vals=Numbers(0, 31.5)) # According to manual, max voltage is 31.5V for DP932E + + self.add_parameter(f'current_{channel}', + label=f'Channel {channel} Current', + unit='A', + get_cmd=f':MEASure:CURRent? CH{channel}', + set_cmd=f':APPLy CH{channel},,,{{}}', + get_parser=float, + vals=Numbers(0, 3.15)) # According to manual, max current is 3.15A for DP932E + + self.add_parameter(f'output_{channel}', + label=f'Channel {channel} Output', + get_cmd=f':OUTPut:STATe? CH{channel}', + set_cmd=f':OUTPut:STATe {{}} CH{channel}', + val_mapping={'ON': 1, 'OFF': 0}) + + # Reset the instrument + self.write('*RST') + self.connect_message() + + def reset(self): + """Resets the instrument to its default settings.""" + self.write('*RST') + + def enable_output(self, channel: int): + """Enables the output for the specified channel.""" + self.write(f':OUTPut CH{channel},ON') + + def disable_output(self, channel: int): + """Disables the output for the specified channel.""" + self.write(f':OUTPut CH{channel},OFF') + + def measure_voltage(self, channel: int) -> float: + """Measures the voltage at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:VOLTage? CH{channel}')) + + def measure_current(self, channel: int) -> float: + """Measures the current at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:CURRent? CH{channel}')) + + def set_voltage(self, channel: int, voltage: float): + """Sets the output voltage for the specified channel.""" + self.write(f':APPLy CH{channel},{voltage}') + + def set_current(self, channel: int, current: float): + """Sets the output current for the specified channel.""" + self.write(f':APPLy CH{channel},,,{current}') + + def set_output_state(self, channel: int, state: str): + """Sets the output state (ON/OFF) for the specified channel.""" + self.write(f':OUTPut:STATe {state},CH{channel}') + + def measure_power(self, channel: int) -> float: + """Measures the power at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:POWEr? CH{channel}')) + \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py b/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py new file mode 100644 index 000000000..a23a2186d --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py @@ -0,0 +1,173 @@ +from qcodes import VisaInstrument +from qcodes.utils.validators import Numbers +import numpy as np +import time + + +class Keithley2400(VisaInstrument): + """ + QCoDeS driver for Keithley 2400 SourceMeter. + Provides functionality for sourcing and measuring voltage, current, and resistance. + Includes support for voltage sweeps and detailed parameter configuration. + """ + + def __init__(self, name: str, address: str, **kwargs): + """ + Initialize the Keithley 2400 SourceMeter. + Args: + name (str): Name of the instrument. + address (str): Visa address of the instrument. + """ + super().__init__(name, address, terminator='\n', **kwargs) + + # Parameters + self.add_parameter( + 'voltage', + label='Set Voltage', + unit='V', + get_cmd=':SOUR:VOLT:LEV:IMM:AMPL?', + set_cmd=':SOUR:VOLT:LEV:IMM:AMPL {:.4f}', + get_parser=float, + vals=Numbers(-210, 210) + ) + + self.add_parameter( + 'current', + label='Measured Current', + unit='A', + get_cmd=self._measure_current + ) + + self.add_parameter( + 'set_current', + label='Set Current', + unit='A', + get_cmd=':SOUR:CURR:LEV:IMM:AMPL?', + set_cmd=':SOUR:CURR:LEV:IMM:AMPL {:.4f}', + get_parser=float, + vals=Numbers(-1.05, 1.05) # Adjust the range based on the Keithley 2400 specs + ) + + self.add_parameter( + 'measured_voltage', + label='Measured Voltage', + unit='V', + get_cmd=self._measure_voltage + ) + + self.add_parameter( + 'resistance', + label='Measured Resistance', + unit='Ohm', + get_cmd=self._measure_resistance + ) + + self.add_parameter( + 'output', + label='Output State', + get_cmd=':OUTP?', + set_cmd=':OUTP {}', + val_mapping={'on': 1, 'off': 0} + ) + + self.add_parameter( + 'current_range', + label='Current Range', + get_cmd=':SENS:CURR:RANG?', + set_cmd=':SENS:CURR:RANG {:.4f}', + get_parser=float, + vals=Numbers(1e-9, 1e1) + ) + + self.add_parameter( + 'auto_range', + label='Auto Range', + get_cmd=':SENS:CURR:RANG:AUTO?', + set_cmd=':SENS:CURR:RANG:AUTO {}', + val_mapping={True: 'ON', False: 'OFF'} + ) + + self.add_parameter( + 'sweep_voltage', + label='Sweep Voltage', + unit='V', + set_cmd=self.set_voltage + ) + + self.connect_message() + + def _measure_voltage(self): + """ + Measure the voltage in the selected range or auto-range. + Returns: + float: Measured voltage in volts. + """ + self.write(':FUNC "VOLT"') + self.write(':FORM:ELEM VOLT') + response = self.ask(':READ?') + return float(response) + + def _measure_current(self): + """ + Measure the current in the selected range or auto-range. + Returns: + float: Measured current in amperes. + """ + self.write(':FUNC "CURR"') + self.write(':FORM:ELEM CURR') + response = self.ask(':READ?') + return float(response) + + def _measure_resistance(self): + """ + Measure the resistance in the selected range. + Returns: + float: Measured resistance in ohms. + """ + self.write(':FUNC "RES"') + self.write(':FORM:ELEM RES') + response = self.ask(':READ?') + return float(response) + + def enable_output(self, state: bool = True): + """ + Enable or disable the output. + Args: + state (bool): Set True to enable, False to disable. + """ + self.output('on' if state else 'off') + + def set_voltage(self, voltage: float): + """ + Set the output voltage. + Args: + voltage (float): The voltage level to set in volts. + """ + self.voltage(voltage) + + def sweep_voltage_measure(self, voltage_start: float, voltage_stop: float, steps: int): + """ + Sweep voltage from start to stop in specified steps and measure voltage and current. + Args: + voltage_start (float): Starting voltage. + voltage_stop (float): Ending voltage. + steps (int): Number of steps. + Returns: + list of dict: Contains 'voltage_set', 'voltage_measured', and 'current_measured'. + """ + voltage_values = np.linspace(voltage_start, voltage_stop, steps) + measurements = [] + self.write(':FUNC "VOLT","CURR"') + self.write(':FORM:ELEM VOLT,CURR') + + for voltage in voltage_values: + self.voltage(voltage) + time.sleep(0.1) # Allow settling + response = self.ask(':READ?') + voltage_measured, current_measured = map(float, response.split(',')) + measurements.append({ + 'voltage_set': voltage, + 'voltage_measured': voltage_measured, + 'current_measured': current_measured + }) + return measurements \ No newline at end of file