Updates to OTF, and added RP100 strain cell power supply, as well as a couple network analysers. Results from the mRS network analyser are questionable at best, and nonsense at worst. Beware.

This commit is contained in:
2025-07-09 13:02:24 +02:00
parent 10acd4a188
commit 5dfe929da5
15 changed files with 2494 additions and 8 deletions

View File

@@ -0,0 +1,132 @@
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Paul M. Neves <pmneves@mit.edu>
# *****************************************************************************
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, IntRange,\
IDLE, BUSY, WARN, ERROR, Drivable, BoolType, Attached
from ast import literal_eval
class RP100IO(StringIO):
"""communication with RP100"""
end_of_line = '\n'
#wait_before = 0.05
identification = [('*IDN?', r'Razorbill,.*')]
class VoltageChannel(HasIO, Drivable):
"""a voltage output with loop"""
temp = Attached('frappy.core.Readable', mandatory=False)
# define the communication class for automatic creation of the IO module
ioClass = RP100IO
# internal property to configure the channel
channel = Property('the voltage channel', datatype=IntRange(1,2))
# modifying a property of inherited parameters (unit is propagated to the FloatRange datatype)
value = Parameter('output voltage', FloatRange(-210, 210, unit='V'),
readonly=True)
target = Parameter('target voltage', FloatRange(-210, 210, unit='V'),
readonly=False)
meas_voltage = Parameter('measured output voltage', FloatRange(-250, 250, unit='V'),
readonly=True)
meas_current = Parameter('measured output current', FloatRange(-0.007, 0.007, unit='A'),
readonly=True)
max_target = Parameter('max. target', FloatRange(0, 210, unit='V'), readonly=False, default=210)
min_target = Parameter('max. target', FloatRange(-210, 0, unit='V'), readonly=False, default=-210)
slew_rate = Parameter('voltage slew rate', FloatRange(0.1e-3, 100e3, unit='V/s'), readonly=False, default=1)
output_state = Parameter('output on or off', BoolType(), readonly=False)
def doPoll(self):
super().doPoll()
# calculate temperature dependent voltage limits
if(self.temp):
temp = self.temp.target
if temp > 250:
self.max_target = 120
self.min_target = -20
elif temp >= 100:
self.max_target = 120
self.min_target = -50 + (temp-100)/5
elif temp >= 10:
self.max_target = 200 - 8*(temp-10)/9
self.min_target = -200 + 5*(temp-10)/3
elif temp < 10:
self.max_target = 200
self.min_target = -200
# if the current voltage exceeds these limits, reduce voltage to max/min
if self.target > self.max_target:
self.write_target(self.max_target)
if self.target < self.min_target:
self.write_target(self.min_target)
def read_value(self):
# using the inherited HasIO.communicate method to send a command and get the reply
reply = self.communicate(f'SOUR{self.channel}:VOLT:NOW?')
return float(reply)
def read_status(self):
while True:
code, text = literal_eval(self.communicate(f'SYST:ERR?'))
if code == 0:
break
self.log.warning('got error %d %s', code, text)
return IDLE, ''
def read_target(self):
# read back the target value
target = float(self.communicate(f'SOUR{self.channel}:VOLT?'))
return target
def write_target(self, target):
# write here the target to the hardware
status = self.read_output_state()
if target > self.max_target:
target = self.max_target
self.log.warning('Attempted to set voltage above maximum allowed voltage. Setting to max allowed instead.')
if target < self.min_target:
target = self.min_target
self.log.warning('Attempted to set voltage below minimum allowed voltage. Setting to min allowed instead.')
self.communicate(f'SOUR{self.channel}:VOLT {target};*OPC?')
return self.read_target() # return the read back value
def read_slew_rate(self):
return float(self.communicate(f'SOUR{self.channel}:VOLT:SLEW?'))
def write_slew_rate(self, slew_rate):
self.communicate(f'SOUR{self.channel}:VOLT:SLEW {slew_rate};*OPC?')
return self.read_slew_rate()
def read_output_state(self):
a = int(self.communicate(f'OUTP{self.channel}?'))
return bool(a)
def write_output_state(self, output_state):
self.communicate(f'OUTP{self.channel} {int(output_state)};*OPC?')
return self.read_slew_rate()
def read_meas_voltage(self):
return float(self.communicate(f'MEAS{self.channel}:VOLT?'))
def read_meas_current(self):
return float(self.communicate(f'MEAS{self.channel}:CURR?'))

View File

@@ -0,0 +1,183 @@
'''
Force reader class (specially for FC100)
Based on the psiSerial class
(c) 2023 Jonas Philipe
'''
import os
import time
import numpy as np
import traceback
import threading
from frappy_psi.uniaxial_cell.psi_serial import *
class psu_RP100_serialwrapper():
_instance = None
_lock = threading.Lock()
port = None
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
print('New RP100 object opened')
cls._instance = super().__new__(cls)
cls.port = psu_RP100(*args, **kwargs)
return cls._instance
class psu_RP100(psiSerial):
def __init__(self,**kwargs):
print('INIT')
super(psu_RP100,self).__init__(**kwargs)
self.customConnectionError = b'DEVICE CONNECTION ERROR\r\n'
self.endOfLine='\n' # different end of line than the standard Arduino
'''
-----------------------------------------
RP100 power supply class.
Serial interaction with power supply.
Inherits from class psiSerial.
-----------------------------------------
Commands:
self.enable(channel):
IN: string "channel", values contain "CH1" or "CH2".
e.g.: self.enable('CH1') enables channel 1.
OUT:
'''
self.channel1=False
self.channel2=False
self.statusChannel1 = None
self.statusChannel2 = None
self._port_opened = True
def __del__(self):
self.reset()
def to_bool(self, line):
return line[0] == b'0'
def reset(self):
self.psi_write('*RST')
time.sleep(1)
def enable(self,channel):
'''
self.enable(channel):
IN: string "channel", values contain "CH1" or "CH2".
e.g.: self.enable('CH1') enables channel 1.
OUT:
'''
try:
# Read status first:
self.statusChannel1=self.to_bool(self.psi_write_readline('OUTP1?',response_code=False))
self.statusChannel2=self.to_bool(self.psi_write_readline('OUTP2?',response_code=False))
except:
print('Cannot read source status.')
try:
if channel.find('CH1')>-1 and not self.statusChannel1:
self.psi_write_readline('OUTP1 1',response_code=False)
self.statusChannel1=self.to_bool(self.psi_write_readline('OUTP1?',response_code=False))
if channel.find('CH2')>-1 and not self.statusChannel2:
self.psi_write_readline('OUTP2 1',response_code=False)
self.statusChannel2=self.to_bool(self.psi_write_readline('OUTP2?',response_code=False))
except:
print('Could not enable the selected channel. Make sure the arguments are correct: "CH1" and / or "CH2". Full TB below')
traceback.print_exc()
def disable(self,channel):
'''
self.enable(channel):
IN: string "channel", values contain "CH1" or "CH2".
e.g.: self.enable('CH1') enables channel 1.
OUT:
'''
try:
# Read status first:
self.statusChannel1=self.to_bool(self.psi_write_readline('OUTP1?',response_code=False))
self.statusChannel2=self.to_bool(self.psi_write_readline('OUTP2?',response_code=False))
except:
print('Cannot read source status.')
traceback.print_exc()
try:
if channel.find('CH1')>-1 and self.statusChannel1:
self.psi_write_readline('OUTP1 0',response_code=False)
self.statusChannel1=self.to_bool(self.psi_write_readline('OUTP1?',response_code=False))
if channel.find('CH2')>-1 and self.statusChannel2:
self.psi_write_readline('OUTP2 0',response_code=False)
self.statusChannel2=self.to_bool(self.psi_write_readline('OUTP2?',response_code=False))
except:
print('Could not enable the selected channel. Make sure the arguments are correct: "CH1" and / or "CH2".')
def read_source_voltage(self,**kwargs):
s1 = self.psi_write_readline('SOUR1:VOLT:NOW?',response_code=False)
s2 = self.psi_write_readline('SOUR2:VOLT:NOW?',response_code=False)
try:
s1 = float(s1)
except:
print(s1)
s1 = None
try:
s2 = float(s2)
except:
print(s2)
s2 = None
return s1,s2
def set_slew(self,slew_value,**kwargs):
'''
Set ramping voltage to all channels.
Keyword: channel.
Example: channel='CH1,CH2' or channel='CH1'
'''
assert(slew_value >= 0.1e-3 and slew_value <= 100e3)
try:
cc = kwargs['channel']
except:
cc = 'CH1,CH2'
if cc.find('CH1')>-1:
self.psi_write_readline('SOUR1:VOLT:SLEW '+str(slew_value),response_code=False)
if cc.find('CH2')>-1:
self.psi_write_readline('SOUR2:VOLT:SLEW '+str(slew_value),response_code=False)
def read_slew(self,**kwargs):
s1 = self.psi_write_readline('SOUR1:VOLT:SLEW?',response_code=False)
s2 = self.psi_write_readline('SOUR2:VOLT:SLEW?',response_code=False)
return s1.decode(),s2.decode()
def measure_voltage(self,**kwargs):
s1 = self.psi_write_readline('MEAS1:VOLT?',response_code=False)
s2 = self.psi_write_readline('MEAS2:VOLT?',response_code=False)
try:
s1 = float(s1)
except:
print(s1)
s1 = None
try:
s2 = float(s2)
except:
print(s2)
s2 = None
return s1,s2
def set_voltage(self,source,voltage,**kwargs):
'''
Need to implement security limits on the voltage at some point!
'''
assert(voltage <= 230 and voltage >= -230)
if source == 'SOUR1' or source == 'SOUR2':
a = self.psi_write_readline(source+':VOLT '+str(voltage),response_code=False)
return a
def clear(self):
'''
Clears status code
'''
a=self.psi_write_readline('*CLS')
return a

View File

@@ -0,0 +1,117 @@
'''
PSI class to interact with arduino
Read capacitance of FC100 cell
'''
import os
import time
import serial
class psiSerial(serial.Serial):
def __init__(self,**kwargs):
super(psiSerial,self).__init__(**kwargs)
# Default values
#self.port='COM8'
#self.baudrate=9600
#self.bytesize=8
#self.bitstop=1
#self.parity='N'
self.timeout=0.1 # Read timeout of 0.1s default
self.customConnectionError = b'DEVICE CONNECTION ERROR\r\n'
self.maxAttempts = 10
self.sleep_after_open = 0.1 #s
self.endOfLine = "\r"
self.writeWhileEmpty = False
def psi_open(self,**kwargs):
self.custom_is_open = False
while not self.custom_is_open:
try:
self.open()
except:
''
if self.is_open:
a=self.readline()
if a != self.customConnectionError:
self.custom_is_open = True
else:
self.close()
print('Serial port is open. Wait for',self.sleep_after_open,'seconds.')
time.sleep(self.sleep_after_open)
def psi_write(self,string,**kwargs):
'''
IN: string
OUT: serial.Serial.write(bstring)
where bstring is a string decorated by
"b'" +string+"\r\n".
Opt.: book write_debug: writes the input string and output
encoded string.
'''
try:
self.write_debug = kwargs['write_debug']
except:
self.write_debug = False
try:
response_code = kwargs['response_code']
except:
response_code = True
self.write_response = 0
natempt = 0
bstring = string+self.endOfLine
bstring=bstring.encode()
if response_code:
while self.write_response != 8 and natempt < self.maxAttempts:
try:
self.write_response=int(self.write(bstring))
if self.write_debug:
print('Input string:',string)
print('Output:',bstring)
print('write response:',self.write_response)
except:
''
natempt+=1
if natempt >= self.maxAttempts:
print('Max attempt reached for psi_write.')
else:
self.write_response=int(self.write(bstring))
if self.write_debug:
print('Input string:',string)
print('Output:',bstring)
print('write response:',self.write_response)
def psi_read(self,**kwargs):
'''
Function psi_read reads one byte.
'''
return self.read()
def psi_readline(self,**kwargs):
a = self.readline()
return a
def psi_write_readline(self,string,**kwargs):
self.psi_write(string,**kwargs)
a=self.psi_readline(**kwargs)
if a == self.customConnectionError:
print('Connection dropped. Restart.')
print('------------------------------')
self.psi_close()
self.psi_open()
self.psi_write_readline(string,**kwargs)
if a == b'' and self.writeWhileEmpty:
print('a = b"". Retry.')
print('------------------------------')
self.psi_close()
self.psi_open()
self.psi_write_readline(string,**kwargs)
return a
def psi_close(self,**kwargs):
self.close()
time.sleep(self.sleep_after_open)