JohannesDittloff 7f7561e4d9 rename prsctrl
2025-05-08 13:07:22 +02:00

264 lines
8.8 KiB
Python

import pyvisa
# import pkg_resources
from ..base import Lock_In_Amp
from prsctrl.util.visa import enumerate_devices
import logging
log = logging.getLogger(__name__)
import numpy as np
class Model7260(Lock_In_Amp):
"""
Wrapper class for the Model 7260 DSP Lock-In controlled via pyvisa
"""
def __init__(self, instr, check_front_switch=True):
self.instr = instr
init_script = """
' INPUT
' voltage
IMODE 0
' set input A
VMODE 1
' set float
FLOAT 1
' set AC coupling
CP 0
' Enable 1x and 2x Line notch filters
LF 4
' Enable synchronous filter < 200Hz
' SYNC 1
' REFERENCE
' Set external TTL rear panel
IE 1
' Set detection harmonic to 1'
REFN 1
' TODO time constant, filter slope,
"""
self.run(init_script)
self._buffer_length = None
def __del__(self):
"""Properly close the instrument connection"""
self.instr.close()
# RUN COMMANDS ON THE DEVICE
def run(self, code, split=True):
"""
Run SCPI code on the device by writing it.
Empty lines, leading whitespaces and lines starting with ' or # are ignored.
Parameters
----------
code : str
SCPI commands
"""
script = [] if split else ''
for line in code.strip(" ").split("\n"):
l = line.strip(" ")
if len(l) == 0 or l[0] in "#'": continue
if split:
script.append(l)
else:
script += l + "\n"
if split:
for cmd in script:
self.run_and_check(cmd)
else:
self.run_and_check(script)
def run_and_check(self, cmd):
"""Run a command and check for errors afterwards by reading the
standard event status byte"""
try:
log.debug(f"Running command(s): --BEGIN--\n{cmd}\n--END--")
ret = self.instr.write(cmd)
status = int(self.query("ST"))
if status & (1 << 1):
raise RuntimeError(f"Invalid command(s):\n{cmd}\n")
elif status & (1 << 5):
raise RuntimeError(f"Command parameter error after command(s):\n{cmd}\n")
# if ret != 8:
# raise RuntimeError(f"Error while writing command(s):\n'{script}'\n\nDevice returned code {ret}")
except pyvisa.VisaIOError as e:
raise RuntimeError(f"VisaIOError raised while writing command(s):\n'{cmd}'\n\nVisaIOError:\n{e}")
def get_frequency(self) -> float:
# TODo
pass
def query(self, query):
return self.instr.query(query, delay=0.05).strip("\n")
def snap(self, what="3,4,5,7"):
"""
Parameters
----------
what : TYPE, optional
1 X
2 Y
3 R
4 θ
5 Aux In 1
6 Aux In 2
7 Aux In 3
8 Aux In 4
9 Reference Frequency
10 CH1 display
11 CH2 display
The default is "3,4,5,7".
Returns
-------
vals : list[float]
Values converted to float
"""
vals = self.query(f"SNAP? {what}").split(",")
vals = map(float, vals)
return vals
def measureTODO(): pass
def read_value(self):
"""Read the value of R"""
return float(self.query("MAG."))
def reset(self):
# TODO
pass
def test_connection(self):
pass
def buffer_setup(self, X=0, Y=0, MAG=1, PHASE=1, SENS=0, ADC1=1, ADC2=2, length=None, interval_ms=5):
"""
Prepare the device for a buffer (curve) measurement
:param X: 1 if the output should be stored, 0 else
:param Y: 1 if the output should be stored, 0 else
:param MAG: 1 if the output should be stored, 0 else
:param PHASE: 1 if the output should be stored, 0 else
:param SENS: 1 if the output should be stored, 0 else
:param ADC1: 1 if the output should be stored, 0 else
:param ADC2: 1 if the output should be stored, 0 else
:param length: Number of values to store in the buffer, or None to use the maximum number of values
:param interval_ms: Interval in ms with resolution of 5ms
"""
self.buffer_cbd = (X << 0) | (Y << 1) | (MAG << 2) | (PHASE << 3) | (SENS << 4) | (ADC1 << 5) | (ADC2 << 6)
self.run(f"CBD {self.buffer_cbd}")
n_outputs = self.buffer_cbd.bit_count()
# length
max_length = 32768 // n_outputs
log.info(f"Setting up buffer, n_outputs={n_outputs}, max_length={max_length}, CBD={self.buffer_cbd:016b}")
if length is None:
self._buffer_length = max_length
else:
self._buffer_length = length
if self._buffer_length > max_length:
raise ValueError(f"For {n_outputs} outputs, the maximum length is {max_length} but {length} was given.")
self.run(f"LEN {self._buffer_length}")
# set the interval
self.run(f"STR {interval_ms}")
# init the buffer (new curve)
self.run("NC")
def buffer_start_fill(self):
if self._buffer_length is None: raise RuntimeError(f"Buffer not set up, call buffer_setup() first.")
self.run("TD") # take data
def buffer_is_done(self) -> bool:
vals = self.query("M").split(",")
vals = list(map(int, vals))
log.info(f"buffer status: {vals[0]}, {vals[1]}, {vals[2]:08b}, {vals[3]}")
return vals[0] == 0
def buffer_get_data(self, raw=False):
"""
Get the data from the buffer.
:return:
"""
if self._buffer_length is None: raise RuntimeError(f"Buffer not set up, call buffer_setup() first.")
n_points = int(self.query("M").split(",")[3])
data = np.empty((n_points, self.buffer_cbd.bit_count()), dtype=int)
self.instr.write(f"DCT {self.buffer_cbd}") # this is a query, we need to read the next <n_points> lines now
for i in range(n_points):
vals = self.instr.read().strip("\n").split(",")
data[i,:] = [int(x) for x in vals]
# check if all values have been transmitted by issuing another command
length = self.query("LEN")
try:
length = int(length)
except ValueError:
raise RuntimeError(f"Error buffer data transmission: Buffer length {length} is not an integer.")
if length != self._buffer_length:
raise RuntimeError(f"Error buffer data transmission: Buffer length reported as {length}, but should be {self._buffer_length}.")
if raw:
return data
return self._buffer_convert_from_full_scale(data)
def _buffer_convert_from_full_scale(self, data):
"""
Convert values from fullscale to Volts and Degrees
See section 6.4.09 on page 141 in the manual
:param data: full scale data
:return: data converted to Volt and Degrees
"""
# get full voltage scale
# if the sensitivity would have been stored (bit 4), its value could be used too
fs = float(self.query("SEN."))
scales = [
1E-4*fs, # X
1E-4*fs, # Y
1E-4*fs, # MAG
1E-2, # Phase FS=18000 = 180°
1, # Sens
1E-3, # ADC1 FS=10000 = 10 V
1E-3, # ADC2 FS=10000 = 10 V
1E-3, # ADC3 FS=10000 = 10 V
]
data_conv = np.empty(data.shape, dtype=float)
i_bit = 0
for i_val in range(self.buffer_cbd.bit_count()):
while i_bit < len(scales) and self.buffer_cbd & (1 << i_bit) == 0:
i_bit += 1
if i_bit == len(scales):
raise RuntimeError(f"Could not find the output of value number {i_val+1}")
# i_valth value is the quantity belonging to bit i_bit
data_conv[:,i_val] = data[:,i_val] * scales[i_bit]
return data_conv
def auto_gain(self):
self.instr.write("AGAN")
# wait until the device responds again, meaning it is no longer busy
self.instr.write("SENS?")
i = 0
while i < 50:
try:
gain = self.instr.read().strip("\n")
return gain
except:
i += 1
raise RuntimeError("Could not get response from device after sending auto gain command")
@staticmethod
def enumerate_devices(query="(GPIB)?*:INSTR"):
return enumerate_devices("7260", query, cmd="ID")
@staticmethod
def connect_device(name):
rm = pyvisa.ResourceManager()
instr = rm.open_resource(name)
return Model7260(instr)
def __str__(self):
return "SR830"