Source code for pyrolab.drivers.smu.keysight

# Copyright © PyroLab Project Contributors
# Licensed under the terms of the GNU GPLv3+ License
# (see pyrolab/__init__.py for details)

"""
Keysight B2900/BL series SMU
============================

.. admonition:: Dependencies
   :class: note

   | pyvisa
   | NI-VISA *or* pyvisa-py
"""

import time

import pyvisa as visa

from pyrolab import __version__
from pyrolab.drivers import VISAResourceExtensions
from pyrolab.drivers.smu import SMU


[docs] class B2900(SMU): """ Simple network controller class for Keysight B2900 SMU. This class is used to control the Keysight B2900B/BL SMU. These are not local devices, nor native PyroLab objects. Therefore, network device autodetection is not supported. """
[docs] @staticmethod def detect_devices(): """ Network device detection not supported. Becuase SMUs are connected to using the IP address, this function does not detect them and instead always returns an empty list. """ device_info = [] return device_info
[docs] def connect(self, address: str = "", timeout: float = 1e3) -> bool: """ Connects to and initializes the SMU. Parameters ---------- address : str The IP address of the instrument. timeout : int, optional The device response timeout in milliseconds (default 1 ms). Pass ``None`` for infinite timeout. """ rm = visa.ResourceManager() self.device = rm.open_resource(f"TCPIP0::{address}::5025::SOCKET") self.device.timeout = timeout self.device.read_termination = "\n" self.write_termination = "\n" self.device.ext_clear_status() self.write("*RST;*CLS") self.write(":DISP:ENAB ON") self.device.ext_error_checking() return True
[docs] def close(self): self.device.close()
@property def timeout(self): """ Network timeout duration in milliseconds (errors out if no response received within timeout). """ return self.device.timeout @timeout.setter def timeout(self, ms): self.device.timeout = ms
[docs] def query(self, message: str = "", delay: float = None): """ A combination of :py:func:`write` and :py:func:`read`. Parameters ---------- message : str The message to send. delay : float, optional Delay in seconds between write and read operations. If None, defaults to ``self.device.query_delay``. Raises ------ VI_ERROR_TMO If there is no data to be read """ return self.device.query(message, delay)
[docs] def write(self, message: str, termination: str = None, encoding: str = None): """ Writes a message to the SMU. Parameters ---------- message : str The message to send. termination : str, optional Alternative character termination to use. If None, the value of write_termination is used. Defaults to None. encoding : str, optional Alternative encoding to use to turn str into bytes. If None, the value of encoding is used. Defaults to None. """ self.device.write(message, termination, encoding)
[docs] def screenshot(self, path: str, form: str = "PNG"): """ Takes a screenshot of the scope and saves it to the specified path. Image format is PNG. Parameters ---------- path : str The local path, including filename and extension, of where to save the file. form : str, optional Format of the created image. Defaults to PNG. Can be JPG, BMP, PNG, or WMF. """ instrument_save_path = "'C:\\temp\\Last_Screenshot.png'" self.write(f"HCOP:SDUM:FORM {form}") self.wait_for_device() self.device.ext_error_checking() self.device.ext_query_bin_data_to_file("HCOP:SDUM:DATA?", str(path)) self.device.ext_error_checking()
[docs] def write_block(self, message: str): """ Writes a message to the scope, waits for it to complete, and checks for errors. Parameters ---------- message : str The message to send (not a query). Notes ----- This function is blocking. """ self.write(message) self.wait_for_device() self.device.ext_error_checking()
[docs] def wait_for_device(self): """ Waits for the device until last action is complete. .. note:: This function is blocking. """ self.device.ext_wait_for_opc()
# measure_ and sweep_ not tested yet
[docs] def measure_current( self, voltage: float, compliance: float, power_line_cycles: int = 2, channel: int = 0, ): """ Performs a spot measurement of current at the speficied voltage Parameters ---------- voltage : float Voltage in Volts compliance : float Safety limit on current, in Amps power_line_cycles : int, optional Duration of measurement in terms of power line cycles, defaults to 2 channel : int, optional Channel number, defaults is 1 """ if channel == 0: channel = "" # resets device self.write("*RST") # sets source mode to current self.write(f":SOUR{channel}:FUNC:MODE VOLT") # sets current self.write_block(f":SOUR{channel}:VOLT {voltage}") # sets output protection (current limit) self.write(f":SENS{channel}:CURR:PROT {compliance}") # sets measurement range to auto self.write(f":SENS{channel}:CURR:RANG:AUTO ON") # turns on voltage sensor self.write(f':SENS{channel}:FUNC "CURR"') # sets measurement speed (aperture time) self.write(f":SENS:CURR:NPLC {power_line_cycles}") self.device.ext_error_checking() # turns on source and measurement channel self.write(":OUTP ON") # takes measurement measurement = self.query(":MEAS:CURR? " + (f"(@2)" if channel == 2 else "(@1)")) # turns off voltage and measurement channel self.write(":OUTP OFF") self.device.ext_error_checking() return measurement
[docs] def measure_voltage( self, current: float, compliance: float, power_line_cycles: int = 2, channel: int = 0, ): """ Performs a spot measurement of voltage at the specified current Parameters ---------- current : float Current in Amps compliance : float Safety limit on voltage, in Volts power_line_cycles : int, optional Duration of measurement in terms of power line cycles, defaults to 2 channel : int, optional Channel number, default is 1 """ if channel == 0: channel = "" # resets device self.write("*RST") # sets source mode to current self.write(f":SOUR{channel}:FUNC:MODE CURR") # sets current self.write_block(f":SOUR{channel}:CURR {current}") # turns on voltage sensor self.write(f':SENS{channel}:FUNC "VOLT"') # sets output protection (voltage limit) self.write(f":SENS{channel}:VOLT:PROT {compliance}") # sets measurement range to auto self.write(f":SENS{channel}:VOLT:RANG:AUTO ON") # sets measurement speed (aperture time) self.write(f":SENS{channel}:VOLT:NPLC {power_line_cycles}") self.device.ext_error_checking() # turns on source and measurement channel self.write(":OUTP ON") # takes measurement measurement = self.query(":MEAS:VOLT? " + (f"(@2)" if channel == 2 else "(@1)")) # turns off current source and measurement channel self.write(":OUTP OFF") self.device.ext_error_checking() return measurement
[docs] def toggle_colors(self): """ Toggles color scheme of SMU display """ color = self.query(":DISP:CSET?") self.write(":DISP:CSET " + str(int(color) % 2 + 1)) # def sweep_voltage(self, start, stop, steps, compliance, measurement_speed, channel="", log=False): """ Staircase sweep measurement of current Parameters ---------- start : float Starting voltage stop : float Finish voltage steps : int Number of steps in meaurement compliance : float Sets current limit measurement_speed : float Aperture speed in seconds channel : int, optional Channel number, defaults to 1 log : bool, optional Use logarithmic sweep spacing. Defaults to linear """ """ #resets device self.write("*RST") #Set sweep source self.write(f":SOUR{channel}:FUNC:MODE VOLT") self.write(f":SOUR{channel}:VOLT:MODE SWE") self.write(f":SOUR:SWE:SPAC " + ("LOG" if log else "LIN")) self.write(f":SOUR{channel}:VOLT:STAR {start}") self.write(f":SOUR{channel}:VOLT:STOP {stop}") self.write(f":SOUR{channel}:VOLT:POIN {steps}") #set measurement self.write(f":SENS{channel}:FUNC \"CURR\"") self.write(f":SENS{channel}:CURR:APER {measurement_speed}") self.write(f":SENS{channel}:CURR:PROT {compliance}") #generate triggers self.write(":TRIG:SOUR AINT") self.write(f":TRIG:COUN {steps}") self.device.ext_error_checking() #enable output self.write(":OUTP ON") #begin measurement self.write_block(":INIT" + (f" (@2)" if channel == 2 else " (@1)")) #turn off output self.write(":OUTP OFF") #read data measurement = self.query(":FETC:ARR:CURR? " + (f"(@2)" if channel == 2 else "(@1)")) self.device.ext_error_checking() return measurement """ # def sweep_current(self, start, stop, steps, compliance, measurement_speed, channel="", log=False): """ Staircase sweep measurement of voltage Parameters ---------- start : float Starting current stop : float Finish current steps : int Number of steps in meaurement compliance : float Sets voltage limit measurement_speed : float Aperture speed in seconds channel : int, optional Channel number, defaults to 1 log : bool, optional Use logarithmic sweep spacing. Defaults to linear """ """ #resets device self.write("*RST") #Set sweep source self.write(f":SOUR{channel}:FUNC:MODE CURR") self.write(f":SOUR{channel}:CURR:MODE SWE") self.write(f":SOUR:SWE:SPAC " + ("LOG" if log else "LIN")) self.write(f":SOUR{channel}:CURR:STAR {start}") self.write(f":SOUR{channel}:CURR:STOP {stop}") self.write(f":SOUR{channel}:CURR:POIN {steps}") #set measurement self.write(f":SENS{channel}:FUNC \"VOLT\"") self.write(f":SENS{channel}:VOLT:APER {measurement_speed}") self.write(f":SENS{channel}:VOLT:PROT {compliance}") #generate triggers self.write(":TRIG:SOUR AINT") self.write(f":TRIG:COUN {steps}") self.device.ext_error_checking() #enable output self.write(":OUTP ON") #begin measurement self.write_block(":INIT" + (f" (@2)" if channel == 2 else " (@1)")) #turn off output self.write(":OUTP OFF") #read data measurement = self.query(":FETC:ARR:VOLT? " + (f"(@2)" if channel == 2 else "(@1)")) self.device.ext_error_checking() return measurement ` """