Source code for spi_rack.spi_rack

#------------------------------------------------------------------------------
# Description    : SPI Rack QCoDeS interface
# Git repository : https://gitlab.com/qblox/packages/software/qblox_instruments.git
# Copyright (C) Qblox BV (2021)
#------------------------------------------------------------------------------

from time import time
from typing import Type, List, Union, Optional

from qcodes.instrument.base import Instrument

from qblox_instruments import build

from spirack.spi_rack import SPI_rack as spi_api

from spi_rack.spi_module_base import spi_module_base, dummy_spi_module
from spi_rack.s4g_module import s4g_module
from spi_rack.d5a_module import d5a_module


[docs]class dummy_spi_api: """ A dummy API that can be used to test the SPI rack driver. """ TEMPERATURE = 25.0 BATTERY_LVLS = [6., -6.] FIRMWARE_VERSION = "v1.0"
[docs] def __init__(self, address, baud_rate, timeout=1.): """ Instantiates the dummy API object. Parameters ---------- address Mock value for the address on which the spi_rack is connected. This value is assigned to a member variable, but is not actually used. address Mock value for the baud_rate for the serial connection. This value is assigned to a member variable, but is not actually used. timeout Mock value for the timeout for the serial connection. This value is assigned to a member variable, but is not actually used. """ self.address = address self.baud_rate = baud_rate self.timeout = timeout self.locked = True
[docs] def get_temperature(self) -> float: """ Return a mock temperature. Returns ---------- float returns `dummy_spi_api.TEMPERATURE` """ return self.TEMPERATURE
[docs] def get_battery(self) -> List[float]: """ Return a mock battery level list. In the actual API these are two values read from the battery ADCs. For the mock API simply constant values are returned. The expected values to be returned can also be gotten through `dummy_spi_api.BATTERY_LVLS`. Returns ---------- List[float] returns `dummy_spi_api.BATTERY_LVLS` """ return self.BATTERY_LVLS
[docs] def get_firmware_version(self) -> str: """ Returns a firmware version string. In the actual API this is returned by the microcontroller. Returns ---------- str returns `dummy_spi_api.FIRMWARE_VERSION` """ return self.FIRMWARE_VERSION
[docs] def close(self): """Not relevant for dummy api but added to remove errors""" pass
[docs] def unlock(self): """ Unlocks the communication to the microcontroller. Since for the dummy implementation there is no communication to the microcontroller, this simply sets a self.locked to False. """ self.locked = False
[docs]class spi_rack(Instrument): """ SPI rack driver class based on `QCoDeS <https://qcodes.github.io/Qcodes/>`. This class relies on the `spirack API <https://github.com/mtiggelman/SPI-rack/>`. Example usage: .. highlight:: python .. code-block:: python from spi_rack.spi_rack import spi_rack from spi_rack.s4g_module import s4g_module spi = spi_rack("my_spi_rack", "COM4") # connects to an SPI rack on COM port 4 spi.add_spi_module(3, "D5a", "alice") # adds an D5a module with address 3 named "alice" spi.add_spi_module(2, "S4g", "bob") # adds an S4g module with address 2 named "bob" spi.add_spi_module(6, s4g_module) # adds an S4g module with address 6 with the default name module6 spi.bob.dac0.current(10e-3) # sets the current of output 1 of the S4g module named "bob" to 10 mA spi.alice.dac6.voltage(-2) # sets the voltage of output 7 of the D5a module named "alice" to -2 V """ _MODULES_MAP = {"S4g": s4g_module, "D5a": d5a_module, "dummy": dummy_spi_module} # If drivers are created for different modules they should be added here
[docs] def __init__(self, name: str, address: str, baud_rate: int = 9600, timeout: float = 1, is_dummy: bool = False): """ Instantiates the driver object. Parameters ---------- name : str Instrument name. address : str COM port used by SPI rack controller unit (e.g. "COM4") baud_rate : int Baud rate timeout : float Data receive timeout in seconds is_dummy : bool If true, the SPI rack driver is operating in "dummy" mode for testing purposes. Returns ---------- """ t0 = time() super().__init__(name) api = dummy_spi_api if is_dummy else spi_api self.spi_rack = api(address, baud_rate, timeout=timeout) self.spi_rack.unlock() self._modules = {} self._add_qcodes_params() self.connect_message(begin_time=t0)
def _add_qcodes_params(self): """ Function to add the QCoDeS parameters to the instrument """ self.add_parameter('temperature', unit='C', set_cmd=False, get_cmd=self.spi_rack.get_temperature, docstring="Returns the temperature in the C1b module. Reads the temperature from the " "internal C1b temperature sensor. Accuracy is +- 0.5 degrees in 0-70 degree range." ) self.add_parameter('battery_voltages', unit='V', set_cmd=False, get_cmd=self.spi_rack.get_battery, docstring='Calculates the battery voltages from the ADC channel values. ' 'Returns: [VbatPlus, VbatMin]' )
[docs] def add_spi_module(self, address: int, module_type: Union[spi_module_base, str], name: str = None, **kwargs): """ Add a module to the driver. Parameters ---------- address : int Address that the module is set to (set internally on the module itself with a jumper). module_type : Union[str, spi_module_base] Either a str that is defined in _MODULES_MAP, or a reference to a class derived from SPI_Module_Base. name: str Optional name of the module. If no name is given or is None, a default name of "module{address}" is used. Returns ---------- Raises ---------- ValueError module_type is not a string or a subclass of SPI_Module_Base """ if name is None: name = "module{}".format(address) if isinstance(module_type, str): module_obj = self._MODULES_MAP[module_type](self, name, address, **kwargs) elif issubclass(module_type, spi_module_base): module_obj = module_type(self, name, address, **kwargs) else: raise ValueError(f"{module_type} is not a valid SPI module.") self._modules[address] = module_obj self.add_submodule(name, module_obj)
[docs] def get_idn(self): """ Generates the IDN dict. Parameters ---------- Returns ---------- dict The QCoDeS style IDN dictionary. Currently only the firmware version is actually read from hardware. Raises ---------- """ return {"manufacturer": "Qblox", # FIXME: This should all be read from microcontroller, not hardcoded. "model": "SPI Rack", "firmware": {"device": self.spi_rack.get_firmware_version(), "driver": build.get_build_info()}}
[docs] def close(self): """ Closes connection to hardware and closes the Instrument. """ self.spi_rack.close() super().close()
[docs] def set_dacs_zero(self): """ Calls the :meth:`set_dacs_zero` function on all the modules, which in turn should cause all output values to be set to 0. """ for mod in self._modules.values(): mod.set_dacs_zero()
[docs] def connect_message(self, idn_param: str = 'IDN', begin_time: Optional[float] = None) -> None: """ Print a standard message on initial connection to an instrument. Overridden from superclass to accommodate IEEE488.2 for IDN. Parameters ---------- idn_param: str Name of parameter that returns ID dict. Default ``IDN``. begin_time: Optional[float] ``time.time()`` when init started. Default is ``self._t0``, set at start of ``Instrument.__init__``. """ # start with an empty dict, just in case an instrument doesn't # heed our request to return all 4 fields. idn = {'manufacturer': None, 'model': None, 'serial': None, 'firmware': None} idn.update(self.get(idn_param)) t = time() - (begin_time or self._t0) con_msg = ('Connected to: {manufacturer} {model} ' '(serial:{serial}, firmware:{firmware}) ' 'in {t:.2f}s'.format(t=t, **idn)) print(con_msg) self.log.info(f"Connected to instrument: {idn}")