Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
124 changes: 60 additions & 64 deletions PyExpLabSys/drivers/keithley_2000.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
""" Simple driver for Keithley 2000 DMM """
import time
from PyExpLabSys.drivers.scpi import SCPI

import pyvisa

class Keithley2000(SCPI):

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

def __init__(
self,
interface,
hostname='',
device='',
baudrate=9600,
gpib_address=None,
visa_string=None,
line_ending='\n',
):
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,
visa_string=visa_string,
line_ending=line_ending,
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)
if interface == 'usbtmc':
SCPI.__init__(self, interface=interface, visa_string=visa_string)
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 @@ -52,13 +41,15 @@ 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

Expand All @@ -79,16 +70,16 @@ def set_integration_time(self, nplc: float = None):
nplc = 0.01
if nplc > 60:
nplc = 60
self.scpi_comm(set_msg.format(nplc))
current_nplc = float(self.scpi_comm(read_msg))
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"""
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):
Expand All @@ -98,20 +89,20 @@ def set_trigger_source(self, external):
otherwise immediate triggering will be chosen.
"""
if external:
self.scpi_comm('TRIGGER:SOURCE External')
self.instr.write('TRIGGER:SOURCE External')
else:
self.scpi_comm('TRIGGER:SOURCE Immediate')
self.instr.write('TRIGGER:SOURCE Immediate')
return external

def read_dc_voltage(self):
"""Read a voltage"""
raw = self.scpi_comm(':MEASURE:VOLTAGE:DC?')
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?')
raw = self.instr.query(':MEASURE:VOLTAGE:AC?')
voltage = float(raw)
return voltage

Expand All @@ -124,41 +115,46 @@ def next_reading(self):
# 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
mav = (meas_event & 2 ** mav_bit) == 2 ** mav_bit
return mav


if __name__ == '__main__':
GPIB = 16
DMM = Keithley2000(interface='gpib', gpib_address=GPIB)
DMM.set_trigger_source(external=True)
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?")))
exit()
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))
print(DMM.read_dc_voltage())

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. DC {:.3f}mV'.format(dt * 1e3, reading * 1e3))
# print(DMM.read_dc_voltage())
# print(DMM.read_dc_voltage())
110 changes: 69 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,62 @@ 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',
# 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