diff --git a/setup.cfg b/setup.cfg index 1bd851a..5c2a17f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ install_requires = moku>=3.0.0 h5py pyyaml + pyserial python_requires = >=3.6 include_package_data = True package_dir = diff --git a/src/splendaq/__init__.py b/src/splendaq/__init__.py index 311874f..ac38395 100644 --- a/src/splendaq/__init__.py +++ b/src/splendaq/__init__.py @@ -1,3 +1,4 @@ from ._version import version as __version__ from . import io from . import daq +from . import control diff --git a/src/splendaq/control/__init__.py b/src/splendaq/control/__init__.py new file mode 100644 index 0000000..7bec055 --- /dev/null +++ b/src/splendaq/control/__init__.py @@ -0,0 +1,2 @@ +from ._hps import * +from ._sr560 import * diff --git a/src/splendaq/control/_hps.py b/src/splendaq/control/_hps.py new file mode 100644 index 0000000..4a00fb7 --- /dev/null +++ b/src/splendaq/control/_hps.py @@ -0,0 +1,237 @@ +import serial + +__all__ = [ + "HarvardPowerSupply", +] + +class HarvardPowerSupply(object): + """ + Class for controlling the Harvard Power Supply at the SLAC + SPLENDOR lab. + + Notes + ----- + The documentation of the power supply can be found here + https://wiki.harvard.edu/confluence/display/ESHOP/TT075%3A+Stanford+HEMT+supply + + The serial commands are reprinted below in case the above link no + longer works, or the use has no access. + + Commands: + + S ; selects active slot. Valid arguments are 1 and 2. + Power-on default is 1. + C ; selects active HEMT channel. Valid arguments are 1 and 2. + Power-on default is 1. + D ; sets drain voltage of active HEMT channel in millivolts. + Valid arguments are floats in the range of 0.0 to 4990.0. + d; reads back drain voltage of selected HEMT channel. Note that + this is mostly for diagnostics, as the "D" command is at least + as accurate as the "d" command. + r; reads back remote voltage (Vds+ - Vds-) of selected HEMT channel. + Mostly for diagnostics unless you anticipate significant + resistance on the Vds line to the HEMT. + G ; sets the Vgs in millivolts. Valid arguments are in the + range of -5000.0 mV to +5000.0 mV. Power-on default sets Vgs + to 0.0V. + g; reads back the previously set Gate voltage. This is a + firmware-only operation, simply returning the previously set + Vgs, rather than measuring it. + N; turns oN the Vds driver of both HEMTs of the active slot. + Power-on default has the drivers turned off . + F; turns ofF the Vds driver of both HEMTs of the active slot. + i; returns the drain current of the selected HEMT, in mA. + + The preferred protocol is to use "D" to set the correct Vds, + then use "N" to enable the output drivers. + + """ + + def __init__(self, port): + """ + Initialization of the HarvardPowerSupply class. Establishes + a connection to a specified port. + + Parameters + ---------- + port : str + The port to connect to that the HarvardPowerSupply is on, + e.g. "COM4". + + """ + + self._HPS = serial.Serial() + self._HPS.port = port + self._HPS.open() + self.nslots = 2 + self.nhemts = 2 + + def choose_slot(self, slot): + """ + Choose which slot to control. + + Parameters + ---------- + slot : int + The slot to connect to, should be 1 or 2. + + Returns + ------- + slot_val : int + The value of the slot that was set. + + """ + + self._HPS.write(f"S{slot};".encode()) + slot_str = self._HPS.read_until(b"!") + self._HPS.write(b'N;') + self._HPS.read_until(b'!') + return int(slot_str[1:-1]) + + def choose_hemt(self, hemt): + """ + Choose which HEMT to control on the specified slot. + + Parameters + ---------- + hemt : int + The HEMT to connect to on the previously specified slot, + should be 1 or 2. + + Returns + ------- + hemt_val : int + The value of the HEMT that was set. + + """ + + self._HPS.write(f'C{hemt};'.encode()) + return int(self._HPS.read_until(b'!')[1:-1]) + + def set_gate_voltage(self, value): + """ + With HEMT and slot chosen, set the gate voltage on the HEMT. + + Parameters + ---------- + value : float + The gate voltage to be set for the HEMT, in mV. Allowed + values are -5000.0 to 5000.0, power-on defaults to 0. + + Returns + ------- + voltage_val : float + The value of the gate voltage that was set, in mV. + + """ + + self._HPS.write(f"G{value};".encode()) + return float(self._HPS.read_until(b'!')[1:-1]) + + def read_gate_voltage(self): + """ + With HEMT and slot chosen, read the gate voltage on the HEMT. + + Returns + ------- + voltage_val : float + The value of the gate voltage that was set, in mV. + + """ + + self._HPS.write(b"g;") + return float(self._HPS.read_until(b'!')[1:-1]) + + def set_drain_voltage(self, value): + """ + With HEMT and slot chosen, set the drain voltage on the HEMT. + + Parameters + ---------- + value : float + The drain voltage to be set for the HEMT, in mV. Allowed + values are 0.0 to 4990.0. + + Returns + ------- + voltage_val : float + The value of the gate voltage that was set, in mV. + + """ + + self._HPS.write(f"D{value};".encode()) + return float(self._HPS.read_until(b'!')[1:-1]) + + def read_drain_voltage(self): + """ + With HEMT and slot chosen, read the drain voltage on the HEMT. + + Returns + ------- + voltage_val : float + The value of the drain voltage that was set, in mV. + + """ + + self._HPS.write(b"d;") + return float(self._HPS.read_until(b'!')[1:-1]) + + def read_remote_voltage(self): + """ + With HEMT and slot chosen, read the remote voltage + (Vds+ - Vds-) on the HEMT. + + Returns + ------- + voltage_val : float + The value of the remote voltage in mV. + + """ + + self._HPS.write(b"d;") + return float(self._HPS.read_until(b'!')[1:-1]) + + def read_drain_current(self): + """ + With HEMT and slot chosen, read the drain current on the HEMT. + + Returns + ------- + current_val : float + The value of the drain current in mA. + + """ + + self._HPS.write(b"i;") + return float(self._HPS.read_until(b'!')[1:-1]) + + def zero_all(self): + """ + Function to zero all voltages, good to run at beginning and end + of running to ensure voltages are zero and the environment is + known. + + Returns + ------- + None + + """ + + for slot in range(1, self.nslots + 1): + self.choose_slot(slot) + for hemt in range(1, self.nhemts + 1): + self.choose_hemt(hemt) + self.set_gate_voltage(0) + self.set_drain_voltage(0) + + def close(self): + """ + Close connection to the port to free it for other users. + + Returns + ------- + None + + """ + + self._HPS.close() diff --git a/src/splendaq/control/_sr560.py b/src/splendaq/control/_sr560.py new file mode 100644 index 0000000..cb905a2 --- /dev/null +++ b/src/splendaq/control/_sr560.py @@ -0,0 +1,393 @@ +import numpy as np +import serial + + +__all__ = [ + "SR560", +] + + +class SR560(object): + """ + Class for controlling an SRS SR560 low-noise preamplifier. + Documentation on the various commands can be found in the SR560 + user manual. + + """ + + def __init__(self, port): + """ + Initialization of the SS560 class. Establishes a connection to + a specified port. + + Parameters + ---------- + port : str + The port to connect to that the SR560 is on, + e.g. "COM4". + + Returns + ------- + None + + """ + + self._SRS = serial.Serial() + self._SRS.port = port + self._SRS.open() + + def set_coupling(self, value): + """ + Set the coupling of the SR560. + + Parameters + ---------- + value : int + Sets the input coupling. Coupling to ground is 0, + DC coupling is 1, and AC coupling is 2. + + Returns + ------- + None + + """ + + self._SRS.write(f"CPLG{value}\r\n".encode()) + + def set_dynamic_reserve(self, value): + """ + Set the dynamic reserve of the SR560. + + Parameters + ---------- + value : int + Sets the dynamic reserve (DR). Low noise is 0, + high DR is 1, and calibration gains is 2. + + Returns + ------- + None + + """ + + self._SRS.write(f"DYNR{value}\r\n".encode()) + + def set_blanking(self, value): + """ + Operates amplifier blanking. + + Parameters + ---------- + value : int + Set 0 to for not blanked. Set to 1 for blanked. + + Returns + ------- + None + + """ + + self._SRS.write(f"BLINK{value}\r\n".encode()) + + def set_filter_mode(self, value): + """ + Sets filter mode. + + Parameters + ---------- + value : int + The filter mode to set, values below + 0 = bypass + 1 = 6 dB lowpass + 2 = 12 dB lowpass + 3 = 6 dB highpass + 4 = 12 dB highpass + 5 = bandpass + + Returns + ------- + None + + """ + + self._SRS.write(f"FLTM{value}\r\n".encode()) + + def set_gain(self, gain): + """ + Controls the gain of the SR560. + + Parameters + ---------- + gain : int + Sets the gain of the device, allowed values are + 1 to 50000 in 1-2-5 sequence. + + Returns + ------- + None + + """ + + gain_dict = { + 1 : 0, + 2 : 1, + 5 : 2, + 10 : 3, + 20 : 4, + 50 : 5, + 100 : 6, + 200 : 7, + 500 : 8, + 1000 : 9, + 2000 : 10, + 5000 : 11, + 10000 : 12, + 20000 : 13, + 50000 : 14, + } + + if gain not in gain_dict: + raise ValueError( + "Not a valid gain, see docstring for allowed values." + ) + + value = gain_dict[gain] + self._SRS.write(f"GAIN{value}\r\n".encode()) + + def set_highpass_filter(self, freq): + """ + Set the highpass filter frequency. + + Parameters + ---------- + freq : float + The highpass filter frequency to be set. Defined in steps + from 0.03, 0.1, 0.3, 1, ..., 10000 Hz. + + Returns + ------- + None + + """ + + freq_dict = { + 0.03 : 0, + 0.1 : 1, + 0.3 : 2, + 1 : 3, + 3 : 4, + 10 : 5, + 30 : 6, + 100 : 7, + 300 : 8, + 1000 : 9, + 3000 : 10, + 10000 : 11, + } + + if freq not in freq_dict: + raise ValueError( + "Not a valid highpass freq, " + "see docstring for allowed values." + ) + + value = freq_dict[freq] + self._SRS.write(f"HFRQ{value}\r\n".encode()) + + def set_signal_invert(self, value): + """ + Sets the signal invert status. + + Parameters + ---------- + value : int + 0 is noninverted. 1 is inverted. + + Returns + ------- + None + + """ + + self._SRS.write(f"INVT{value}\r\n".encode()) + + def listen_all(self): + """ + Makes all attached SR560s listeners. + + Returns + ------- + None + + """ + + self._SRS.write(b"LALL\r\n") + + def listen(self, value): + """ + Listen to a specific SR560. + + Parameters + ---------- + value : int + Makes SR560 with address `value` a listener. + Can be 0, 1, 2, or 3. + + Returns + ------- + None + + """ + + self._SRS.write(f"LISN{value}\r\n".encode()) + + def set_lowpass_filter(self, freq): + """ + Set the lowpass filter frequency. + + Parameters + ---------- + freq : float + The lowpass filter frequency to be set. Defined in steps + from 0.03, 0.1, 0.3, 1, ..., 1000000 Hz. + + Returns + ------- + None + + """ + + freq_dict = { + 0.03 : 0, + 0.1 : 1, + 0.3 : 2, + 1 : 3, + 3 : 4, + 10 : 5, + 30 : 6, + 100 : 7, + 300 : 8, + 1000 : 9, + 3000 : 10, + 10000 : 11, + 30000 : 12, + 100000 : 13, + 300000 : 14, + 1000000 : 15, + } + + if freq not in freq_dict: + raise ValueError( + "Not a valid lowpass freq, " + "see docstring for allowed values." + ) + + value = freq_dict[freq] + self._SRS.write(f"LFRQ{freq}\r\n".encode()) + + def reset_overload(self): + """ + Resets overload for 0.5 seconds. + + Returns + ------- + None + + """ + + self._SRS.write(b"ROLD\r\n") + + def set_input_source(self, value): + """ + Sets the input source. + + Parameters + ---------- + value : int + Input source. Set 0 for "A", + set to 1 for "A-B", and set to 2 for "B". + + Returns + ------- + None + + """ + + self._SRS.write(f"SRCE{value}\r\n".encode()) + + def set_vernier_gain_status(self, value): + """ + Set the vernier gain status. + + Parameters + ---------- + value : int + Set to 0 for cal'd gain. Set to 1 for vernier gain. + + Returns + ------- + None + + """ + + self._SRS.write(f"UCAL{value}\r\n".encode()) + + def set_vernier_gain(self, gain): + """ + Set the vernier gain on the SR560. + + Parameters + ---------- + gain : float + Set the vernier gain in 0.5% steps from 0 to 100. + + Returns + ------- + None + + """ + + allowed_vals = np.linspace(0, 100, num=201) + + if gain not in allowed_vals: + raise ValueError( + "Vernier gain must be between 0 and 100 in steps of 0.5." + ) + + self._SRS.write(f"UCGN{gain}\r\n".encode()) + + def unlisten_all(self): + """ + Unlisten. Unaddresses all attached SR560s. + + Returns + ------- + None + + """ + + self._SRS.write(b"UNLS\r\n") + + def reset_to_defaults(self): + """ + Reset. Recalls default settings. + + Returns + ------- + None + + """ + + self._SRS.write(b"*RST\r\n") + + def close(self): + """ + Close connection to the port to free it for other users. + + Returns + ------- + None + + """ + + self._SRS.close()