-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from CINF/feature/driver_updates
Feature/driver updates
- Loading branch information
Showing
8 changed files
with
731 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
""" Simple driver for Keithley 2000 DMM """ | ||
from PyExpLabSys.drivers.scpi import SCPI | ||
|
||
|
||
class Keithley2000(SCPI): | ||
""" | ||
Simple driver for Keithley 2000 DMM | ||
""" | ||
|
||
def __init__(self, interface, hostname='', device='', | ||
baudrate=9600, gpib_address=None): | ||
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) | ||
|
||
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 + '?') | ||
value = float(value_raw) | ||
return value | ||
|
||
def set_range(self, value: float): | ||
""" | ||
Set the measurement range of the device, 0 will indicate auto-range | ||
""" | ||
if value > 1000: | ||
value = 1000 | ||
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') | ||
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?') | ||
actual_range = float(actual_range_raw) | ||
return actual_range | ||
|
||
def set_integration_time(self, nplc: float = None): | ||
""" | ||
Set the measurement integration time | ||
""" | ||
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?')) | ||
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?') | ||
return actual | ||
|
||
def read_ac_voltage(self): | ||
""" Read a voltage """ | ||
raw = self.scpi_comm(':MEASURE:VOLTAGE:AC?') | ||
voltage = float(raw) | ||
return voltage | ||
|
||
def next_reading(self): | ||
""" 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?') | ||
voltage = float(raw) | ||
return voltage | ||
|
||
def measurement_available(self): | ||
meas_event = int(self.scpi_comm('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) | ||
|
||
# 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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
""" Simple driver for Keithley 2182 Nanovolt Meter """ | ||
from PyExpLabSys.drivers.scpi import SCPI | ||
|
||
|
||
class Keithley2182(SCPI): | ||
""" | ||
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): | ||
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) | ||
|
||
# For now, turn off continous trigger - this might need reconsideration | ||
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 | ||
""" | ||
if channel1 is not None: | ||
if channel1 > 120: | ||
channel1 = 120 | ||
if channel1 == 0: | ||
self.scpi_comm(':SENSE:VOLT:CHANNEL1:RANGE:AUTO ON') | ||
else: | ||
self.scpi_comm(':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') | ||
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?') | ||
range1 = float(actual_channel1_raw) | ||
range2 = float(actual_channel2_raw) | ||
return range1, range2 | ||
|
||
def set_integration_time(self, nplc: float = None): | ||
""" | ||
Set the measurement integration time | ||
""" | ||
if nplc is not None: | ||
if nplc < 0.01: | ||
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?')) | ||
return current_nplc | ||
|
||
def read_voltage(self, channel: int): | ||
""" 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?') | ||
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)) | ||
|
||
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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
""" Simple driver for Keithley 2400 SMU """ | ||
from PyExpLabSys.drivers.scpi import SCPI | ||
|
||
|
||
class Keithley2400(SCPI): | ||
""" Simple driver for Keithley 2400 SMU """ | ||
|
||
def __init__(self, interface, hostname='', device='', | ||
baudrate=9600, gpib_address=None): | ||
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 == 'lan': | ||
SCPI.__init__(self, interface=interface, hostname=hostname) | ||
if interface == 'gpib': | ||
SCPI.__init__(self, interface=interface, gpib_address=gpib_address) | ||
|
||
def output_state(self, output_state: bool = None): | ||
""" Turn the output on or off """ | ||
if output_state is not None: | ||
if output_state: | ||
self.scpi_comm('OUTPUT:STATE 1') | ||
else: | ||
self.scpi_comm('OUTPUT:STATE 0') | ||
actual_state_raw = self.scpi_comm('OUTPUT:STATE?') | ||
actual_state = actual_state_raw[0] == '1' | ||
return actual_state | ||
|
||
def set_current_measure_range(self, current_range=None): | ||
""" Set the current measurement range """ | ||
# TODO! | ||
raise NotImplementedError | ||
|
||
def set_integration_time(self, nplc: float = None): | ||
""" | ||
Set the measurement integration time | ||
In principle the current ant voltage value can be set | ||
independently, but for now they are synchronized | ||
""" | ||
if nplc is not None: | ||
if nplc < 0.01: | ||
nplc = 0.01 | ||
if nplc > 10: | ||
nplc = 10 | ||
self.scpi_comm('SENSE:CURRENT:NPLCYCLES {}'.format(nplc)) | ||
self.scpi_comm('SENSE:VOLTAGE:NPLCYCLES {}'.format(nplc)) | ||
current_nplc = float(self.scpi_comm('SENSE:CURRENT:NPLCYCLES?')) | ||
return current_nplc | ||
|
||
def _parse_status(self, status_string): | ||
status_table = { | ||
0: ('OFLO', 'Measurement was made while in over-range'), | ||
1: ('Filter', 'Measurement was made with the filter enabled'), | ||
# 2: ('Front/Rear', 'FRONT terminals are selected'), | ||
3: ('Compliance', 'In real compliance'), | ||
4: ('OVP', 'Over voltage protection limit was reached'), | ||
5: ('Math', 'Math expression (calc1) is enabled'), | ||
6: ('Null', 'Null is enabled'), | ||
7: ('Limits', 'Limit test (calc2) is enabled'), | ||
# Bits 8 and 9 (Limit Results) — Provides limit test results | ||
# (see scpi command reference 18-51) | ||
10: ('Auto-ohms', 'Auto-ohms enabled'), | ||
11: ('V-Meas', 'V-Measure is enabled'), | ||
12: ('I-Meas', 'I-Measure is enabled'), | ||
13: ('Ω-Meas', 'Ω-Measure is enabled'), | ||
14: ('V-Sour', 'V-Source used'), | ||
15: ('I-Sour', 'I-Source used'), | ||
16: ('Range Compliance', 'In range compliance'), | ||
17: ('Offset Compensation', 'Offset Compensated Ohms is enabled'), | ||
18: ('Contact check failure', '(see Appendix F in manual'), | ||
# Bits 19, 20 and 21 (Limit Results) — Provides limit test results | ||
# (see scpi command reference 18-51) | ||
22: ('Remote Sense', '4-wire remote sense selected'), | ||
23: ('Pulse Mode', 'In the Pulse Mode') | ||
} | ||
|
||
status_messages = [] | ||
status_value = int(float(status_string)) | ||
# Strip 0b from the string and fill to 24 bits | ||
bin_status = bin(status_value)[2:].zfill(24) | ||
bin_status = bin_status[::-1] # Reverse string, to get correct byte order | ||
for i in range(0, 24): | ||
bit_value = int(bin_status[i]) == 1 | ||
if bit_value and i in status_table: | ||
status_messages.append(status_table[i]) | ||
return status_messages | ||
|
||
def read_current(self): | ||
""" | ||
Read the measured current | ||
Returns None if the output is off. | ||
""" | ||
if self.output_state(): | ||
raw = self.scpi_comm('MEASURE:CURRENT?') | ||
else: | ||
raw = None | ||
if raw is None: | ||
return | ||
|
||
# Values are: voltage, current, ohm, time, status | ||
# Only the current is measured, voltage is either | ||
# NaN or the source-setpoint. | ||
values = raw.split(',') | ||
current = float(values[1]) | ||
# timestamp = float(values[3]) | ||
# print(self._parse_status(values[4])) | ||
# Also return timestamp? | ||
return current | ||
|
||
def read_voltage(self): | ||
""" Read the measured voltage """ | ||
if self.output_state(): | ||
raw = self.scpi_comm('MEASURE:VOLTAGE?') | ||
else: | ||
raw = None | ||
if raw is None: | ||
return | ||
|
||
# Values is: voltage, current, ohm, time, status | ||
# Only the voltage is measured, current is either | ||
# NaN or the source-setpoint. | ||
values = raw.split(',') | ||
voltage = float(values[0]) | ||
# timestamp = float(values[3]) | ||
# print(self._parse_status(values[4])) | ||
# Also return timestamp? | ||
return voltage | ||
|
||
def set_source_function(self, function=None): | ||
if function in ('i', 'I'): | ||
self.scpi_comm('SOURCE:FUNCTION CURRENT') | ||
if function in ('v', 'V'): | ||
self.scpi_comm('SOURCE:FUNCTION VOLTAGE') | ||
actual_function = self.scpi_comm('SOURCE:FUNCTION?') | ||
return actual_function | ||
|
||
def set_current_limit(self, current: float = None): | ||
""" Set the desired current limit """ | ||
if current is not None: | ||
self.scpi_comm('CURRENT:PROTECTION {:.9f}'.format(current)) | ||
actual = self.scpi_comm('CURRENT:PROTECTION?') | ||
return actual | ||
|
||
def set_voltage_limit(self, voltage: float = None): | ||
""" Set the desired voltate limit """ | ||
if voltage is not None: | ||
self.scpi_comm('VOLTAGE:PROTECTION {:.9f}'.format(voltage)) | ||
actual = self.scpi_comm('VOLTAGE:PROTECTION?') | ||
return actual | ||
|
||
def set_current(self, current: float): | ||
""" Set the desired current """ | ||
self.scpi_comm('SOURCE:CURRENT {:.9f}'.format(current)) | ||
return True | ||
|
||
def set_voltage(self, voltage: float): | ||
""" Set the desired current """ | ||
self.scpi_comm('SOURCE:VOLT {:.9f}'.format(voltage)) | ||
return True | ||
|
||
|
||
if __name__ == '__main__': | ||
GPIB = 22 | ||
SMU = Keithley2400(interface='gpib', gpib_address=GPIB) | ||
SMU.set_source_function('v') | ||
SMU.output_state(True) | ||
print(SMU.set_current_limit(100e-6)) | ||
|
||
# SMU.set_voltage_limit(1e-1) | ||
SMU.set_voltage(0.0) | ||
print(SMU.read_software_version()) | ||
# SMU.output_state(True) | ||
|
||
# print(SMU.output_state()) | ||
|
||
current = SMU.read_current() | ||
voltage = SMU.read_voltage() | ||
|
||
print('Current: {:.1f}uA. Voltage: {:.2f}mV. Resistance: {:.1f}ohm'.format( | ||
current * 1e6, voltage * 1000, voltage / current)) |
Oops, something went wrong.