Added measurement methods that use overlappedY()

This commit is contained in:
Matthias@Dell 2023-04-27 01:40:12 +02:00
parent e1ba44127e
commit a285fab1a1
2 changed files with 233 additions and 183 deletions

View File

@ -1,6 +1,6 @@
"""
run this before using this library:
ipython -i keithley_interactive.py
ipython -i k_teng_interactive.py
always records iv-t curves
i-data -> smua.nvbuffer1
@ -29,10 +29,10 @@ if __name__ == "__main__":
from .keithley import keithley as _keithley
from .keithley.measure import measure_count as _measure_count, measure as _measure
from .utility import data as _data
from .utility.data import load_dataframe
from .utility import file_io
from .utility import testing
_runtime_vars = {
"last-measurement": ""
@ -41,7 +41,7 @@ _runtime_vars = {
settings = {
"datadir": path.expanduser("~/data"),
"name": "measurement",
"interval": 0.01,
"interval": 0.02,
"beep": True,
}
config_path = path.expanduser("~/.config/k-teng.json")
@ -52,85 +52,113 @@ test = False
k = None
def _measure(max_measurements=None, max_points_shown=None, monitor=False):
def _update_print(i, ival, vval):
print(f"{i:5d} - {ival:.12f} A - {vval:.5f} V" + " "*10, end='\r')
class _Monitor:
"""
Monitor the voltage with matplotlib.
Monitor v and i data
"""
def __init__(self, max_points_shown=None, use_print=False):
self.max_points_shown = max_points_shown
self.use_print = use_print
self.index = []
self.vdata = []
self.idata = []
plt.ion()
self.fig1, (self.vax, self.iax) = plt.subplots(2, 1)
self.vline, = self.vax.plot(self.index, self.vdata, color="g")
self.vax.set_ylabel("Voltage [V]")
self.vax.grid(True)
self.iline, = self.iax.plot(self.index, self.idata, color="m")
self.iax.set_ylabel("Current [A]")
self.iax.grid(True)
def update(self, i, ival, vval):
if self.use_print:
_update_print(i, ival, vval)
self.index.append(i)
self.idata.append(ival)
self.vdata.append(vval)
# update data
self.iline.set_xdata(self.index)
self.iline.set_ydata(self.idata)
self.vline.set_xdata(self.index)
self.vline.set_ydata(self.vdata)
# recalculate limits and set them for the view
self.iax.relim()
self.vax.relim()
if self.max_points_shown and i > self.max_points_shown:
self.iax.set_xlim(i - self.max_points_shown, i)
self.vax.set_xlim(i - self.max_points_shown, i)
self.iax.autoscale_view()
self.vax.autoscale_view()
# update plot
self.fig1.canvas.draw()
self.fig1.canvas.flush_events()
def __del__(self):
plt.close(self.fig1)
def monitor_count(count=5000, interval=settings["interval"], max_points_shown=160):
"""
Take <count> measurements in <interval> and monitor live with matplotlib.
@details:
- Resets the buffers
- Opens a matplotlib window and takes measurements depending on settings["interval"]
- Waits for the user to press a key
Uses the device internal overlappedY measurement method, which allows for greater precision
You can take the data from the buffer afterwards, using save_csv
@param count: count
@param interval: interval, defaults to settings["interval"]
@param max_points_shown: how many points should be shown at once. None means infinite
@param max_measurements : maximum number of measurements. None means infinite
You can take the data from the buffer afterwards, using save_csv """
global k, settings, test, _runtime_vars
print(f"Starting measurement with:\n\tinterval = {settings['interval']}s\nUse <C-c> to stop. Save the data using 'save_csv()' afterwards.")
_runtime_vars["last_measurement"] = dtime.now().isoformat()
if not test:
_keithley.reset(k, verbose=True)
k.write("smua.source.output = 1")
k.write("format.data = format.ASCII\nformat.asciiprecision = 12")
# jupyter:
# clear_output(wait=True)
# plt.plot(data)
# plt.show()
index = []
vdata = []
idata = []
if monitor:
plt.ion()
fig1, (vax, iax) = plt.subplots(2, 1)
"""
plt_monitor = _Monitor(max_points_shown, use_print=True)
update_func = plt_monitor.update
vline, = vax.plot(index, vdata, color="g")
vax.set_ylabel("Voltage [V]")
vax.grid(True)
iline, = iax.plot(index, idata, color="m")
iax.set_ylabel("Current [A]")
iax.grid(True)
print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.")
try:
i = 0
while max_measurements is None or i < max_measurements:
index.append(i)
if test:
idata.append(testing.testcurve(i, peak_width=1, amplitude=5e-8))
vdata.append(-testing.testcurve(i, peak_width=2, amplitude=15))
# data.append(np.random.rand())
else:
# data.append(float(k.query("print(smua.measure.v(smua.nvbuffer1))").strip('\n')))
i_val, v_val = tuple(float(v) for v in k.query("print(smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2))").strip('\n').split('\t'))
idata.append(i_val)
vdata.append(v_val)
print(f"{i:5d} - {idata[-1]:.12f} A - {vdata[-1]:.5f} V", end='\r')
if monitor:
# update data
iline.set_xdata(index)
iline.set_ydata(idata)
vline.set_xdata(index)
vline.set_ydata(vdata)
# recalculate limits and set them for the view
iax.relim()
vax.relim()
if max_points_shown and i > max_points_shown:
iax.set_xlim(i - max_points_shown, i)
vax.set_xlim(i - max_points_shown, i)
iax.autoscale_view()
vax.autoscale_view()
# update plot
fig1.canvas.draw()
fig1.canvas.flush_events()
sleep(settings["interval"])
i += 1
_measure_count(k, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05, testing=test)
except KeyboardInterrupt:
if not test:
k.write("smua.source.output = 0")
if monitor:
plt.close(fig1)
print("Measurement stopped" + " "*50)
return vdata, idata
k.write(f"smua.source.output = smua.OUTPUT_OFF")
print("Monitoring cancelled, measurement might still continue" + " "*50)
else:
print("Measurement finished" + " "*50)
def measure_count(count=5000, interval=settings["interval"]):
"""
Take <count> measurements in <interval>
@details:
- Resets the buffers
- Takes <count> measurements depending on settings["interval"]
Uses the device internal overlappedY measurement method, which allows for greater precision
You can take the data from the buffer afterwards, using save_csv
@param count: count
@param interval: interval, defaults to settings["interval"]
"""
update_func = _update_print
print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.")
try:
_measure_count(k, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05, testing=test)
except KeyboardInterrupt:
if not test:
k.write(f"smua.source.output = smua.OUTPUT_OFF")
print("Monitoring cancelled, measurement might still continue" + " "*50)
else:
print("Measurement finished" + " "*50)
def monitor(max_measurements=None, max_points_shown=160):
def monitor(interval=settings["interval"], max_measurements=None, max_points_shown=160):
"""
Monitor the voltage with matplotlib.
@ -138,13 +166,20 @@ def monitor(max_measurements=None, max_points_shown=160):
- Resets the buffers
- Opens a matplotlib window and takes measurements depending on settings["interval"]
- Waits for the user to press a key
Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision.
You can take the data from the buffer afterwards, using save_csv.
@param max_points_shown : how many points should be shown at once. None means infinite
@param max_measurements : maximum number of measurements. None means infinite
You can take the data from the buffer afterwards, using save_csv """
_measure(max_measurements=max_measurements, max_points_shown=max_points_shown, monitor=True)
"""
global _runtime_vars
_runtime_vars["last_measurement"] = dtime.now().isoformat()
print(f"Starting measurement with:\n\tinterval = {interval}s\nUse <C-c> to stop. Save the data using 'save_csv()' afterwards.")
plt_monitor = _Monitor(use_print=True, max_points_shown=max_points_shown)
update_func = plt_monitor.update
_measure(k, interval=interval, max_measurements=max_measurements, update_func=update_func, testing=test)
def measure(max_measurements=None):
def measure(interval=settings["interval"], max_measurements=None):
"""
Measure voltages
@ -152,23 +187,45 @@ def measure(max_measurements=None):
- Resets the buffers
- Measure voltages
- Waits for the user to press a key
Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision.
You can take the data from the buffer afterwards, using save_csv.
@param max_measurements : maximum number of measurements. None means infinite
You can take the data from the buffer afterwards, using save_csv """
_measure(max_measurements=max_measurements, monitor=False)
"""
global _runtime_vars
_runtime_vars["last_measurement"] = dtime.now().isoformat()
print(f"Starting measurement with:\n\tinterval = {interval}s\nUse <C-c> to stop. Save the data using 'save_csv()' afterwards.")
update_func = _update_print
_measure(k, interval=interval, max_measurements=max_measurements, update_func=update_func, testing=test)
def automeasure(repeat, repeat_delay=0, max_measurements=None, max_points_shown=120, monitor=True):
def repeat(measure_func: callable, count: int, repeat_delay=0):
"""
Measure and save to csv multiple times
@details
Repeat count times:
- call measure_func
- call save_csv
- sleep for repeat_delay
@param measure_func: The measurement function to use. Use a lambda to bind your parameters!
@param count: Repeat count times
Example: Repeat 10 times:
repeat(lambda : monitor_count(count=6000, interval=0.02, max_points_shown=200), 10)
"""
for i in range(repeat):
_measure(max_measurements=max_measurements, max_points_shown=max_points_shown, monitor=monitor)
try:
for _ in range(count):
measure_func()
save_csv()
sleep(repeat_delay)
except KeyboardInterrupt:
pass
def get_dataframe():
"""
Get a pandas dataframe from the data in smua.nvbuffer1
Get a pandas dataframe from the data in smua.nvbuffer1 and smua.nvbuffer2
"""
global k, settings, _runtime_vars
if test:
@ -241,16 +298,18 @@ def load_settings():
global settings, config_path
with open(config_path, "r") as file:
settings = json.load(file)
settings["datadir"] = path.expanduser(settings["datadir"])
settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~
def help(topic=None):
if topic == None:
print("""
Functions:
measure - measure the voltage
monitor - measure the voltage with live monitoring in a matplotlib window
automeasure - measure and save to csv multiple times
get_dataframe - return smua.nvbuffer1 as pandas dataframe
measure - take measurements
monitor - take measurements with live monitoring in a matplotlib window
measure_count - take a fixed number of measurements
monitor_count - take a fixed number of measurements with live monitoring in a matplotlib window
repeat - measure and save to csv multiple times
get_dataframe - return smua.nvbuffer 1 and 2 as pandas dataframe
save_csv - save the last measurement as csv file
save_pickle - save the last measurement as pickled pandas dataframe
load_dataframe - load a pandas dataframe from csv or pickle
@ -303,7 +362,7 @@ def init():
| < ______ | | | __)_ / | \ / \ ___
| | \ /_____/ | | | \/ | \\ \_\ \
|____|__ \ |____| /_______ /\____|__ / \______ /
\/ \/ \/ \/ 1.0
\/ \/ \/ \/ 1.1
Interactive Shell for TENG measurements with Keithley 2600B
---
Enter 'help()' for a list of commands""")

View File

@ -1,115 +1,106 @@
from time import sleep
import numpy as np
from matplotlib import pyplot as plt
import pyvisa
smua_settings = """
display.clear()
display.settext('starting')
smua.reset()
smua.measure.autorangev = smua.AUTORANGE_ON
smua.measure.autozero = smua.AUTOZERO_ONCE
smua.source.output = smua.OUTPUT_OFF
-- max 20 V expected
smua.measure.rangev = 20
from .keithley import reset
from ..utility import testing as _testing
def measure_count(instr, V=True, I=True, count=100, interval=0.05, update_func=None, update_interval=0.5, beep_done=True, verbose=True, testing=False):
"""
Take <count> measurements with <interval> inbetween
script_dir = "scripts/"
scripts = {
"buffer_reset": "buffer_reset.lua",
"smua_reset": "smua_reset.lua",
}
for key,val in scripts.items():
scripts[key] = script_dir + scripts[key]
def run_lua(instr, script_path, verbose=False):
"""
Run a lua script from the host on the instrument
@details
Uses the devices overlappedY function to make the measurements asynchronosly
The update_func is optional and only used when I == True and V == True
The update_func does not necessarily get all the values that are measured. To obtain the whole measurement, get them from the device buffers (smua.nvbufferX)
@param instr: pyvisa instrument
@param script_path : full path to the script
@param update_func: Callable that processes the measurements: (index, ival, vval) -> None
@param update_interval: interval at which the update_func is called
"""
with open(script_path, "r") as file:
script = file.read()
if verbose: print(f"Running script: {script_path}")
instr.write(script)
def measure_V(instr, count=100, interval=0.05):
"""
"""
data = []
for _ in range(1000):
data.append(tuple(float(v) for v in instr.query("print(smua.measure.v())").strip('\n').split('\t')))
# print(i, data[-1])
# clear_output(wait=True)
plt.plot(data)
plt.show()
sleep(0.05)
def reset(instr, verbose=False):
"""
Reset smua and its buffers
@param instr : pyvisa instrument
"""
run_lua(instr, scripts["smua_reset"], verbose=verbose)
run_lua(instr, scripts["buffer_reset"], verbose=verbose)
def measure_count(instr, V=True, I=True, count=100, interval=0.05, beep_done=True, verbose=True):
"""
take n measurements at dt interval
@param instr : pyvisa instrument
"""
reset(instr, verbose=verbose)
f_meas = None
if V and I:
f_meas = "smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2)"
f_meas = "smua.measure.overlappediv(smua.nvbuffer1, smua.nvbuffer2)"
elif V:
f_meas = "smua.measure.v(smua.nvbuffer1)"
f_meas = "smua.measure.overlappedv(smua.nvbuffer1)"
elif I:
f_meas = "smua.measure.i(smua.nvbuffer1)"
f_meas = "smua.measure.overlappedi(smua.nvbuffer1)"
else:
print("I and/or V needs to be set to True")
return
i = 0
if not testing:
reset(instr, verbose=verbose)
instr.write(f"smua.measure.count = {count}")
instr.write(f"smua.measure.interval = {interval}")
# start measurement
instr.write(f"smua.source.output = smua.OUTPUT_ON")
instr.write(f_meas)
condition = lambda: float(instr.query("print(status.operation.measuring.condition)").strip("\n ")) != 0
else:
condition = lambda: i < int(float(count) * interval / update_interval)
sleep(update_interval)
# for live viewing
# will return 2.0 while measruing
while condition():
if update_func and V and I:
try:
if not testing:
ival = float(instr.query("print(smua.nvbuffer1.readings[smua.nvbuffer1.n])").strip("\n"))
vval = float(instr.query("print(smua.nvbuffer2.readings[smua.nvbuffer2.n])").strip("\n"))
else:
ival = _testing.testcurve(i, peak_width=1, amplitude=5e-8)
vval = -_testing.testcurve(i, peak_width=2, amplitude=15)
update_func(i, ival, vval)
except ValueError:
pass
sleep(update_interval)
i += 1
if not testing:
instr.write(f"smua.source.output = smua.OUTPUT_OFF")
if beep_done:
instr.write("beeper.beep(0.3, 1000)")
def event_test_TODO():
# Type of event we want to be notified about
event_type = pyvisa.constants.EventType.service_request
# Mechanism by which we want to be notified
event_mech = pyvisa.constants.EventMechanism.queue
keithley.enable_event(event_type, event_mech)
# Instrument specific code to enable service request
# (for example on operation complete OPC)
keithley.write("*SRE 1")
keithley.write("INIT")
with open("script.lua", "r") as file:
script = file.read()
# for line in script.split('\n'):
# input(line)
# keithley.write(line)
keithley.write(script)
# Wait for the event to occur
response = keithley.wait_on_event(event_type, 1000)
assert response.event.event_type == event_type
assert response.timed_out == False
instr.disable_event(event_type, event_mech)
keithley.query_ascii_values("printbuffer(1, 10, smua.nvbuffer1)", 6)
print(voltages)
def measure(instr, interval, update_func=None, max_measurements=None, testing=False):
"""
@details:
- Resets the buffers
- Until KeyboardInterrupt:
- Take measurement
- Call update_func
- Wait interval
Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision
You can take the data from the buffer afterwards, using save_csv
@param instr: pyvisa instrument
@param update_func: Callable that processes the measurements: (index, ival, vval) -> None
@param max_measurements : maximum number of measurements. None means infinite
"""
if not testing:
reset(instr, verbose=True)
instr.write("smua.source.output = smua.OUTPUT_ON")
instr.write("format.data = format.ASCII\nformat.asciiprecision = 12")
try:
i = 0
while max_measurements is None or i < max_measurements:
if testing:
ival = _testing.testcurve(i, peak_width=1, amplitude=5e-8)
vval = -_testing.testcurve(i, peak_width=2, amplitude=15)
else:
ival, vval = tuple(float(v) for v in instr.query("print(smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2))").strip('\n').split('\t'))
if update_func:
update_func(i, ival, vval)
sleep(interval)
i += 1
except KeyboardInterrupt:
pass
if not testing:
instr.write("smua.source.output = smua.OUTPUT_OFF")
print("Measurement stopped" + " "*50)