Compare commits

...

4 Commits

Author SHA1 Message Date
Matthias@Dell
38914268ea 1.0 -> 1.1 2023-04-27 01:41:30 +02:00
Matthias@Dell
b1ab667336 Changed Autozero value 2023-04-27 01:41:06 +02:00
Matthias@Dell
04996793b9 Fixed bug in get_next_filename 2023-04-27 01:40:56 +02:00
Matthias@Dell
a285fab1a1 Added measurement methods that use overlappedY() 2023-04-27 01:40:12 +02:00
5 changed files with 253 additions and 191 deletions

View File

@ -1,6 +1,6 @@
""" """
run this before using this library: run this before using this library:
ipython -i keithley_interactive.py ipython -i k_teng_interactive.py
always records iv-t curves always records iv-t curves
i-data -> smua.nvbuffer1 i-data -> smua.nvbuffer1
@ -29,10 +29,10 @@ if __name__ == "__main__":
from .keithley import keithley as _keithley 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 import data as _data
from .utility.data import load_dataframe from .utility.data import load_dataframe
from .utility import file_io from .utility import file_io
from .utility import testing
_runtime_vars = { _runtime_vars = {
"last-measurement": "" "last-measurement": ""
@ -41,7 +41,7 @@ _runtime_vars = {
settings = { settings = {
"datadir": path.expanduser("~/data"), "datadir": path.expanduser("~/data"),
"name": "measurement", "name": "measurement",
"interval": 0.01, "interval": 0.02,
"beep": True, "beep": True,
} }
config_path = path.expanduser("~/.config/k-teng.json") config_path = path.expanduser("~/.config/k-teng.json")
@ -52,85 +52,113 @@ test = False
k = None 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: @details:
- Resets the buffers - Resets the buffers
- Opens a matplotlib window and takes measurements depending on settings["interval"] - 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_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 """ plt_monitor = _Monitor(max_points_shown, use_print=True)
global k, settings, test, _runtime_vars update_func = plt_monitor.update
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)
vline, = vax.plot(index, vdata, color="g") print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.")
vax.set_ylabel("Voltage [V]")
vax.grid(True)
iline, = iax.plot(index, idata, color="m")
iax.set_ylabel("Current [A]")
iax.grid(True)
try: try:
i = 0 _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)
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
except KeyboardInterrupt: except KeyboardInterrupt:
if not test: if not test:
k.write("smua.source.output = 0") k.write(f"smua.source.output = smua.OUTPUT_OFF")
if monitor: print("Monitoring cancelled, measurement might still continue" + " "*50)
plt.close(fig1) else:
print("Measurement stopped" + " "*50) print("Measurement finished" + " "*50)
return vdata, idata
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. Monitor the voltage with matplotlib.
@ -138,13 +166,20 @@ def monitor(max_measurements=None, max_points_shown=160):
- Resets the buffers - Resets the buffers
- Opens a matplotlib window and takes measurements depending on settings["interval"] - Opens a matplotlib window and takes measurements depending on settings["interval"]
- Waits for the user to press a key - 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_points_shown : how many points should be shown at once. None means infinite
@param max_measurements : maximum number of measurements. 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 Measure voltages
@ -152,23 +187,45 @@ def measure(max_measurements=None):
- Resets the buffers - Resets the buffers
- Measure voltages - Measure voltages
- Waits for the user to press a key - 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 @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 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): try:
_measure(max_measurements=max_measurements, max_points_shown=max_points_shown, monitor=monitor) for _ in range(count):
measure_func()
save_csv() save_csv()
sleep(repeat_delay) sleep(repeat_delay)
except KeyboardInterrupt:
pass
def get_dataframe(): 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 global k, settings, _runtime_vars
if test: if test:
@ -241,16 +298,18 @@ def load_settings():
global settings, config_path global settings, config_path
with open(config_path, "r") as file: with open(config_path, "r") as file:
settings = json.load(file) settings = json.load(file)
settings["datadir"] = path.expanduser(settings["datadir"]) settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~
def help(topic=None): def help(topic=None):
if topic == None: if topic == None:
print(""" print("""
Functions: Functions:
measure - measure the voltage measure - take measurements
monitor - measure the voltage with live monitoring in a matplotlib window monitor - take measurements with live monitoring in a matplotlib window
automeasure - measure and save to csv multiple times measure_count - take a fixed number of measurements
get_dataframe - return smua.nvbuffer1 as pandas dataframe 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_csv - save the last measurement as csv file
save_pickle - save the last measurement as pickled pandas dataframe save_pickle - save the last measurement as pickled pandas dataframe
load_dataframe - load a pandas dataframe from csv or pickle 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 Interactive Shell for TENG measurements with Keithley 2600B
--- ---
Enter 'help()' for a list of commands""") Enter 'help()' for a list of commands""")

View File

@ -1,115 +1,106 @@
from time import sleep from time import sleep
import numpy as np import numpy as np
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
import pyvisa
smua_settings = """ from .keithley import reset
display.clear() from ..utility import testing as _testing
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
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/" @details
scripts = { Uses the devices overlappedY function to make the measurements asynchronosly
"buffer_reset": "buffer_reset.lua", The update_func is optional and only used when I == True and V == True
"smua_reset": "smua_reset.lua", 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)
}
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
@param instr: pyvisa instrument @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 f_meas = None
if V and I: if V and I:
f_meas = "smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2)" f_meas = "smua.measure.overlappediv(smua.nvbuffer1, smua.nvbuffer2)"
elif V: elif V:
f_meas = "smua.measure.v(smua.nvbuffer1)" f_meas = "smua.measure.overlappedv(smua.nvbuffer1)"
elif I: elif I:
f_meas = "smua.measure.i(smua.nvbuffer1)" f_meas = "smua.measure.overlappedi(smua.nvbuffer1)"
else: else:
print("I and/or V needs to be set to True") print("I and/or V needs to be set to True")
return return
i = 0
if not testing:
reset(instr, verbose=verbose)
instr.write(f"smua.measure.count = {count}") instr.write(f"smua.measure.count = {count}")
instr.write(f"smua.measure.interval = {interval}") instr.write(f"smua.measure.interval = {interval}")
# start measurement
instr.write(f"smua.source.output = smua.OUTPUT_ON") instr.write(f"smua.source.output = smua.OUTPUT_ON")
instr.write(f_meas) 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") instr.write(f"smua.source.output = smua.OUTPUT_OFF")
if beep_done: if beep_done:
instr.write("beeper.beep(0.3, 1000)") instr.write("beeper.beep(0.3, 1000)")
def measure(instr, interval, update_func=None, max_measurements=None, testing=False):
def event_test_TODO(): """
# Type of event we want to be notified about @details:
event_type = pyvisa.constants.EventType.service_request - Resets the buffers
# Mechanism by which we want to be notified - Until KeyboardInterrupt:
event_mech = pyvisa.constants.EventMechanism.queue - Take measurement
keithley.enable_event(event_type, event_mech) - Call update_func
- Wait interval
# Instrument specific code to enable service request Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision
# (for example on operation complete OPC) You can take the data from the buffer afterwards, using save_csv
keithley.write("*SRE 1") @param instr: pyvisa instrument
keithley.write("INIT") @param update_func: Callable that processes the measurements: (index, ival, vval) -> None
@param max_measurements : maximum number of measurements. None means infinite
"""
with open("script.lua", "r") as file: if not testing:
script = file.read() reset(instr, verbose=True)
# for line in script.split('\n'): instr.write("smua.source.output = smua.OUTPUT_ON")
# input(line) instr.write("format.data = format.ASCII\nformat.asciiprecision = 12")
# keithley.write(line) try:
keithley.write(script) i = 0
while max_measurements is None or i < max_measurements:
# Wait for the event to occur if testing:
response = keithley.wait_on_event(event_type, 1000) ival = _testing.testcurve(i, peak_width=1, amplitude=5e-8)
assert response.event.event_type == event_type vval = -_testing.testcurve(i, peak_width=2, amplitude=15)
assert response.timed_out == False else:
ival, vval = tuple(float(v) for v in instr.query("print(smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2))").strip('\n').split('\t'))
instr.disable_event(event_type, event_mech) if update_func:
keithley.query_ascii_values("printbuffer(1, 10, smua.nvbuffer1)", 6) update_func(i, ival, vval)
print(voltages) sleep(interval)
i += 1
except KeyboardInterrupt:
pass
if not testing:
instr.write("smua.source.output = smua.OUTPUT_OFF")
print("Measurement stopped" + " "*50)

View File

@ -23,7 +23,8 @@ def get_next_filename(basename, directory=".", digits=3):
for file in files: for file in files:
if not file.startswith(basename): continue if not file.startswith(basename): continue
try: try:
number = int(file.split('.')[0].strip(basename)) number = file[:file.rfind('.')].replace(basename, "")
number = int(number)
if number < lowest_number: continue if number < lowest_number: continue
lowest_number = number lowest_number = number
except ValueError: except ValueError:

View File

@ -1,15 +1,25 @@
# TENG scripts # K-TENG
Helper scripts for measuring TENG-based sensor output from a Keithley 2611B using pyvisa in a jupyter notebook Helper scripts for measuring **T**ribo**e**lectric **N**ano**g**enerator-based sensor output with a Keithley 2611B SMU using pyvisa
## Requirements
## Features
### Useful functions for scripts
- Measure Voltage - Measure Voltage
- Measure Voltage and Current - Measure Voltage and Current
- Save to host - Save to host
- run lua script
- auto-filenames
### Interactive (shell) mode
- Live view - Live view
- autofilenames
- press buttons for start and fin - press buttons for start and fin
- settings: - settings:
- interval - interval
- count - count
- run arbitrary command - easily run arbitrary command on device
- run lua script
## Shell mode
Start with:
```python
ipython -i k_teng_interactive.py
```
Use `help()` to get a list of available commands

View File

@ -2,7 +2,8 @@
smua.reset() smua.reset()
smua.measure.autorangev = smua.AUTORANGE_ON smua.measure.autorangev = smua.AUTORANGE_ON
smua.measure.autorangei = smua.AUTORANGE_ON smua.measure.autorangei = smua.AUTORANGE_ON
smua.measure.autozero = smua.AUTOZERO_ONCE smua.measure.autozero = smua.AUTOZERO_AUTO
-- smua.measure.autozero = smua.AUTOZERO_ONCE
-- set output to 0A DC -- set output to 0A DC
smua.source.output = smua.OUTPUT_OFF smua.source.output = smua.OUTPUT_OFF
smua.source.func = smua.OUTPUT_DCAMPS smua.source.func = smua.OUTPUT_DCAMPS