Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c2c8adb
Better AC/DC handling. Allow setting external trigger.
robertjensen Oct 31, 2023
c99272a
Allow setting trigger source. Implement read-fresh command.
robertjensen Oct 31, 2023
4279724
Added some test-code that later on needs to be integrated in driver
robertjensen Oct 31, 2023
3a41683
Prelimenary driver for oxford mercury cryostat electronics
robertjensen Oct 31, 2023
d811eda
Preliminary updates to driver
robertjensen Oct 31, 2023
b5eabbc
Allwo different encodings, imporve network performance.
robertjensen Oct 31, 2023
4f37b53
Obey the new convention of running code though `black`
robertjensen Oct 31, 2023
330be26
Obey the new convention of running code though `black`
robertjensen Oct 31, 2023
fb78f53
Change communication to pyvisa
robertjensen May 31, 2024
a073518
Remove irrelevant out-commented statement
robertjensen Jun 4, 2024
e021aab
Port to pyvisa
robertjensen Jun 4, 2024
77fb1cd
Port to pyvisa
robertjensen Jun 4, 2024
0c70fcd
Added more functions
robertjensen Jun 4, 2024
06548b5
Draft for Keithley 2450 driver
robertjensen Jun 18, 2024
31a6ae2
Small little draft for driver Keithley 6517B
robertjensen Jun 19, 2024
7e5a0b8
Port to PyVISA (deprecating scpi.py)
robertjensen Nov 4, 2024
b4234a5
Add lan support (for 2450) fix a few bugs in PyVISA port
robertjensen Nov 4, 2024
4a5689d
Add functionality to configure digital port
robertjensen Nov 4, 2024
6074a38
Fix bug from PyVISA port
robertjensen Nov 4, 2024
6f661f7
Various test-code. Will be cleaned up soon.
robertjensen Nov 4, 2024
d1fbf85
Change to pyvisa rather than internal scpi driver.
robertjensen Nov 25, 2024
7404cc4
Lates version of 24xx drivers for cryostat operation
robertjensen Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 85 additions & 54 deletions PyExpLabSys/drivers/keithley_2000.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
""" Simple driver for Keithley 2000 DMM """
from PyExpLabSys.drivers.scpi import SCPI
import time

import pyvisa

class Keithley2000(SCPI):

class Keithley2000:
"""
Simple driver for Keithley 2000 DMM
"""

def __init__(
self, interface, hostname='', device='', baudrate=9600, gpib_address=None
):
def __init__(self, interface, device='', baudrate=9600, gpib_address=None):
rm = pyvisa.ResourceManager('@py')
if interface == 'serial':
SCPI.__init__(
self,
interface=interface,
device=device,
baudrate=baudrate,
line_ending='\n',
conn_string = 'ASRL{}::INSTR'.format(device)
self.instr = rm.open_resource(conn_string)
self.instr.set_visa_attribute(
pyvisa.constants.VI_ATTR_ASRL_FLOW_CNTRL,
pyvisa.constants.VI_ASRL_FLOW_XON_XOFF,
)
self.comm_dev.timeout = 2
self.comm_dev.rtscts = False
self.comm_dev.xonxoff = False
self.instr.read_termination = '\n'
self.instr.write_termination = '\n'
self.instr.baud_rate = baudrate
if interface == 'gpib':
SCPI.__init__(self, interface=interface, gpib_address=gpib_address)
pass

def set_bandwith(self, measurement='voltage:ac', bandwidth=None):
scpi_cmd = 'SENSE:{}:DETector:BANDwidth'.format(measurement)
if bandwidth is not None:
DMM.scpi_comm(scpi_cmd + ' {}'.format(bandwidth))
value_raw = DMM.scpi_comm(scpi_cmd + '?')
self.instr.write(scpi_cmd + ' {}'.format(bandwidth))
value_raw = self.instr.query(scpi_cmd + '?')
value = float(value_raw)
return value

Expand All @@ -41,87 +41,118 @@ def set_range(self, value: float):
if value < 0:
value = 0
if value == 0:
self.scpi_comm(':SENSE:VOLT:DC:RANGE:AUTO ON')
self.scpi_comm(':SENSE:VOLT:AC:RANGE:AUTO ON')
self.instr.write(':SENSE:VOLT:DC:RANGE:AUTO ON')
self.instr.write(':SENSE:VOLT:AC:RANGE:AUTO ON')
else:
self.scpi_comm(':SENSE:VOLT:DC:RANGE {:.5f}'.format(value))
self.scpi_comm(':SENSE:VOLT:AC:RANGE {:.5f}'.format(value))

actual_range_raw = self.scpi_comm(':SENSE:VOLTAGE:AC:RANGE?')
self.instr.write(':SENSE:VOLT:DC:RANGE {:.5f}'.format(value))
self.instr.write(':SENSE:VOLT:AC:RANGE {:.5f}'.format(value))
# The instrument cannot process this infinitiely fast and might end up
# lacking behind and filling up buffers - wait a little while to prevent this
# time.sleep(0.4)
actual_range_raw = self.instr.query(':SENSE:VOLTAGE:AC:RANGE?')
actual_range = float(actual_range_raw)
return actual_range

def set_integration_time(self, nplc: float = None):
"""
Set the measurement integration time
"""

if 'AC' in self.configure_measurement_type():
set_msg = 'SENSE:VOLTAGE:AC:NPLCYCLES {}'
read_msg = 'SENSE:VOLTAGE:AC:NPLCYCLES?'
else:
set_msg = 'SENSE:VOLTAGE:DC:NPLCYCLES {}'
read_msg = 'SENSE:VOLTAGE:DC:NPLCYCLES?'

if nplc is not None:
if nplc < 0.01:
nplc = 0.01
if nplc > 60:
nplc = 60
self.scpi_comm('SENSE:VOLTAGE:AC:NPLCYCLES {}'.format(nplc))
# self.scpi_comm('SENSE:VOLTAGE:DC:NPLCYCLES {}'.format(nplc))
current_nplc = float(self.scpi_comm('SENSE:VOLTAGE:AC:NPLCYCLES?'))
self.instr.write(set_msg.format(nplc))
current_nplc = float(self.instr.query(read_msg))
return current_nplc

def configure_measurement_type(self, measurement_type=None):
""" Setup measurement type """
"""Setup measurement type"""
if measurement_type is not None:
# todo: Ensure type is an allow type!!!!
self.scpi_comm(':CONFIGURE:{}'.format(measurement_type))
actual = self.scpi_comm(':CONFIGURE?')
self.instr.write(':CONFIGURE:{}'.format(measurement_type))
actual = self.instr.query(':CONFIGURE?')
return actual

def set_trigger_source(self, external):
"""
Set the trigger source either to external or immediate.
If external is true, trigger will be set accordingly
otherwise immediate triggering will be chosen.
"""
if external:
self.instr.write('TRIGGER:SOURCE External')
else:
self.instr.write('TRIGGER:SOURCE Immediate')
return external

def read_dc_voltage(self):
"""Read a voltage"""
raw = self.instr.query(':MEASURE:VOLTAGE:DC?')
voltage = float(raw)
return voltage

def read_ac_voltage(self):
""" Read a voltage """
raw = self.scpi_comm(':MEASURE:VOLTAGE:AC?')
"""Read a voltage"""
raw = self.instr.query(':MEASURE:VOLTAGE:AC?')
voltage = float(raw)
return voltage

def next_reading(self):
""" Read next reading """
"""Read next reading"""
t0 = time.time()
while not self.measurement_available():
time.sleep(0.001)
if (time.time() - t0) > 10:
# Todo: This is not good enough
print('Keithley 2000 TIMEOUT!')
break
raw = self.scpi_comm(':DATA?')
raw = self.instr.query(':DATA?')
voltage = float(raw)
return voltage

def measurement_available(self):
meas_event = int(self.scpi_comm('STATUS:MEASUREMENT:EVENT?'))
# todo: Check if pyvisa has some neat trick for this
meas_event = int(self.instr.query('STATUS:MEASUREMENT:EVENT?'))
mav_bit = 5
mav = (meas_event & 2 ** mav_bit) == 2 ** mav_bit
return mav


if __name__ == '__main__':
import time

GPIB = 16
DMM = Keithley2000(interface='gpib', gpib_address=GPIB)
device = '/dev/serial/by-id/usb-FTDI_Chipi-X_FT6EYK1T-if00-port0'
DMM = Keithley2000(
interface='serial',
device=device,
baudrate=9600,
)
print(repr(DMM.instr.query("*IDN?")))
print(DMM.set_bandwith())

# Errors:
# +802 - RS232 overrun
# -113 - Undefined header
DMM.set_trigger_source(external=False)

DMM.set_range(0)
for _ in range(0, 10):
# print()
# print(DMM.read_software_version())
# DMM.set_trigger_source(external=False)
voltage = DMM.next_reading()
print(voltage)

# TODO! Something changes with the configuration when this
# command is called, measurement is much slower and
# NPLC command fails?!?!
# print(DMM.configure_measurement_type('volt:ac'))

DMM.set_range(0.1)
print(DMM.set_integration_time(2))
# print(DMM.set_bandwith())
# print(DMM.set_integration_time(10))

for i in range(0, 20):
# time.sleep(0.05)
t = time.time()
# meas_event = DMM.scpi_comm('STATUS:MEASUREMENT:EVENT?')
# print(bin(int(meas_event)))
while not DMM.measurement_available():
time.sleep(0.05)
reading = DMM.next_reading()
dt = time.time() - t
print('Time: {:.2f}ms. AC {:.3f}uV'.format(dt * 1e3, reading * 1e6))
# print(DMM.read_dc_voltage())
# print(DMM.read_dc_voltage())
109 changes: 68 additions & 41 deletions PyExpLabSys/drivers/keithley_2182.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,53 @@
""" Simple driver for Keithley 2182 Nanovolt Meter """
from PyExpLabSys.drivers.scpi import SCPI
import time

import pyvisa

class Keithley2182(SCPI):

class Keithley2182:
"""
Simple driver for Keithley 2182 Nanovolt Meter
Actual implementation performed on a 2182a - please
double check if you have a 2182.
"""

def __init__(
self, interface, hostname='', device='', baudrate=9600, gpib_address=None
):
def __init__(self, interface, hostname='', device='', baudrate=19200):
rm = pyvisa.ResourceManager('@py')
if interface == 'serial':
SCPI.__init__(
self,
interface=interface,
device=device,
baudrate=baudrate,
line_ending='\n',
)
self.comm_dev.timeout = 2
self.comm_dev.rtscts = False
self.comm_dev.xonxoff = False
if interface == 'gpib':
SCPI.__init__(self, interface=interface, gpib_address=gpib_address)

conn_string = 'ASRL{}::INSTR'.format(device)
self.instr = rm.open_resource(conn_string)
self.instr.read_termination = '\r'
self.instr.write_termination = '\r'
self.instr.baud_rate = baudrate
# For now, turn off continous trigger - this might need reconsideration
self.scpi_comm('INIT:CONT OFF')
# self.scpi_comm('INIT:CONT OFF')

def set_range(self, channel1: float = None, channel2: float = None):
"""
Set the measurement range of the device, 0 will indicate auto-range
"""
print('Set range of channel 1')
if channel1 is not None:
if channel1 > 120:
channel1 = 120
if channel1 == 0:
self.scpi_comm(':SENSE:VOLT:CHANNEL1:RANGE:AUTO ON')
self.instr.write(':SENSE:VOLT:CHANNEL1:RANGE:AUTO ON')
else:
self.scpi_comm(':SENSE:VOLT:CHANNEL1:RANGE {:.2f}'.format(channel1))
self.instr.write(':SENSE:VOLT:CHANNEL1:RANGE {:.2f}'.format(channel1))

if channel2 is not None:
if channel2 > 12:
channel2 = 12
if channel2 == 0:
self.scpi_comm(':SENSE:VOLTAGE:CHANNEL2:RANGE:AUTO ON')
self.instr.write(':SENSE:VOLTAGE:CHANNEL2:RANGE:AUTO ON')
else:
self.scpi_comm(':SENSE:VOLT:CHANNEL2:RANGE {:.2f}'.format(channel2))

actual_channel1_raw = self.scpi_comm(':SENSE:VOLTAGE:CHANNEL1:RANGE?')
actual_channel2_raw = self.scpi_comm(':SENSE:VOLTAGE:CHANNEL2:RANGE?')
self.instr.write(':SENSE:VOLT:CHANNEL2:RANGE {:.2f}'.format(channel2))
print('Check the actual range')
actual_channel1_raw = self.instr.query(':SENSE:VOLTAGE:CHANNEL1:RANGE?')
actual_channel2_raw = self.instr.query(':SENSE:VOLTAGE:CHANNEL2:RANGE?')
range1 = float(actual_channel1_raw)
range2 = float(actual_channel2_raw)
print('Value is: ', range1)
return range1, range2

def set_integration_time(self, nplc: float = None):
Expand All @@ -64,29 +59,61 @@ def set_integration_time(self, nplc: float = None):
nplc = 0.01
if nplc > 60:
nplc = 60
self.scpi_comm('SENSE:VOLTAGE:NPLCYCLES {}'.format(nplc))
current_nplc = float(self.scpi_comm('SENSE:VOLTAGE:NPLCYCLES?'))
self.instr.write('SENSE:VOLTAGE:NPLCYCLES {}'.format(nplc))
# print('waiting....')
time.sleep(nplc * 0.25)
# print('done')
current_nplc = float(self.instr.query('SENSE:VOLTAGE:NPLCYCLES?'))
return current_nplc

def set_trigger_source(self, external):
"""
Set the trigger source either to external or immediate.
If external is true, trigger will be set accordingly
otherwise immediate triggering will be chosen.
"""
if external:
self.instr.write(':TRIGGER:SOURCE External')
else:
self.instr.write(':TRIGGER:SOURCE Immediate')
return external

def read_fresh(self):
"""
Read a single value from current channel. This will also be a new value
(or will fail if channel is not trigged.
"""
raw = self.instr.query(':DATA:FRESh?') # DF? also works
try:
voltage = float(raw)
except ValueError:
voltage = None
return voltage

def read_voltage(self, channel: int):
""" Read the measured voltage """
"""Read the measured voltage"""
if channel not in (1, 2):
return None
self.scpi_comm(":SENSE:FUNC 'VOLT:DC'")
self.scpi_comm(':SENSE:CHANNEL {}'.format(channel))
raw = self.scpi_comm(':READ?')
self.instr.write(":SENSE:FUNC 'VOLT:DC'")
time.sleep(0.5)
self.instr.write(':SENSE:CHANNEL {}'.format(channel))
time.sleep(0.5)
# raw = self.instr.query(':READ?')
raw = self.read_fresh()
voltage = float(raw)
return voltage


if __name__ == '__main__':
GPIB = 7
NVM = Keithley2182(interface='gpib', gpib_address=GPIB)

print(NVM.set_range(1, 0.01))
print(NVM.set_integration_time(10))
# usb-1a86_USB2.0-Ser_-if00-port0 # Vxx
# usb-FTDI_Chipi-X_FT6EYK1T-if00-port0 # DMM
# usb-FTDI_Chipi-X_FT6F1A7R-if00-port0 # Old gate
# usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0 # Vxy
# NVM = Keithley2182(interface='gpib', gpib_address=GPIB)
NVM = Keithley2182(
interface='serial',
device='/dev/serial/by-id/usb-1a86_USB2.0-Ser_-if00-port0',
)

for i in range(0, 10):
print()
print('Channel 1: {:.3f}uV'.format(NVM.read_voltage(1) * 1e6))
print('Channel 2: {:.3f}uV'.format(NVM.read_voltage(2) * 1e6))
print(NVM.instr.query('*IDN?'))
print(NVM.set_range(1, 0.1))
Loading