diff --git a/docs/examples/Tektronix_Keithley_6500.ipynb b/docs/examples/Tektronix_Keithley_6500.ipynb index c5bced3af..4979b6c9e 100644 --- a/docs/examples/Tektronix_Keithley_6500.ipynb +++ b/docs/examples/Tektronix_Keithley_6500.ipynb @@ -7,6 +7,13 @@ "# QCoDeS Example with Tektronix Keithley Digital Multimeter DMM6500" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialization and Connection" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -28,24 +35,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Create the instrument (in this case a DMM6500 connected via USB)" + "Create the instrument (in this case a DMM6500 connected via USB). If a scanner card is inserted, it will be detected." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Connected to: KEITHLEY INSTRUMENTS DMM6500 (serial:04438044, firmware:1.0.04b) in 0.05s\n" + "Connected to: KEITHLEY INSTRUMENTS DMM6500 (serial:04438044, firmware:1.0.04b) in 0.07s\n", + "Scanner card 2000-SCAN detected.\n" ] } ], "source": [ - "dmm = dmm6500.Keithley_6500('dmm-1','USB0::0x05E6::0x6500::04438044::INSTR')" + "dmm = dmm6500.Keithley_6500('dmm-1','TCPIP0::192.168.1.12::inst0::INSTR')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Performing simple measurements" ] }, { @@ -57,16 +72,16 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2986.62" + "5799.959" ] }, - "execution_count": 3, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -74,6 +89,208 @@ "source": [ "dmm.res.measure.get()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The commands for the different measurable quantities are:\n", + "- dmm.res.measure.get(): 2 wire resistance\n", + "- dmm.fres.measure.get(): 4 wire resistance\n", + "- dmm.volt.measure.get(): DC voltage\n", + "- dmm.curr.measure.get(): DC current" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively the measurable quantities can be accessd directly via one of the following commands:\n", + "- dmm.resistance.get(): 2 wire resistance\n", + "- dmm.resistance_4w.get(): 4 wire resistance\n", + "- dmm.voltage_dc.get(): DC voltage\n", + "- dmm.current_dc.get(): DC current\n", + "For instance, the resistance via a two wire measurement can be measured with the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5793.865" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dmm.resistance.get()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Querying the active terminal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The DMM6500 has a front and a rear terminal. The active terminal can only be switched via a knob on the front panel of the multimeter. The currently active terminal can be queried via:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'FRON'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dmm.active_terminal.get()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the rear terminal is active, the same command returns:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'REAR'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dmm.active_terminal.get()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using the 2000-SCAN scanning card" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The 2000-SCAN scanning card allows to measure up to 10 channels via the rear terminal. To measure e.g. the resistance via channel 1, use:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5798.519" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dmm.ch1.resistance.get()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Checking the active terminal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the wrong terminal is active, an error is raised:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "('Front terminal is active instead of rear terminal.', 'getting dmm-1_ch1_resistance')", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdmm\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mch1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mresistance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\instrument\\parameter.py\u001b[0m in \u001b[0;36mget_wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 583\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 584\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'getting {}'\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 585\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 586\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 587\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mget_wrapper\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\instrument\\parameter.py\u001b[0m in \u001b[0;36mget_wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 570\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 571\u001b[0m \u001b[1;31m# There might be cases where a .get also has args/kwargs\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 572\u001b[1;33m \u001b[0mraw_value\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_function\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 573\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 574\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_from_raw_value_to_value\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mraw_value\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\utils\\command.py\u001b[0m in \u001b[0;36m__call__\u001b[1;34m(self, *args)\u001b[0m\n\u001b[0;32m 176\u001b[0m raise TypeError(\n\u001b[0;32m 177\u001b[0m 'command takes exactly {} args'.format(self.arg_count))\n\u001b[1;32m--> 178\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexec_function\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mc:\\users\\frequency conversion\\documents\\code\\qcodes_contrib_drivers\\qcodes_contrib_drivers\\drivers\\Tektronix\\Keithley_2000_Scan.py\u001b[0m in \u001b[0;36m_measure\u001b[1;34m(self, quantity)\u001b[0m\n\u001b[0;32m 63\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mask\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"READ?\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 64\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 65\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Front terminal is active instead of rear terminal.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m: ('Front terminal is active instead of rear terminal.', 'getting dmm-1_ch1_resistance')" + ] + } + ], + "source": [ + "dmm.ch1.resistance.get()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "('Rear terminal is active instead of front terminal.', 'getting dmm-1_resistance')", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdmm\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mresistance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\instrument\\parameter.py\u001b[0m in \u001b[0;36mget_wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 583\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 584\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'getting {}'\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 585\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 586\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 587\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mget_wrapper\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\instrument\\parameter.py\u001b[0m in \u001b[0;36mget_wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 570\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 571\u001b[0m \u001b[1;31m# There might be cases where a .get also has args/kwargs\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 572\u001b[1;33m \u001b[0mraw_value\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_function\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 573\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 574\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_from_raw_value_to_value\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mraw_value\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\frequency conversion\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\qcodes\\utils\\command.py\u001b[0m in \u001b[0;36m__call__\u001b[1;34m(self, *args)\u001b[0m\n\u001b[0;32m 176\u001b[0m raise TypeError(\n\u001b[0;32m 177\u001b[0m 'command takes exactly {} args'.format(self.arg_count))\n\u001b[1;32m--> 178\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexec_function\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mc:\\users\\frequency conversion\\documents\\code\\qcodes_contrib_drivers\\qcodes_contrib_drivers\\drivers\\Tektronix\\Keithley_6500.py\u001b[0m in \u001b[0;36m_measure\u001b[1;34m(self, quantity)\u001b[0m\n\u001b[0;32m 152\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mask\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mf\"MEAS:{quantity}?\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 153\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 154\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Rear terminal is active instead of front terminal.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m: ('Rear terminal is active instead of front terminal.', 'getting dmm-1_resistance')" + ] + } + ], + "source": [ + "dmm.resistance.get()" + ] } ], "metadata": { diff --git a/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2000_Scan.py b/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2000_Scan.py new file mode 100644 index 000000000..cb0d91e63 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2000_Scan.py @@ -0,0 +1,65 @@ +from functools import partial +from typing import TYPE_CHECKING + +from qcodes.instrument import InstrumentChannel + +if TYPE_CHECKING: + from .Keithley_6500 import Keithley_6500 + + +class Keithley_2000_Scan_Channel(InstrumentChannel): + """ + This is the qcodes driver for a channel of the 2000-SCAN scanner card. + """ + def __init__(self, dmm: "Keithley_6500", channel: int, **kwargs) -> None: + """ + Initialize instance of scanner card Keithley 2000-SCAN + Args: + dmm: Instance of digital multimeter Keithley6500 containing the scanner card + channel: Channel number + **kwargs: Keyword arguments to pass to __init__ function of InstrumentChannel class + """ + super().__init__(dmm, f"ch{channel}", **kwargs) + self.channel = channel + self.dmm = dmm + + self.add_parameter('resistance', + unit='Ohm', + label=f'Resistance CH{self.channel}', + get_parser=float, + get_cmd=partial(self._measure, 'RES')) + + self.add_parameter('resistance_4w', + unit='Ohm', + label=f'Resistance (4-wire) CH{self.channel}', + get_parser=float, + get_cmd=partial(self._measure, 'FRES')) + + self.add_parameter('voltage_dc', + unit='V', + label=f'DC Voltage CH{self.channel}', + get_parser=float, + get_cmd=partial(self._measure, 'VOLT')) + + self.add_parameter('current_dc', + unit='A', + label=f'DC current CH{self.channel}', + get_parser=float, + get_cmd=partial(self._measure, 'CURR')) + + def _measure(self, quantity: str) -> str: + """ + Measure given quantity at rear terminal of the instrument. Only perform measurement if rear terminal is + active. Send SCPI command to measure and read out given quantity. + Args: + quantity: Quantity to be measured + + Returns: Measurement result + + """ + if self.dmm.active_terminal.get() == 'REAR': + self.write(f"SENS:FUNC '{quantity}', (@{self.channel:d})") + self.write(f"ROUT:CLOS (@{self.channel:d})") + return self.ask("READ?") + else: + raise RuntimeError("Front terminal is active instead of rear terminal.") diff --git a/qcodes_contrib_drivers/drivers/Tektronix/Keithley_6500.py b/qcodes_contrib_drivers/drivers/Tektronix/Keithley_6500.py index 221d49bfb..7a2951822 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/Keithley_6500.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/Keithley_6500.py @@ -2,10 +2,21 @@ from qcodes.instrument import InstrumentChannel from qcodes.utils.validators import Numbers from functools import partial +from .Keithley_2000_Scan import Keithley_2000_Scan_Channel class Keithley_Sense(InstrumentChannel): + """ + This is the class for a measurement channel, i.e. the quantity to be measured (e.g. resistance, voltage). + """ def __init__(self, parent: VisaInstrument, name: str, channel: str) -> None: + """ + + Args: + parent: VisaInstrument instance of the Keithley Digital Multimeter + name: Channel name (e.g. 'CH1') + channel: Name of the quantity to measure (e.g. 'VOLT' for DC voltage measurement) + """ valid_channels = ['VOLT', 'CURR', 'RES', 'FRES'] if channel.upper() not in valid_channels: raise ValueError(f"Channel must be one of the following: {', '.join(valid_channels)}") @@ -15,42 +26,129 @@ def __init__(self, parent: VisaInstrument, name: str, channel: str) -> None: unit=partial(self._get_unit, channel), label=partial(self._get_label, channel), get_parser=float, - get_cmd=f":MEAS:{channel}?", + get_cmd=partial(self.parent._measure, channel), docstring="Measure value of chosen quantity (Current/Voltage/Resistance)." ) - self.add_parameter('nlpc', - label='NLPC', + self.add_parameter('nplc', + label='NPLC', get_parser=float, get_cmd=f"SENS:{channel}:NPLC?", set_cmd=f"SENS:{channel}:NPLC {{:.4f}}", - vals=Numbers(0.0005, 12) + vals=Numbers(0.0005, 12), + docstring="Integration rate (Number of Power Line Cycles)" ) @staticmethod - def _get_unit(channel: str) -> str: + def _get_unit(quantity: str) -> str: + """ + + Args: + quantity: Quantity to be measured + + Returns: Corresponding unit string + + """ channel_units = {'VOLT': 'V', 'CURR': 'A', 'RES': 'Ohm', 'FRES': 'Ohm'} - return channel_units[channel] + return channel_units[quantity] @staticmethod - def _get_label(channel: str) -> str: + def _get_label(quantity: str) -> str: + """ + + Args: + quantity: Quantity to be measured + + Returns: Corresponding parameter label + + """ channel_labels = {'VOLT': 'Measured voltage.', 'CURR': 'Measured current.', 'RES': 'Measured resistance', 'FRES': 'Measured resistance (4w)'} - return channel_labels[channel] + return channel_labels[quantity] class Keithley_6500(VisaInstrument): - + """ + This is the qcodes driver for a Keithley DMM6500 digital multimeter. + """ def __init__(self, name: str, address: str, terminator="\n", **kwargs): + """ + Initialize instance of digital multimeter Keithley6500. Check if scanner card is inserted. + Args: + name: Name of instrument + address: Address of instrument + terminator: Termination character for SCPI commands + **kwargs: Keyword arguments to pass to __init__ function of VisaInstrument class + """ super().__init__(name, address, terminator=terminator, **kwargs) + for quantity in ['VOLT', 'CURR', 'RES', 'FRES']: + channel = Keithley_Sense(self, quantity.lower(), quantity) + self.add_submodule(quantity.lower(), channel) + + self.add_parameter('active_terminal', + label='active terminal', + get_cmd="ROUTe:TERMinals?", + docstring="Active terminal of instrument. Can only be switched via knob on front panel.") + + self.add_parameter('resistance', + unit='Ohm', + label='Measured resistance', + get_parser=float, + get_cmd=partial(self._measure, 'RES'), + ) - for sense in ['VOLT', 'CURR', 'RES', 'FRES']: - channel = Keithley_Sense(self, sense.lower(), sense) - self.add_submodule(sense.lower(), channel) + self.add_parameter('resistance_4w', + unit='Ohm', + label='Measured resistance', + get_parser=float, + get_cmd=partial(self._measure, 'FRES') + ) + + self.add_parameter('voltage_dc', + unit='V', + label='Measured DC voltage', + get_parser=float, + get_cmd=partial(self._measure, 'VOLT') + ) + + self.add_parameter('current_dc', + unit='A', + label='Measured DC current', + get_parser=float, + get_cmd=partial(self._measure, 'CURR') + ) self.connect_message() + + # check if scanner card is connected + # If no scanner card is connected, the query below returns "Empty Slot". + # For the Scanner Card 2000-SCAN used for development of this driver the output was + # "2000,10-Chan Mux,0.0.0a,00000000". + scan_idn_msg = self.ask(":SYSTem:CARD1:IDN?") + if scan_idn_msg != "Empty Slot": + msg_parts = scan_idn_msg.split(",") + print(f"Scanner card {msg_parts[0]}-SCAN detected.") + for ch_number in range(1, 11): + scan_channel = Keithley_2000_Scan_Channel(self, ch_number) + self.add_submodule(f"ch{ch_number:d}", scan_channel) + + # only measure if front terminal is active + def _measure(self, quantity: str) -> str: + """ + Measure given quantity at front terminal of the instrument. Only perform measurement if front terminal is + active. Send SCPI command to measure and read out given quantity. + Args: + quantity: Quantity to be measured + + Returns: Measurement result + + """ + if self.active_terminal.get() == 'FRON': + return self.ask(f"MEAS:{quantity}?") + else: + raise RuntimeError("Rear terminal is active instead of front terminal.")